From c409bd0d3d80d6c674bb99b3ec0b2c15a891112c Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Mon, 20 Apr 2020 15:31:06 -0700 Subject: [PATCH] Add test for native COM client consuming managed COM server with Events. (#35151) --- src/coreclr/tests/src/Interop/CMakeLists.txt | 1 + .../src/Interop/COM/NETServer/EventTesting.cs | 29 ++ .../Interop/COM/NativeClients/Events.csproj | 27 ++ .../COM/NativeClients/Events/App.manifest | 17 ++ .../COM/NativeClients/Events/CMakeLists.txt | 17 ++ .../NativeClients/Events/CoreShim.X.manifest | 16 ++ .../COM/NativeClients/Events/EventTests.cpp | 257 ++++++++++++++++++ 7 files changed, 364 insertions(+) create mode 100644 src/coreclr/tests/src/Interop/COM/NETServer/EventTesting.cs create mode 100644 src/coreclr/tests/src/Interop/COM/NativeClients/Events.csproj create mode 100644 src/coreclr/tests/src/Interop/COM/NativeClients/Events/App.manifest create mode 100644 src/coreclr/tests/src/Interop/COM/NativeClients/Events/CMakeLists.txt create mode 100644 src/coreclr/tests/src/Interop/COM/NativeClients/Events/CoreShim.X.manifest create mode 100644 src/coreclr/tests/src/Interop/COM/NativeClients/Events/EventTests.cpp diff --git a/src/coreclr/tests/src/Interop/CMakeLists.txt b/src/coreclr/tests/src/Interop/CMakeLists.txt index 9fcec4b3319f8..6f0464527650e 100644 --- a/src/coreclr/tests/src/Interop/CMakeLists.txt +++ b/src/coreclr/tests/src/Interop/CMakeLists.txt @@ -79,6 +79,7 @@ if(CLR_CMAKE_TARGET_WIN32) add_subdirectory(COM/NativeClients/Licensing) add_subdirectory(COM/NativeClients/DefaultInterfaces) add_subdirectory(COM/NativeClients/Dispatch) + add_subdirectory(COM/NativeClients/Events) add_subdirectory(COM/ComWrappers/MockReferenceTrackerRuntime) add_subdirectory(WinRT/NativeComponent) diff --git a/src/coreclr/tests/src/Interop/COM/NETServer/EventTesting.cs b/src/coreclr/tests/src/Interop/COM/NETServer/EventTesting.cs new file mode 100644 index 0000000000000..ba909ebdd74e4 --- /dev/null +++ b/src/coreclr/tests/src/Interop/COM/NETServer/EventTesting.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using System.Text; +using System.Runtime.InteropServices; +using Server.Contract; + +#pragma warning disable 618 // Must test deprecated features + +[ComVisible(true)] +[Guid(Server.Contract.Guids.EventTesting)] +[ComSourceInterfaces(typeof(Server.Contract.TestingEvents))] +public class EventTesting : Server.Contract.IEventTesting +{ + public delegate void OnEventHandler(string msg); + public event OnEventHandler OnEvent; + + public void FireEvent() + { + var h = this.OnEvent; + if (h != null) + { + h(nameof(FireEvent)); + } + } +} \ No newline at end of file diff --git a/src/coreclr/tests/src/Interop/COM/NativeClients/Events.csproj b/src/coreclr/tests/src/Interop/COM/NativeClients/Events.csproj new file mode 100644 index 0000000000000..54575775db600 --- /dev/null +++ b/src/coreclr/tests/src/Interop/COM/NativeClients/Events.csproj @@ -0,0 +1,27 @@ + + + Exe + true + true + true + BuildOnly + true + true + BLOCK_WINDOWS_NANO + + + + + + + + + + + 1 + + + + + + diff --git a/src/coreclr/tests/src/Interop/COM/NativeClients/Events/App.manifest b/src/coreclr/tests/src/Interop/COM/NativeClients/Events/App.manifest new file mode 100644 index 0000000000000..d06a797929997 --- /dev/null +++ b/src/coreclr/tests/src/Interop/COM/NativeClients/Events/App.manifest @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/coreclr/tests/src/Interop/COM/NativeClients/Events/CMakeLists.txt b/src/coreclr/tests/src/Interop/COM/NativeClients/Events/CMakeLists.txt new file mode 100644 index 0000000000000..43063bd532d3a --- /dev/null +++ b/src/coreclr/tests/src/Interop/COM/NativeClients/Events/CMakeLists.txt @@ -0,0 +1,17 @@ +project (COMClientEvents) +include_directories( ${INC_PLATFORM_DIR} ) +include_directories( "../../ServerContracts" ) +include_directories( "../../NativeServer" ) +set(SOURCES + EventTests.cpp + App.manifest) + +# add the executable +add_executable (COMClientEvents ${SOURCES}) +target_link_libraries(COMClientEvents ${LINK_LIBRARIES_ADDITIONAL}) + +# Copy CoreShim manifest to project output +file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/CoreShim.X.manifest INPUT ${CMAKE_CURRENT_SOURCE_DIR}/CoreShim.X.manifest) + +# add the install targets +install (TARGETS COMClientEvents DESTINATION bin) diff --git a/src/coreclr/tests/src/Interop/COM/NativeClients/Events/CoreShim.X.manifest b/src/coreclr/tests/src/Interop/COM/NativeClients/Events/CoreShim.X.manifest new file mode 100644 index 0000000000000..e8284bac6e342 --- /dev/null +++ b/src/coreclr/tests/src/Interop/COM/NativeClients/Events/CoreShim.X.manifest @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/coreclr/tests/src/Interop/COM/NativeClients/Events/EventTests.cpp b/src/coreclr/tests/src/Interop/COM/NativeClients/Events/EventTests.cpp new file mode 100644 index 0000000000000..ee5212cd15505 --- /dev/null +++ b/src/coreclr/tests/src/Interop/COM/NativeClients/Events/EventTests.cpp @@ -0,0 +1,257 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include +#include +#include + +// COM headers +#include +#include + +#define COM_CLIENT +#include + +#define THROW_IF_FAILED(exp) { hr = exp; if (FAILED(hr)) { ::printf("FAILURE: 0x%08x = %s\n", hr, #exp); throw hr; } } +#define THROW_FAIL_IF_FALSE(exp) { if (!(exp)) { ::printf("FALSE: %s\n", #exp); throw E_FAIL; } } + +#include +#include + +namespace +{ + class EventSink : public UnknownImpl, public TestingEvents + { + std::map _firedEvents; + + public: + void ResetFiredState(_In_ DISPID id) + { + _firedEvents.erase(id); + } + + bool DidFire(_In_ DISPID id, _Out_ std::wstring& message) + { + auto iter = _firedEvents.find(id); + if (iter == std::end(_firedEvents)) + return false; + + message = iter->second; + return true; + } + + public: // IDispatch + virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( + /* [out] */ __RPC__out UINT* pctinfo) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( + /* [in] */ UINT iTInfo, + /* [in] */ LCID lcid, + /* [out] */ __RPC__deref_out_opt ITypeInfo** ppTInfo) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( + /* [in] */ __RPC__in REFIID riid, + /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR* rgszNames, + /* [range][in] */ __RPC__in_range(0, 16384) UINT cNames, + /* [in] */ LCID lcid, + /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID* rgDispId) + { + return E_NOTIMPL; + } + + virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( + /* [annotation][in] */ + _In_ DISPID dispIdMember, + /* [annotation][in] */ + _In_ REFIID riid, + /* [annotation][in] */ + _In_ LCID lcid, + /* [annotation][in] */ + _In_ WORD wFlags, + /* [annotation][out][in] */ + _In_ DISPPARAMS* pDispParams, + /* [annotation][out] */ + _Out_opt_ VARIANT* pVarResult, + /* [annotation][out] */ + _Out_opt_ EXCEPINFO* pExcepInfo, + /* [annotation][out] */ + _Out_opt_ UINT* puArgErr) + { + // + // Note that arguments are received in reverse order for IDispatch::Invoke() + // + + switch (dispIdMember) + { + case DISPATCHTESTINGEVENTS_DISPID_ONEVENT: + { + return OnFireEventHandler(dispIdMember, pDispParams); + } + } + + return E_NOTIMPL; + } + + private: + HRESULT OnFireEventHandler(_In_ DISPID dispId, _In_ DISPPARAMS* dispParams) + { + if (dispParams == nullptr) + return E_POINTER; + + if (dispParams->cArgs != 1) + return E_INVALIDARG; + + VARIANTARG* msgMaybe = dispParams->rgvarg; + if (msgMaybe->vt != VT_BSTR) + return E_INVALIDARG; + + _firedEvents.insert({ dispId, msgMaybe->bstrVal }); + return S_OK; + } + + public: // IUnknown + STDMETHOD(QueryInterface)( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) + { + return DoQueryInterface(riid, ppvObject, + static_cast(this), + static_cast(this)); + } + + DEFINE_REF_COUNTING(); + }; + + void VerifyAdviseUnadviseFromEvent() + { + HRESULT hr; + + ComSmartPtr et; + THROW_IF_FAILED(::CoCreateInstance(CLSID_EventTesting, nullptr, CLSCTX_INPROC, IID_IEventTesting, (void**)&et)); + + ComSmartPtr cpc; + THROW_IF_FAILED(et->QueryInterface(&cpc)); + + ComSmartPtr cp; + THROW_IF_FAILED(cpc->FindConnectionPoint(IID_TestingEvents, &cp)); + + // Create event sink + ComSmartPtr es; + es.Attach(new EventSink()); + + DWORD cookie; + ComSmartPtr uk; + THROW_IF_FAILED(es->QueryInterface(IID_IUnknown, (void**)&uk)); + THROW_IF_FAILED(cp->Advise(uk, &cookie)); + + // Ensure state is valid. + es->ResetFiredState(DISPATCHTESTINGEVENTS_DISPID_ONEVENT); + + THROW_IF_FAILED(et->FireEvent()); + + // Validate the event fired. + { + std::wstring eventName; + THROW_FAIL_IF_FALSE(es->DidFire(DISPATCHTESTINGEVENTS_DISPID_ONEVENT, eventName)); + THROW_FAIL_IF_FALSE(eventName.compare(L"FireEvent") == 0); + } + + THROW_IF_FAILED(cp->Unadvise(cookie)); + + // Reset state. + es->ResetFiredState(DISPATCHTESTINGEVENTS_DISPID_ONEVENT); + + THROW_IF_FAILED(et->FireEvent()); + + // Validate the event was not fired. + { + std::wstring eventName; + THROW_FAIL_IF_FALSE(!es->DidFire(DISPATCHTESTINGEVENTS_DISPID_ONEVENT, eventName)); + THROW_FAIL_IF_FALSE(eventName.empty()); + } + } + + void VerifyEnumConnectionPoints() + { + HRESULT hr; + + ComSmartPtr et; + THROW_IF_FAILED(::CoCreateInstance(CLSID_EventTesting, nullptr, CLSCTX_INPROC, IID_IEventTesting, (void**)&et)); + + ComSmartPtr cpc; + THROW_IF_FAILED(et->QueryInterface(&cpc)); + + ComSmartPtr ecp; + THROW_IF_FAILED(cpc->EnumConnectionPoints(&ecp)); + + bool foundEventInterface = false; + ULONG fetched; + LPCONNECTIONPOINT ptRaw = nullptr; + while ((hr = ecp->Next(1, &ptRaw, &fetched)) == S_OK) + { + THROW_FAIL_IF_FALSE(fetched == 1); + THROW_FAIL_IF_FALSE(ptRaw != nullptr); + + ComSmartPtr pt; + pt.Attach(ptRaw); + ptRaw = nullptr; + + IID iidMaybe; + THROW_IF_FAILED(pt->GetConnectionInterface(&iidMaybe)); + foundEventInterface = (iidMaybe == IID_TestingEvents); + + // There should only be one event interface + THROW_FAIL_IF_FALSE(foundEventInterface); + } + + THROW_IF_FAILED(hr); + THROW_FAIL_IF_FALSE(foundEventInterface); + } +} + +template +struct ComInit +{ + const HRESULT Result; + + ComInit() + : Result{ ::CoInitializeEx(nullptr, TM) } + { } + + ~ComInit() + { + if (SUCCEEDED(Result)) + ::CoUninitialize(); + } +}; + +using ComMTA = ComInit; + +int __cdecl main() +{ + ComMTA init; + if (FAILED(init.Result)) + return -1; + + try + { + CoreShimComActivation csact{ W("NETServer"), W("EventTesting") }; + + VerifyAdviseUnadviseFromEvent(); + VerifyEnumConnectionPoints(); + } + catch (HRESULT hr) + { + ::printf("Test Failure: 0x%08x\n", hr); + return 101; + } + + return 100; +}