С# 4, COM-взаимодействие и UPnP: попытка триумвирата

Я пытаюсь написать немного кода (только для домашнего использования), который использует UPnP для обхода NAT, используя С# 4 и Microsoft COM-based NAT API обхода (Hnetcfg.dll).

К сожалению (или, возможно, к счастью), последний раз, когда мне приходилось делать COM-взаимодействие в .NET, было что-то около последнего ледникового периода, и я, похоже, в основном смущен тем, что С# использует динамические типы для взаимодействия и как писать обратный вызов (так что 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 and Remove работают абсолютно нормально. Потрясающе.

Проблема заключается в том, что я хотел бы знать, когда изменилось количество записей сопоставления портов, и для этого мне нужно зарегистрировать интерфейс обратного вызова (INATEventManager:: put_NumberOfEntriesCallback), который должен поддерживать интерфейсы INATNumberOfEntriesCallback или IDispatch. Объектный браузер VS2012 описывает INATEventManager:: put_NumberOfEntriesCallback таким образом:

dynamic NumberOfEntriesCallback { set; }

Правильно, поэтому у меня создалось впечатление, что в С# 4 мне не нужно ничего украшать с помощью причудливых атрибутов и что я могу зарегистрировать свой обратный вызов, просто поместив делегата в INATEventManager:: put_NumberOfEntriesCallback вульгарным способом и уйдя. NET, чтобы беспокоиться о IDispatch и очистить беспорядок; но кажется, что я ужасно ошибаюсь.

Итак, er... Что мне делать, чтобы вызывать метод NewNumberOfEntries?

Я также немного обеспокоен тем, что я могу написать nat.NATEventManager.NumberOfEntriesCallback = 1; или nat.NATEventManager.NumberOfEntriesCallback = "Sausages"; без исключения исключения.

Ответ 1

Кажется, что я смог заставить его работать. Два варианта - с пользовательским интерфейсом "INATNumberOfEntriesCallback" (который, кажется, не объявлен в библиотеке типов btw, вам нужно объявить его самостоятельно) и используя обычную отправку с DispId (0). Преобразование в IDispatch/IUnknown предварительно выполняется каркасом. Итак:

Вариант 1.

Объявите INATNumberOfEntriesCallback и создайте класс обратного вызова, который реализует этот интерфейс (сложная часть - 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), Так что в основном следующая конструкция, похоже, работает в режиме "отправки":

[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");
    }
}

Ответ 2

У меня была та же проблема, что и у вас, и поскольку на эту тему не так много, ваша публикация очень помогла! Это не позволило мне прокомментировать ваш ответ, потому что у меня недостаточно очков или чего-то еще, но ваш ответ лучший, но не совсем работает, как я думал, что это будет.

nat.NATEventManager.ExternalIPAddressCallback = new CallbackDisp();

Работает, используя ту же отправку, и сообщит вам, когда изменяется внешний IP-адрес. ОДНАКО,

nat.NATEventManager.NumberOfEntriesCallback = new CallbackDisp();

сообщает только изменения карты UPnP из этих условий: A.) Он был добавлен/удален экземпляром NATUPnP.. В этом случае:

    nat.StaticPortMappingCollection.Add();

ИЛИ B.) он был уже отображен при создании экземпляра:

 var nat = new UPnPNAT();

В качестве примера, если Utorrent запускался при запуске вашей программы, и у вас было что-то, чтобы заблокировать запуск программы (Console.WriteLine();), например.. Когда вы выходите из Utorrent, обратный вызов запускает и уведомляет вы меняете карту. Это именно то, что я хотел в первую очередь. Однако, если вы повторно открываете Utorrent или любое другое приложение, использующее UPnP, оно не будет запускать обратный вызов и не будет уведомлять вас об этом изменении.

Излишне говорить, что это было очень неприятно. Если вы выясните, пожалуйста, поделитесь! Я знаю, что могу легко реализовать функциональность, опросив StaticPortMappingCollection за определенный интервал, но для меня это кажется немного "взломанным".