引言
前段時間為客戶開發一套印表機配套的軟體,對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屬性提供了對Managed 程式碼與Unmanaged 程式碼見資料封送。
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#中對非託管函數的調用,以及相互之間的資料封裝還是一個比較難的地方,有空還需要整理一下。
文章來源:http://spaces.msn.com/sharkoo/Blog/cns!D8E832CE4545AF!158.entry
補充:在2.0中,fixed關鍵字可以用於定義一個固定大小的數組緩衝,而不是像1.x中那樣還需要定義一個數字大小。但這種方式只能用於結構(struct)而不能用於類(class)的定義。