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

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

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

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

dynamic NumberOfEntriesCallback { set; }

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

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

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

2 ответа

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

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

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

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 с заданным интервалом, но мне это кажется немного "хакерским".

Другие вопросы по тегам