一、需求
由于公司打印纸张浪费比较严重,领导要求对所有的打印进行记录。对比了几款商业软件有的功能比较强大但是价格比较贵而且好多功能并不需要,而有的功能又过于简单。我需要的只是对打印进行记录并不需要计费等功能,而且考虑最好可以和公司现有的OA系统整合在一起,让领导在OA的界面中就可以查询到打印记录,最后决定尝试自己动手进行开发。
二、查找资料
由于平时用C#比较多一些所以首先查找了一些用C#写的开源程序,但是由于版本都比较老了根本无法运行。又找到一个C++写的可以运行但是C++实在是搞不定,也不是一两天能学会的所以放弃。虽然找了两天没有找到可以运行的完整的程序,但是确定了一个方向,就是调用Windows API函数Enumjobs来获得打印记录。并且还搜到了一份现成的用C#调用Enumjobs的代码,虽然不完整但是确实可以用。
三、规划
1、首先程序需要安装到打印服务器所以最好是以服务的方式运行
2、需要通过配置文件来确定需要监控的打印机
四、开始动手
1、新建一个服务
(1)首先打开Visual studio 2008,新建一个windows service
(2)在Service1上点右键选Add Installer
(3)在ProjectInstaller中设置好服务的运行账号和相关设置
serviceProcessInstaller1 设置属性account为LocalSystem
serviceInstaller1 设置属性starttype为Automatic
(4)添加一个timer
注意这里不能添加工具栏那个,要添加System.timer命名空间下的
(5)设置服务运行时启动timer
- protected override void OnStart(string[] args)
- {
- timer1.Enabled = true;
- }
(6)定时调用监控程序
- private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
- {
- pm.peekPrinterJobs(config.GetPrinterName());
- }
(7)读取配置文件
- private static string ReadXml(string nodeName, string attr)
- {
- XmlDocument xml = new XmlDocument();
- xml.Load("config.xml");
- return xml.SelectSingleNode("config/" + nodeName).Attributes[attr].Value;
- }
- public static string GetPrinterName()
- {
- return ReadXml("printer", "name");
- } public static string GetOutputPath()
- {
- return ReadXml("output", "path");
- }
配置文件
- <?xml version="1.0" encoding="utf-8" ?>
- <config>
- <output path="c:\\monitor.log"></output>
- <printer name="HP5100"></printer>
- </config>
(8)新建一个类,核心的监控代码,主要就是通过调用peekPrinterJobs这个函数返回打印的记录
- public static int oldPrintId = 0;
- [StructLayout(LayoutKind.Sequential)]
- public struct SYSTEMTIME
- {
- public short wYear;
- public short wMonth;
- public short wDayOfWeek;
- public short wDay;
- public short wHour;
- public short wMinute;
- public short wSecond;
- public short wMilliseconds;
- }
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
- public struct JOB_INFO_1
- {
- public int JobId;
- public string pPrinterName;
- public string pMachineName;
- public string pUserName;
- public string pDocument;
- public string pDatatype;
- public string pStatus;
- public int Status;
- public int Priority;
- public int Position;
- public int TotalPages;
- public int PagesPrinted;
- public SYSTEMTIME Submitted;
- }
- [DllImport("winspool.drv", CharSet = CharSet.Auto)]
- public static extern bool OpenPrinter(string pPrinterName, out IntPtr phPrinter, IntPtr pDefault);
- [DllImport("winspool.drv", CharSet = CharSet.Auto)]
- public static extern bool ClosePrinter(IntPtr hPrinter);
- [DllImport("winspool.drv", CharSet = CharSet.Auto)]
- public static extern int EnumJobs(IntPtr hPrinter, int FirstJob, int NoJobs, int Level, IntPtr pInfo, int cdBuf,
- out int pcbNeeded, out int pcReturned);
- public static void peekPrinterJobs(string printerToPeek)
- {
- IntPtr handle;
- int FirstJob = 0;
- int NumJobs = 127;
- int pcbNeeded;
- int pcReturned;
- // open printer
- OpenPrinter(printerToPeek, out handle, IntPtr.Zero);
- // get num bytes required, here we assume the maxt job for the printer quest is 128 (0..127)
- EnumJobs(handle, FirstJob, NumJobs, 1, IntPtr.Zero, 0, out pcbNeeded, out pcReturned);
- // allocate unmanaged memory
- IntPtr pData = Marshal.AllocHGlobal(pcbNeeded);
- // get structs
- EnumJobs(handle, FirstJob, NumJobs, 1, pData, pcbNeeded, out pcbNeeded, out pcReturned);
- // create array of managed job structs
- JOB_INFO_1[] jobs = new JOB_INFO_1[pcReturned];
- // marshal struct to managed
- int pTemp = pData.ToInt32(); //start pointer
- for (int i = 0; i < pcReturned; ++i)
- {
- jobs[i] = (JOB_INFO_1)Marshal.PtrToStructure(new IntPtr(pTemp), typeof(JOB_INFO_1));
- if (jobs[i].Status != 16) break;
- if (jobs[i].JobId == oldPrintId)
- {
- break;
- }
- else
- {
- pTemp += Marshal.SizeOf(typeof(JOB_INFO_1));
- //记录到日志,可以自己写方法写到数据库或者其他地方
- RecordJobToLog(jobs[i]);
- oldPrintId = jobs[i].JobId;
- }
- }
- Marshal.FreeHGlobal(pData);
- ClosePrinter(handle);
- }
- private static void RecordJobToLog(JOB_INFO_1 job)
- {
- string logText = job.JobId + "-" + job.pMachineName + "-" + job.pUserName + "-" + job.pDocument + "-" + job.PagesPrinted + "-" + job.TotalPages + "-" + job.Status + "-" + DateTime.Now.ToString("yyyy-MM-dd HH:mm");
- WriteToLog(logText);
- }
3、安装部署
编译程序,把程序拷到服务器
然后运行c:\Windows\Microsoft.NET\Framework\v2.0.50727\installutil.exe installutil yourproject.exe 安装服务
卸载服务c:\Windows\Microsoft.NET\Framework\v2.0.50727\installutil.exe /u yourproject.exe
打印一份文件,然后就可以到指定目录查看打印日志了
服务的配置文件一定要拷贝到c:\windows\system32中
五、总结
通过这个程序学习了服务的开发、读取xml和C#调用api函数,自己开发的优势是可以把日志存已自己需要的格式存放在需要的地方,比如配置文件或者数据库方便和现有平台的整合。JOB_INFO_1所得到的数据比较有限,比如无法取得打印份数等等,通过JOB_INFO_2可以取得更多的打印信息,原理都是一样的先能用慢慢在修改完善。
六、参考链接
http://msdn.microsoft.com/en-us/library/dd162861(v=VS.85).aspx
http://msdn.microsoft.com/en-us/library/dd162625(VS.85).aspx
http://msdn.microsoft.com/en-us/library/dd145020(v=VS.85).aspx
http://support.microsoft.com/kb/160129http://msdn.microsoft.com/en-us/library/ff556443.aspx
http://msdn.microsoft.com/en-us/library/dd183565(v=VS.85).aspx
http://www.cnblogs.com/waxic/archive/2009/05/07/1451952.html
http://www.cnblogs.com/caca/archive/2005/02/25/109028.html
本文出自 “dqw” 博客,转载请与作者联系!