From 4b9d8254ffc40f314748a4ac5848c1be17c8a695 Mon Sep 17 00:00:00 2001 From: supersega Date: Mon, 17 Jan 2022 17:10:43 +0200 Subject: [PATCH 1/4] [ManagedServer] Add COM events for managed PetShop because it is fun --- Interfaces/IPetShop.idl | 14 +++++++++++++- Interfaces/Interfaces.idl | 1 + ManagedServer/PetShop.cs | 10 ++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Interfaces/IPetShop.idl b/Interfaces/IPetShop.idl index cd70145..769640a 100644 --- a/Interfaces/IPetShop.idl +++ b/Interfaces/IPetShop.idl @@ -19,4 +19,16 @@ interface IPetShop : IUnknown { HRESULT BuyDog([out, retval] IDog** dog); HRESULT GetAddress([out, retval] Address* address); -}; \ No newline at end of file + HRESULT Open(); +}; + +[ + uuid(A8545F26-9028-46DB-9D89-A7B395B3260F) +] +dispinterface _DPetShopEvents +{ + properties: + methods: + [id(1)] + void Opened(); +}; diff --git a/Interfaces/Interfaces.idl b/Interfaces/Interfaces.idl index 4cf51ef..a15adbc 100644 --- a/Interfaces/Interfaces.idl +++ b/Interfaces/Interfaces.idl @@ -18,5 +18,6 @@ library Interfaces interface IDog; interface IPostman; interface IPetShop; + dispinterface _DPetShopEvents; }; diff --git a/ManagedServer/PetShop.cs b/ManagedServer/PetShop.cs index e48651c..962d05e 100644 --- a/ManagedServer/PetShop.cs +++ b/ManagedServer/PetShop.cs @@ -6,9 +6,14 @@ namespace ManagedServer { [Guid("5011c315-994d-49b4-b737-03a846f590a0")] [ProgId("ManagedServer.PetShop.1")] + [ComSourceInterfaces(typeof(_DPetShopEvents))] [ComVisible(true)] public class PetShop : IPetShop { + public delegate void Opened_Delegate(); + + private event Opened_Delegate Opened; + public PetShop() { @@ -27,5 +32,10 @@ public Address GetAddress() address.City = "Oslo"; return address; } + + public void Open() + { + Opened?.Invoke(); + } } } From 11f4d699b7218ec61fdf7aefac7dba5d63efaaaa Mon Sep 17 00:00:00 2001 From: supersega Date: Mon, 17 Jan 2022 17:12:30 +0200 Subject: [PATCH 2/4] [Interfaces] Add IListener for event subscription --- AtlFreeServer/AtlFreeServer.vcxproj | 8 ++++++++ Interfaces/IListener.idl | 14 ++++++++++++++ Interfaces/Interfaces.idl | 2 ++ Interfaces/interfaces.vcxproj | 1 + Interfaces/interfaces.vcxproj.filters | 1 + 5 files changed, 26 insertions(+) create mode 100644 Interfaces/IListener.idl diff --git a/AtlFreeServer/AtlFreeServer.vcxproj b/AtlFreeServer/AtlFreeServer.vcxproj index 152c6a0..a0f2ce9 100644 --- a/AtlFreeServer/AtlFreeServer.vcxproj +++ b/AtlFreeServer/AtlFreeServer.vcxproj @@ -146,6 +146,14 @@ NotUsing NotUsing + + NotUsing + NotUsing + + + NotUsing + NotUsing + Create diff --git a/Interfaces/IListener.idl b/Interfaces/IListener.idl new file mode 100644 index 0000000..3da3e44 --- /dev/null +++ b/Interfaces/IListener.idl @@ -0,0 +1,14 @@ +import "oaidl.idl"; +import "ocidl.idl"; + +[ + oleautomation, + object, + uuid(2E2944D8-CD27-45ED-ADAB-9644C87CE6B8), + pointer_default(unique) +] +interface IListener : IUnknown +{ + HRESULT Start([in] IUnknown* toListen); + HRESULT Stop(); +}; diff --git a/Interfaces/Interfaces.idl b/Interfaces/Interfaces.idl index a15adbc..6704e4f 100644 --- a/Interfaces/Interfaces.idl +++ b/Interfaces/Interfaces.idl @@ -5,6 +5,7 @@ import "IHen.idl"; import "IDog.idl"; import "IPostman.idl"; import "IPetShop.idl"; +import "IListener.idl"; [ uuid(8edbf75d-ecf2-4a1f-affc-07fcb8dee00a), @@ -18,6 +19,7 @@ library Interfaces interface IDog; interface IPostman; interface IPetShop; + interface IListener; dispinterface _DPetShopEvents; }; diff --git a/Interfaces/interfaces.vcxproj b/Interfaces/interfaces.vcxproj index fa09f7a..a26be08 100644 --- a/Interfaces/interfaces.vcxproj +++ b/Interfaces/interfaces.vcxproj @@ -11,6 +11,7 @@ + diff --git a/Interfaces/interfaces.vcxproj.filters b/Interfaces/interfaces.vcxproj.filters index 3d1699d..31c29d9 100644 --- a/Interfaces/interfaces.vcxproj.filters +++ b/Interfaces/interfaces.vcxproj.filters @@ -7,6 +7,7 @@ + From c9e8100213626842bb4e12e0bf3dff0aad698b56 Mon Sep 17 00:00:00 2001 From: supersega Date: Mon, 17 Jan 2022 17:15:46 +0200 Subject: [PATCH 3/4] [TutorialsAndTests] Add base class for event listening --- .../Tests/ManagedServerTests.cpp | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/TutorialsAndTests/Tests/ManagedServerTests.cpp b/TutorialsAndTests/Tests/ManagedServerTests.cpp index b2a232c..e236b47 100644 --- a/TutorialsAndTests/Tests/ManagedServerTests.cpp +++ b/TutorialsAndTests/Tests/ManagedServerTests.cpp @@ -1,9 +1,13 @@ #include "../pch.h" #include +#include #include #include #include #include +#include +#include +#include using Microsoft::WRL::ComPtr; @@ -57,4 +61,36 @@ TEST(ManagedServerTests, SysFreeString(address.Street); SysFreeString(address.PostalCode); SysFreeString(address.City); -} \ No newline at end of file +} + +// Base class to listen events from COM event source +template +struct ListenerImpl : + IDispEventSimpleImpl, + IListener +{ + // Connect to toListen event source + HRESULT Start(IUnknown* toListen) override + { + m_toListen = toListen; + // Setup the connection with the event source + DispEventAdvise(m_toListen); + + return S_OK; + } + + // Disconnect from event source + HRESULT Stop() override + { + // Break the connection with the event source + DispEventUnadvise(m_toListen); + + // Release the application + m_toListen.Release(); + + return S_OK; + } + +protected: + CComPtr m_toListen; +}; From 8d7a97bc71930bd6a84e49bb66b6bc94371ab374 Mon Sep 17 00:00:00 2001 From: supersega Date: Mon, 17 Jan 2022 17:16:55 +0200 Subject: [PATCH 4/4] [TutorialsAndTests] Add first example to show how managed event source can be used with native handler --- .../Tests/ManagedServerTests.cpp | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/TutorialsAndTests/Tests/ManagedServerTests.cpp b/TutorialsAndTests/Tests/ManagedServerTests.cpp index e236b47..bbede5e 100644 --- a/TutorialsAndTests/Tests/ManagedServerTests.cpp +++ b/TutorialsAndTests/Tests/ManagedServerTests.cpp @@ -1,5 +1,6 @@ #include "../pch.h" #include +#include #include #include #include @@ -94,3 +95,67 @@ struct ListenerImpl : protected: CComPtr m_toListen; }; + +// Interface to add call expectations +struct IEventsHandler +{ + virtual ~IEventsHandler() = default; + virtual void OnOpenedImpl() const = 0; +}; + +_ATL_FUNC_INFO OnOpenedInfo = { CC_STDCALL, VT_EMPTY, 0 }; + +// Real PetShopListener, inspired from : +// https://github.com/microsoft/VCSamples/blob/9e1d4475555b76a17a3568369867f1d7b6cc6126/VC2008Samples/ATL/General/ATLEventHandling/Simple.h +struct PetShopListener : + CComObjectRootEx, + CComCoClass, + ListenerImpl<1, PetShopListener, _DPetShopEvents>, + IEventsHandler +{ + BEGIN_COM_MAP(PetShopListener) + COM_INTERFACE_ENTRY(IListener) + END_COM_MAP() + + void __stdcall OnOpened() + { + OnOpenedImpl(); + } + + MOCK_METHOD(void, OnOpenedImpl, (), (const override)); + + BEGIN_SINK_MAP(PetShopListener) + SINK_ENTRY_INFO(/*nID =*/ 1, __uuidof(_DPetShopEvents), /*dispid =*/ 1, OnOpened, &OnOpenedInfo) + END_SINK_MAP() +}; + +TEST(ManagedServerTests, + RequireThat_Open_TriggersOpenedEvent_WhenCalled) +{ + CLSID petShopClsid{}; + HR(CLSIDFromString(L"{5011c315-994d-49b4-b737-03a846f590a0}", &petShopClsid)); + + // Create managed PetShop what can Raise events + ATL::CComPtr petShop; + HR(CoCreateInstance(petShopClsid, nullptr, CLSCTX_INPROC_SERVER, __uuidof(IPetShop), reinterpret_cast(&petShop))); + EXPECT_NE(petShop, nullptr); + + // Create local instance for PetShopListener + auto petShopListener = make_self(); + + // Subscribe to the petShop events + petShopListener->Start(petShop); + + // We expect that OnOpenedImpl called while we subscribed + EXPECT_CALL(*petShopListener, OnOpenedImpl()) + .Times(1); + + // Raise Opened event, this should call PetShopListener::OnOpened + petShop->Open(); + + // Unsubscribe from the petShop events + petShopListener->Stop(); + + // Raise Opened event, but no Listeners and PetShopListener::OnOpened is not called + petShop->Open(); +}