I am trying to receive a callback from a v3 printer driver or port monitor, but want to do so from C#. The docs say that we should be able to just pass an object implementing IPrintAsyncNotifyCallback
to RegisterForPrintAsyncNotifications
, and there is a sample in C++, but I cannot find a TLB to be able to implement this interface in C#.
Further, RegisterForPrintAsyncNotifications
doesn't appear to be exported from the dll named in the documentation (Spoolss.dll
).
How can I implement IPrintAsyncNotifyCallback
without a TLB? How do I locate RegisterForPrintAsyncNotifications
?
Firstly, consider writing a v4 print driver instead. While it is not likely that v3 driver support will be dropped at any point soon, v4 drivers are certainly the way to go for new development. They also have a .Net API for callbacks, which avoids having to write the interop yourself. See PrinterExtensionSample
.
To implement IPrintAsyncNotifyCallback
and the IPrintAsyncNotifyChannel
and the IPrintAsyncNotifyDataObject
interfaces it refereces, we can find their definitions in prnasnot.h
, reproduced below.
// ... snip ...
DEFINE_GUID(IID_IPrintAsyncNotifyChannel, 0x4a5031b1, 0x1f3f, 0x4db0, 0xa4, 0x62, 0x45, 0x30, 0xed, 0x8b, 0x04, 0x51);
DEFINE_GUID(IID_IPrintAsyncNotifyCallback, 0x7def34c1, 0x9d92, 0x4c99, 0xb3, 0xb3, 0xdb, 0x94, 0xa9, 0xd4, 0x19, 0x1b);
DEFINE_GUID(IID_IPrintAsyncNotifyDataObject, 0x77cf513e, 0x5d49, 0x4789, 0x9f, 0x30, 0xd0, 0x82, 0x2b, 0x33, 0x5c, 0x0d);
// ... snip ...
DECLARE_INTERFACE_(IPrintAsyncNotifyDataObject, IUnknown)
{
// ... snip ...
};
// ... snip ...
DECLARE_INTERFACE_(IPrintAsyncNotifyChannel, IUnknown)
{
// ... snip ...
};
// ... snip ...
DECLARE_INTERFACE_(IPrintAsyncNotifyCallback, IUnknown)
{
STDMETHOD(QueryInterface)(
THIS_
_In_ REFIID riid,
_Outptr_ void **ppvObj
) PURE;
STDMETHOD_(ULONG, AddRef)(
THIS
) PURE;
STDMETHOD_(ULONG, Release)(
THIS
) PURE;
STDMETHOD(OnEventNotify)(
THIS_
_In_ IPrintAsyncNotifyChannel *pChannel,
_In_ IPrintAsyncNotifyDataObject *pData
) PURE;
STDMETHOD(ChannelClosed)(
THIS_
_In_ IPrintAsyncNotifyChannel *pChannel,
_In_ IPrintAsyncNotifyDataObject *pData
) PURE;
};
The IID
s at the top and the method ordering and signatures allow us to translate these to the appropriate ComImport
s.
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("77cf513e-5d49-4789-9f30-d0822b335c0d")]
public interface IPrintAsyncNotifyDataObject
{
void AcquireData(out IntPtr data, out uint cbData, out IntPtr schema);
void ReleaseData();
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("4a5031b1-1f3f-4db0-a462-4530ed8b0451")]
public interface IPrintAsyncNotifyChannel
{
void SendNotification(IPrintAsyncNotifyDataObject data);
void CloseChannel(IPrintAsyncNotifyDataObject data);
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("7def34c1-9d92-4c99-b3b3-db94a9d4191b")]
public interface IPrintAsyncNotifyCallback
{
void OnEventNotify(IPrintAsyncNotifyChannel channel, IPrintAsyncNotifyDataObject data);
void ChannelClosed(IPrintAsyncNotifyChannel channel, IPrintAsyncNotifyDataObject data);
}
public enum PrintAsyncNotifyUserFilter : uint
{
kPerUser = 0,
kAllUsers = 1
}
public enum PrintAsyncNotifyConversationStyle : uint
{
kBiDirectional = 0,
kUniDirectional = 1
}
Since the C++ sample works as-is, and RegisterForPrintAsyncNotifications
is an import – rather than a macro – the linker will look at the WinSpool.lib
file named in the documentation to find the appropriate dll. We can do the same using dumpbin
.
c:\Drop>dumpbin -headers "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10586.0\um\x64\WinSpool.Lib" > out.txt
// ... snip ...
Version : 0
Machine : 8664 (x64)
TimeDateStamp: 56F9F510 Mon Mar 28 22:22:56 2016
SizeOfData : 00000030
DLL name : WINSPOOL.DRV
Symbol name : RegisterForPrintAsyncNotifications
Type : code
Name type : name
Hint : 162
Name : RegisterForPrintAsyncNotifications
// ... snip ...
which shows that RegisterForPrintAsyncNotifications
is actually exported from WINSPOOL.DRV
.
[DllImport("WINSPOOL.DRV", PreserveSig = false, ExactSpelling = true)]
public static extern void RegisterForPrintAsyncNotifications(
[MarshalAs(UnmanagedType.LPWStr)] string name,
[MarshalAs(UnmanagedType.LPStruct)] Guid notificationType, PrintAsyncNotifyUserFilter filter,
PrintAsyncNotifyConversationStyle converstationStyle,
IPrintAsyncNotifyCallback callback, out PrintAsyncNotificationSafeHandle handle);
[DllImport("WINSPOOL.DRV", PreserveSig = true, ExactSpelling = true)]
public static extern int UnRegisterForPrintAsyncNotifications(IntPtr handle);
public sealed class PrintAsyncNotificationSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public PrintAsyncNotificationSafeHandle()
: base(true)
{
}
protected override bool ReleaseHandle()
{
return UnRegisterForPrintAsyncNotifications(handle) == 0 /* S_OK */;
}
}