-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add test for native COM client consuming managed COM server with Even…
…ts. (#35151)
- Loading branch information
1 parent
faebda7
commit c409bd0
Showing
7 changed files
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
src/coreclr/tests/src/Interop/COM/NETServer/EventTesting.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
27
src/coreclr/tests/src/Interop/COM/NativeClients/Events.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
17 changes: 17 additions & 0 deletions
17
src/coreclr/tests/src/Interop/COM/NativeClients/Events/App.manifest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
17 changes: 17 additions & 0 deletions
17
src/coreclr/tests/src/Interop/COM/NativeClients/Events/CMakeLists.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
16 changes: 16 additions & 0 deletions
16
src/coreclr/tests/src/Interop/COM/NativeClients/Events/CoreShim.X.manifest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
257
src/coreclr/tests/src/Interop/COM/NativeClients/Events/EventTests.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |