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