我正在尝试使用C#编写的Windows Service在Windows 10 x64下监视打印队列中的作业。
我的代码中使用“ FindFirstPrinterChangeNotification”和“ FindNextPrinterChangeNotification”打印API的部分与此项目相同。(https://www.codeproject.com/Articles/51085/Monitor-jobs-in-a-printer-queue-NET)
唯一的区别是我仅监视“ PRINTER_CHANGES.PRINTER_CHANGE_ADD_JOB”事件。
部分代码:
public class PrintQueueMonitor
{
#region DLL Import Functions
[DllImport("winspool.drv",
EntryPoint = "OpenPrinterA",SetLastError = true,
CharSet = CharSet.Ansi,ExactSpelling = true,
CallingConvention = CallingConvention.StdCall)]
public static extern bool OpenPrinter(String pPrinterName,
out IntPtr phPrinter,
Int32 pDefault);
[DllImport("winspool.drv",
EntryPoint = "ClosePrinter",
SetLastError = true,
ExactSpelling = true,
CallingConvention = CallingConvention.StdCall)]
public static extern bool ClosePrinter
(Int32 hPrinter);
[DllImport("winspool.drv",
EntryPoint = "FindFirstPrinterChangeNotification",
SetLastError = true, CharSet = CharSet.Ansi,
ExactSpelling = true,
CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr FindFirstPrinterChangeNotification
([InAttribute()] IntPtr hPrinter,
[InAttribute()] Int32 fwFlags,
[InAttribute()] Int32 fwOptions,
[InAttribute(),
MarshalAs(UnmanagedType.LPStruct)]
PRINTER_NOTIFY_OPTIONS pPrinterNotifyOptions);
[DllImport("winspool.drv", EntryPoint =
"FindNextPrinterChangeNotification",
SetLastError = true, CharSet = CharSet.Ansi,
ExactSpelling = false,
CallingConvention = CallingConvention.StdCall)]
public static extern bool FindNextPrinterChangeNotification
([InAttribute()] IntPtr hChangeObject,
[OutAttribute()] out Int32 pdwChange,
[InAttribute(),
MarshalAs(UnmanagedType.LPStruct)]
PRINTER_NOTIFY_OPTIONS pPrinterNotifyOptions,
[OutAttribute()] out IntPtr lppPrinterNotifyInfo
);
#endregion
#region Constants
const int PRINTER_NOTIFY_OPTIONS_REFRESH = 1;
#endregion
#region Events
public event PrintJobStatusChanged OnJobStatusChange;
#endregion
#region private variables
private IntPtr _printerHandle = IntPtr.Zero;
private string _spoolerName = "";
private ManualResetEvent _mrEvent = new ManualResetEvent(false);
private RegisteredWaitHandle _waitHandle = null;
private IntPtr _changeHandle = IntPtr.Zero;
private PRINTER_NOTIFY_OPTIONS _notifyOptions =
new PRINTER_NOTIFY_OPTIONS();
private Dictionary<int, string> objJobDict =
new Dictionary<int, string>();
private PrintQueue _spooler = null;
#endregion
#region constructor
public PrintQueueMonitor(string strSpoolName)
{
// Let us open the printer and get the printer handle.
_spoolerName = strSpoolName;
//Start Monitoring
Start();
}
#endregion
#region destructor
~PrintQueueMonitor()
{
Stop();
}
#endregion
#region StartMonitoring
public void Start()
{
OpenPrinter(_spoolerName, out _printerHandle, 0);
if (_printerHandle != IntPtr.Zero)
{
//We got a valid Printer handle.
//Let us register for change notification....
_changeHandle = FindFirstPrinterChangeNotification(
_printerHandle, (int)PRINTER_CHANGES.PRINTER_CHANGE_JOB, 0,
_notifyOptions);
// We have successfully registered for change
// notification. Let us capture the handle...
_mrEvent.Handle = _changeHandle;
//Now, let us wait for change notification from the printer queue....
_waitHandle = ThreadPool.RegisterWaitForSingleObject(_mrEvent,
new WaitOrTimerCallback(PrinterNotifyWaitCallback),
_mrEvent, -1, true);
}
_spooler = new PrintQueue(new PrintServer(), _spoolerName);
foreach (PrintSystemJobInfo psi in _spooler.GetPrintJobInfoCollection())
{
objJobDict[psi.JobIdentifier] = psi.Name;
}
}
#endregion
#region StopMonitoring
public void Stop()
{
if (_printerHandle != IntPtr.Zero)
{
ClosePrinter((int)_printerHandle);
_printerHandle = IntPtr.Zero;
}
}
#endregion
#region Callback Function
public void PrinterNotifyWaitCallback(Object state,bool timedOut)
{
if (_printerHandle == IntPtr.Zero) return;
#region read notification details
_notifyOptions.Count = 1;
int pdwChange = 0;
IntPtr pNotifyInfo = IntPtr.Zero;
bool bResult = FindNextPrinterChangeNotification(_changeHandle,
out pdwChange, _notifyOptions, out pNotifyInfo);
//If the Printer Change Notification Call did not give data, exit code
if ((bResult == false) || (((int)pNotifyInfo) == 0)) return;
//If the Change Notification was not relgated to job, exit code
bool bJobRelatedChange =
((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_ADD_JOB) ==
PRINTER_CHANGES.PRINTER_CHANGE_ADD_JOB) ||
((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_SET_JOB) ==
PRINTER_CHANGES.PRINTER_CHANGE_SET_JOB) ||
((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_DELETE_JOB) ==
PRINTER_CHANGES.PRINTER_CHANGE_DELETE_JOB) ||
((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_WRITE_JOB) ==
PRINTER_CHANGES.PRINTER_CHANGE_WRITE_JOB);
if (!bJobRelatedChange) return;
#endregion
#region populate Notification Information
//Now, let us initialize and populate the Notify Info data
PRINTER_NOTIFY_INFO info =
(PRINTER_NOTIFY_INFO)Marshal.PtrToStructure(pNotifyInfo,
typeof(PRINTER_NOTIFY_INFO));
int pData = (int)pNotifyInfo +
Marshal.SizeOf(typeof(PRINTER_NOTIFY_INFO));
PRINTER_NOTIFY_INFO_DATA[] data =
new PRINTER_NOTIFY_INFO_DATA[info.Count];
for (uint i = 0; i < info.Count; i++)
{
data[i] = (PRINTER_NOTIFY_INFO_DATA)Marshal.PtrToStructure(
(IntPtr)pData, typeof(PRINTER_NOTIFY_INFO_DATA));
pData += Marshal.SizeOf(typeof(PRINTER_NOTIFY_INFO_DATA));
}
#endregion
#region iterate through all elements in the data array
for (int i = 0; i < data.Count(); i++)
{
if ( (data[i].Field ==
(ushort)PRINTERJOBNOTIFICATIONTYPES.JOB_NOTIFY_FIELD_STATUS) &&
(data[i].Type == (ushort)PRINTERNOTIFICATIONTYPES.JOB_NOTIFY_TYPE)
)
{
JOBSTATUS jStatus = (JOBSTATUS)Enum.Parse(typeof(JOBSTATUS),
data[i].NotifyData.Data.cbBuf.ToString());
int intJobID = (int)data[i].Id;
string strJobName = "";
PrintSystemJobInfo pji = null;
try
{
_spooler = new PrintQueue(new PrintServer(), _spoolerName);
pji = _spooler.GetJob(intJobID);
if (!objJobDict.ContainsKey(intJobID))
objJobDict[intJobID] = pji.Name;
strJobName = pji.Name;
}
catch
{
pji = null;
objJobDict.TryGetValue(intJobID, out strJobName);
if (strJobName == null) strJobName = "";
}
if (OnJobStatusChange != null)
{
//Let us raise the event
OnJobStatusChange(this,
new PrintJobChangeEventArgs(intJobID,strJobName,jStatus,pji));
}
}
}
#endregion
#region reset the Event and wait for the next event
_mrEvent.Reset();
_waitHandle = ThreadPool.RegisterWaitForSingleObject(_mrEvent,
new WaitOrTimerCallback(PrinterNotifyWaitCallback), _mrEvent, -1, true);
#endregion
}
#endregion
}
问题是:当我打印某些东西时,很少收到通知,而作业ID来自
int intJobID = (int)data[i].Id;
line与我在操作系统的打印队列窗口中看到的id不同。然后,当然在下面的代码行中找不到具有该ID的作业:
try
{
_spooler = new PrintQueue(new PrintServer(), _spoolerName);
pji = _spooler.GetJob(intJobID);
if (!objJobDict.ContainsKey(intJobID))
objJobDict[intJobID] = pji.Name;
strJobName = pji.Name;
}
它在此行引发异常:
pji = _spooler.GetJob(intJobID);
这种情况很少见,但有可能发生。我不知道为什么会这样,怎么解决?