Scrivere una DLL in C / C ++ per l’interoperabilità .Net

Nella mia applicazione C #, vorrei scrivere una parte del codice in C. Ho intenzione di scrivere una DLL che sarebbe interoperabile con .Net. Come lo posso fare?

Ci sono essenzialmente tre modi giusti per farlo:

  • Usa C ++ / CLI. Questo è il modo ottimale se questa DLL verrà utilizzata solo da .NET.
  • Utilizza un’API compatibile ” extern "C" , come la stessa API di Windows. Questo è il più portatile, ma non è così conveniente per i chiamanti quanto l’utilizzo di un modello di class per rappresentare i tuoi oggetti.
    • Questa è l’opzione migliore se si intende realmente scrivere in ANSI C (non in C ++).
    • Per questo percorso, scrivi le tue funzioni come extern "C" returntype __stdcall __declspec(dllexport) func(params) { ... }
    • Si dovrebbe anche usare un modello di memoria “caller-fornisce-il-buffer”, piuttosto che restituire un buffer allocato all’interno della libreria. Nei casi in cui è necessario allocare memoria per lo stato interno, il chiamante dovrebbe vederlo come un handle opaco e si dovrebbero fornire funzioni di accesso per il chiamante per estrarre i dati. In nessuna circostanza il chiamante dovrebbe rilasciare deallocate la memoria allocata all’interno della libreria, tuttavia è opportuno che il chiamante chieda alla libreria di effettuare la deallocazione.
  • Usa COM o un’API simile a COM. Qui si restituisce (spesso tramite il parametro out) un puntatore a un’interfaccia, che è una class con funzioni virtuali pure, senza funzioni non virtuali e senza dati.
    • L’implementazione è in classi concrete derivate da questa interfaccia astratta, possono avere dati e funzioni di supporto a bizzeffe, dal momento che ciò non influisce sull’interfaccia binaria.
    • Questo è molto più lavoro in biblioteca, ma estremamente portatile e facile da usare per il consumatore.

E c’è una cosa assolutamente da NON fare:

  • usa __declspec(dllexport) su classi C ++.

EDIT: Voglio anche spiegare alcune buone pratiche per l’opzione n. 2 che massimizzerà la portabilità e rendere le parti native C / C ++ utilizzabili anche da applicazioni non gestite.

Puoi renderlo più semplice con una macro, il solito modo di farlo è:

Nel tuo file di intestazione, tutte le dichiarazioni di funzioni sono simili

 MYPROJECTAPI(returntype) PublicFunc(params); 

Nel tuo progetto, la definizione è

 #define MYPROJECTAPI(returntype) \ extern "C" returntype __stdcall __declspec(dllexport) 

Nei progetti dei consumatori

 #define MYPROJECTAPI(returntype) \ extern "C" returntype __stdcall __declspec(dllimport) 

e quindi puoi definire la macro in modo diverso per altri compilatori come gcc che non usano __declspec .

La soluzione completa apparirebbe (nel file di intestazione pubblico myproject.h ):

 #if _WIN32 # if BUILDMYPROJECT # define MYPROJECTAPI(returntype) \ extern "C" returntype __stdcall __declspec(dllexport) # else # define MYPROJECTAPI(returntype) \ extern "C" returntype __stdcall __declspec(dllimport) # endif #else # define MYPROJECTAPI(returntype) extern "C" returntype #endif 

e quindi il progetto di Visual C ++ causerebbe BUILDMYPROJECT da definire durante la creazione di myproject.dll

In poche parole:

(1) Creare un nuovo progetto di libreria C ++ / CLI.

(2) Scrivi il tuo codice. Per le classi che devono essere accessibili dal tuo progetto C #, assicurati di crearle come classi CLR:

 public ref class R {/*...*/}; // CLR class public value class V {/*...*/}; // CLR struct public interface class I {/*...*/}; // CLR interface 

(3) Compilare il progetto e aggiungere un riferimento ad esso nel progetto C #.

Di seguito è riportato un esempio per un’applicazione in cui ho dovuto fare proprio questo. Nel mio caso, avevo bisogno di una DLL per racchiudere le chiamate a funzioni che erano disponibili solo in un .lib. La parte chiave è l’ extern "C" __declspec (dllexport) nella dichiarazione. Questo è praticamente tutto ciò di cui hai bisogno. Il resto stava semplicemente usando dllimport nell’app C # e ottenendo il marshalling giusto.

 extern "C" __declspec (dllexport) LONG EstablishContext(DWORD dwScope, LPCVOID pvReserved1, LPCVOID pvReserved2, LPSCARDCONTEXT phContext) { return SCardEstablishContext(dwScope, pvReserved1, pvReserved2, phContext); }