Newsgroup: it.comp.lang.delphi
Titolo: DLL ed Istanze Multiple

Salve a tutti,
Ho creato una dll per isolare un modulo di una mia applicazione. Questa dll,
utilizza un database Interbase
tramite il solito DataModule, oltre che un form per la gestione della base
dati.
Il problema che mi si presenta č legato al fatto che dall'applicazione
principale č possibile lanciare
piů volte lo stesso modulo. Tuttavia, le variabili dichiarate globalmente
nel modulo stesso, vengono
condivise tra le istanze multiple!
In un altro progetto, ho risolto il tutto dichiarandole locali alle
procedure che le utilizzavano. In questo
caso, ho una struttura simile:
UnitForm.pas
Tmioform  = class(TForm)
...
...
end;
...
var
    mioform    : Tmioform;
...
...
implementation
uses DMUnit...;
procedure Tmioform.FormShow(Sender:TObject);
begin
    DataModule1:=TDataModule1.Create(Self);
    ...
end;
Nella unit DMUnit.pas ho:
TDataModule1 = class(TDataModule)
...
end;
var
    DataModule1: TDataModule1;
implementation
uses MainUnit,...;
e nell'evento AfterScroll di una tabella, ho bisogno di accedere ad un
componente sito sul form di cui sopra.
procedure XXXX.AfterScroll(...);
begin
    mioform.Componente.Testo:='Ciao';
    ...
end;
Quello che succede č che se lancio due o piů istanze dello stesso modulo, e
successivamente ne chiudo una, mi viene
generato un bell'errore alla chiusura delle altre istanze. Il tutto perchč
le variabili globali relative al Form ed al
DataModule vengono condivise tra le diverse istanze.
Volendole dichiarare come membri delle rispettive classi, dovrei spostare le
direttive "uses XXX" prima dell'implementation
ma, cosě facendo, genero delle "Circular References" e non mi viene
compilato nulla.
Ho provato che le "threadvar" (tentativo disperato)  ma il risultato non
cambia.
Qualche suggerimento?
Saluti,
Carmine

 ma, così facendo, genero delle "Circular References" e non mi viene


 dichiarare le variabili localmente alla form.
La dll la carico mediante LoadLibrary, e la procedura che mi serve la
richiamo
attraverso GetProcAddress.
Non ho problemi di "ritorno al processo base". Infatti, ogni volta che dal
programma principale
lancio il modulo (dll) mi appare il form e tutto (o quasi :) ) funziona
regolarmente.
L'unico problema che ho è il fatto che le variabili globali vengono
condivise tra le
diverse istanze della dll.
Inoltre, essendo che sia il form che il datamodule hanno necessità di
interagire fra di loro
non posso dichiarare entrambe come locali per via delle circular references.
Saluti,
Carmine

 non posso dichiarare entrambe come locali per via delle circular
references.
Ok. Invece di richiamare l'apertura della form sempre dalla stessa ed unica
istanza della dll, apri più istanze della stessa, usa loadlibrary e
getprocaddress tante volte quante ti serve aprire una form.
Predisponi un componente che ti faccia sto lavoro in modo semplice.
C'è però un problema: se hai poi necessità di far dialogare le forms aperte
tramite l'area dei dati globali non potrai più farlo.
Ciao.
Giuseppe

 tramite l'area dei dati globali non potrai più farlo.
Questo problema l'ho già risolto. I vari moduli comunicano via TCP/IP.
Saluti,
Carmine

 ha scritto nel messaggio
news:ulDfa.1528$i26....@news2.tin.it...

 le sue istanze aperte ci si deve appoggiare al File Mapping.
infatti..non capisco come possa succedere che "le variabili dichiarate 
globalmente nel modulo stesso, vengono condivise tra le istanze multiple".
(P.S. eventuali risposte le leggerò tra una settimana.)
-- 
Mauro Falzari ( 
http://digilander.libero.it/mauro.falzari
 )
"Se guardi nell'abisso, anche l'abisso guarda in te." (Friedrich Nietzsche)

 del tuo modulo.
Infatti, in un'altra applicazione giocattolo, avevo utilizzato il file
mapping. Ora abbandonato
in favore dello stack TCP/IP.
Ti saluto e ti ringrazio per il tuo aiuto.
Carmine

 globalmente nel modulo stesso, vengono condivise tra le istanze multiple".
Sono curioso di capirlo pure io :)
Saluti,
Carmine

 viene richiamata LoadLibrary.
Ecco! :-) Sta qui l'errore. Fai fare tutto al sistema. Tu comunque carica la
nuova istanza della DLL, anche se ne hai un'altra dello stesso tipo aperta,
windows assegnerà ad essa una nuova area dati. Quando non ti serve più fai
subito il FreeLibrary, non aspettare la chiusura del programma.
Come fai ora il programma funziona fino a quando non apri due form dello
stesso tipo... proprio perché non carichi più volte la stessa DLL. Fregatene
se carichi 20 volte la stessa DLL, tanto il sistema alloca l'area codice
solo 1 volta, ed è lui stesso a gestire o meno la sua definitiva liberazione
quando tutte le istanze vengono chiuse.
Mauro consigliava di smessaggiare fra il modulo e la form principale per far
liberare l'handle. Funziona, ma mi lascia dei dubbi il lasso di tempo che
intercorre tra l'invio del messaggio e l'effettiva uscita dalla DLL. Certo è
infinitesimale, ma esiste.
Io seguirei la strada di lasciare l'istanza aperta e riutilizzarla se la
Form è stata chiusa. Di tanto in tanto controllare le varie istanze e il
loro utilizzo per eventualmente chiuderle. Altrimenti se apri 20 istanze, e
finisci di usarle tutte e 20, ti rimangono le istanze aperte occupando
inutilmente l'handle e la memoria per le loro aree dati.
Ciao.
Giuseppe

 quando tutte le istanze vengono chiuse.
Ho appena fatto la modifica al codice che ti ho mostrato nel messaggio
precedente.
Adesso è:
        if Assigned(ModHandleList) then
            with ModHandleList do
            begin
//                idx:=IndexOf(Modulo);
//                if idx<>-1 then
//                    Result:=THandle(Objects[idx])
//                else
                begin
                    Result := LoadLibrary(PChar(Modulo));
                    AddObject(Modulo,TObject(Result));
                    MInformazioni.Lines.Add(format('Modulo %s(%d) **
CARICATO **',[Modulo,Result]));
                end;
            end;
In questo modo, LoadLibrary viene chiamata ogni volta che ho bisogno di
lanciare un modulo.
Sorpresa!...Il problema è rimasto!!! :((( :-O
L'handle che LoadLibrary restituisce è sempre lo stesso dalla prima chiamata
in poi.
Questo è normale se pensiamo al fatto che non viene effettivamente caricata
più di una volta la dll.
A questo punto, mi sento un tantino sperduto. :-/
Adesso provo a creare un eseguibile ed una dll ridotti all'osso.
Dall'eseguibile faccio
caricare più volte la dll e vedo cosa succede.
Nella dll ricreo a grandi linee la struttura di quella in uso dal programma
reale. Cioè, connessione
al database Interbase e form per la gestione.
Nel frattemo, altri suggerimenti?
Saluti,
Carmine

Ciao Giuseppe,
Come ti ho accennato nella risposta precedente, ho scritto un programmino ed
una dll stupida
che replicano il funzionamento del programma reale.
Includo il .pas dell'eseguibile:
type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
    function GetModHandle(const Modulo:string):THandle;
    procedure ApriModulo(const Modulo:String;NumeroOrdine:integer);
  end;
  TOpenModuleProc   = function (Proprietario:TForm):boolean; stdcall;
var
  Form1: TForm1;
  ModHandleList : TStringList;
  ModHandle     : THandle;
  OpenModule    : TOpenModuleProc;
implementation
{$R *.DFM}
function TForm1.GetModHandle(const Modulo:string):THandle;
var
    idx         : integer;
    info        : TStringList;
    versione    : string;
begin
    // Controllo eventuali aggiornamenti
    Result:=0;
        if Assigned(ModHandleList) then
            with ModHandleList do
            begin
                    Result := LoadLibrary(PChar(Modulo));
                    AddObject(Modulo,TObject(Result));
                    Memo1.Lines.Add(format('Modulo %s(%d) ** CARICATO
**',[Modulo,Result]));
            end;
end;
procedure TForm1.ApriModulo(const Modulo:String;NumeroOrdine:integer);
begin
    ModHandle:=GetModHandle(Modulo);
    if ModHandle = 0 then
         MessageDlg(format('Impossibile caricare la libreria (%s)',
[Modulo]),mtError,[MBOK],0);
    @OpenModule := GetProcAddress(ModHandle, 'Open');
    if @OpenModule = Nil then
        MessageDlg(format('Impossibile trovare la procedura (%s) in (%s)',
['Open', Modulo]),mtError,[MBOK],0);
    if not OpenModule(Form1) then
        MessageDlg(format('Impossibile creare il form associato al modulo
(%s)', [Modulo]),mtError,[MBOK],0);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
    ApriModulo('prova.dll',-1);
end;
procedure TForm1.FormShow(Sender: TObject);
begin
    ModHandleList:=TStringList.Create;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
    i   : integer;
begin
    Action:=caFree;
    if Assigned(ModHandleList) then
    begin
        for i:=0 to ModHandleList.Count-1 do
            if THandle(ModHandleList.Objects[i])<>0 then
            begin
                try
                    FreeLibrary(THandle(ModHandleList.Objects[i]));
                    ModHandleList.Objects[i]:=Nil;
                except
                    on E:Exception do
                        MessageDlg(format('Si è verificato l''errore:
%s'+#13#10+'Durante la chiusura di:
%s',[E.Message,ModHandleList.Strings[i]]),mtError,[MBOK],0);
                end;
            end;
        FreeAndNil(ModHandleList);
    end;
end;
Mentre, la dll è questa:
type
  TFProva = class(TForm)
    Label1: TLabel;
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  FProva: TFProva;
  numero : integer=0;
  function Open(Proprietario:TForm):boolean; stdcall; export;
implementation
{$R *.DFM}
function Open(Proprietario:TForm):boolean;
begin
    Result:=False;
    try
        inc(numero);
        FProva:=TFProva.Create(Proprietario);
        FProva.Show;
        Result:=True;
    except
        on Exception do
            Result:=False;
    end;
end;
procedure TFProva.FormShow(Sender: TObject);
begin
    Label1.Caption:=format('%d',[numero]);
end;
----------------------------------------------------------
Indovina?
La variabile "numero" della dll conserva il valore tra le chiamata
successive!
Se lancio la dll 5 volte, i form mostrano gli incrementi di "numero"!
Alla chiusura del programma principale, mi piazza pure un errore. :(
Cos'è che sbaglio?
Grazie per l'aiuto.
Saluti,
Carmine


Ciao.
Giuseppe

Hello there!

Continua a comportarsi nello stesso modo.
Non puň essere una questione di pc, l'ho provato su 2 macchine diverse con
gli stessi
risultati.
Ho scritto un altro componente, tempo fa, che tramite thread multipli
richiamava una o piů dll
che eseguivano delle operazioni su database ed altri compiti.
Ricordo che ebbi un problema simile e l'epilogo fu che dovetti utilizzare
una variabile locale per il datamodule della dll.
Li fu possibile perchč il datamodule non doveva accedere direttamente ai
componenti del
form.
Stavolta me la vedrň brutta, non ho proprio idea di cosa possa essere. :)

Ciao Carmine.
Ho fatto anch'io una semplicissima prova. Effettivamente caricando più volte
la DLL dallo stesso processo ritorna sempre lo stesso handle e di
conseguenza lo stesso tutto. A sto punto mi viene il dubbio che ricordavo
proprio male. Forse in passato avevo usato questa tecnica da processi
diversi... piuttosto che avere un unico processo e tante istanze della dll,
avevo fatto tanti processi che usavano la stessa dll.
Scusa per il tempo che t'ho fatto perdere.
Troviamo un'altra soluzione.
Perchè non provi, nelle funzioni della dll, a farti qualcosa che colleziona
le varie forms? Tipo:
library miaDll;
uses ...
var
mieforms: tobjectlist;
procedure Open;
var
f: tform1;
index: integer;
begin
f:= tform1.create(nil);
index:= mieforms.add(f);
f.show;
end;
begin  //* Dll Entry
mieforms:= mieforms.create;
mieforms.ownsobjects:= false;  // in previsione di un sistema di distruzione
delle varie forms secondo me è meglio disabilitarlo
end.
Ci vuole anche qualcosa che distrugga la form quando esci. Ma non dovrebbe
essere un problema farlo. Credo se ne sia parlato non troppi thread fa.
Qualcosa che riguardava "l'action"...
Mettendo l'objects list nella stessa unit della form (globale), usando
l'evento OnClose per rimuovere dalla lista la Form (Self), si dovrebbe
liberare tutto della form che viene chiusa.
Ciao.
Giuseppe

 le varie forms? Tipo:
Potrebbe essere un'alternativa praticabile.
Tuttavia, rimarrebbe sempre il problema delle circular reference tra il form
e il datamodule.
Appena torno a casa stasera, mi ci rimetto a lavoro.
Fammi sapere, per favore, se ti viene in mente qualcosa.
Saluti,
Carmine

 condivise tra le istanze multiple!
Ricapitolando, nella DLL hai un Form ed un Datamodule che, se non
capisco male, sono accoppiati: il DM usa la form, la Form (crea e) usa
il DM.
Devi poter avere N istanze della coppia Form-DM.
Direi che dovrebbe essere sufficiente qualcosa di questo tipo: levare i
riferimenti inseriti in automatico da Delphi.
Segue esempio (scritto qui, quindi non compila, ma spero si capisca)
Saluti
Paolo Arosio
 // -------------------- unit della FORM
unit ufmrDLL;
uses SysUtils, Classes, Forms, ...
type
TfrmDll = class(TForm)
  ...
private
  TheDM: TDatamodule;
public
  ...
end;
 // levare riferimento creato da Delphi
implementation
uses
  uDLLDataModule; // dove hai il DataModule
procedure TfrmDll.Create(aOwner: TComponent);
begin
  TheDM := TDLLDataModule.Create(Self);
  // init DataModule
end;
procedure TfrmDll.btnRefreshClick(Sender: TObject);
begin
  TDLLDataModule(TheDM).SomeDataSet.Refresh;
end;
 //  ---------------     Unit del DATAMODULE
unit uDLLDataModule;
uses DB, ...
type
  TDLLDataModule = class(TDataModule)
  ...
  end;
// levare riferimento creato da Delphi
implementation
uses
  ufrmDLL;
procedure TDLLDataModule.SomeTableAfrerScroll(DataSet: TDataSet);
begin
  (Owner as TfrmDll).SomeControl.SomeProperty := SomeValue;
end;
Saluti
Paolo Arosio

 riferimenti inseriti in automatico da Delphi.
Hai centrato perfettamente il problema.
Ho già fatto qualcosa di simile al tuo esempio.
Nonostante ciò, il problema è il seguente:
- Nella dll ho una funzione "Open" che crea il form e, di conseguenza, il
datamodule.
- Nel programma eseguibile, ho una serie di funzioni che mi procurano un
handle
alla dll per mezzo di LoadLibrary(...), e che procurano un puntatore alla
funzione
"Open" per mezzo di GetProcAddr(...).
- Se dall'eseguibile chiamo 2 o più volte la funzione "Open" della dll, mi
si aprono
n volte i form del caso. Fin qui tutto ok.
Il casino è che se mi sposto sul database in un form, tutti gli altri form
aperti si
spostano concordemente. Posso vederlo chiaramente per mezzo di una dbgrid
piazzata
sul form.
Inoltre, quando chiudo i form aperti in precedenza, perdo la connessione al
database
negli altri ed alla fine ho una bella exception che non riesco a tracciare
(errore $C00005 ecc...).
Se può essere utile posso mostrarti del codice che ho scritto.
Grazie ancora per l'interessamento.
Saluti,
Carmine

 sul form.
Ti mando (se il tuo indirizzo e' valido) un piccolo esempiello: non
evidenzia il problema che hai tu (penso ancora che si tratti di un
problema di istanza singola).
Saluti
Paolo Arosio

 problema di istanza singola).
Ti ringrazio enormemente per il tuo aiuto!
Ho ricevuto l'esempio che mi hai mandato.
Il casino che mi succede dipende solo ed esclusivamente dal fatto che il
data source che io uso, li ho tutti nel datamodule!
Infatti, lo stesso programma banale che ho scritto io l'ho provato a
ricompilare
utilizzando un datasource posto sul form che creo e non mi da problemi!
Se ti va, prova a fare la stessa cosa con l'esempio che mi hai mandato.
Anzichè utilizzare il datasource sul form, mettilo nel datamodule e
sperimenterai
lo stesso infernale comportamento :)
- Spostamento in contemporanea di un record su ogni form aperto.
- Se distruggi il primo form, automaticamente tutti gli altri perderanno
  l'accesso al database.
Questa cosa, mi sta facendo impazzire...forse sono io che sono scemo.
Saluti,
Carmine

 lo stesso infernale comportamento :)
Negativo.
Non mettere collegamenti fra DM e form a design time: dopo tutto, quel
riferimento che usi a design-time non esistera' piu' a runtime.
Ho spostato il DataSource nel DM, l'ho chiamato dsrcEmployee e collegato
la sua proprieta' dataset a dsEmployee. Ho lasciato vuota la proprieta'
datasource dalla DbGrid.
Nel formCreate:
procedure TfrmDll.FormCreate(Sender: TObject);
begin
  fMyDatamodule := TdmDll.Create(Self);
  DBGrid1.DataSource := TdmDll(fMyDatamodule).dsrcEmployee;
end;
e lavora tutto as-expected.
Saluti
Paolo Arosio

 datasource dalla DbGrid.
Si, impostando manualmente il collegamento al datasource tutto funziona
regolarmente.
La sorpresa era legata al fatto che istanze diverse, create con variabili
locali
si influenzassero a vicenda.
Il problema della condivisione delle variabili globali č una conseguenza
diretta
del fatto che la dll viene utilizzata piů volte da uno stesso processo, no?
Saluti,
Carmine

 processo, no?
Una Dll e' mappata 1 ed una sola volta nello spazio di indirizzamento
del processo, e l'area di codice una sola volta nella memoria del
sistema. Se c'e' una sola _copia_ dell'area dati, e' _dovuto_ che se usi
un riferimento con visibilita' globale per istanziare un oggetto,
quell'istanza sia globalmente visibile. L'errore (di usare un
riferimento per allocare piu' istanza) e' riproducibile facilmente anche
in un exe stand-alone:
procedure TForm1.Button1Click(Sender: TObject);
begin
  Form1 := TForm1.Create(Self);
end;
Hai tante form quante volte hai premuto il bottone + 1 (quella allocata
nel dpr) ma la varibile (globale) Form1 punta solo all'ultima.
Saluti
Paolo Arosio