Skip to content

Commit

Permalink
Add test for native COM client consuming managed COM server with Even…
Browse files Browse the repository at this point in the history
…ts. (#35151)
  • Loading branch information
AaronRobinsonMSFT committed Apr 20, 2020
1 parent faebda7 commit c409bd0
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/coreclr/tests/src/Interop/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
29 changes: 29 additions & 0 deletions src/coreclr/tests/src/Interop/COM/NETServer/EventTesting.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
27 changes: 27 additions & 0 deletions src/coreclr/tests/src/Interop/COM/NativeClients/Events.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<IgnoreCoreCLRTestLibraryDependency>true</IgnoreCoreCLRTestLibraryDependency>
<CLRTestScriptLocalCoreShim>true</CLRTestScriptLocalCoreShim>
<RequiresMockHostPolicy>true</RequiresMockHostPolicy>
<IlrtTestKind>BuildOnly</IlrtTestKind>
<TestUnsupportedOutsideWindows>true</TestUnsupportedOutsideWindows>
<DisableProjectBuild Condition="'$(TargetsUnix)' == 'true'">true</DisableProjectBuild>
<DefineConstants>BLOCK_WINDOWS_NANO</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(InteropCommonDir)ExeLauncherProgram.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="Events/CMakeLists.txt" />
<ProjectReference Include="../NetServer/NetServer.csproj" />
<ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
</ItemGroup>
<PropertyGroup>
<CLRTestNeedTarget>1</CLRTestNeedTarget>
</PropertyGroup>
<ItemGroup>
<TraitTags Include="OsSpecific" />
</ItemGroup>
<Import Project="$([MSBuild]::GetPathOfFileAbove(Interop.settings.targets))" />
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
type="win32"
name="COMClientEvents"
version="1.0.0.0"/>

<dependency>
<dependentAssembly>
<!-- RegFree COM - CoreCLR Shim -->
<assemblyIdentity
type="win32"
name="CoreShim.X"
version="1.0.0.0"/>
</dependentAssembly>
</dependency>
</assembly>
Original file line number Diff line number Diff line change
@@ -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}/$<CONFIG>/CoreShim.X.manifest INPUT ${CMAKE_CURRENT_SOURCE_DIR}/CoreShim.X.manifest)

# add the install targets
install (TARGETS COMClientEvents DESTINATION bin)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

<assemblyIdentity
type="win32"
name="CoreShim.X"
version="1.0.0.0" />

<file name="CoreShim.dll">
<!-- EventTesting -->
<comClass
clsid="{4DBD9B61-E372-499F-84DE-EFC70AA8A009}"
threadingModel="Both" />
</file>

</assembly>
257 changes: 257 additions & 0 deletions src/coreclr/tests/src/Interop/COM/NativeClients/Events/EventTests.cpp
Original file line number Diff line number Diff line change
@@ -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 <xplatform.h>
#include <cassert>
#include <Server.Contracts.h>

// COM headers
#include <objbase.h>
#include <combaseapi.h>

#define COM_CLIENT
#include <Servers.h>

#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 <map>
#include <string>

namespace
{
class EventSink : public UnknownImpl, public TestingEvents
{
std::map<DISPID, std::wstring> _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<TestingEvents*>(this),
static_cast<IDispatch*>(this));
}

DEFINE_REF_COUNTING();
};

void VerifyAdviseUnadviseFromEvent()
{
HRESULT hr;

ComSmartPtr<IEventTesting> et;
THROW_IF_FAILED(::CoCreateInstance(CLSID_EventTesting, nullptr, CLSCTX_INPROC, IID_IEventTesting, (void**)&et));

ComSmartPtr<IConnectionPointContainer> cpc;
THROW_IF_FAILED(et->QueryInterface(&cpc));

ComSmartPtr<IConnectionPoint> cp;
THROW_IF_FAILED(cpc->FindConnectionPoint(IID_TestingEvents, &cp));

// Create event sink
ComSmartPtr<EventSink> es;
es.Attach(new EventSink());

DWORD cookie;
ComSmartPtr<IUnknown> 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<IEventTesting> et;
THROW_IF_FAILED(::CoCreateInstance(CLSID_EventTesting, nullptr, CLSCTX_INPROC, IID_IEventTesting, (void**)&et));

ComSmartPtr<IConnectionPointContainer> cpc;
THROW_IF_FAILED(et->QueryInterface(&cpc));

ComSmartPtr<IEnumConnectionPoints> 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<IConnectionPoint> 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<COINIT TM>
struct ComInit
{
const HRESULT Result;

ComInit()
: Result{ ::CoInitializeEx(nullptr, TM) }
{ }

~ComInit()
{
if (SUCCEEDED(Result))
::CoUninitialize();
}
};

using ComMTA = ComInit<COINIT_MULTITHREADED>;

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;
}

0 comments on commit c409bd0

Please sign in to comment.