我正在尝试编写一些使用UPnP进行NAT遍历的代码(仅供家庭使用),使用C#4和Microsoft的基于COM的NAT traversal API(Hnetcfg.dll)。

不幸的是(或者幸运的是)我上一次在.NET中进行COM互操作是在上一个冰河时代前后的某个时候,而且我似乎对C#使用动态类型进行互操作以及如何编写回调(从根本上感到困惑) COM服务器调用我的托管代码)。

以下是令人兴奋的几行代码:

// Referencing COM NATUPNPLib ("NATUPnP 1.0 Type Library")

using System;
using NATUPNPLib;

class NATUPnPExample
{
    public delegate void NewNumberOfEntriesDelegate(int lNewNumberOfEntries);

    public static void NewNumberOfEntries(int lNewNumberOfEntries)
    {
        Console.WriteLine("New number of entries: {0}", lNewNumberOfEntries);
    }

    public static void Main(string[] args)
    {
        UPnPNAT nat = new UPnPNAT();
        NewNumberOfEntriesDelegate numberOfEntriesCallback = NewNumberOfEntries;

        nat.NATEventManager.NumberOfEntriesCallback = numberOfEntriesCallback;

        nat.StaticPortMappingCollection.Add(4555, "TCP", 4555, "192.168.0.1", true, "UPnPNAT Test");

        // Presumably my NewNumberOfEntries() method should be called by the COM component about now

        nat.StaticPortMappingCollection.Remove(4555, "TCP");
    }
}

在上面的代码中,Add和Remove调用绝对可以正常工作。了不起。

麻烦的是,我还想知道何时更改了端口映射条目的数量,为此,我需要注册一个回调接口(interface)(INATEventManager::put_NumberOfEntriesCallback),该接口(interface)必须支持INATNumberOfEntriesCallback或IDispatch接口(interface)。 VS2012的对象浏览器由此描述了INATEventManager::put_NumberOfEntriesCallback:
dynamic NumberOfEntriesCallback { set; }

是的,所以我给人的印象是,在C#4中,我不必装饰任何带有奇特属性的东西,而我可以通过简单地将代理人塞入INATEventManager::put_NumberOfEntriesCallback并让.NET烦恼来注册我的回调函数。关于IDispatch并清理困惑;但看来我错了。

因此,呃...我应该怎么做才能确保调用NewNumberOfEntries方法?

我还稍微担心我可以编写nat.NATEventManager.NumberOfEntriesCallback = 1;nat.NATEventManager.NumberOfEntriesCallback = "Sausages";而不会引发异常。

最佳答案

看来我能够做到这一点。两个选项-使用自定义接口(interface)“INATNumberOfEntriesCallback”(似乎没有在类型库btw中声明,您需要自己声明),并使用带有DispId(0)的纯调度。框架自动执行到IDispatch/IUnknown的转换。所以:

选项1。

声明INATNumberOfEntriesCallback并创建一个实现该接口(interface)的回调类(棘手的部分是Guid-它来自“Natupnp.h”文件,并且似乎不在类型库中)。

// declare INATNumberOfEntriesCallback interface
[ComVisible(true)]
[Guid("C83A0A74-91EE-41B6-B67A-67E0F00BBD78")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface INATNumberOfEntriesCallback
{
    void NewNumberOfEntries(int val);
};

// implement callback object
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class CallbackNewNumberOfEntries : INATNumberOfEntriesCallback
{
    public void NewNumberOfEntries(int val)
    {
        Console.WriteLine("Number of entries changed: {0}", val);
    }
}

class NATUPnPExample
{
    public static void Main(string[] args)
    {
        var nat = new UPnPNAT();

        nat.NATEventManager.NumberOfEntriesCallback = new CallbackNewNumberOfEntries();

        nat.StaticPortMappingCollection.Add(4555, "TCP", 4555, "192.168.0.1", true, "UPnPNAT Test");

        // Presumably my NewNumberOfEntries() method should be called by the COM component about now

        nat.StaticPortMappingCollection.Remove(4555, "TCP");
    }
}

选项2。

使用普通调度。该文档说您可以使用dispid(0),并且应使用4(!)参数对其进行调用(请参阅docs中的备注部分)。因此,基本上,以下构造似乎以"dispatch"方式起作用:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class CallbackDisp
{
    [DispId(0)]
    public void OnChange(string updateType, object obj, object name, object val)
    {
        Console.WriteLine("{0}: {1} = {2}", updateType, name, val);
    }
}

class NATUPnPExample
{
    public static void Main(string[] args)
    {
        var nat = new UPnPNAT();

        nat.NATEventManager.NumberOfEntriesCallback = new CallbackDisp();

        nat.StaticPortMappingCollection.Add(4555, "TCP", 4555, "192.168.0.1", true, "UPnPNAT Test");

        // Presumably my NewNumberOfEntries() method should be called by the COM component about now

        nat.StaticPortMappingCollection.Remove(4555, "TCP");
    }
}

10-07 16:20
查看更多