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