View Full Version : ancora classi e DLL :P
allora, vi espongo quello che dovrei fare: ho sempre la solita serie di classi implementate in una DLL, tra cui ce n'è una che la logica suggerirebbe di implementare parzialmente nella DLL e parzialmente nell'eseguibile. questa classe si chiama CResource; ecco quella che per ora ne è la dichiarazione:
class CResource {
public:
virtual ~CResource() = 0;
virtual void LockRead() = 0;
virtual void UnlockRead() = 0;
virtual void LockWrite() = 0;
virtual void UnlockWrite() = 0;
virtual COUNTER GetCounter(unsigned int uCode) = 0;
virtual void SetCounter(unsigned int uCode, COUNTER value) = 0;
};
i metodi che dovrebbero essere implementati dalla DLL sono il getter e il setter dei contatori: GetCounter e SetCounter; mentre gli altri 4 metodi (LockRead, UnlockRead, LockWrite e UnlockWrite) vorrei implementarli nell'exe.
come forse avrete capito, i primi 4 metodi servono ad effettuare la mutua esclusione della risorsa; a dire il vero non è proprio mutua: nel server ci sono numerosi thread che possono lavorarci o in lettura o in scrittura; quando ci lavorano in lettura devono usare LockRead/UnlockRead, quando in scrittura LockWrite/UnlockWrite. In tal modo è possibile che più thread possano leggere contemporaneamente la risorsa, ma uno solo può scriverla, e se un thread la sta scrivendo nessun altro può leggerla (dicono che fosse un tipico problema di concorrenza; io ci ho sbattuto parecchio la testa... :p).
attualmente il design che ho applicato sarebbe questo: i metodi devono essere implementati tutti quanti dalla DLL, però ho anche esportato dall'exe delle implementazioni predefinite dei metodi Lock/Unlock che la DLL è formalmente obbligata a chiamare.
ora però il problema è che per realizzare le implementazioni predefinite ho bisogno di due mutex per ogni risorsa, 8 bytes associati ad ogni oggetto CResource... come faccio a fare in modo che la DLL istanzi anche questi 8 bytes negli oggetti CResource? :cry:
devo metterli come variabili membro? si può fare?
grazie a chiunque risponda ^^'
dimenticavo: poi questi 8 bytes dovrebbero anche essere inizializzati...
e il problema è che l'oggetto viene costruito dalla DLL, non dall'exe... :cry:
quantomeno dovrebbero essere inizializzati a 0, ma così il design inizierebbe ad essere un po' debole...
come posso risolvere? si accettano anche idee per altri designs thx ^^
L'unico modo che mi viene in mente è farli static... Non c'è altro modo a meno di instanziare la classe nelal DLL...cosa che non puoi fare perchè è astratta...
ghiotto86
30-07-2005, 08:36
L'unico modo che mi viene in mente è farli static... Non c'è altro modo a meno di instanziare la classe nelal DLL...cosa che non puoi fare perchè è virtuale pura...
ehm vero, ha ragione cionci.
te non puoi instanziare una classe astratta pura
ma io (come dissi nell'altro thread) non devo mai istanziare CResource: devo istanziare le sue implementazioni fornite dalla DLL, ovvero classi derivate da CResource; l'eseguibile non sa neanche quali siano queste classi, che sono interne alla DLL, usa solo CResource come interfaccia comune.
cmq cos'è che dovrei fare static? le variabili membro o i metodi Lock/Unlock?
ma io (come dissi nell'altro thread) non devo mai istanziare CResource: devo istanziare le sue implementazioni fornite dalla DLL, ovvero classi derivate da CResource; l'eseguibile non sa neanche quali siano queste classi, che sono interne alla DLL, usa solo CResource come interfaccia comune.
cmq cos'è che dovrei fare static? le variabili membro o i metodi Lock/Unlock?
Se ho capito il tuo problema, mi sembra abbastanza chiaro che hai una classe che cerca di fare due cose (getter/setter di una risorsa e poi anche il lock/unlock).
In genere e' buona norma dare ad una classe una e una sola responsabilita', altrimenti sorgono problemi come il tuo.
Prova a separare i quattro metodi di lock in una classe (Lock, ad esempio) e vedi se ne esci cosi'.
Vado a occhio:
class Lock
{
virtual void Lock...
static Create(...); // Factory method (se ti serve)
};
class Resouce
{
virtual void AssignLock(Lock* lock);
virtual COUNTER Get...
virtual void Set...
};
Sia le variabili membro che i metodi che accedono ai membri statici...
Per l'inzializzazzione basta fare all'interno della DLL: CResource::membro = 0;
The3DProgrammer
30-07-2005, 09:40
Domanda stupida: ma xkè nn può usare i due mutex come membri dato della classe? Li dichiari come membri dato, e fai 1 funzione InitMutexes(HANDLE h1,HANDLE h2) che deve essere chiamato a carico dell'eseguibile. Tutto IMHO ovviamente... :p
(dicono che fosse un tipico problema di concorrenza; io ci ho sbattuto parecchio la testa... ).
è un problema lettore/scrittore... :Prrr: ciauz
Se ho capito il tuo problema, mi sembra abbastanza chiaro che hai una classe che cerca di fare due cose (getter/setter di una risorsa e poi anche il lock/unlock).
In genere e' buona norma dare ad una classe una e una sola responsabilita', altrimenti sorgono problemi come il tuo.
Prova a separare i quattro metodi di lock in una classe (Lock, ad esempio) e vedi se ne esci cosi'. hum, già, probabilmente è la cosa migliore
Vado a occhio:[...] però una debolezza nel tuo design c'è comunque: la DLL è "obbligata" a chiamare AssignLock nel costruttore... ovvio che lo fa, ma mi sembra poco elegante :(
dici che viene troppo incasinato se CResource derivasse da CLock, considerando che CLock viene implementata dall'eseguibile e CResource dalla DLL? :mbe:
dici che viene troppo incasinato se CResource derivasse da CLock, considerando che CLock viene implementata dall'eseguibile e CResource dalla DLL? :mbe: come non detto: l'implementazione sempre una classe derivata è: se derivo CResource da CLock, non la posso derivare dalla classe (interna all'exe) che implementa CLock... urgh! O_o'
Domanda stupida: ma xkè nn può usare i due mutex come membri dato della classe? Li dichiari come membri dato, e fai 1 funzione InitMutexes(HANDLE h1,HANDLE h2) che deve essere chiamato a carico dell'eseguibile. Tutto IMHO ovviamente... :p la mia domanda è: si può (visto che la classe CResource è in realtà una classe astratta usata come interfaccia dall'eseguibile e implementata da una classe derivata interna alla DLL)?
Per l'inzializzazzione basta fare all'interno della DLL: CResource::membro = 0; questo risolverebbe il mio problema, ma obbligare formalmente la DLL a fare qualcosa sarebbe un design debole.
hum, già, probabilmente è la cosa migliore
però una debolezza nel tuo design c'è comunque: la DLL è "obbligata" a chiamare AssignLock nel costruttore... ovvio che lo fa, ma mi sembra poco elegante :(
Capisco, e' un problema questo.
dici che viene troppo incasinato se CResource derivasse da CLock, considerando che CLock viene implementata dall'eseguibile e CResource dalla DLL? :mbe:
CLock e' un'interfaccia (ma non usare le C, dai :p), che a quanto ho capito vuoi implementare nell'exe, mentre CResource e' da implementare nella DLL, ho capito bene?
Non puoi derivare l'una dall'altra, devi ragionare in termini di interfacce e composizione qui. Mi sembra che la Composizione si adatti meglio all'Ereditarieta' nel tuo problema.
Ho bisogno di sapere:
- Chi crea gli oggetti CLock e CResource
- Chi implementa le interfacce
- Il ciclo di vita dei due oggetti
E poi risolviamo il problema di assegnare un CLock ad un CResource e chi deve farlo. Male che vada puoi avere un factory method di CResource che assegna sempre il Clock e non puoi dimenticarlo; io di solito risolvo questo tipo di problemi cosi'.
The3DProgrammer
30-07-2005, 09:50
Qui credo nn ci sia problema: I mutex servono a tutte le classi derivate da CResource, x cui basta dichiararli (mutex e funzione) nella classe astratta e definire il metodo CreateMutexes(...) come metodo concreto public, accessibile anke quindi da tutte le classi derivate. Più che altro nn so se si può creare qualke casino con gli HANDLE dei mutex tra DLL/eseguibile, ma nn credo cmq...
Quello che mi sto domandando è: ma esisteranno diverse istanze della mutex o solo una... Nel senso: tutti i tuoi utilizzatori useranno una sola mutex o hai bisogno di una istanza per ogni produttore/consumatore ?
questo risolverebbe il mio problema, ma obbligare formalmente la DLL a fare qualcosa sarebbe un design debole.
Inizializzare è considerato fare qualcosa ?!?!!?
Comunque mi ero dimenticato il tipo:
tipo CResource::membro_static = 0;
Questa è considerata a tutti gli effetti una inizializzazione...
The3DProgrammer
30-07-2005, 10:04
ogni risorsa dovrebbe avere i suoi mutex. Ai thread passi un puntatore alla risorsa, x cui tutti i thread condividerebbero lo stesso oggetto (mi sono appena alzato, quindi se dico qualke cretinata nn mi linciate :D)
ciauz
ogni risorsa dovrebbe avere i suoi mutex. Ai thread passi un puntatore alla risorsa, x cui tutti i thread condividerebbero lo stesso oggetto (mi sono appena alzato, quindi se dico qualke cretinata nn mi linciate :D)
Chiaro, ma dipende da quello che deve fare...
The3DProgrammer
30-07-2005, 10:10
si, scusa,avevo frainteso il tuo post:coffee: :)
servono due mutex per ciascun oggetto, e ora mi sono anche ricordato che oltre agli handle dei mutex servono pure un intero e un flag (diciamo altri 8 bytes); qui sotto vi riporto la versione Java della classe CResource: prima di fare la versione C++ di questo programma avevo iniziato la versione Java, poi sono cambiate le direttive provenienti dall'alto (:p:p:p) e allora ho preferito di gran lunga il C++. la versione Java era questa:
import java.lang.*;
import java.io.*;
public class Resource extends Object implements Serializable {
public int nID;
private static int nNextID = 1;
public Counters counters = null;
private int nReaders = 0;
private boolean fLocked = false;
private Object lock1 = new Object(), lock2 = new Object();
private static int getNewID() {
return nNextID++;
}
public Resource() {
nID = getNewID();
counters = new Counters();
}
public Resource(Counters counters) {
nID = getNewID();
try {
this.counters = (Counters)counters.clone();
}
catch (CloneNotSupportedException e) {
this.counters = counters;
}
}
// lock for read when a resource must be consistent during a read process
public void lockRead() {
synchronized (lock1) {
synchronized (lock2) {
nReaders++;
while (fLocked);
}
}
}
public void unlockRead() {
synchronized (lock1) {
if (nReaders > 0) {
nReaders--;
}
}
}
// lock for write when the consistency of a resource might change during a
// write process
public void lockWrite() {
synchronized (lock2) {
while (fLocked);
fLocked = true;
while (0 != nReaders);
}
}
public void unlockWrite() {
fLocked = false;
}
}
nel codice Java i due mutex sono realizzati come vedete da due oggetti, lock1 e lock2. inoltre a parte tutta la questione dell'implementazione C++, volevo sapere da voi (leggasi: da fek :D) se secondo voi (secondo fek :D) era giusta la soluzione che ho trovato io per quel problema di concorrenza (o produttore/consumatore, che dir si voglia :p).
PS: ma ancora non capisco: queste variabili membro, cioè i due handles, l'intero e il flag, potrebbero essere messe direttamente come mebri di CResource, che è una classe astratta? se no, perché? volevo sapere il vostro parere su questo punto prima di sperimentare direttamente... :)
Qui credo nn ci sia problema: I mutex servono a tutte le classi derivate da CResource, x cui basta dichiararli (mutex e funzione) nella classe astratta e definire il metodo CreateMutexes(...) come metodo concreto public, accessibile anke quindi da tutte le classi derivate. Più che altro nn so se si può creare qualke casino con gli HANDLE dei mutex tra DLL/eseguibile, ma nn credo cmq... ma scusa, il metodo concreto CreateMutexes dove lo implemento? :confused:
è possibile implementare parte dei metodi nell'exe e usarli nella DLL?? :confused:
e allora ho risolto... :mbe:
cmq no, gli handle scambiati tra DLL ed exe non fanno casino: sono validi in tutto il processo.
CLock e' un'interfaccia (ma non usare le C, dai :p), che a quanto ho capito vuoi implementare nell'exe, mentre CResource e' da implementare nella DLL, ho capito bene? precisamente.
Non puoi derivare l'una dall'altra, devi ragionare in termini di interfacce e composizione qui. Mi sembra che la Composizione si adatti meglio all'Ereditarieta' nel tuo problema. ehm... ok, mi devi aiutare tu :D :D
scherzi a parte, ti ringrazio vivamente per la tua disponibilità! :)
Ho bisogno di sapere:
- Chi crea gli oggetti CLock e CResource CLock non lo so, ma CResource è creata sicuramente dalla DLL; inoltre tieni presente che la DLL non istanzia solo classi sue interne derivate da CResource, ma anche classi sue interne derivate da ulteriori interfacce astratte derivate a loro volta da CResource; cioè ad esempio:
class CResource ...
class CAccount : public CResource ...
class CAccountImpl : public CAccount ...
dove CAccountImpl è l'implementazione della DLL di CAccount; CAccountImpl è quella che di fatto viene istanziata, anche se è sconosciuta all'exe.
- Chi implementa le interfacce CLock in tutto il discorso è l'unica che dovrebbe essere implementata dall'exe; in teoria potrebbe anche essere implementata da ogni singola DLL che carico nel processo, ma preferisco che l'implementazione della mutua esclusione sia una sola e che stia nell'exe.
- Il ciclo di vita dei due oggetti dunque, per quanto riguarda il ciclo di vita le cose stanno così: non ho utilizzato un sistema di reference counting tipo quello di COM: semplicemente le DLL esportano alcune funzioni che istanziano ste classi, e poi sta all'exe la responsabilità di distruggerle quando vuole.
E poi risolviamo il problema di assegnare un CLock ad un CResource e chi deve farlo. Male che vada puoi avere un factory method di CResource che assegna sempre il CLock e non puoi dimenticarlo; io di solito risolvo questo tipo di problemi cosi'. capisco; quindi anche se il design ha questo difetto diciamo al limite chissene... ok, se proprio non si può risolvere altrimenti...
Chiaro, ma dipende da quello che deve fare... The3DProgrammer ha capito bene, se ho ben capito quello che ha capito :p
ciascun oggetto (non pensiamo ora alle classi, pensiamo agli oggetti già istanziati) deve avere due mutex e un paio di altri campi per gestire l'esclusione; tutti i thread hanno lo stesso puntatore di un determinato oggetto, e vedono gli stessi suoi campi; a seconda che chiamino LockRead, LockWrite, ecc., bloccano e sbloccano il mutex 1 o il 2 o entrambi, ma comunque per ogni oggetto i due mutex su cui lavorano sono gli stessi.
The3DProgrammer
30-07-2005, 12:19
class CResource {
public:
void CreateMutexes();
... //i tuoi metodi astratti
private:
HANDLE m_hMutex1,m_hMutex2;
};
ora:
siccome sarà l'eseguibile a dover lanciare createMutexes(), ti fai un bel resource.cpp in cui implementi CreateMutexes() nel progetto dell'eseguibile. Siccome mi pare di aver capito ke la DLL dovrà solo istanziare oggetti di classi derivate da CResource, nn hai bisogno di includere il .cpp dove definisci CreateMutexes() nella DLL, in quanto il codice nn è necessario (la funzione nn verrà mai eseguita, quindi dovrebbe linkare la DLL tranquillamente, al limite includi pure resource.cpp nella DLL). Così dovrebbe funzionare
ciauz
CLock in tutto il discorso è l'unica che dovrebbe essere implementata dall'exe; in teoria potrebbe anche essere implementata da ogni singola DLL che carico nel processo, ma preferisco che l'implementazione della mutua esclusione sia una sola e che stia nell'exe.
Ok, prova a separare la classe in due allora.
E guarda i seguenti due design pattern se ti possono aiutare:
- Factory Method
- Builder
Vedi se questa idea ha senso, lato client:
// anche qui magari ti puo' servire un factory in futuro, per ora non mi sembra
CLock* aLock = new CLockImpl();
// qui il factory method serve
// uso una stringa, ma ovviamente puoi usare quello che ti pare per richiedere
// la creazione di un qualche tipo di risorsa in particolare
CResouce* aResource = CResource::Create("MyResourceType", aLock);
...
CResource::Destroy(aResource);
Io eviterei il reference counting come la peste, ho sempre avuto piu' problemi di quanti ne ho risolti.
Una possibilita' e' di avere un metodo statico di distruzione, invece di chiamare direttamente il distruttore, perche' e' buona norma creare/distruggere la memoria nello stesso modulo. Immagina che succede se la DLL usa un heap differente, tu pensi che la delete distrugga l'oggetto invece va a sovrascrivere memoria a caso (e mi e' successo).
Dichiare esplicitamente un distruttore privato nell'interfaccia e non implementarlo, cosi' costringi il cliente ad usare solo il tuo metodo Destroy. Se pensi che tanto nessuno sara' cosi' fesso da usare l'operatore delete quando c'e' un metodo Destroy, sappi che lo fanno (e mi e' successo pure questo). Se sei il solo a programmare, ti dimenticherai un giorno che esiste il metodo Destroy e userai la delete :)
In C# e Java tutti questi problemi non esistono :D
siccome sarà l'eseguibile a dover lanciare createMutexes(), ti fai un bel resource.cpp in cui implementi CreateMutexes() nel progetto dell'eseguibile. Siccome mi pare di aver capito ke la DLL dovrà solo istanziare oggetti di classi derivate da CResource, nn hai bisogno di includere il .cpp dove definisci CreateMutexes() nella DLL, in quanto il codice nn è necessario (la funzione nn verrà mai eseguita, quindi dovrebbe linkare la DLL tranquillamente, al limite includi pure resource.cpp nella DLL). Così dovrebbe funzionare
ciauz
Cosi' rompe la convenzione che CResource e' un'interfaccia pura (con tutti i problemi che derivano da questa rottura).
L'interfaccia fra una DLL e il resto del mondo dovrebbe sempre essere esposta via interfacce pure. COM e' nato proprio con lo scopo di formalizzare questa convenzione.
The3DProgrammer ha capito bene, se ho ben capito quello che ha capito :p
ciascun oggetto (non pensiamo ora alle classi, pensiamo agli oggetti già istanziati) deve avere due mutex e un paio di altri campi per gestire l'esclusione; tutti i thread hanno lo stesso puntatore di un determinato oggetto, e vedono gli stessi suoi campi; a seconda che chiamino LockRead, LockWrite, ecc., bloccano e sbloccano il mutex 1 o il 2 o entrambi, ma comunque per ogni oggetto i due mutex su cui lavorano sono gli stessi.
Allora metti i due mutex nell'oggetto CLock.
Puoi sempre mantenere l'interfaccia di CLock anche in CResource ed implementare un CResourceBase che contiene un'istanza di CLock passata dall'esterno e implementa l'interfaccia CResource limitandosi a delegare i Lock/Release all'oggetto CLock, per semplificare l'uso da parte del cliente.
Alla fine hai due classi ognuna con una sola responsabilita:
- CLock: si occupa della sincronizzazione
- CResource: gestisce la risorsa e delega i compiti di sincronizzazione
The3DProgrammer
30-07-2005, 12:59
Cosi' rompe la convenzione che CResource e' un'interfaccia pura (con tutti i problemi che derivano da questa rottura).
L'interfaccia fra una DLL e il resto del mondo dovrebbe sempre essere esposta via interfacce pure. COM e' nato proprio con lo scopo di formalizzare questa convenzione.
ah:D
nn era specificato dovesse essere un'interfaccia pura ;)
Per curiosità, quali problemi possono scaturire? In genere, è una tecnica che funziona abbastanza bene (l'ho usata spesso)
ciauz
Perchè non ti fai un oggetto (anzi due mi sembra di capire) un CMutex (attento che in MFC esiste già) ?
Lo metti come membro della CResource e lo inzializzi nel costruttore della CResource... Le classi derivate avranno già tali membri inizializzati...
ah:D
nn era specificato dovesse essere un'interfaccia pura ;)
Per curiosità, quali problemi possono scaturire? In genere, è una tecnica che funziona abbastanza bene (l'ho usata spesso)
ciauz
Immagina di avere campi in una classe che diachiari in un header file che condividi fra DLL e EXE e immagina di compilare DLL e EXE con due diversi valori di allineamento dei campi delle strutture. DLL e EXE pensano di lavorare sullo stesso oggetto che ha due rappresentazioni diverse in memoria e *puff* scompaiono due giorni di debugging (mi e' successo pure questo) :D
Oppure una qualunque variazione sul tema.
Una volta che ci si abitua a lavorare per interfacce e' tutto molto piu' semplice. Io uso quasi sempre interfacce pure come confine fra "aree" diverse anche nello stesso modulo per mantenere le interfacce stesse pulite.
The3DProgrammer
30-07-2005, 15:01
interessante, nn ci avevo mai pensato
e nelle situazioni in cui verrebbe naturale spostare funzionalità comuni nella classe base astratta, come ti comporti? Composition? Ad esempio, una classe con un template method in questo modo nn sarebbe utilizzabile...o c'è qualke trukketto?
ciauz
interessante, nn ci avevo mai pensato
e nelle situazioni in cui verrebbe naturale spostare funzionalità comuni nella classe base astratta, come ti comporti? Composition? Ad esempio, una classe con un template method in questo modo nn sarebbe utilizzabile...o c'è qualke trukketto?
ciauz
Composition sempre e comunque quando sia possibile. Tendo ad ereditare molto poco e solo quando strettamente necessario (ad esempio implementazioni di interfacce).
Altrimenti faccio cose cosi':
Animation <- AnimationBase <- AnimationProxy (AnimationImpl)
<- AnimationImpl
<- eredita
() compisizione
Chi riconosce il design pattern? (facile facile)
The3DProgrammer
30-07-2005, 15:57
è (dovrebbe essere :p ) un proxy
ciauz
dunque dunque: grazie ai vostri spunti il design per il quale ho optato è il seguente: la thread-safety (i due mutex, l'intero e il flag) è gestita a parte in CLockImpl, la cui interfaccia astratta è CLock e sta nell'header condiviso. (btw: non uso MFC perché il sorgente deve essere il più possibile portabile; per ora c'è solo un file che deve essere rifatto per Linux, mettendoci anche CLockImpl saranno due).
CResource non deriva da CLock, ma tutte le classi interne della DLL derivate da CResource o da interfacce derivate da CResource (come CAccount) vengono istanziate dalla DLL sotto richiesta dell'eseguibile tramite opportuni metodi esportati dalla DLL stessa; tutti questi metodi tra i parametri vogliono anche un puntatore a un'istanza di CLock creata dall'exe.
per costringere la DLL ad implementare CResource utilizzando un'istanza di CLock, metterò in CResource un costruttore puro virtuale (si può fare, vero? :stordita: ) con un parametro CLock*.
ora però vengono i problemoni: la distruzione di tutto sto macello! :cry:
come consiglia giustamente fek, conviene sempre deallocare memoria nello stesso modulo in cui la si alloca (altrimenti se i moduli usano heap differenti possiamo allegramente assistere a curiosi fenomeni cosmici); bene: qui è praticamente il contrario... le cose allocate da uno vengono deallocate dall'altro... :doh: :fagiano: :help: :mc:
allora, le implementazioni di CResource o delle interfacce derivate vengono allocate dalla DLL e vengono distrutte dall'exe; inoltre ciascuna di esse contiene un'istanza di CLock creata dall'exe e deallocata dalla DLL... :help:
per distruggere CResource posso (come suggerisce fek) metterci un metodo astratto Destroy implementato dalla DLL che deve chiama il distruttore (che metterò come membro privato per evitare che un giorno io mi dimetichi dell'esistenza di Destroy); in tal modo a distruggere le istanze di CResource è la DLL (non più l'exe), la quale quindi deve anche occuparsi di distruggere il CLock, e questo non va bene perché sennò assistiamo ai fenomeni cosmici; e io come faccio? semplice!! uso la tecnica del Destroy anche in CLock!!! :)
risolto! (ho capito la soluzione mentre scrivevo :D)
grazie a tutti, specie fek :)
EDIT: vorrei un ultimo vostro parere: ma è proprio necessario un design così complesso? non si può proprio fare qualcosa di più semplice? :mbe:
EDIT: vorrei un ultimo vostro parere: ma è proprio necessario un design così complesso? non si può proprio fare qualcosa di più semplice? :mbe:
Dipende da quello che stai cercando di fare.
Secondo me e' troppo complesso, sono certo che puoi migliorare le specifiche e semplificare il design.
E non usare quelle C di fronte al nome della classe! :D
Dipende da quello che stai cercando di fare.
Secondo me e' troppo complesso, sono certo che puoi migliorare le specifiche e semplificare il design. ad ogni modo mi sono accorto che quel design non si può applicare perché non è possibile creare costruttori astratti... cosa giustissima, se pensi che un costruttore è come un metodo statico (anche se in realtà agisce su this).
e a questo punto inizio ad essere seriamente in difficoltà: :help: non riesco a creare un oggetto implementato parzialmente da un modulo e parzialmente da un altro... :help:
eppure imho questo è quello che va fatto: il concetto di "risorsa" nel mio progetto deve essere implementato completamente dalle varie DLL, tranne l'esclusione che preferisco implementare nell'exe (in maniera tale da avere un'implementazione unica).
certo, implementando l'esclusione in ciascuna delle DLL si risolve tutto quanto, ma è "brutto" da fare, sei d'accordo anche tu? :help:
E non usare quelle C di fronte al nome della classe! :D uffaaaa!! :D ormai mi sono aibutato così!! le usa anche MFC!! (rima! :D)
e poi le ho usate dappertutto ormai :Prrr:
il massimo che posso concederti è di usare le "I" al posto nelle "C" nelle classi astratte... :rolleyes:
alla fine ho risolto così (stavolta penso proprio che sia la soluzione definitiva):
class CLock {
public:
virtual void LockRead() = 0;
virtual void UnlockRead() = 0;
virtual void LockWrite() = 0;
virtual void UnlockWrite() = 0;
virtual void Destroy() = 0;
};
class CResource : public CLock {
public:
virtual CLock *GetLock() = 0;
virtual unsigned int GetID() = 0;
virtual COUNTER GetCounter(unsigned int uCode) = 0;
virtual void SetCounter(unsigned int uCode, COUNTER value) = 0;
virtual void Destroy() = 0;
};
in più ogni funzione della DLL che crea un oggetto CResource riceve in ingresso un puntatore a un oggetto CLock (che sarebbe in realtà CLockImpl).
il design una debolezza ce l'ha, ma alla fine ho detto kissene :p
la debolezza sarebbe che la DLL è costretta ad assegnare l'oggetto CLock che le viene passato alla risorsa che deve creare; alla fine non è neanche tanto una debolezza dal momento che quando l'exe chiama GetLock la DLL dovrà pur restituire qualcosa :D
se restituisce NULL sono costretto a scaricare la DLL e mostrare sulla console un messaggio di errore che dice in poche parole che quella DLL non funziona. :p
ultima cosa: qualcuno mi dice se la mia versione dell'esclusione va bene? :D
CLockImpl::CLockImpl() {
VERIFY(hMutex1 = CreateMutex(NULL, FALSE, NULL));
VERIFY(hMutex2 = CreateMutex(NULL, FALSE, NULL));
uReaders = 0;
fWriting = 0;
}
CLockImpl::~CLockImpl() {
VERIFY(CloseHandle(hMutex1));
VERIFY(CloseHandle(hMutex2));
}
void CLockImpl::LockRead() {
VERIFY(WAIT_OBJECT_0 == WaitForSingleObject(hMutex1, INFINITE));
VERIFY(WAIT_OBJECT_0 == WaitForSingleObject(hMutex2, INFINITE));
uReaders++;
while (fWriting);
VERIFY(ReleaseMutex(hMutex2));
VERIFY(ReleaseMutex(hMutex1));
}
void CLockImpl::UnlockRead() {
VERIFY(WAIT_OBJECT_0 == WaitForSingleObject(hMutex1, INFINITE));
ASSERT(uReaders);
uReaders--;
VERIFY(ReleaseMutex(hMutex1));
}
void CLockImpl::LockWrite() {
VERIFY(WAIT_OBJECT_0 == WaitForSingleObject(hMutex2, INFINITE));
while (fWriting);
fWriting = 1;
while (uReaders);
VERIFY(ReleaseMutex(hMutex2));
}
void CLockImpl::UnlockWrite() {
fWriting = 0;
}
void CLockImpl::Destroy() {
delete this;
}
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.