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ビットシステムのそれほど複雑でないソリューションに興味があるなら、私は元の答えを残しました。

注: .net 4.0では廃止されたを使用しCorBindToRuntimeExているため、古き良きWin32APIを使用した.net2.0準拠のソリューションを想定します。

したがって、C#とC ++(この場合IntPtrはデリゲートの)の間でデータを渡すために、2つの簡単なメソッドを使用してSharedMemという名前の小さなWin32DLLプロジェクトを作成します。

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を拡大したり、関数を追加したりしてください。

これが上記のメソッドを使用する方法です。デリゲートを64ビット値としてSetSharedMem()設定するためにC#側で使用します(コードが32ビットシステムで実行されているか64ビットシステムで実行されているかに関係なく)。IntPtr

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()これは、デリゲート関数を変換するために使用することに基づくソリューションです。ptr。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 に答える