CLR主机:使用任意方法签名调用函数?

时间:2012-03-06 20:44:28

标签: c# clr clr-hosting

我需要一个C ++程序,加载CLR并在C#库中调用一个函数。我需要调用的函数将COM接口作为参数。

我的问题是,CLR托管界面似乎只允许您使用此签名调用方法:

int Foo(String arg)

示例,此C ++代码加载CLR并在" test.exe"中运行P.Test函数:

ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(NULL, L"wks", 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*)&pClrHost);

HRESULT hrStart = pClrHost->Start();

DWORD retVal;
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(L"C:\\Test.exe", L"P", L"Test", L"", &retVal);

我需要做的是用这个方法签名调用一个函数(注意我拥有C#代码,所以我可以改变它):

void SomeFunction(IFoo interface)

其中IFoo是com接口。如果我可以调用这样的函数,我甚至可以做我需要的东西:

IntPtr SomeFunction();

我可以让SomeFunction构造一个正确的委托,然后使用Marshal.GetFunctionPointerForDelegate。但是,我无法弄清楚除了使用int func(string)签名调用函数之外,如何使托管接口执行任何操作。

有没有人知道如何使用不同的签名从C ++代码调用C#函数?

(注意我不能使用C ++ / CLI。我需要使用托管API。)

1 个答案:

答案 0 :(得分:8)

编辑:我答应更新我的答案以包含传递64位值的代码,所以这里就是..

  • 如果有人对32位系统的不太复杂的解决方案感兴趣,我就离开了原来的答案。

注意:由于你使用的是{。{1}},这在.net 4.0中已经过时了,我将假设使用旧的Win32 API符合.net 2.0标准。

因此,为了在C#和C ++之间传递数据(在我们的例子中是委托的CorBindToRuntimeEx),我们将创建一个名为 SharedMem 的小型Win32 DLL项目,有两种直截了当的方法。

<强> SharedMem.h

IntPtr

现在为实现文件:

<强> SharedMem.cpp

#pragma once

#ifdef SHAREDMEM_EXPORTS
#define SHAREDMEM_API __declspec(dllexport)
#else
#define SHAREDMEM_API __declspec(dllimport)
#endif

#define SHAREDMEM_CALLING_CONV __cdecl

extern "C" {
    SHAREDMEM_API BOOL SHAREDMEM_CALLING_CONV SetSharedMem(ULONGLONG _64bitValue);
    SHAREDMEM_API BOOL SHAREDMEM_CALLING_CONV GetSharedMem(ULONGLONG* p64bitValue);
}
  • 请注意,为简单起见,我只是共享64位值,但这是在C#和C ++之间共享内存的一般方法。您可以随意放大SHARED_MEM_SIZE和/或添加功能,以便根据需要共享其他数据类型。

这就是我们将如何使用上述方法:我们将在C#端使用#include "stdafx.h" #include "SharedMem.h" HANDLE hMappedFileObject = NULL; // handle to mapped file LPVOID lpvSharedMem = NULL; // pointer to shared memory const int SHARED_MEM_SIZE = sizeof(ULONGLONG); BOOL CreateSharedMem() { // Create a named file mapping object hMappedFileObject = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, SHARED_MEM_SIZE, TEXT("shmemfile") // Name of shared mem file ); if (hMappedFileObject == NULL) { return FALSE; } BOOL bFirstInit = (ERROR_ALREADY_EXISTS != GetLastError()); // Get a ptr to the shared memory lpvSharedMem = MapViewOfFile( hMappedFileObject, FILE_MAP_WRITE, 0, 0, 0); if (lpvSharedMem == NULL) { return FALSE; } if (bFirstInit) // First time the shared memory is accessed? { ZeroMemory(lpvSharedMem, SHARED_MEM_SIZE); } return TRUE; } BOOL SetSharedMem(ULONGLONG _64bitValue) { BOOL bOK = CreateSharedMem(); if ( bOK ) { ULONGLONG* pSharedMem = (ULONGLONG*)lpvSharedMem; *pSharedMem = _64bitValue; } return bOK; } BOOL GetSharedMem(ULONGLONG* p64bitValue) { if ( p64bitValue == NULL ) return FALSE; BOOL bOK = CreateSharedMem(); if ( bOK ) { ULONGLONG* pSharedMem = (ULONGLONG*)lpvSharedMem; *p64bitValue = *pSharedMem; } return bOK; } ,以便将委托的SetSharedMem()设置为64位值(无论代码是否运行)在32位或64位系统上。)

C#代码

IntPtr
  • 请注意使用namespace CSharpCode { delegate void VoidDelegate(); static public class COMInterfaceClass { [DllImport( "SharedMem.dll" )] static extern bool SetSharedMem( Int64 value ); static GCHandle gcDelegateHandle; public static int EntryPoint(string ignored) { IntPtr pFunc = IntPtr.Zero; Delegate myFuncDelegate = new VoidDelegate( SomeMethod ); gcDelegateHandle = GCHandle.Alloc( myFuncDelegate ); pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate ); bool bSetOK = SetSharedMem( pFunc.ToInt64() ); return bSetOK ? 1 : 0; } public static void SomeMethod() { MessageBox.Show( "Hello from C# SomeMethod!" ); gcDelegateHandle.Free(); } } } 以防止代理被垃圾回收。
  • 为了获得良好的衡量标准,我们将使用返回值作为成功/失败标志。

在C ++方面,我们将使用GCHandle提取64位值,将其转换为函数指针并调用C#委托。

C ++代码

GetSharedMem()

原始答案 - 适用于32位系统

这是一个基于使用#include "SharedMem.h" typedef void (*VOID_FUNC_PTR)(); void ExecCSharpCode() { ICLRRuntimeHost *pClrHost = NULL; HRESULT hrCorBind = CorBindToRuntimeEx( NULL, L"wks", 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*)&pClrHost ); HRESULT hrStart = pClrHost->Start(); DWORD retVal; HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain( szPathToAssembly, L"CSharpCode.COMInterfaceClass", L"EntryPoint", L"", &retVal // 1 for success, 0 is a failure ); if ( hrExecute == S_OK && retVal == 1 ) { ULONGLONG nSharedMemValue = 0; BOOL bGotValue = GetSharedMem(&nSharedMemValue); if ( bGotValue ) { VOID_FUNC_PTR CSharpFunc = (VOID_FUNC_PTR)nSharedMemValue; CSharpFunc(); } } } 来转换委托func的解决方案。 PTR。到从静态C#EntryPoint方法返回的IntPtr.ToInt32()

(*)注意使用int以防止代理被垃圾收集。

C#代码

GCHandle

C ++代码 我们需要将返回的namespace CSharpCode { delegate void VoidDelegate(); public class COMInterfaceClass { static GCHandle gcDelegateHandle; public static int EntryPoint(string ignored) { IntPtr pFunc = IntPtr.Zero; Delegate myFuncDelegate = new VoidDelegate( SomeMethod ); gcDelegateHandle = GCHandle.Alloc( myFuncDelegate ); pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate ); return (int)pFunc.ToInt32(); } public static void SomeMethod() { MessageBox.Show( "Hello from C# SomeMethod!" ); gcDelegateHandle.Free(); } } } 值转换为函数指针,因此我们将从定义void函数ptr开始。类型:

int

其余代码看起来非常像原始代码,添加了转换和执行函数ptr。

typedef void (*VOID_FUNC_PTR)();

一点点额外

您还可以使用ICLRRuntimeHost *pClrHost = NULL; HRESULT hrCorBind = CorBindToRuntimeEx( NULL, L"wks", 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*)&pClrHost ); HRESULT hrStart = pClrHost->Start(); DWORD retVal; HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain( szPathToAssembly, L"CSharpCode.COMInterfaceClass", L"EntryPoint", L"", &retVal ); if ( hrExecute == S_OK ) { VOID_FUNC_PTR func = (VOID_FUNC_PTR)retVal; func(); } 输入来选择要执行的方法:

string
  • 您可以通过使用反射来获得更多通用,以便根据给定的字符串输入找到正确的方法。