CLR хостинг: вызов функции с произвольной сигнатурой метода?

мне нужно взять программу на C++, загрузить CLR и вызвать функцию в библиотеке C#. Функция, которую мне нужно вызвать, принимает в качестве параметра COM-интерфейс.

моя проблема в том, что интерфейс хостинга CLR только позволяет вам вызывать метод с этой подписью:

int Foo(String arg)

пример, этот код c++ загружает CLR и запускает функцию P. Test в "test.exe":

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 построить правильный делегат, а затем использовать Маршал.GetFunctionPointerForDelegate. Однако я не могу понять, как заставить интерфейсы хостинга делать что-либо, кроме вызова функции с сигнатурой int func(string).

кто-нибудь знает, как вызвать функцию C# из кода C++ с помощью другая подпись?

(примечание Я не могу использовать C++ / CLI для этого. Мне нужно использовать API хостинга.)

1 ответов


Edit: я обещал обновить свой ответ, чтобы включить код для передачи 64-битных значений, так что вот идет..

  • я оставил оригинальный ответ, если кто-то заинтересован в более сложное решение для 32-битной системы.

Примечание: так как вы используете CorBindToRuntimeEx, который устарел в .net 4.0, я предположу, что .net 2.0 совместимое решение с использованием старого доброго Win32 API.

Итак, для передачи данных между C# и C++ (в нашем случае -IntPtr делегата), мы создадим небольшой проект Win32 DLL с именем SharedMem, С двумя прямыми методами.

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() для того, чтобы преобразования делегата func. запись 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 input, чтобы выбрать, какой метод выполнить:

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();
}
  • вы можете получить еще более общий, используя отражение, чтобы найти правильный метод в соответствии с заданной строкой ввода.