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();
+}