c # — Предоставление управляемых событий COM с использованием переполнения стека

Можно выставлять управляемые события, написанные на C #, для показа и использования в COM-объекте, написанном с использованием c ++. не слишком знаком с com и atl. Не могли бы вы показать, как будет выглядеть сторона C ++ для примера, показанного в статье MSDN

http://msdn.microsoft.com/en-us/library/dd8bf0x3.aspx

Показанный код VB6 доказывает, что это выполнимо.

1

Решение

Самый простой способ в C ++ — IMO — реализовать приемник событий с помощью ATL. IDispEventImpl а также IDispEventSimpleImpl шаблоны. Объяснение с примером проекта можно найти здесь.

Есть много интернет-ресурсов о том, как это сделать, например, этот или же этот, но вот список необходимых шагов:

Сначала давайте посмотрим на управляемую сторону.

Чтобы предоставить события, мы должны сделать следующее:

  • объявить интерфейс события (IDispatch-основан)
  • пометить кокласс ComSourceInterfaces атрибут для привязки интерфейса события к Coclass
  • реализовать соответствующие события в Coclass

Вот управляемый код:

[ComVisible(true),
Guid("D6D3565F-..."),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] //! must be IDispatch
public interface IMyEvents
{
[DispId(1)] // the dispid is used to correctly map the events
void SomethingHappened(DateTime timestamp, string message);
}

[ComVisible(true)]
[Guid("E22E64F7-...")]
[ProgId("...")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IMyEvents))] // binding the event interface
public class MyComServer : IMyComServer
{
// here we declare the delegate for the event
[ComVisible(false)]
public delegate void MyEventHandler(DateTime timestamp, string message);

// and a public event which matches the method in IMyEvents
// your code will raise this event when needed
public event MyEventHandler SomethingHappened;
...
}

Теперь вернемся к неуправляемой стороне. Я буду использовать ATL, так как считаю, что это наиболее эффективный способ написания COM-клиентов, но вы можете попробовать MFC или сделать это «вручную».

Требуются следующие шаги:

  • раковина унаследует IDispEventSimpleImpl (или же IDispEventImpl)
  • объявлена ​​карта приемника со всеми необходимыми методами
  • методы-обработчики пишутся для каждого события
  • приемник зарегистрирован с источником события
  • в конце концов, когда больше не нужен, раковина отключается

Вот код в клиенте ATL C ++:

// import the typelib of your COM server
// 'named_guids' ensures friendly ID of event interface
#import "myserver.tlb" named_guids
const UINT SINK_ID = 234231341; // we need some sink id

class MyClient : public IDispEventSimpleImpl<SINK_ID, MyClient, &MyServer::DIID_IMyEvents >
{
public:
// now you need to declare a sink map - a map of methods handling the events
BEGIN_SINK_MAP(MyClient)
SINK_ENTRY_INFO(SINK_ID, MyServer::DIID_IMyEvents, 0x1, OnSomethingHappened, &someEvent)
^      ^      ^                   ^
// event interface id (can be more than 1)---+      |      |                   |
// must match dispid of your event -----------------+      |                   |
// method which handles the event  ------------------------+                   |
// type information for event, see below --------------------------------------+
END_SINK_MAP()

// declare the type info object. You will need one for each method with different signature.
// it will be defined in the .cpp file, as it is a static member
static _ATL_FUNC_INFO someEvent;  // 'placeholder' object to carry event information (see below)

// method which handles the event
STDMETHOD (OnSomethingHappened)(DATE timestamp, BSTR message)
{
// usually it is defined it in the .cpp file
}

...

}

Теперь нам нужно определить элементы типа info в файле cpp (т.е. someEvent экземпляр из примера выше):

_ATL_FUNC_INFO MyClient::traceEvent = { CC_STDCALL, VT_EMPTY, 2 , {VT_DECIMAL, VT_BSTR} };  // dispid = 1
^        ^     ^              ^
// calling convention (always stdcall) --------+        |     |              |
// type of return value (only VT_EMPTY makes sense) ----+     |              |
// number of parameters to the event -------------------------+              |
// Variant types of event arguments -----------------------------------------+

Это может быть сложно, так как сопоставления типов не всегда очевидны (например, может быть ясно, что int карты для VT_I4, но менее очевидно, что DateTime карты для VT_DECIMAL).
Вам нужно объявлять каждое событие, которое вы планируете использовать в карте приемников — если вам не нужны все они, не отображайте их.

Теперь вам нужно подключить ваш приемник к источнику событий:

// IUnknown* pUnk = interface to you COM server instance
pMyClient->DispEventAdvise(pUnk);
// .. from this point, events will be caught by the client
// when you are done, disconnect:
pMyClient->DispEventUnadvise(pUnk);

Вот оно, более или менее. С помощью IDispEventImpl вместо IDispEventSimpleImpl в результате получается немного меньше кода, так как вам не нужно предоставлять объекты типа info, которые могут быть самой уродливой частью. Однако у него есть два недостатка:

  • требует доступа к typelib (так как он должен прочитать метаданные интерфейса для предоставления самой информации о типе)
  • немного медленнее (но я бы предположил, что не так значительно)
7

Другие решения

Если вы можете использовать C ++ / CLI, вы можете просто сделать (источник):

// class that defines methods that will called when event occurs
ref class EventReceiver {
public:
void OnMyClick(int i, double d) {
Console::WriteLine("OnClick: {0}, {1}", i, d);
}

void OnMyDblClick(String^ str) {
Console::WriteLine("OnDblClick: {0}", str);
}
};

int main() {
EventSource ^ MyEventSource = gcnew EventSource();
EventReceiver^ MyEventReceiver = gcnew EventReceiver();

// hook handler to event
MyEventSource->OnClick += gcnew ClickEventHandler(MyEventReceiver, &EventReceiver::OnMyClick);
}
0

Решение, предложенное Здеславом Войковичем, было для меня почти полным ответом, однако я столкнулся с проблемами стабильности при реализации приемника в надстройке Outlook. После первого уведомления система стала нестабильной и могла дать сбой при следующем уведомлении. Решением для меня было сделать мойку как COM-объект.

Для краткости я добавил только ATL-сторону решения, поскольку управляемый код можно оставить в точности так, как предложил Здеслав Войкович.

Я следовал за этими шагами в Visual Studio 2017:

Создать простой объект ATL

  1. Щелкните правой кнопкой мыши Project и выберите Add> New Item …
  2. Выберите простой объект ATL и нажмите кнопку Добавить.
  3. На вкладке «Другое» выберите «Отдельно» для модели потоков.
  4. Нажмите Готово

Заполните детали интерфейса приемника

В сгенерированный idl добавьте обработчики для инициализации и выключения:

[
object,
uuid(a5211fba-...),
dual,
nonextensible,
pointer_default(unique)
]
interface IMyClient : IDispatch
{
[id(1), helpstring("method InitHandler"), local] HRESULT InitHandler(IUnknown* myserver);
[id(2), helpstring("method ShutdownHandler"), local] HRESULT ShutdownHandler(void);
};

Заполните файл заголовка (MyClient.h)

const UINT SINK_ID = 234231341;
extern _ATL_FUNC_INFO SomethingHappenedInfo;

// LIBID_MyATLComLib should point to the LIBID of the type library
class ATL_NO_VTABLE CMyClient :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMyClient, &CLSID_MyClient>,
public IDispatchImpl<IMyClient, &IID_IMyClient, &LIBID_MyATLComLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IDispEventSimpleImpl<SINK_ID, CMyClient, &MyServer::DIID_IMyEvents>
{
public:

typedef IDispEventSimpleImpl</*nID =*/ SINK_ID, CMyClient, &MyServer::DIID_IMyEvents> SomethingHappenedEvent;

...

BEGIN_SINK_MAP(CMyClient)
SINK_ENTRY_INFO(SINK_ID, MyServer::DIID_IMyEvents, 0x1, OnSomethingHappened, &SomethingHappenedInfo)
END_SINK_MAP()

...

private:
CComQIPtr<MyServer::IMyComServer> mMyComServer;

public:
STDMETHOD(InitHandler)(IUnknown* myserver);
STDMETHOD(ShutdownHandler)(void);

void __stdcall OnSomethingHappened(DateTime timestamp, string message);
};

Часть сгенерированного кода оставлена ​​как «…»

Заполните код C ++ (MyClient.cpp)

_ATL_FUNC_INFO SomethingHappenedInfo = { CC_STDCALL, VT_EMPTY, 2 , {VT_DECIMAL, VT_BSTR} };STDMETHODIMP CMyClient::InitHandler(IUnknown* myserver)
{
this->mMyComServer = myserver;
SomethingHappenedEvent::DispEventAdvise((IDispatch*)this->mMyComServer);

return S_OK;
}STDMETHODIMP CMyClient::ShutdownHandler(void)
{
SomethingHappenedEvent::DispEventUnadvise(this->mMyComServer);

return S_OK;
}

void __stdcall CMyClient::OnSomethingHappened(DateTime timestamp, string message)
{
...
}

Обратите внимание, что вызовы Advise / Unadvise здесь выполняются иначе.

0