Creazione di DLL c ++ senza metodi statici

Sto creando una DLL in C ++. Ecco un esempio:

namespace MathFuncs { class MyMathFuncs { public: // Returns a + b static __declspec(dllexport) double Add(double a, double b); // Returns a - b static __declspec(dllexport) double Subtract(double a, double b); // Returns a * b static __declspec(dllexport) double Multiply(double a, double b); // Returns a / b // Throws DivideByZeroException if b is 0 static __declspec(dllexport) double Divide(double a, double b); }; } 

Tutti i metodi sono statici e ci sono molte limitazioni con i metodi statici, quindi la mia domanda è: come posso implementare lo stesso senza metodi statici? Devo sempre avere metodi statici in DLL? Voglio importare questa DLL in C # e nelle app IOS.

Come nota a margine ho fatto 1 esperimento pochi giorni fa con mingw / c ++ che può essere chiaro per te.

Ho avuto un contatore di riferimento globale per scoprire perdite di memoria nel mio programma,

 class ReferenceCounter /** other implementations details are omitted.*/ { public: static int GlobalReferenceCounter; //version 1 static int getReferenceCount1() { return GlobalReferenceCounter;} //verison 2 static int getReferenceCount2(); //same code of version 1 but moved into .cpp file }; 

Quando compilo la mia libreria usando il contatore di riferimento in una DLL, la variabile viene duplicata, 1 versione è compilata nella DLL e una versione è compilata nel codice client.

Quando chiedo istanze di materiale conteggio di riferimento dai metodi factory della DLL, solo il contatore di riferimento all’interno della DLL viene aumentato / ridotto. Quando il codice client utilizza le proprie classi ereditate dal contatore Rif, il contatore di riferimento client viene aumentato / ridotto.

Quindi, per verificare la presenza di perdite di memoria, devo fare alla fine del programma

 assert(ReferenceCounter.getReferenceCount1() == 0); assert(ReferenceCoutner.getReferenceCount2() == 0); 

questo perché in caso di perdita di memoria uno di questi valori sarà maggiore di 0. Se il primo valore è maggiore di 1, la perdita di memoria è causata da classi utente non allocate, se il secondo valore è maggiore di 0, quindi la perdita di memoria è causata dalla libreria classi.

Si noti che se la perdita è causata da classi di librerie non allocate, questo non è necessariamente un bug della libreria, poiché l’utente è ancora in grado di filtrare quelle classi, anche se ciò dovrebbe significare una mancanza di progettazione nella libreria, poiché idealmente dovrebbe essere restituito in opportuni puntatori intelligenti per la sicurezza.)

Ovviamente dovresti specificare che “GlobalReferenceCoutner” è duplicato nella documentazione, altrimenti alcuni utenti inconsapevoli possono solo pensare che 2 getter siano ridondanti e penseranno che hai fatto un errore. (se ansible evitare di fare qualcosa del genere, è oscuro e poco chiaro)

Questo dovrebbe anche avvisare che l’accesso al metodo statico tramite DLL è altamente pericoloso. Ad esempio Se nella mia class volevo avere solo 1 contatore di riferimento invece di 2 ho dovuto farlo per garantire la sicurezza:

 class ReferenceCounter { public: static int GlobalReferenceCounter; static void duplicate() { increaseGlobalCounter(); } static void release() { decreaseGlobalCounter(); } static int getGlobalCounter() { return privateGetGlobalCounter(); } private: static increaseGlobalCounter(); // implementation into Cpp file static decreaseGlobalCounter(); // implementation into Cpp file static privateGetGlobalCounter(); // implementation into Cpp file }; 

facendo ciò vi garantiremo che lo stesso valore viene utilizzato tra i limiti della DLL e nell’applicazione utente. quindi invece di avere 2 variabili diverse qui usiamo 1 variabile (probabilmente il GlobalCounter è ancora compilato nell’eseguibile dell’utente, ma nessuno lo sta usando rimuovendo “l’effetto clone”)

È necessario utilizzare metodi globali in stile C. La ragione di ciò è descritta qui .

Fondamentalmente si tratta di questo: le funzioni C si traducono bene nelle esportazioni DLL perché C è “più vicino alla terra” in termini di funzionalità linguistiche. C si traduce più direttamente in codice macchina. C ++ fa molto a livello di compilatore, offrendoti molte funzionalità che non possono essere utilizzate al di fuori di un ambiente C ++. Per questo motivo, le funzioni esportate dovrebbero seguire uno stile C per funzionare correttamente attraverso i confini della DLL. Ciò significa che nessun modello, nessun codice inline, nessuna class o struttura non POD.

Considera questo codice:

 extern "C" { __declspec(dllexport) int GlobalFunc(int n) { return n; } namespace SomeNamespace { __declspec(dllexport) int NamespaceFunction(int n) { return n; } } class MyClass { __declspec(dllexport) int ClassNonStatic(int n) { return n; } __declspec(dllexport) static int ClassStatic(int n) { return n; } }; } 

Ciò risulta nei nomi di funzione esportati DLL seguenti:

? ClassNonStatic @ MiaClasse @@ AAEHH @ Z

? ClassStatic @ MiaClasse @@ CAHH @ Z

GlobalFunc

NamespaceFunction

Quelli con i nomi divertenti sono essenzialmente incompatibili con qualsiasi altro che non sia i progetti C ++ creati da Visual Studio. Questo è chiamato mangling del nome , incorporando alcune informazioni di tipo nel nome stesso come soluzione alternativa ai limiti delle funzioni esportate di cui sto parlando. È ansible utilizzare tecnicamente queste funzioni esternamente, ma è fragile e dipende dalle sfumature del comportamento specifico del compilatore.

La regola empirica per esportare le funzioni in una DLL è: puoi farlo in C? Se non puoi, allora è quasi certo che avrai problemi.

Si noti qui che anche i metodi di class statici (che sono essenzialmente globali) hanno ancora il mangling dei nomi, anche con la extern "C" . Ma funzioni autonome in un’esportazione di namespace senza manomissione di nomi (sebbene perdano lo scope namespace).

Puoi cominciare a capire perché questa regola empirica ha senso.


Se vuoi esportare una class, segui la regola generale e progetta l’interfaccia DLL come faresti in C. Ecco un esempio. Prendiamo questa class C ++:

  class Employee { private: std::string firstName; std::string lastName; public: void SetFirstName(std::string& s) { this->firstName = s; } void SetLastName(std::string& s) { this->lastName = s; } std::string GetFullName() { return this->firstName + " " + this->lastName; } }; 

Non puoi semplicemente attaccare __declspec(dllexport) su questo. È necessario fornire un’interfaccia C per questo ed esportarlo. Come questo:

  extern "C" { __declspec(dllexport) Employee* employee_Construct() { return new Employee(); } __declspec(dllexport) void employee_Free(Employee* e) { delete e; } __declspec(dllexport) void employee_SetFirstName(Employee* e, char* s) { e->SetFirstName(std::string(s)); } __declspec(dllexport) void employee_SetLastName(Employee* e, char* s) { e->SetLastName(std::string(s)); } __declspec(dllexport) int employee_GetFullName(Employee* e, char* buffer, int bufferLen) { std::string fullName = e->GetFullName(); if(buffer != 0) strncpy(buffer, fullName.c_str(), bufferLen); return fullName.length(); } } 

Quindi scrivi un altro piccolo wrapper sul lato C # e hai eseguito correttamente il marshalling di questa class.

Specificamente per il marshalling in C #, un’altra opzione è quella di fornire un’interfaccia COM alla tua class invece di un’interfaccia C. Essenzialmente è la stessa cosa, ma ci sono un sacco di classi helper e supporto per compilatori speciali per aggiungere il supporto COM direttamente alle classi C ++ senza scrivere wrapper separati. Gli oggetti COM possono essere referenziati direttamente da C #.

Questo non ti aiuterà con ios però …