一、需求
由於公司打印紙張浪費比較嚴重,領導要求對所有的打印進行記錄。對比了幾款商業軟件有的功能比較強大但是價格比較貴而且好多功能並不需要,而有的功能又過於簡單。我需要的只是對打印進行記錄並不需要計費等功能,而且考慮最好可以和公司現有的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” 博客,轉載請與作者聯繫!