一、起因
由於國產化系統逐步的推廣應用,需要將在window系統中實現的功能;遷移到國產系統(UOS(統信OS)、麒麟操作系統等)中運行。
在windows環境中主要采用Framework4.0開發的后台運行程序。主體思路采用將Windows程序功能遷移成Linux系統后台服務運行。
特記錄在適配過程中相關問題記錄
二、問題記錄
1、System.Draw.Common包使用問題:
由於后台服務需要生成pdf報告功能;需要用到gdi相關類型:如Font、Pen、Image、Bitmap等類型;在跨平台的處理中微軟提供了:System.Draw.Common包在跨平台中使用。
注意點:
a)System.Draw.Common在linux環境中依賴gdip包;需要在環境中安裝gdip包:
//安裝libgdiplus: apt-get install -y libgdiplus
b)在.NET6以后System.Draw.Common被歸為 Windows 特定的庫。 在為非 Windows 操作系統編譯時,平台分析器會在編譯時發出警告;運行時會出現以下異常:
System.TypeInitializationException : The type initializer for 'Gdip' threw an exception. ---- System.PlatformNotSupportedException : System.Drawing.Common is not supported on non-Windows platforms. See https://aka.ms/systemdrawingnonwindows for more information.
更改原因:
由於
System.Drawing.Common
被設計為 Windows 技術的精簡包裝器,因此其跨平台實現欠佳。
libgdiplus
是本機端System.Drawing.Common
跨平台實現的主要提供程序。libgdiplus
實際上是對System.Drawing.Common
所依賴的 Windows 部分的重新實現。 該實現使libgdiplus
成為一個重要的組件。 它大約有 30,000 行 C 代碼,大部分未經測試,而且缺少很多功能。libgdiplus
還具有許多用於圖像處理和文本呈現的外部依賴項,例如cairo
、pango
和其他本機庫。 這些依賴項使得維護和交付組件更具挑戰性。 自從包含 Mono 跨平台實現以來,我們已將許多從未得到修復的問題重定向到libgdiplus
。 相比之下,我們采用的其他外部依賴項,例如icu
或openssl
,都是高質量的庫。 使libgdiplus
的功能集和質量與 .NET 堆棧的其余部分相媲美是不可行的。通過對 NuGet 包的分析,我們觀察到
System.Drawing.Common
主要用於跨平台的圖像處理,例如 QR 代碼生成器和文本呈現。 由於我們的跨平台圖形支持不完整,我們還沒有注意到大量的圖形使用。System.Drawing.Common
在非 Windows 環境中的使用通常得到 SkiaSharp 和 ImageSharp 的良好支持。
System.Drawing.Common
將僅在 Windows 窗體和 GDI+ 的上下文中繼續演變。
解決辦法:可通過將 runtimeconfig.json 文件中的 System.Drawing.EnableUnixSupport
System.Drawing.EnableUnixSupport
設置為 true
來啟用對 .NET 6 中非 Windows 平台的支持
{ "configProperties": { "System.Drawing.EnableUnixSupport": true } }
可以在項目中添加:runtimeconfig.template.json 設置以上內容;編譯時會自動添加到對於配置文件中。
2、打印機信息獲取:
在國產系統中打印機管理主要使用CUPS管理打印機:
a)安裝CUPS:(如果未安裝改服務)
sudo apt-get update && sudo apt-get install cups cups-client lpr
b) 獲取打印機列表:安裝后可以使用以下命令獲取打印機列表
//輸出打印機信息 lpstat -p //輸出信息: 打印機 HP-Color-LaserJet-MFP-M281fdw 目前空閑。從 2022年03月15日 星期二 17時49分10秒 開始啟用 打印機 PDF 目前空閑。從 2022年03月17日 星期四 19時09分47秒 開始啟用
c)獲取打印機基本信息:如紙張信息、紙張來源、雙面打印等
//打印機相關信息 lpoptions -p HP-Color-LaserJet-MFP-M281fdw -l //輸出信息 //紙張信息 PageSize/Media Size: *Letter Legal Executive FanFoldGermanLegal 4x6 5x8 A4 A5 A6 B5 B6 102x152mm Oficio 195x270mm 184x260mm 7.75x10.75 Postcard DoublePostcardRotated Env10 EnvMonarch EnvISOB5 EnvC5 EnvDL Custom.WIDTHxHEIGHT //雙面打印 Collate/Collate: True False //雙面打印設置 Duplex/Two-Sided: *None DuplexNoTumble DuplexTumble //紙張來源 InputSlot/Paper Feed: *Auto Tray1 Tray2 ManualFeed HPOption_Duplexer/Duplex Unit: True *False MediaType/Media Type: *Unspecified Plain HPEcoFFICIENT HPLaserJet90 HPColorLaserMatte105 HPPremiumChoiceMatte120 HPBrochureMatte150 HPCoverMatte200 HPMattePhoto200 HPPremiumPresentationGlossy120 HPBrochureGlossy150 HPTrifoldBrochureGlossy150 HPBrochureGlossy200 Light6074 Intermediate8595 MidWeight96110 Heavy111130 ExtraHeavy131175 HeavyGlossy111130 ExtraHeavyGlossy131175 CardGlossy176220 ColorLaserTransparency Labels Letterhead Envelope HeavyEnvelope Preprinted Prepunched Colored Bond Recycled Rough HeavyRough OpaqueFilm HPPJLColorAsGray/Print Color as Gray: True *False HPRGBEmulation/RGB Color: *DefaultSRGB PhotoSRGB Adobe VividSRGB HPRGBEmulationNone HPEdgeControl/Edge Control: HPEdgeControlOff Light *Normal Max HPGeneralHalftone/Halftone: *Smooth Detail HPTextNeutralGrays/Text Neutral Grays: *Black ProcessBlack HPGraphicsNeutralGrays/Graphics Neutral Grays: *Black ProcessBlack HPPhotoNeutralGrays/Photo Neutral Grays: Black *ProcessBlack
有了前面的命令就可以封裝獲取打印機名稱及打印機信息的方法了:
//命令執行幫助類 public static class ShellHelper { public static string RunCommand(string? command, string? args) { if (string.IsNullOrWhiteSpace(command)) return string.Empty; var process = new Process() { StartInfo = new ProcessStartInfo { FileName = command, Arguments = args, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, } }; process.Start(); string output = process.StandardOutput.ReadToEnd(); string error = process.StandardError.ReadToEnd(); process.WaitForExit(); if (string.IsNullOrEmpty(error)) { return output; } else { return error; } } }
獲取打印機名稱:
public static object GetAllPrinterNames(ILogger? logger = null) { object result = null; if (printerCache.TryGetValue(PrinterName, out result) && (result is PrinterSettings.StringCollection && (result as PrinterSettings.StringCollection).Count == PrinterSettings.InstalledPrinters.Count)) { result = printerCache[PrinterName]; } else { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { string cmdResult = ShellHelper.RunCommand("lpstat", "-p"); string[] strArray = cmdResult.Split('\n'); List<string> strList = new List<string>(); int index1, index2; for (int i = 0; i < strArray.Length; i++) { index1 = strArray[i].IndexOf(' '); if (index1 <= 0) continue; index2 = strArray[i].IndexOf(' ', index1 + 1); if (index2 <= 0) continue; strList.Add(strArray[i].Substring(index1 + 1, index2 - index1 - 1)); } //獲取打印機信息 result = strList; } else { result = PrinterSettings.InstalledPrinters; } printerCache[PrinterName] = result; } return result; }
獲取打印機信息:需注意不同打印機獲取出來的設置信息不同,需要獲取通用的打印機信息:紙張來源、紙張、雙面打印等
public static List<PrinterInfo> GetAllPrinters(ILogger? logger = null) { List<PrinterInfo> result = null; if (printerCache.TryGetValue(PrinterInfo, out object objresult) && (objresult is List<PrinterInfo> && (objresult as List<PrinterInfo>).Count == PrinterSettings.InstalledPrinters.Count)) { result = objresult as List<PrinterInfo>; } else { var resultPrinterInfos = new List<PrinterInfo>(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { string cmdResult = ShellHelper.RunCommand("lpstat", "-p"); string[] strArray = cmdResult.Split('\n'); List<string> strList = new List<string>(); int index1, index2; for (int i = 0; i < strArray.Length; i++) { index1 = strArray[i].IndexOf(' '); if (index1 <= 0) continue; index2 = strArray[i].IndexOf(' ', index1 + 1); if (index2 <= 0) continue; strList.Add(strArray[i].Substring(index1 + 1, index2 - index1 - 1)); } //獲取打印機信息 foreach (var item in strList) { cmdResult = ShellHelper.RunCommand("lpoptions", $"-p \"{item}\" -l"); string[] strArray2 = cmdResult.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); /* PageSize/Media Size: *Letter Legal Executive FanFoldGermanLegal 4x6 5x8 A4 A5 A6 B5 B6 102x152mm Oficio 195x270mm 184x260mm 7.75x10.75 Postcard DoublePostcardRotated Env10 EnvMonarch EnvISOB5 EnvC5 EnvDL Custom.WIDTHxHEIGHT Collate/Collate: True False Duplex/Two-Sided: *None DuplexNoTumble DuplexTumble InputSlot/Paper Feed: *Auto Tray1 Tray2 ManualFeed HPOption_Duplexer/Duplex Unit: True *False MediaType/Media Type: *Unspecified Plain HPEcoFFICIENT HPLaserJet90 HPColorLaserMatte105 HPPremiumChoiceMatte120 HPBrochureMatte150 HPCoverMatte200 HPMattePhoto200 HPPremiumPresentationGlossy120 HPBrochureGlossy150 HPTrifoldBrochureGlossy150 HPBrochureGlossy200 Light6074 Intermediate8595 MidWeight96110 Heavy111130 ExtraHeavy131175 HeavyGlossy111130 ExtraHeavyGlossy131175 CardGlossy176220 ColorLaserTransparency Labels Letterhead Envelope HeavyEnvelope Preprinted Prepunched Colored Bond Recycled Rough HeavyRough OpaqueFilm HPPJLColorAsGray/Print Color as Gray: True *False HPRGBEmulation/RGB Color: *DefaultSRGB PhotoSRGB Adobe VividSRGB HPRGBEmulationNone HPEdgeControl/Edge Control: HPEdgeControlOff Light *Normal Max HPGeneralHalftone/Halftone: *Smooth Detail HPTextNeutralGrays/Text Neutral Grays: *Black ProcessBlack HPGraphicsNeutralGrays/Graphics Neutral Grays: *Black ProcessBlack HPPhotoNeutralGrays/Photo Neutral Grays: Black *ProcessBlack */ PrinterInfo pinfo = new PrinterInfo(); pinfo.name = item; pinfo.color = true; pinfo.isvalid = true; foreach (var opItem in strArray2) { string[] kvArr = opItem.Split(":".ToArray(), StringSplitOptions.RemoveEmptyEntries); if (kvArr.Length == 2) { string key = kvArr.First(); string[] values = kvArr.Last().Split(" ".ToArray(), StringSplitOptions.RemoveEmptyEntries); int nIndex = key.IndexOf('/'); if (nIndex > 0) key = key.Substring(0, nIndex);//todo:判斷規則可能只取/前面的部分 switch (key) { //紙張 case "PageSize": // /Media Size /Page Size List<PaperSize> psList = new List<PaperSize>(); foreach (var ps in values) { PaperSize paperSize = new PaperSize(); paperSize.PaperName = ps.Trim('*'); psList.Add(paperSize); } pinfo.paper = new PaperSizeCollection(psList.ToArray()); break; //紙張來源 case "InputSlot": // /Paper Feed /Media Source List<PaperSource> psourceList = new List<PaperSource>(); for (int i = 0; i < values.Length; i++) { string ps = values[i]; PaperSource pSource = new PaperSource(); pSource.SourceName = ps.Trim('*'); pSource.RawKind = i; psourceList.Add(pSource); } pinfo.papersource = new PaperSourceCollection(psourceList.ToArray()); break; //雙面打印 //Duplex/Two-Sided: *None DuplexNoTumble DuplexTumble case "Duplex": // /Two-Sided /Double-Sided Printing for (int i = 0; i < values.Length; i++) { string ps = values[i]; if (ps.Contains("*")) { pinfo.duplex = i; break; } } pinfo.canduplex = values.Length > 1; break; } } } if (pinfo.paper == null) { pinfo.paper = new PaperSizeCollection(new PaperSize[0]); } if (pinfo.papersource == null) { pinfo.papersource = new PaperSourceCollection(new PaperSource[0]); } resultPrinterInfos.Add(pinfo); } } else { var printerNames = PrinterSettings.InstalledPrinters; if (printerNames.Count > 0) { foreach (string printName in printerNames) { try { PrinterSettings ps = new PrinterSettings(); ps.PrinterName = printName; if (ps.IsValid) { PrinterSettings.PaperSourceCollection psSources = ps.PaperSources; resultPrinterInfos.Add(new PrinterInfo( printName, ps.PaperSources, ps.PaperSizes, ps.SupportsColor, (int)ps.Duplex, ps.IsDefaultPrinter, ps.IsValid, ps.CanDuplex )); } } catch (Exception ex) { logger?.LogError(ex, $"獲取打印機【{printName}】信息失敗-請檢查打印機是否正常訪問"); } } } } result = resultPrinterInfos; printerCache[PrinterInfo] = result; } return result; }
3、打印文件:
前面解決獲取打印相關信息,接下來需要解決打印文檔的問題:
/// <summary> /// 打印內容 /// </summary> /// <param name="pdfBytes">打印內容byte數組</param> /// <param name="psi"></param> public static void Print(byte[] pdfBytes, PrintSettings psi) { string tmpFileName = Path.GetTempFileName(); try { File.WriteAllBytes(tmpFileName, pdfBytes); ShellHelper.RunCommand("lp", BuildLpArgs(tmpFileName, psi)); } finally { try { File.Delete(tmpFileName); } catch { } } } /// <summary> /// 構建打印參數 /// </summary> /// <param name="filePath"></param> /// <param name="psi"></param> /// <returns></returns> private static string BuildLpArgs(string filePath, PrintSettings psi) { StringBuilder str = new StringBuilder(); if (psi != null && psi.PrinterSetting != null) { //打印機名稱 if (!string.IsNullOrWhiteSpace(psi.PrinterSetting.PrinterName)) { str.Append($"-d {psi.PrinterSetting.PrinterName} "); } //雙面打印 if (psi.PrinterSetting.Duplex != Duplex.Default) { switch (psi.PrinterSetting.Duplex) { case Duplex.Simplex: case Duplex.Vertical: str.Append($"-o sides=two-sided-long-edge "); break; case Duplex.Horizontal: str.Append($"-o sides=two-sided-short-edge "); break; } } //打印份數 if (psi.PrinterSetting.Copies > 1) { str.Append($"-n {psi.PrinterSetting.Copies} "); } } if (psi != null && psi.PaperSetting != null) {//紙張名稱 if (!string.IsNullOrWhiteSpace(psi.PaperSetting.PaperName)) { str.Append($"-o media={psi.PaperSetting.PaperName} "); /* 全部模板及尺寸如下: Letter- US Letter (8.5×11 inches, or 216x279mm) Legal- US Legal (8.5×14 inches, or 216x356mm) A4- ISO A4 (8.27×11.69 inches, or 210x297mm) COM10- US #10 Envelope (9.5×4.125 inches, or 241x105mm) DL- ISO DL Envelope (8.66×4.33 inches, or 220x110mm) Transparency- Transparency media type or source Upper- Upper paper tray Lower- Lower paper tray MultiPurpose- Multi-purpose paper tray LargeCapacity- Large capacity paper tray 也可以自定義尺寸,比如想輸出照片6寸(15.2cmx10.2cm) 只需要輸入指令lp -o media=Custom.15.2×10.2cm filename */ } //是否橫行 if (psi.PaperSetting.Landscape == 1) { str.Append($"-o landscape "); } } str.Append(filePath); return str.ToString(); }