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