Visualizzazione dei risultati da 1 a 7 su 7
  1. #1
    Utente di HTML.it L'avatar di XWolverineX
    Registrato dal
    Aug 2005
    residenza
    Prague
    Messaggi
    2,565

    C++ - Scegliere implementazione a runtime

    Salve.
    Ho un piccolo problema per il quale chiedo un consiglio.
    Supponiamo di avere una classe base di questo tipo:

    codice:
    class IPlugin
    {
    	public:
    		virtual void Init() = 0;
    		virtual void Work() = 0;
    		virtual const char* GetModuleName() = 0;
    };
    Ora voglio che un utente esterno possa estendere tale classe e implementare dei comportamenti opportuni. Per fare quindi una prova al volo, ho scritto una cosa del genere (sono solo delle prove quindi perdonate errori di qualsiasi genere, concentratevi sul concetto):

    codice:
    #include "stdafx.h"
    
    class IPlugin
    {
    	public:
    		virtual void Init() = 0;
    		virtual void Work() = 0;
    		virtual const char* GetModuleName() = 0;
    };
    
    class MyLib : public IPlugin
    {
    	public:
    		void Init() { OutputDebugStringA("Init ok\n"); }
    		void Work() { OutputDebugStringA("Run ok\n");  }
    		const char* GetModuleName() { return "MyLib"; }
    };
    
    
    extern "C" __declspec(dllexport) IPlugin* Create() { return new MyLib(); }
    Nel programma host ho poi richiamato il tutto usando una cosa del genere
    codice:
    #include <Windows.h>
    
    
    class IPlugin
    {
    	public:
    		virtual void Init() = 0;
    		virtual void Work() = 0;
    		virtual const char* GetModuleName() = 0;
    };
    
    typedef IPlugin* (*create)();
    
    
    int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdline, int nCmdShow)
    {
    	create CreateLib;
    
    	HMODULE lib = LoadLibraryA("Lib.dll");
    		CreateLib = (create)GetProcAddress(lib,"Create");
    
    		IPlugin *p = CreateLib();
    		p->Init();
    		p->Work();
    		OutputDebugStringA(p->GetModuleName());
    
    		delete p;
    	FreeLibrary(lib);
    
    	return 0;
    }
    Quello che però avevo in mente è un pò diverso e credo richieda l'utilizzo di più pattern.

    L'intento è "chiedere" alla DLL quali sono le classi che essa esporta e poi scegliere dal programma host quale implementazione chiedere alla DLL.

    Un esempio pratico chiarirà meglio la situazione

    Programma host: leggi file .conf
    Nel file conf c'è scritto -> carica dll1, dll2 e dll3
    Per ogni dll il programma la carica e chiede: "Mi dai la lista delle classi che implementano l'interfaccia Plugin che abbiamo concordato?"
    La dll da la lista.

    Sempre nel file conf c'è scritto: "Questa cosa devi gestirla con l'implementazione A e quest'altra con l'implementazione Z"
    Il programma cerca in quale HMODULE è implementata dale classe e ne chiede un puntatore.

    Spero di essere stato chiaro.
    Grazie in anticipo per l'aiuto.
    "Se proprio devono piratare, almeno piratino il nostro." (Bill Gates)

    "Non è possibile che 2 istituzioni statali mi mettano esami nello stesso giorno." (XWolverineX)

    http://xvincentx.netsons.org/programBlog

  2. #2
    Il problema con questo genere di necessità è che il C++ in sé non ha né un'ABI fissata né un sistema di reflection sufficiente per gestire in maniera automatica questi problemi. Per questo motivo ci sono diverse soluzioni extra-standard delle quali la "classica" su Windows è COM (per non parlare di .NET, ma lì si va fuori dall'ambito del codice nativo). Il problema di COM è che è decisamente complicato da imparare e da utilizzare, per cui, a meno che non risulti strettamente necessario, per il momento lo eviterei.

    Personalmente, una volta scelto di evitare COM, io mi limiterei ad esportare in ogni dll una classe factory che fornisca al chiamante una lista delle classi (che derivano dalla tua interfaccia) che è in grado di istanziare (ciascuna identificata da una costante), assieme eventualmente a qualche informazione aggiuntiva. Per istanziarle poi viene fornito un metodo separato, da richiamare con la costante identificativa della classe.

    Naturalmente si presta il fianco a tutti i soliti problemi che si hanno nell'usare roba C++ "a cavallo" di diversi moduli: a meno che tutto non sia compilato con lo stesso compilatore, stessa versione, stessi flag:
    - vietato passare in giro container STL e altri oggetti della libreria standard (i loro internals possono cambiare addirittura a seconda dei flag di compilazione, per cui il layout binario di un std::vector potrebbe essere diverso secondo l'exe o per la dll);
    - occhio con le allocazioni: o si stabilisce un allocatore comune (GlobalAlloc/LocalAlloc, new se e solo se entrambe exe e dll fanno riferimento alla stessa versione in dll della CRT), oppure l'unico sistema "sicuro" è fare sì che ogni dll esporti una sua funzione per deallocare la memoria;
    - occhio alla ABI: se si usano compilatori con ABI differenti c'è il rischio che esploda tutto; le parti particolarmente sensibili sono, ad esempio, la gestione delle eccezioni, delle vtable, l'ereditarietà multipla.
    In effetti, spesso per evitare questi rischi ci si riduce ad un'interfaccia puramente C, che almeno ha un'ABI definita.

    Probabilmente ci sono altri caveat e metodi per evitare questi rischi (il più semplice, appunto, è compilare tutto alla stessa maniera, ma a quel punto forse quasi conviene fare un exe monolitico ), non me ne intendo particolarmente; ma in fin dei conti, se è stato inventato COM è stato anche per evitare questi problemi (creandone degli altri, ma questa è un'altra storia).
    Amaro C++, il gusto pieno dell'undefined behavior.

  3. #3
    Utente di HTML.it L'avatar di XWolverineX
    Registrato dal
    Aug 2005
    residenza
    Prague
    Messaggi
    2,565
    Per il compilatore non c'è problema. Era, è e sarà sempre Visual C++.

    La tua (ottima) spiegazione conferma il dubbio che si era formato: l'unico modo è davvero questo

    codice:
    IPlugin *Create(string type)
    {
           if(type == "Lib1") return new Lib1();
           if (type == "Lib2") return new Lib2();
    }
    Inserendo in ogni dll un metodo che tira fuori un array di stringhe istanziabili.
    "Se proprio devono piratare, almeno piratino il nostro." (Bill Gates)

    "Non è possibile che 2 istituzioni statali mi mettano esami nello stesso giorno." (XWolverineX)

    http://xvincentx.netsons.org/programBlog

  4. #4
    Originariamente inviato da XWolverineX
    Per il compilatore non c'è problema. Era, è e sarà sempre Visual C++.
    Il problema è che le cose cambiano anche solo a cambiare alcuni flag del compilatore; diciamo che devi stare molto attento a quello che fai, l'unica soluzione veramente sicura è compilare tutto con stessa versione e stessi flag, ma a quel punto si perde un po' il senso di avere i plugin in dll separate, tanto valeva compilare tutto insieme in un eseguibile monolitico.
    La tua (ottima) spiegazione conferma il dubbio che si era formato: l'unico modo è davvero questo

    codice:
    IPlugin *Create(int type)
    {
           if(type == 0) return new Lib1();
           if (type == 1) return new Lib2();
    }
    ... abbinato a
    codice:
    void Destroy(IPlugin *obj)
    {
        delete obj;
    }
    se non puoi garantire che il new della dll e il new dell'exe stanno usando sicuramente la stessa CRT dietro le quinte (cosa che si dà solo se entrambi sono compilati per usare la CRT in versione release dll, ovviamente stessa versione).
    Amaro C++, il gusto pieno dell'undefined behavior.

  5. #5
    Utente di HTML.it L'avatar di shodan
    Registrato dal
    Jun 2001
    Messaggi
    2,381
    In questo articolo si affronta lo stesso problema, proponendo la soluzione per la delete del puntatore (che personalmente reputo una genialata).
    http://chadaustin.me/cppinterface.html
    This code and information is provided "as is" without warranty of any kind, either expressed
    or implied, including but not limited to the implied warranties of merchantability and/or
    fitness for a particular purpose.

  6. #6
    Utente di HTML.it L'avatar di XWolverineX
    Registrato dal
    Aug 2005
    residenza
    Prague
    Messaggi
    2,565
    Veramente un ottimo articolo, grazie!
    "Se proprio devono piratare, almeno piratino il nostro." (Bill Gates)

    "Non è possibile che 2 istituzioni statali mi mettano esami nello stesso giorno." (XWolverineX)

    http://xvincentx.netsons.org/programBlog

  7. #7
    Davvero interessante, ringrazio anch'io!
    Amaro C++, il gusto pieno dell'undefined behavior.

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved.