Zoom Icon

Giochiamo col Polimorfismo

From UIC Archive

Giochiamo col polimorfismo in C++

Contents


Giochiamo col Polimorfismo
Author: Quake2
Email: [email protected]
Website: http://pmode.cjb.net
Date: 27/07/2004 (dd/mm/yyyy)
Level: Slightly hard
Language: Italian Flag Italian.gif
Comments: Facciamo fare al nostro programma cose impensabili :))



Introduzione

In questo tutorial un po' inusuale, vedremo una tecnica interessante per poter istanziare in base al loro nome delle classi, ma a che può servire? Beh la prima cosa che mi viene in mente è un linguaggio di scripting, o cose simili, insomma situazioni in cui uno non può sapere cosa deve fare, e deve affidarsi a qualche tecnica. Ad esempio, una tecnica consiste nell'esportare da una dll due funzioni, una per creare un oggetto e una per distruggerlo, in questo modo è molto più semplice creare classi in base al loro nome, basta infatti usare una nomenclatura standard per le funzioni e cambiargli solo l'ultima parte in base alla classe, poi con una funzione che wrappa LoadLibrary e GetProcAddress si fa tutto facilmente. Ma se noi non volessimo usare una dll? Che ci facciamo GetProcAddress su un eseguibile? Si potremmo (infondo un eseguibile può avere una export table), ma sarebbe bruttissimo, e poi porterebbe qualche complicazione, mentre il modo che vi mostrerò può essere usato sia per un eseguibile sia per una dll, e non porta particolari complicazioni.

Ristrutturato il 11/04/2007 da ValerioSoft


Tools usati

Un compilatore C++ abbastanza recente (quindi il turbo c 1.0 non va bene), il codice l'ho sviluppato e provato sotto Visual C, ma sono sicuro che funziona anche su GCC, l'unico problema è che non so come si attiva il RTTI su GCC, ma se lo usate dovreste saperlo :).


URL o FTP del programma

Quale programma? :)


Notizie sul programma

Ancora? Ma siete duri eh :)


Essay

Bene, siccome ho parlato molto di quello che vedremo nell'introduzione, qua iniziamo subito col codice, per prima create un nuovo progetto col Visual C, fate Win32 Console Application, create un empty project, e attivate il RTTI, andando su Project->Properties->C/C++->Language mettendo Enable Run-Time Type Info a yes, fatto ciò siamo pronti per partire (ovviamente questo tutorial presuppone una conoscenza del C++ e del polimorfismo in generale, perché parlare del polimorfismo richiederebbe un tutorial a parte (o meglio un libro) e va fuori da ciò di cui voglio parlare :)):

  1. include <iostream>
  2. include <map>
  3. include <typeinfo.h>

using namespace std;

//questa macro serve per creare una funzione che ritorna un puntatore all'oggetto baseclass (che in //questo caso è un interfaccia), dal quale deriva classname la funzione avrà nome function

  1. define DYNAMIC_LOAD(baseclass, classname, function) \
   inline baseclass *function() \
   { \
       return new classname; \
   } \

//questa macro invece serve per generare il puntatore a funzione (la funzione definita da DYNAMIC_LOAD), //baseclass è l'oggetto/interfaccia dalla quale dovranno derivare le classi che si desidera istanziare //in questo modo

  1. define LOAD_FUNCTION(baseclass, function) \
   typedef baseclass *(*function)(); \

//classe base da cui devono derivare tutte le classi che si desidera istanziare in questo modo, da notare //che è un interfaccia (costruttore protetto, ovvero la classe non è istanziabile direttamente) //il mio consiglio è di usare sempre un interfaccia tipo quella che ho fatto io per questo genere di cose //una cosa IMPORTANTISSIMA: la classe/interfaccia base deve contenere ALMENO UNA funzione virtuale per //far si che typeid(*this) ritorni le informazioni giuste, questo perché una classe/interfaccia //senza funzioni virtuali (o virtuali pure) non ha senso per il concetto stesso di polimorfismo, //quindi typeid vi ritorna sempre il tipo della classe base, indipendentemente da cosa gli passate //mentre se la classe contiene almeno una funzione virtuale, typeid(this) ritorna il tipo //della classe base (che però noi conosciamo) mentre typeid(*this) ritornerà il tipo della //classe derivata, che invece non conosciamo a runtime class Object {

   protected:
       Object() {}
   public:
       virtual ~Object() {}
       const type_info& Type() const { return typeid(*this); }
       bool IsEqual(const Object& obj) { return (Type() == obj.Type()); }

};

//la prima classe che istanzieremo dinamicamente class ObjSon1 : public Object {

   public:
       ObjSon1() { cout << "ObjSon1 creato." << endl; }
       ~ObjSon1() { cout << "ObjSon1 distrutto." << endl; }

};

//e dopo la prima c'è la seconda :) class ObjSon2 : public Object {

   public:
       ObjSon2() { cout << "ObjSon2 creato." << endl; }
       ~ObjSon2() { cout << "ObjSon2 distrutto." << endl; }

};

//generiamo le varie funzioni necessarie LOAD_FUNCTION(Object, LoadClass) DYNAMIC_LOAD(Object, ObjSon1, LoadSon1) DYNAMIC_LOAD(Object, ObjSon2, LoadSon2)

//una map che conterrà l'associazione nomelcasse->funzione, questo permetterà di poter istanziare una //classe in base al suo nome, ovviamente la funzione è un puntatore a funzione che ritorna il tipo //generico Object, che poi verrà trasformato nel tipo che ci serve, potenza del polimorfismo :) map clsMap;

int main() {

   string choise;
   Object *obj = NULL;
     
   //aggiungiamo gli elementi alla mappa, non siamo costretti ad usare lo stesso nome per la classe, 
   //ma possiamo usarne uno qualsiasi l'importante è che il nome della funzione sia lo stesso che 
   //abbiamo generato con la macro
   clsMap["ObjSon1"] = LoadSon1;
   clsMap["ObjSon2"] = LoadSon2;
   cout << "Che classe vuoi istanziare? ";
   cin >> choise;
   //dopo aver chiesto che classe creare, la creiamo! :), come vedete, non dobbiamo fare ne if ne niente
   //, diamo alla map solo il nome, ovviamente ci sarebbe da fare un po' di error checking, ma questo è 
   //solo un esempio, le () servono perché come ricorderete, il secondo elemento della map era un
   //puntatore a funzione, quindi facendo clsMap[chiave] effettivamente ritorniamo un puntatore
   //a funzione
   obj = clsMap[choise]();
   //per convincervi ulteriormente che questo metodo funziona, faccio stampare il tipo dell'oggetto 
   //creato, e magicamente vederete che per ObjSon1 sarà 
   //"class ObjSon1" per ObjSon2 sarà "class ObjSon2"
   cout << "Ho creato qualcosa! :) E precisamente: " << obj->Type().name() << endl;
   cout << "E ora lo distruggo!" << endl;
   delete obj;
   return 0;

}


Ok compilate il file ed eseguite, scegliere la classe che volete creare, e magicamente verrà creata :) Ovviamente le possibilità non finiscono qua, ansi il metodo utilizzato da questo tutorial è molto statico (basta vedere tutte le macro), ad esempio avreste potuto che so utilizzare un file dal quale leggete le classi che volete istanziare e magari in che dll stanno queste classi, la dll esposterà delle funzioni ben definite, e voi potete farvi una classe che permette di caricare in maniera dinamica le classi sia da una dll, sia dal vostro stesso exe, in modo automatico, se utilizzate lo stesso metodo. Oppure potete farvi tipo un sistema per gestire delle configurazioni, ma voi non sapete il numero di configurazioni da gestire, in questo modo vi fate un array di Object *, e poi create gli oggetti a seconda di come stanno scritti in questo file, insomam le possibilità sono infinite, basta un po' di fantasia :).

Beh ho finito, so che è un tutorial microscopico, ma preferisco le cose piccole ma buone (non pensate male) :)


Note finali

Beh come al solito spero vi sia piaciuto il tutorial, e come sempre vi invito a mandare le minacce per email. Stavolta non saluto nessuno perché ho salutato tutti nel mio precedente tutorial fatto lo stesso giorno di questo, quindi andatevi a vedere quello per i saluti :)
Ciao!


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.