Подключение к событиям COM в C# - поддержка как управляемых, так и неуправляемых серверов
Я пишу код C#, который должен подключаться к событиям COM. Я реализовал использование IConnectionPointContainer и IConnectionPoint таким образом:
IConnectionPointContainer connectionPointContainer = internalGenerator as IConnectionPointContainer;
if (connectionPointContainer == null)
{
Debug.Fail("The script generator doesn't support the required interface - IConnectionPointContainer");
throw new InvalidCastException("The script generator doesn't support the required interface - IConnectionPointContainer");
}
Guid IID_IScriptGeneratorEvents = typeof(IScriptGeneratorCallback).GUID;
connectionPointContainer.FindConnectionPoint(ref IID_IScriptGeneratorEvents, out m_connectionPoint);
m_connectionPoint.Advise(this, out m_cookie);
Проблема заключается в том, что когда COM-сервер фактически реализован в.Net (скажем, C#), после того, как.Net его создает, он обрабатывает его как объект.Net, а не как объект COM. Поскольку объект.Net не реализует интерфейс IConnectionPointContainer, я получаю нулевое значение при попытке привести объект к этому интерфейсу.
Любая идея, как я могу обойти это? Конечно, я могу реализовать IConnectionPointContainer самостоятельно на C# COM-сервере, однако мне бы хотелось более простое решение, которое я могу легко объяснить другим разработчикам, которым необходимо внедрить COM-сервер.
PS Я должен использовать IConnectionPointContainer, поскольку COM-сервер может быть реализован не в.Net (C++, Java).
Спасибо, Инбар
5 ответов
Я не нашел способ сделать это. Со временем я определю другой интерфейс в.Net и напишу 2 пути кода, один для объектов.Net и один для реальных объектов COM.
IConnectionPointContainer реализован в CCW (вызываемой оболочке COM), которую.NET автоматически генерирует при внешнем представлении вашего объекта.NET как объекта COM.
Попробуйте вызвать Marshal.GetComInterfaceForObject для объекта.NET, чтобы получить COM-интерфейс для IConnectionPointContainer, а не просто приводить его.
обновить... и если это не сработает, Marshal.GetIUnknownForObject должен что-то вернуть, и, возможно, тогда он будет поддерживать вызов Marshal.QueryInterface.
Я получил немного дальше, так как у меня возникла та же проблема с ручным подключением событий при представлении элемента управления.NET в качестве ActiveX с помощью COM-взаимодействия.
Если вы немного покопаетесь в классе UserControl с помощью Reflector (например, Redgate Reflector), вы увидите вложенный класс ActiveXImpl, который содержит другой вложенный статический класс с именем 'AdviseHelper', который имеет члены ComConnectionPointContainer и ComConnectionPoint. Он также имеет вспомогательные функции для подключения точек, как вам нужно.
Есть проблема. Когда COM-взаимодействие от вашего элемента управления (источника события) подключается к контейнеру точки подключения (который содержит приемник событий для событий вашего элемента управления), вызывается функция IQuickActivate.QuickActivate, которая затем вызывает класс AdviseHelper 'AdviseConnectionPoint функция.
Указатель интерфейса IUnknown на приемник событий на вашем клиенте (т. Е. Не на вашем контроле, а на том, что его содержит) передается в эту функцию QuickActivate (параметр 'pUnkEventSink'. В отражателе эта функция выглядит следующим образом, и я выделил, где она делает фактическое подключение события:
internal void QuickActivate(UnsafeNativeMethods.tagQACONTAINER pQaContainer, UnsafeNativeMethods.tagQACONTROL pQaControl)
{
int num;
this.LookupAmbient(-701).Value = ColorTranslator.FromOle((int) pQaContainer.colorBack);
this.LookupAmbient(-704).Value = ColorTranslator.FromOle((int) pQaContainer.colorFore);
if (pQaContainer.pFont != null)
{
Control.AmbientProperty ambient = this.LookupAmbient(-703);
IntSecurity.UnmanagedCode.Assert();
try
{
Font font2 = Font.FromHfont(((UnsafeNativeMethods.IFont) pQaContainer.pFont).GetHFont());
ambient.Value = font2;
}
catch (Exception exception)
{
if (ClientUtils.IsSecurityOrCriticalException(exception))
{
throw;
}
ambient.Value = null;
}
finally
{
CodeAccessPermission.RevertAssert();
}
}
pQaControl.cbSize = UnsafeNativeMethods.SizeOf(typeof(UnsafeNativeMethods.tagQACONTROL));
this.SetClientSite(pQaContainer.pClientSite);
if (pQaContainer.pAdviseSink != null)
{
this.SetAdvise(1, 0, (IAdviseSink) pQaContainer.pAdviseSink);
}
IntSecurity.UnmanagedCode.Assert();
try
{
((UnsafeNativeMethods.IOleObject) this.control).GetMiscStatus(1, out num);
}
finally
{
CodeAccessPermission.RevertAssert();
}
pQaControl.dwMiscStatus = num;
if ((pQaContainer.pUnkEventSink != null) && (this.control is UserControl))
{
Type defaultEventsInterface = GetDefaultEventsInterface(this.control.GetType());
if (defaultEventsInterface != null)
{
IntSecurity.UnmanagedCode.Assert();
try
{
**AdviseHelper.AdviseConnectionPoint(this.control, pQaContainer.pUnkEventSink, defaultEventsInterface, out pQaControl.dwEventCookie);**
}
catch (Exception exception2)
{
if (ClientUtils.IsSecurityOrCriticalException(exception2))
{
throw;
}
}
finally
{
CodeAccessPermission.RevertAssert();
}
}
}
if ((pQaContainer.pPropertyNotifySink != null) && UnsafeNativeMethods.IsComObject(pQaContainer.pPropertyNotifySink))
{
UnsafeNativeMethods.ReleaseComObject(pQaContainer.pPropertyNotifySink);
}
if ((pQaContainer.pUnkEventSink != null) && UnsafeNativeMethods.IsComObject(pQaContainer.pUnkEventSink))
{
UnsafeNativeMethods.ReleaseComObject(pQaContainer.pUnkEventSink);
}
}
Переменная 'pUnkEventSink' передается в эту функцию через структуру tagQACONTROL, но, как вы можете видеть, в отличие от IAdviseSink, контейнера элемента управления, стиля шрифта и т. Д., Эта переменная не установлена ни для одного члена класса ActiveXImpl, и следовательно, вы не можете добраться до него после того, как эта функция была первоначально вызвана фреймворком.
Вам необходимо получить эту переменную IUnknown pUnkEventSink для вызова функции AdviseHelper.AdviseConnectionPoint(), которая будет выполнять ручное подключение событий. И это проблема, с которой я столкнулся - к сожалению, вы просто не можете ее решить.
У кого-нибудь еще были какие-то дальнейшие разработки с этим, дайте мне знать!
Я знаю, что немного опоздал на это, но я смог заставить работать события приемника, используя IConnectionPoint. Смотрите мой ответ здесь. Специально проверить MyAppDotNetWrapper
Класс и как он используется в тесте.
В конечном итоге я думаю, что ваш m_connectionPoint.Advise(this, out m_cookie);
не удается, потому что this
должно быть [ComVisible(true)]
, [ClassInterface(ClassInterfaceType.None)]
а также public
,
Проблема заключается в том, что выполнение вызова GetIUnknownForObject возвращает указатель, который затем можно успешно вызвать, чтобы получить IConnectionPointContainer для объекта, используя его GUID. Но этот вызов QueryInterface затем просто возвращает исходный объект.NET, а не интерфейс IConnectionPointContainer.
Я также застрял с этим, и если у кого-то есть какие-либо дополнительные идеи, пожалуйста, поделитесь. Мой сценарий состоит в том, что я представляю элемент управления.NET как ActiveX с помощью взаимодействия COM. У меня есть ComSourceInterface, определенный для приемника событий, но на хосте VB6 события не подключаются должным образом. Поэтому я пытаюсь получить интерфейс IConnectionPointContainer для моего открытого элемента управления.NET, чтобы вручную подключать события, но не могу добраться до этого интерфейса, если он действительно реализован - или, возможно, я просто смотрю на неправильный объект?