8

我需要使用 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。)

4

1 回答 1

10

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

  • 如果有人对 32 位系统的不太复杂的解决方案感兴趣,我会留下原始答案。

注意:由于您使用CorBindToRuntimeEx的是 .net 4.0 中已过时的 .net 2.0 解决方案,因此我将假设使用良好的旧 Win32 API 的 .net 2.0 兼容解决方案。

因此,为了在 C# 和 C++ 之间传递数据(在我们的例子中 -IntPtr委托),我们将创建一个名为SharedMem的小型 Win32 DLL 项目,其中包含两个直接的方法。

SharedMem.h

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

现在对于实现文件:

SharedMem.cpp

#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;
}
  • 请注意,为简单起见,我只是共享一个 64 位值,但这是在 C# 和 C++ 之间共享内存的一般方式。随意放大 SHARED_MEM_SIZE 和/或添加函数,以便共享您认为合适的其他数据类型。

这就是我们使用上述方法的方式:我们将SetSharedMem()在 C# 端使用,以便将委托设置IntPtr为 64 位值(无论代码在 32 位还是 64 位系统上运行)。

C# 代码

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();
        }
    }
}
  • 请注意使用GCHandle以防止委托被垃圾收集。
  • 对于好的衡量标准,我们将使用返回值作为成功/失败标志。

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

C++ 代码

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

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

这是一个基于使用IntPtr.ToInt32()以转换委托函数的解决方案。点。int从静态 C# EntryPoint 方法返回的。

(*) 注意使用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();
        }
    }
}

C++ 代码 我们需要将返回int值转换为函数指针,因此我们将从定义一个 void 函数 ptr 开始。类型:

typedef void (*VOID_FUNC_PTR)();

其余的代码看起来很像你的原始代码,除了转换和执行函数 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输入来选择要执行的方法:

public static int EntryPoint( string interfaceName )
{
    IntPtr pFunc = IntPtr.Zero;

    if ( interfaceName == "SomeMethod" )
    {
        Delegate myFuncDelegate = new VoidDelegate( SomeMethod );
        gcDelegateHandle = GCHandle.Alloc( myFuncDelegate );
        pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate );
    }

    return (int)pFunc.ToInt32();
}
  • 您可以通过使用反射来获得更通用的方法,以便根据给定的字符串输入找到正确的方法。
于 2012-03-07T00:00:44.120 回答