2010-04-09 5 views
7

Quindi quello che ho è una API C++ contenuta in un file * .dll e voglio usare un'applicazione C# per chiamare metodi all'interno dell'API.Esporre un'API C++ a C#

Finora ho creato un progetto/CLR C++ che include il C++ native API ed è riuscito a creare una classe "ponte" che sembra un po 'come il seguente:

// ManagedBridge.h 
#include <CoreAPI.h> 
using namespace __CORE_API; 

namespace ManagedAPIWrapper 
{ 
    public ref class Bridge 
    { 
     public: 
      int    bridge_test(void); 
      int    bridge_test2(api_struct* temp); 
    } 
} 

.

// ManagedBridge.cpp 
#include <ManagedBridge.h> 

int Bridge::bridge_test(void) 
{ 
    return test(); 
} 

int Bridge::bridge_test2(api_struct* temp) 
{ 
    return test2(temp); 
} 

devo anche un'applicazione C# che ha un riferimento al C++/CLR "Bridge.dll" e quindi utilizza i metodi contenuti all'interno. Ho un certo numero di problemi con questo:

  1. io non riesco a capire come chiamare bridge_test2 all'interno del programma C#, in quanto non è a conoscenza di ciò che un api_struct è in realtà. So che ho bisogno di eseguire il marshalling dell'oggetto da qualche parte, ma lo faccio nel programma C# o nel bridge C++/CLR?
  2. Questo mi sembra molto modo prolisso di esporre tutti i metodi nell'API, non c'è forse un modo più semplice che mi manca fuori? (Che non utilizza P/Invoke!)

EDIT: Ok, così ho le basi di lavoro ora, grazie alle risposte di seguito, tuttavia il mio struct (chiamarla "api_struct2" per questo esempio) ha sia un enum nativo e l'unione nel codice C++ nativo, come il seguente:

typedef struct 
{ 
    enum_type1 eEnumExample; 
    union 
    { 
      long  lData; 
      int  iData; 
      unsigned char ucArray[128]; 
      char  *cString; 
      void  *pvoid; 
    } uData; 
} api_struct2; 

mi pare di aver capito come ottenere il lavoro enum; L'ho re-dichiarato nel codice gestito e sto eseguendo un "native_enum test = static_cast (eEnumExample)" per convertire la versione gestita in nativa.

Tuttavia il sindacato mi ha messo perplesso, non sono proprio sicuro di come attaccarlo .. Idee qualcuno?

risposta

3

Sì, stai passando una struttura non gestita per riferimento. Questo è un problema per un programma C#, i puntatori sono abbastanza incompatibili con la garbage collection.Senza contare il fatto che probabilmente non ha nemmeno la dichiarazione per la struttura.

È possibile risolverlo dichiarando una versione gestita della struttura:

public value struct managed_api_struct { 
    // Members... 
}; 

Ora è possibile dichiarare il metodo come

int bridge_test2(managed_api_struct temp); // pass by value 

o

int bridge_test2(managed_api_struct% temp); // pass by reference 

Scegli il secondo se la struttura ha più di 4 campi (~ 16 byte). Il metodo deve copiare i membri della struttura, uno alla volta, in una api_struct non gestita e chiamare il metodo di classe non gestito. Questo è purtroppo necessario perché il layout di memoria di una struttura gestita non è prevedibile.

Questo è tutto abbastanza meccanica, si potrebbe ottenere aiuto from SWIG. Non l'ho usato da solo, non sono sicuro che sia abbastanza intelligente da gestire una struttura passata.

Un approccio completamente diverso è quello di rendere il più pulito classe wrapper dandogli un costruttore e/o in che consente di creare il contenuto di un api_struct. Oppure potresti dichiarare una classe ref wrapper per la struttura, proprio come faresti nel codice gestito.

+0

Quindi, questo significherebbe che creo il managed_api_struct nel C++/CLI * .dll o nel codice C# stesso? Inoltre, ho pensato che si potesse passare una struct gestita al codice nativo fino a quando si è utilizzato l'attributo StructLayout? – Siyfion

+0

Non importa, ma C++/CLI ha senso evitare le dipendenze circolari. Sì, [StructLayout] funziona ma tu * devi * utilizzare una chiamata Marshal :: StructureToPtr(). Il layout di una struttura gestita non è prevedibile. –

+0

Ok grazie, darò un'occhiata ora, vedere dove mi prendo anche io. – Siyfion

2

in quanto non è a conoscenza di ciò che un api_struct in realtà è

È necessario definire una versione gestita in un assembly .NET, che utilizza gli attributi (come StructLayoutAttribute) per assicurarsi che marescialli correttamente.

Questo mi sembra un molto prolisso [...]

L'altro approccio è quello di creare un wrapper COM (ad esempio utilizzando ATL) intorno al vostro API. Questo potrebbe essere uno sforzo maggiore, ma almeno si evita il doppio codice delle definizioni di struct e function necessarie per P/Invoke.

Correzione: È stato creato un progetto C++/CLI: quindi basta aggiungere corretto '#pragma' per dire al compilatore questo è il codice .NET e quindi l'uscita è un'assemblea il progetto C# può semplicemente fare riferimento .

+0

Che cosa aggiungerà il #pragma aiutarmi con? E c'è un modo in cui posso mettere tutto il marshalling nel progetto C++/CLI, in modo che ogni progetto che fa riferimento a esso non debba ri-dichiarare le strutture/classi .NET equivalenti? – Siyfion

+0

@Siyfion: '#pragma managed' consente di attivare e disattivare il codice gestito. Vedi http://msdn.microsoft.com/en-us/library/0adb9zxe(VS.100).aspx – Richard

+0

Lo capisco, ma funziona al momento, quindi non riesco a vedere quale aggiunta mi permetterà di fare! ? – Siyfion

2

Yuo stanno cercando di fare in questo modo più complicato che è veramente. Quello che vuoi è due strutture diverse. Uno gestito e uno non gestito. Esponi la versione gestita esternamente (alla tua app C#). Sarà tutto ".Net-ish" senza concetti di sindacato o così.

Nel vostro ponte si visualizza la versione gestita della struct, creare manualmente la struct non gestito e scrivere il codice per spostare i dati, campo per campo verso la struct non gestito. Quindi chiama il tuo codice non gestito e infine sposta nuovamente i dati nella struttura gestita.

La cosa bella di C++/CLI è che il codice gestito può anche funzionare con codice e dati non gestiti (e includere i file .h non gestiti).