架构深渊

慢慢走进程序的深渊……关注领域驱动设计、测试驱动开发、设计模式、企业应用架构模式……积累技术细节,以设计架构为宗。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

关于C#中枚举打印机

Posted on 2008-11-06 19:47  chen eric  阅读(708)  评论(0编辑  收藏  举报

解决方案一 使用WMI获取打印机信息

WMI,全称Windows Management Instrumentation。是可伸缩的系统管理结构,它采用一个统一的、基于标准的、可扩展的面向对象接口。WMI 为您提供与系统管理信息和基础 WMI API 交互的标准方法。WMI 主要由系统管理应用程序开发人员和管理员用来访问和操作系统管理信息。

.Net Framework中System.Management类提供了对WMI的支持,其中ManagementObjectSearcher用于根据指定的查询或枚举检索 ManagementObjectManagementClass 对象的集合。

 /// <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#中对非托管函数的调用,以及相互之间的数据封装还是一个比较难的地方,有空还需要整理一下。