windows - C ++: Dynamisk indlæsning af klasser fra dlls

Indlæg af Hanne Mølgaard Plasc

Problem



For mit nuværende projekt vil jeg være i stand til at indlæse nogle klasser fra en dll (som ikke altid er den samme og måske ikke engang eksisterer, når min app er samlet). Der kan også være flere alternative dll'er til en given klasse (f.eks. En implementering til Direct3D9 og en for OpenGL), men kun en af ​​dll'erne vil blive indlæst/brugt til enhver tid.


Jeg har et sæt af grundlæggende klasser, der definerer grænsefladen plus nogle grundlæggende metoder/medlemmer (dvs. dem til refrence tælling) af de klasser, jeg vil indlæse, som dll-projekterne derefter stammer fra, når der oprettes der klasser.


//in namespace base
class Sprite : public RefCounted//void AddRef(), void Release() and unsigned refCnt
{
public:
    virtual base::Texture *GetTexture()=0;
    virtual unsigned GetWidth()=0;
    virtual unsigned GetHeight()=0;
    virtual float GetCentreX()=0;
    virtual float GetCentreY()=0;
    virtual void SetCentre(float x, float y)=0;

    virtual void Draw(float x, float y)=0;
    virtual void Draw(float x, float y, float angle)=0;
    virtual void Draw(float x, float y, float scaleX, flota scaleY)=0;
    virtual void Draw(float x, float y, float scaleX, flota scaleY, float angle)=0;
};


Sagen er jeg ikke sikker på hvordan man gør det hele, så de eksekverbare og andre dlls kan indlæse og bruge disse klasser, da ive kun nogensinde har brugt dlls, hvor der kun var en dll, og jeg kunne få Visual Studio linkeren til at sortere det hele ud ved hjælp af .lib filen, jeg får, når jeg compilerer dll 's.


Jeg har ikke noget imod at bruge fabriksmetoder til at indføre klasserne, mange af dem allerede ved design (dvs. en sprite-klasse er oprettet af den vigtigste grafikklasse, fx Graphics-> CreateSpriteFromTexture (base :: Texture *)


REDIGERE:
Da jeg havde brug for at skrive nogle c ++ dlls til brug i python, brugte jeg et bibliotek kaldet pyCxx. Den resulterende dll eksporterede kun en metode, som skabte en forekomst af modulmodulet, som derefter kunne indeholde fabriksmetoder til at skabe andre klasser mv.


Den resulterende dll kunne importeres i python bare med 'import [[dllname]]'.


//dll compiled as cpputill.pyd
extern "C" void initcpputill()//only exported method
{
    static CppUtill* cpputill = new CppUtill;
}

class CppUtill : public Py::ExtensionModule<CppUtill>
{
public:
    CppUtill()
    : Py::ExtensionModule<CppUtill>("cpputill")
    {
        ExampleClass::init\_type();

        add\_varargs\_method("ExampleClass",&CppUtill::ExampleClassFactory, "ExampleClass(), create instance of ExampleClass");
        add\_varargs\_method("HelloWorld",  &CppUtill::HelloWorld,  "HelloWorld(), print Hello World to console");

        initialize("C Plus Plus module");
    }
...
class ExampleClass
...
    static void init\_type()
    {
        behaviors().name("ExampleClass");
        behaviors().doc ("example class");
        behaviors().supportGetattr();
        add\_varargs\_method("Random", &ExampleClass::Random, "Random(), get float in range 0<=x<1");
    }


Hvordan virker det præcist, og kan jeg bruge det i et rent c + + miljø til at løse mit problem her?

Bedste reference


Den nemmeste måde at gøre dette på, IMHO, er at have en simpel C-funktion, der returnerer en pointer til en grænseflade, der er beskrevet andetsteds. Derefter kan din app kalde alle funktionerne i den grænseflade uden at vide, hvilken klasse den bruger.


Rediger: Her er et simpelt eksempel.


I din hovedapp-kode oprettes en overskrift til grænsefladen:


class IModule
{
public:
    virtual ~IModule(); // <= important!
    virtual void doStuff() = 0;
};


Hovedappen er kodet for at bruge grænsefladen ovenfor uden nogen detaljer om den faktiske implementering af grænsefladen.


class ActualModule: public IModule
{
/* implementation */
};


Modulerne - DLL'erne har nu de faktiske implementeringer af denne grænseflade, og disse klasser behøver ikke at blive eksporteret - \_\_declspec (dllexport) er ikke nødvendig. Det eneste krav til modulerne er at eksportere en enkelt funktion, der ville skabe og returnere en implementering af grænsefladen:


\_\_declspec (dllexport) IModule* CreateModule()
{
    // call the constructor of the actual implementation
    IModule * module = new ActualModule();
    // return the created function
    return module;
}


Bemærk: Fejlfinding udelukket - du vil normalt kontrollere, om ny returnerede den korrekte pointer, og du bør beskytte dig mod de undtagelser, der kunne blive kastet i konstruktøren af ​​ActualModule klassen.


Derefter skal du bare indlæse modulet (LoadLibrary funktionen og finde funktionen CreateModule (GetProcAddr funktionen i din hovedapp. Derefter bruger du klassen gennem grænsefladen.


Rediger 2: din RefCount (base klasse af grænsefladen), kan implementeres i (og eksporteres fra) hovedapp. Så skal hele dit modul linke til hovedfilens lib-fil (ja! EXE-filer kan have LIB-filer ligesom DLL-filer!) Og det skal nok være.

Andre referencer 1


Du genopfinder COM. Din RefCounted klasse er IUnknown. Din abstrakte klasse er en grænseflade. En COM-server i en DLL har et entrypoint, der hedder DllGetClassObject (), det er en klassefabrik. Der er masser af dokumentation tilgængelig fra Microsoft på COM, kig rundt lidt for at se, hvordan de gjorde det.

Andre referencer 2


[[Rediger: Mens jeg komponerede alt dette, redigerede Paulius Maruška sin kommentar til at sige stort set det samme. Så undskyld for enhver overlapning. Selvom jeg formoder, at du har fået en til ekstra :)]]


Fra toppen af ​​mit hoved, og under forudsætning af Visual C ++ ...


Du skal bruge LoadLibrary til at indlæse en DLL dynamisk, og brug derefter GetProcAddress til at hente adressen til en funktion, der vil oprette den faktiske afledte klasse, som DLL-koden implementerer. Hvordan du beslutter dig for at gøre dette netop, er op til dig (DLL'erne behøver at finde, måden de afslører, deres funktionalitet skal specificere osv.), Så lad os nu antage, at plugins kun giver nye Sprite-implementeringer.


For at gøre dette skal du beslutte dig for underskrift af funktionen i DLL'en, som hovedprogrammet skal ringe for at oprette en af ​​disse nye sprites. Denne ser passende ud:


typedef Sprite *(*CreateSpriteFn)();


Derefter skal du fra hovedprogrammet indlæse en DLL (igen, hvordan du finder denne DLL er op til dig) og få sprite creation funktionen fra det. Jeg har besluttet, at sprite creation funktionen vil blive kaldt ' CreateSprite ':


HMODULE hDLL=LoadLibrary(pDLLName);
CreateSpriteFn pfnCreateSprite=CreateSpriteFn(GetProcAddress(hDLL,"CreateSprite"));


Så for at faktisk oprette en af ​​disse, skal du bare ringe til funktionen:


Sprite *pSprite=(*pfnCreateSprite)();


Når du er færdig med DLL'en, og der ikke er nogen objekter tilbage, der blev oprettet af den, bruger du derefter FreeLibrary til at slippe af med det:


FreeLibrary(hDLL);


For at oprette en DLL, der sport denne grænseflade, skriv koden for den afledte klasse osv., Så et sted i DLL-koden giver CreateSprite-funktionen, som det kaldende program behøver, ved hjælp af den relevante signatur:


\_\_declspec(dllexport)
extern "C"
Sprite *CreateSprite()
{
    return new MyNewSprite;
}


Dllexport-sagen betyder, at du kan bruge GetProcAddress til at vælge denne funktion ved navn, og den eksterne 'C' sikrer, at navnet er ubemærket og ikke slutter som 'CreateSprite @ 4' eller noget lignende.


To andre noter:



  1. GetProcAddress returnerer 0, hvis den ikke kunne finde funktionen, så du kan bruge denne til at scanne gennem en liste over DLL'er (f.eks. returneret fra FindFirstFile og venner) på udkig efter DLL'er, der understøtter grænsefladen og/eller forsøger at finde flere indgangspunkter og understøt flere typer pr. plugin.

  2. Hvis hovedprogrammet skal slette spritet ved hjælp af sletningsoperatøren, kræver dette, at både DLL og hovedprogrammet er bygget ved hjælp af det samme runtime bibliotek, så hovedprogrammets sletninger og DLL'ernes nye er begge bruger den samme bunke. Du kan omgå dette i et vist omfang ved at have DLL'en en DeleteSprite-funktion, der sletter spritet ved hjælp af DLL'ens runtime-bibliotek i stedet for hovedprogrammerne, men du skal stadig passe på resten af ​​dit program, så du vil måske bare tvinge DLL-forfattere til at bruge det samme runtime bibliotek som dit program. (Jeg vil ikke sige, at du er i godt selskab ved at gøre dette, men det er ikke ualmindeligt. 3D Studio MAX kræver dette for eksempel.)


Andre referencer 3


Måske vil du se på DLL Delay-Loading (http://www.codeproject.com/KB/DLL/Delay\_Loading\_Dll.aspx) - dette vil give dig det, du vil have uden at skulle arbejde for hårdt for det [16]