2012-03-06 14 views
6

Ho bisogno di prendere un programma C++, caricare CLR e chiamare una funzione in una libreria C#. La funzione che devo chiamare utilizza un'interfaccia COM come parametro.CLR Hosting: chiamare una funzione con una firma del metodo arbitrario?

Il mio problema è, l'interfaccia di hosting CLR sembra solo di lasciare che si chiama un metodo con questa firma:

int Foo(String arg) 

esempio, questo codice C++ carichi CLR e gestisce la funzione p.test in "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); 

che cosa devo fare è chiamare una funzione con questo metodo di firma (nota possiedo il codice C#, in modo da poter cambiare):

void SomeFunction(IFoo interface) 

Dove IFoo è un'interfaccia com. Potrei anche fare quello che devo se potevo chiamare una funzione come questa:

IntPtr SomeFunction(); 

avrei potuto SomeFunction costruire una corretta delegato quindi utilizzare Marshal.GetFunctionPointerForDelegate. Tuttavia, non riesco a capire come rendere le interfacce di hosting fare qualcosa di diverso da chiamare una funzione con una firma int func (stringa).

Qualcuno sa come chiamare una funzione C# dal codice C++ con una firma diversa?

(. Nota non posso usare C++/CLI per questo ho bisogno di utilizzare le API di hosting.)

+0

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

+0

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

+0

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

risposta

7

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.
+0

Si presume che IntPtr si adatti a un intero a 32 bit con non sempre il caso. Suppongo che potrei chiamare due volte per ottenere la metà alta/bassa del numero intero che è un tale hack gigante. =/ – LCC

+0

Concordato. Ho acceso il mio PC proprio ora per scrivere un disclaimer sul ToInt32() e il gap di 64 bit e vedo che ci sei già. Non penso che tu debba chiamare due volte, ma qualcosa dovrebbe essere fatto. Mi viene in mente una soluzione 'MemoryMappedFile', ma ora è troppo tardi per cui dovrò aggiornare la mia risposta più tardi. – AVIDeveloper

+0

@AVIDeveloper Domanda Noob: nel caso di 64 bit, i file mappati in memoria sono davvero il percorso più semplice? A parte questo, solo per trasmettere un valore di un tipo più grande attorno a me sembra strano che sia necessario un intero nuovo set di strumenti. E, possiamo restituire valori di tipo String^di nuovo a C++ land (usandolo come una stringa: 'static_cast (retVal.bstrVal)', vedere 'http://blogs.msdn.com/b/msdnforum/archive/2010/07/09/use-clr4-hosting-api-to-invoke-net-assembly-from-native-c.aspx'), e le stringhe dovrebbero essere in grado di trasportare valori a 64 bit. –

Problemi correlati