如何从 C# 注册 `IPrintAsyncNotifyCallback`

How can I register for `IPrintAsyncNotifyCallback` from C#

我正在尝试从 v3 打印机驱动程序或端口监视器接收回调,但想从 C# 接收回调。 The docs say that we should be able to just pass一个实现IPrintAsyncNotifyCallbackRegisterForPrintAsyncNotifications的对象,在C++中有一个示例,但是我找不到能够在C#中实现这个接口的TLB。

此外,RegisterForPrintAsyncNotifications 似乎并未从文档 (Spoolss.dll) 中指定的 dll 中导出。

如何在没有 TLB 的情况下实现 IPrintAsyncNotifyCallback?我如何找到 RegisterForPrintAsyncNotifications

首先,考虑改为编写 v4 打印驱动程序。虽然 v3 驱动程序支持不太可能很快被取消,但 v4 驱动程序无疑是新开发的必由之路。他们还有一个用于回调的 .Net API,这避免了必须自己编写互操作。 See PrinterExtensionSample.

在没有 TLB 的情况下实现 COM 接口

要实现 IPrintAsyncNotifyCallbackIPrintAsyncNotifyChannel 以及它引用的 IPrintAsyncNotifyDataObject 接口,我们可以在 prnasnot.h 中找到它们的定义,转载如下。

// ... 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;
};

顶部的 IID 以及方法排序和签名允许我们 translate these to the appropriate ComImports

[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
}

正在查找 RegisterForPrintAsyncNotifications

由于 C++ 示例按原样工作,并且 RegisterForPrintAsyncNotifications 是一个导入 - 而不是宏 - 链接器将查看文档中命名的 WinSpool.lib 文件以找到合适的 dll .我们可以使用 dumpbin.

做同样的事情
c:\Drop>dumpbin -headers "C:\Program Files (x86)\Windows Kits\Lib.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 ...

这表明 RegisterForPrintAsyncNotifications 实际上是从 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 */;
    }
}