Edit: Ho promesso di aggiornare la mia risposta per includere il codice per il passaggio di valori a 64 bit, in modo da ecco che va ..
- Ho lasciato la risposta originale se qualcuno è interessato a una soluzione meno complicata per un sistema a 32 bit.
Nota: Dal momento che si sta utilizzando CorBindToRuntimeEx
, che è obsoleta in .NET 4.0, darò per scontato un .net 2.0 soluzione compatibile con il buon vecchio Win32 API.
Così, al fine di passaggio dei dati tra C# e C++ (nel nostro caso - la IntPtr
di un delegato), creeremo un piccolo progetto DLL Win32, chiamato SHAREdmem, con due metodi straight-forward.
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);
}
Ora per il file di implementazione:
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;
}
- Si noti che per semplicità sto solo la condivisione di un Valore a 64 bit, ma questo è un generale modo di condividere la memoria tra C# e C++. Sentiti libero di ingrandire SHARED_MEM_SIZE e/o aggiungere funzioni per condividere altri tipi di dati come meglio credi.
Ecco come faremo consumare i metodi di cui sopra: useremo SetSharedMem()
sul lato C# per impostare del IntPtr
come un valore a 64 bit (a prescindere se il codice viene eseguito su un 32 o un suo delegato Sistema a 64 bit).
C# Codice
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();
}
}
}
- Nota l'uso di
GCHandle
al fine di evitare che il delegato di essere garbage collection.
- Per buone misure, utilizzeremo il valore restituito come indicatore di successo/fallimento.
Sul lato C++ si svuota il valore a 64 bit utilizzando GetSharedMem()
, convertirlo in un puntatore a funzione e richiamare il C# delegato.
C++ Codice
#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();
}
}
}
La risposta originale - Per sistemi a 32 bit
Ecco una soluzione che si basa sull'utilizzo di IntPtr.ToInt32()
al fine di convertire il func delegato . ptr. allo int
che viene restituito dal metodo EntryPoint C# statico.
(*) Si noti l'uso di GCHandle
per impedire che il delegato venga raccolto in modo inutile.
C# Codice
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();
}
}
}
codice C++ Abbiamo bisogno di convertire il int
valore restituito ad un puntatore a funzione, così noi cominceremo definendo una funzione void ptr. Tipo:
typedef void (*VOID_FUNC_PTR)();
E il resto del codice sembra più o meno come il tuo codice originale, con l'aggiunta di conversione e l'esecuzione della funzione 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();
}
Un po 'di extra
È inoltre possibile utilizzare l'ingresso string
al fine di scegliere il metodo da eseguire:
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();
}
- È possibile ottenere ancora di più generico utilizzando la riflessione per trovare il metodo corretto in base all'input della stringa data.
Stai cercando di interagire con il programma C# (ad esempio, inviare un'interfaccia COM come parametro o recuperarne uno) o chiamare semplicemente un metodo da C++ a C#? – AVIDeveloper
Ho davvero bisogno di chiamare una funzione C# da C++ (e ottenere un IntPtr da C#, non un int).Posso occuparmi di tutto il resto, ma non vedo un modo per farlo. – LCC
Penso che un po 'più di dettaglio su ciò che stai facendo sarebbe di aiuto. Stai solo cercando di ottenere il codice C# per fare una cosa, poi tornare, o ti aspetti un'interpolazione più continua. Perché C++/CLI non è una possibilità se controlli sia i lati gestiti che quelli non gestiti? Infine c'è un modo per ridefinire il crossover da gestire avviato (usando quindi DllImport). – Guvante