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/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..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,5 +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 @@ + 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(); + } } } diff --git a/TutorialsAndTests/Tests/ManagedServerTests.cpp b/TutorialsAndTests/Tests/ManagedServerTests.cpp index b2a232c..bbede5e 100644 --- a/TutorialsAndTests/Tests/ManagedServerTests.cpp +++ b/TutorialsAndTests/Tests/ManagedServerTests.cpp @@ -1,9 +1,14 @@ #include "../pch.h" #include +#include +#include #include #include #include #include +#include +#include +#include using Microsoft::WRL::ComPtr; @@ -57,4 +62,100 @@ 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; +}; + +// 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(); +}