windows - C ++ wrapper DLL'er til statiske LIB'er

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg har nogle statisk kompilerede biblioteker (.lib), som jeg bruger i mit projekt, som er skrevet i C ++ og bygget på både Windows og Linux. På mit projekts indgangspunkt til disse biblioteker bruger jeg kun en eller to funktioner fra 'main' -biblioteket i den statiske bibliotekssuite, virkelig (men jeg er sikker på at disse funktioner kalder mange andre i de andre biblioteker i suite).


Jeg vil helst gerne have en række dynamisk forbundne biblioteker (DLL'er), der ombryder rundt om hver af libs i den statiske lib-suite; Jeg har læst/hørt, at måden at gøre dette på Windows (f.eks. Visual Studio 2005/2008/2010) er at 'skabe en wrapper DLL' med nogle udsatte funktioner, der kalder de underliggende statiske biblioteksfunktioner. nogen kan give mig nogle detaljerede trin for trin herunder muligvis nogle uddrag af hvordan man skal gøre om det i MS Visual Studio 2005/2008 2010. Jeg er sikker på, at nogle af jer måske allerede gør det på en dag-til- dag, din oplevelse er meget værdsat.


Redigere:


Til gavn for andre som mig selv bogfører jeg det første 'nyttige' link, jeg fandt:
http://tom-shelton.net/index.php/2008/12/11/creating-a-managed-wrapper-for-a-lib-file/[10]

Bedste reference


'Konverter et bibliotek til en anden bibliotekstype' forekommer let, men det er det ikke. Der er ingen straight-forward trin-for-trin måde at gøre dette på, fordi C ++ og DLL'er ikke spiller godt sammen, og din kode skal tilpasses til at understøtte en DLL-grænseflade.


En kortfattet måde at beskrive problemet på er:



  • En. libs grænseflade er C ++

  • En .dlls grænseflade er C



Således understøtter en DLLs grænseflade simpelthen ikke C ++, og du skal være klog for at få det til at fungere - det er derfor, de tvetydige eksisterende svar.


En standard måde er via COM, hvilket betyder at opbygge en hel COM wrapper til biblioteket, komplet med klassefabrik, grænseflader, objekter og brug af BSTR i stedet for std::string. Jeg vil gætte er ikke praktisk.


En anden løsning er at skabe en C-grænseflade til dit C ++-bibliotek, som er DLL-sikkert. Det betyder stort set at skabe en winapi-stil grænseflade, som igen sandsynligvis ikke er praktisk eller besejrer formålet med at bruge dit bibliotek overhovedet. Dette er hvad @David Heffernan foreslår. Men hvad han ikke taler om, er, hvordan du skal ændre din kode for at være DLL-kompatibel.


Et vigtigt, men subtile problem er, at du ikke kan passere nogen templerede C ++-objekter på tværs af DLL-grænser. Dette betyder at passere std::string ind eller ud af en DLL-funktion anses for usikre. Hvert binært får sin egen kopi af std::string koden, og der er ingen garanti for, at de vil ske for at lege fint med hinanden. Hver binær (potentielt) får også sin egen kopi af CRT'en, og du vil rydde op indre tilstand af et modul ved at manipulere objekter fra en anden.


Rediger : Du kan eksportere C ++-objekter i MSVC ved hjælp af \_\_declspec(dllexport) og importere dem ved hjælp af \_\_declspec(dllimport). Men der er mange restriktioner på dette og subtiliteter, der giver problemer. Dybest set er det en genvej for at få kompilatoren til at oprette en billig C-style interface til din eksporterede klasse eller funktion. Problemet er, at det ikke advarer dig om, hvor meget usikre ting der sker. At gentage:



  1. Hvis der er nogen templated symboler, der krydser DLL-grænser, er det ikke sikkert (std::* for eksempel).

  2. Alle objekter med CRT-styret tilstand bør ikke krydse DLL-grænser (f.eks. FILE*.


Andre referencer 1


Dette var lidt stort at tilføje som en kommentar til tifour s svar ...


Hvis du stadig vil vedligeholde en C ++ API, når du bruger DLL-wrapper, kan du sætte C + + til C-konverteringsfunktioner i headerfilen. Dette sikrer, at kun C-kompatible datatyper nogensinde krydser DLL-grænsen.


Som et eksempel


//MyDLL.h

class MyDLL {
 public:
  ...
  int Add2ToValues(std::vector<int>& someValues) {
   int* cValues = new int[someValues.size()];
   memcpy(cValues, &someValues[0], someValues.size() * sizeof(int));
   int retVal = Add2ToValues\_Internal(cValues, someValues.size());
   someValues.assign(std::begin(cValues), std::end(cValues));
   delete [] cValues;
   return retVal;
  }

private:
  int Add2ToValues\_Internal(int* valuesOut, const int numValues);
};

//MyDLL.cpp

 int MyDLL::Add2ToValues\_Internal(int* values, const int numValues)
 {
   for(int i = 0; i < numValues; ++i) {
     values[i] += 2;
   }

   return 0;
 }


En fangst, jeg løb ind i, når du laver disse wrappers, er at du skal allokere og allokere enhver hukommelse i headerfilen. Da hovedfilen bliver kompileret af den applikation, der bruger dit bibliotek, bruger den CRT til den kompilator, du bruger til at opbygge din applikation. Alle interaktioner med DLL'en bruger C, så du vil ikke løbe ind i nogen runtime mismatches, og al hukommelse er allokeret og frigivet enten helt inden for DLL eller helt inden for applikationen, så du ikke har problemer med DLL-hukommelsesstyring heller. I eksemplet tildelte jeg begge og fordelte i overskriften. Hvis du skal tildele data i \_Internal-funktionen, skal du også tilføje en funktion, der giver dig mulighed for at frigøre hukommelsen inden for DLL'en. Når du er inde i \_Internal-funktionerne, er du fri til at bruge så meget C ++ som du vil.