引言
前段時間為客戶開發一套打印機配套的軟件,對C#中調用打印機做了些研究。
---------------------------------------------
問題
.Net Framework 1.1給我們提供了一個PrinterSettings類,以提供指定有關文檔打印方式的信息,其中包括打印文檔的打印機。其中的靜態屬性InstalledPrinters可以使我們獲取安裝在計算機上所有打印機的名稱。
但是可惜的是,該屬性僅僅能夠提供已安裝的打印機的名稱。對於獲取該打印機的相關信息(如打印機類型等)卻無能為力。問題就產生了,由於客戶無法提供打印機的SDK,所以對打印機的篩選(處於商業目的,客戶要求軟件只能在使用他們的打印機時才能輸出)只能通過打印機驅動的辨認來實現。
----------------------------------------------
解決方案一 使用WMI獲取打印機信息
WMI,全稱Windows Management Instrumentation。是可伸縮的系統管理結構,它采用一個統一的、基於標准的、可擴展的面向對象接口。WMI 為您提供與系統管理信息和基礎 WMI API 交互的標准方法。WMI 主要由系統管理應用程序開發人員和管理員用來訪問和操作系統管理信息。
.Net Framework中System.Management類提供了對WMI的支持,其中ManagementObjectSearcher用於根據指定的查詢或枚舉檢索 ManagementObject 或 ManagementClass 對象的集合。
/**//// <summary>
/// Code 1:WMI搜索示例
/// <summary>
/// <param name="strDrivername">驅動名稱</param>
/// <returns>返回找到的打印機列表</returns>
/// <remarks>strDrivername支持”%“以及”_“通配符查詢,類似於SQL語句中的查詢<remarks>
public StringCollection GetPrintsWithDrivername( string strDrivername )
{
StringCollection scPrinters = new StringCollection();
string strcheck = "";
if( strDrivername !="" && strDrivername != "*" )
strcheck = " where DriverName like \'" + strDrivername + "\'";
string searchQuery = "SELECT Name FROM Win32_Printer" + strcheck;
ManagementObjectSearcher searchPrinters =
new ManagementObjectSearcher(searchQuery);
ManagementObjectCollection printerCollection = searchPrinters.Get();
foreach(ManagementObject printer in printerCollection)
{
string printname = printer.PropertIEs["Name"].Value.ToString();
scPrinters.Add(printname);
}
searchPrinters.Dispose();
printerCollection.Dispose();
return scPrinters;
}
問題看上去基本解決了,運行程序的確是獲得了正確的打印機列表。可是用戶用了一段時間後發現,有的時候打印機無法正確獲得,看來DOTNET調用WMI穩定性的確有點問題啊。。。。。。
WMI本身功能還是相當強大的,通過VBS基本可以涵蓋Windows最基本的操作。詳細可以參加MSDN的文檔。
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/wmi_start_page.ASP
-------------------------------------------
解決方案二 使用WIN32API獲取打印機
轉來轉去,又回到WIN32API上來了,無奈啊。。。。。。怪不得C++依然這麼吃香 啊。。。。。
.Net給我們提供了DllImport來操作非托管的DLL(發現C#如此的強啊~~~~暗自偷笑)。
主要使用到winspool.drv中的EnumPrinters函數,代碼如下:
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumPrinters ([MarshalAs(UnmanagedType.U4)] PRINTER_ENUM flags,
[MarshalAs(UnmanagedType.LPStr)] string sName,
uint iLevel,
IntPtr pPrinterDesc,
uint iSize,
[MarshalAs(UnmanagedType.U4)] ref uint iNeeded,
[MarshalAs(UnmanagedType.U4)] ref uint iReturned
);
說明:Marshal屬性提供了對托管代碼與非托管代碼見數據封送。
EnumPrinters 的 WIN32 API的定義如下:
BOOL EnumPrinters(
DWord Flags, // printer object types
LPTSTR Name, // name of printer object
DWord Level, // information level
LPBYTE pPrinterEnum, // printer information buffer
DWord cbBuf, // size of printer information buffer
LPDWord pcbNeeded, // bytes received or required
LPDWord pcReturned // number of printers enumerated
);
問題又來啦,EnumPrinters通過Level來獲取PRINTER_INFO,而能獲得打印機驅動的是PRINTER_INFO_2,而C#中又沒有PRINTER_INFO_2結構,偶又開始暈了。。。。。
查了半天資料,網上基本上都是PRINTER_INFO_1的定義,而PRINTER_INFO_2不同與PRINTER_INFO_1,其中還包括DEVMODE結構,非托管的結構套結構,偶開始飄了~~~~
最後發現與其在C#中定義結構來對應非托管的結構,還不如直接用類來替代。所以定義了兩個類
PRINTER_INFO_2以及DEVMODE(注:由於PRINTER_INFO_2中只用到了DEVMODE結構來接收打印機驅動的信息,所以只定義了這個類,對於其他類都沒有做具體實現)。
在PRINTER_INFO_2中,對於所有的DWord類型數據,全部對應到Int32類型上面,而對於所有LPTSTR、LPDEVMODE以及PSECURITY_DESCRIPTOR一律對應到IntPtr指針類型。
為了獲取非托管中的數據,使用了一下函數獲取打印機信息
.
PRINTER_INFO_2 pi = new PRINTER_INFO_2();
//把數據從非托管內存傳送到到托管內存
for(int i = 0; i < numPrinters; i++)
{
Marshal.PtrToStructure( prInfo, pi ); //prInfo是由上面EnumPrinters獲得的打印機
string driver = Marshal.PtrToStringAuto( pi.pDriverName );
if ( printerdriver == "" || driver.ToLower().IndexOf( printerdriver ) != -1)
{
// 做相關處理
}
prInfo = new IntPtr(prInfo.ToInt32() + Marshal.SizeOf(typeof(PRINTER_INFO_2))); // 獲取下一個打印機信息段開始
}
.
問題至此基本解決。但C#中對非托管函數的調用,以及相互之間的數據封裝還是一個比較難的地方,有空還需要整理一下。
補充:在2.0中,fixed關鍵字可以用於定義一個固定大小的數組緩存,而不是像1.x中那樣還需要定義一個數字大小。但這種方式只能用於結構(struct)而不能用於類(class)的定義。