Newsgroup: it.comp.lang.delphi
Titolo: Thread e Dll

Salve a tutti,
Ho un problemino e non riesco ancora a venirne a capo.
Supponiamo di avere un eseguibile che crea ed attiva un certo numero di
thread (l'attivazione
la faccio per mezzo di un bottone su di un form).
I thread così creati tra le altre cose richiamano una funzione in una dll.
Detta funzione, accede ad una tabella Paradox (in sola lettura), preleva
alcune informazioni da un record
e le ripassa al thread chiamante in una TStringList.
Finchè utilizzo 1 thread tutto procede bene. Da 2 in poi il programma
solleva un'eccezione e
mi si pianta in maniera molto dolorosa.
Questa è la funzione che richiamo dal thread:
function DescriviUnita(const
IDUnita:string;Dal,Al:TDateTime;Descrizione:TStringList):boolean; stdcall;
begin
    Result:=bNo;
    if (IDUnita<>'') then
    begin
        if not Assigned(Descrizione) then
        begin
            MessageDlg('Lista Descrizione Unità NON
ISTANZIATA!!',mtError,[MBOK],0);
            Exit;
        end;
            try
                DataModule1.TAnaDip.Active:=bSi;
                with DataModule1 do
                begin
                    TAnaDip.Filter:=format('[Azienda]=%s and
[Codice]=%s',[Copy(IDUnita,1,Pos('@',IDUnita)-1),Copy(IDUnita,Pos('@',IDUnit
a)+1,255)]);
                    TAnaDip.Filtered:=bSi;
                    if TAnaDip.RecordCount>0 then
                        Descrizione.Add(format('%s Matricola: %s Assunto il:
%s',[TAnaDipCognomeeNome.Value,TAnaDipMatricola.Value,TAnaDipDataassunzione.
AsString]))
                    else
                        Descrizione.Add(format('Unità %s NON
TROVATA!',[IDUnita]));
                end;
                DataModule1.TAnaDip.Active:=bNo;
                Result:=bSi;
            except
                on E:Exception do
                begin
                    DataModule1.TAnaDip.Active:=bNo;
                    Result:=bNo;
                    raise E;
                end;
            end;
    end;
end;
L'inizializzazione del Data Module e la relativa distruzione, vengono
eseguite nelle sezioni "Initialization"
e "Finalization" della unit contenente il data module.
Mentre la porzione di codice che dal thread richiama questa funzione è:
        @fPluginDescriviUnita:=GetPluginProc(fPluginHandle,'DescriviUnita');
        // Eseguiamo le operazioni per l'inizializzazione.
            try
                if not
fPluginDescriviUnita(fIDUnita,fInizio,fFine,fDescrizioneUnita) then
                    Exit;
            except
                on E:Exception do
                    ShowMessage(E.Message);
            end;
La classe dell'eccezione che precede il piantamento totale è: Invalid
Pointer Operation (almeno così
pare...).
Qualche suggerimento?
Ringrazio anticipatamente,
Saluti.

 mi si pianta in maniera molto dolorosa.
forse devi usare synchronize per l'accesso al database (non mi
pare che bde sia thread safe).

 pare che bde sia thread safe).
Anche se l'accesso alla tabellaè in sola lettura ed è situato in una dll
esterna?
Ho provato a limitare le operazioni sulla base dati ad una semplice coppia:
Tabella.Active:=True;
ShowMessage('Fatto');
Tabella.Active:=False;
Nonostante ciò si incasina a morte.
Mi è venuta in mente una cosa, che or ora mi accingo a provare, e cioè che
la variabile
DataModule1 è globale alla unit dove è definita.
Non vorrei che essendo lo spazio di indirizzamento della dll unico per il
processo, mi si combinano
casini proprio nell'istanziamento dell'oggetto.
Grazie per l'interessamento!
Saluti

si potrebbe aggiungere alla dll una funzione che ti restituisce una istanza
di un TCriticalSection.
Su questo handle tu potresi nel tuo thread fare una cosa del tipo:
..
csDll := GetCriticalSectionByDll;
csDll.Acquire;
try
   Qualcosa := QualcheFunzioneDellaDll(...);
finally
  csDll.Release
end;
....
la funzione GetCriticalSectionByDll restituirebbe l'handle di una istanza di
TCriticalSection creata nel data module incluso nella dll:
procedure TTuoDataModule.Create(....)
begin
    FCriticalSection := TcriticalSection.Create;
    ...
end;
procedure TTuoDataModule.Destroy(....)
begin
    ....
    FCriticalSection.Free;
end;
la funzione da esportare:
function GetCriticalSectionByDll: TCriticalSection;
begin
    result := TuoDataModule.getCriticalSection
end;
Bye
A.

 di un TCriticalSection.
Grazie per il suggerimento, proverò quanto prima ad implementare il tutto.
Nel frattempo, ho fatto altre ricerche ed ho trovato una soluzione possibile
al problema ... ed anche una
possibile causa dello stesso :)
In Delphi c'è un flag "IsMultiThread" che viene attivato automaticamente
dalle applicazioni che fanno
uso di più thread. Ciò non avviene in maniera automatica nelle Dll.
Tuttavia, c'è una possibilità per tener traccia del numero di
Thread/Processi che fanno uso di una dll
Ciò è fattibile per mezzo dell'entry point della dll e tenendo presente il
flusso di operazioni che
vengono fatte per attivare una dll.
1) Inizializzazione di tutte le unit collegate
2) Chiamata al nucleo principale della dll
Alla luce di questo fatto, nella dll ho fatto così:
library BasePlugin;
uses
  ShareMem,
  Windows,
  SysUtils,
  Dialogs,
  Classes,
  UPlugin in 'UPlugin.pas',
  UDataModule in 'UDataModule.pas' {DataModule1: TDataModule};
{$R *.RES}
var
    LocalThreads    : integer = 0;
function NumeroThreads:integer;
begin
    Result:=LocalThreads;
end;
procedure EntryPoint(Reason:integer);
begin
    case Reason of
        DLL_PROCESS_ATTACH: ShowMessage('Modulo Inizializzato dal
processo!');
        DLL_PROCESS_DETACH: ShowMessage('Modulo Finalizzato dal processo!');
        DLL_THREAD_ATTACH:begin
            inc(LocalThreads);
            if LocalThreads>1 then
                isMultiTHread:=True;
        end;
    end;
end;
exports
    NumeroThreads,
    DescriviUnita;
begin
    DLLProc:=@EntryPoint;
end.
La funzione esportata "DescriviUnita", non utilizza più la variabile
globalmente accessibile
presente nella unit "UDataModule", bensì ne crea un'istanza che gestisce
localmente (Crea -> usa ->  libera).
Il resto del programma eseguibile è rimasto inalterato.
Il flusso dell'eseguibile è del tipo:
1) Click su bottone per agganciare la Dll (Necessario altrimenti NON potrà
essere eseguito il punto 2)
2) Click su bottone per aggiungere un thread (creato Sospeso).
3) Click su bottone per riprendere l'esecuzione di tutti i Thread aggiunti
al punto 2.
Al momento del Resume di un thread, il metodo Execute si comporta così:
1) Notifica l'avvenuta attivazione del thread tramite messaggio in un Memo.
(il Memo appartiene
    al thread, ed è accessibile solo da esso, penso che Synchronize non sia
vitale a questo punto).
2) Crea un'istanza di "TUnitaElaborativa" passando tra l'altro anche
l'Handle della Dll caricata. Se OK vai a 3
3) Fai tutti i casini che vuoi con l'istanza di UnitaElaborativa creata.
il codice del metodo Execute del thread è:
    try
        fFormUnita.MInfo.Lines.Add(format('Kernel Thread Avviato
%s...',[FormatDateTime('dd/mm/yyyy hh.nn.ss.zzz',Now)]));
        fDataElaborata:=fDal;
        fAvviato:=Now;
fUnitaElaborativa:=TCMUnitaElaborativa.Create(fIDUnita,fDal,fAl,HPlugin);
        // EsitoInizializzazione è LOCALE al metodo Execute!
(*A*)        EsitoInizializzazione:=fUnitaElaborativa.Inizializzazione;
        if not (cRC_UnitaElaborativa_Inizializzazione_Ok in
EsitoInizializzazione) then
        begin
            fFormUnita.MInfo.Lines.Add(format('Errore durante
l''inizializzazione dell''Unità
Elaborativa:[%s]',[RTTIElencaInsieme(TypeInfo(TTMResultCodes),TIntegerSet(Es
itoInizializzazione))]));
            raise Exception.CreateFmt('Inizializzazione Unità Elaborativa
fallita con:
%s',[RTTILetterale(TypeInfo(TTMResultCodes),integer(EsitoInizializzazione))]
);
        end;
fFormUnita.MInfo.Lines.AddStrings(fUnitaElaborativa.DescrizioneUnita);
        While not Terminated do
        begin
            Application.ProcessMessages;
            while (fDataElaborata<=fAl) and (not Terminated) do
            begin
                // Inizio Elaborazione DataElaborata
                // Fine Elaborazione DataElaborata
                fDataElaborata:=fDataElaborata+1;
            end;
            // Fine Elaborazione
            if fFinito=0.0 then
            begin
                fFinito:=Now;
                if Assigned(fFormUnita) then
                    fFormUnita.MInfo.Lines.Add(format('Kernel Thread
Arrestato %s...',[FormatDateTime('dd/mm/yyyy hh.nn.ss.zzz',Now)]));
            end;
        end;
    except
        on E:Exception do
        begin
            Suspend;
            MessageDlg(format('Errore Indefinito: %s'+#13#10+'Il Kernel
Thread dell''unità %s DEVE essere
TERMINATO!',[E.Message,fIDUnita]),mtError,[],0);
        end;
    end;
il punto marcato con (*A*) è dove accade l'ingrippamento totale!
La chiamata "fUnitaElaborativa.Inizializzazione" per il momento fa soltanto:
        @fPluginDescriviUnita:=GetPluginProc(fPluginHandle,'DescriviUnita');
        if @fPluginDescriviUnita=Nil then
        begin
Result:=Result+[cRC_UnitaElaborativa_Inizializzazione_Plugin_DescriviUnita_M
ancante];
            Exit;
        end;
        // Eseguiamo le operazioni per l'inizializzazione.
            try
                if not
fPluginDescriviUnita(fIDUnita,fInizio,fFine,fDescrizioneUnita) then
                begin
Result:=Result+[cRC_UnitaElaborativa_Inizializzazione_Plugin_DescriviUnita_F
allita];
                    Exit;
                end;
                Result:=[cRC_UnitaElaborativa_Inizializzazione_Ok];
            except
                on E:Exception do
                    Result:=[cRC_UnitaElaborativa_Inizializzazione_Fallita];
            end;
Dopo tutto questo casino il bello è che:
1) Se lancio il programma dall'IDE di Delphi va tutto meravigliosamente
bene. (E sono contento)
2) Se lancio il programma fuori dall'IDE di Delphi va tutto
meravigliosamente a rotoli (E NON sono contento :) )
Non viene sollevata alcuna eccezione "visibile" dall'esterno (! :( )
Sono molto, molto disorientato.
Proverò ad utilizzare le Critical Section come mi hai suggerito.
Qualche indizio/suggerimento/benedizione/rito vodoo/proposta di lavoro
diversa dal programmatore
e preferibilmente nel settore alimentare (Caseario di preferenza: No
multithreaded mozzerella!) ?
Scusandomi per il papiello e ringraziando tutti per l'interessamento, vi
Saluto.
Carmine

 Scusandomi per il papiello e ringraziando tutti per l'interessamento
Nota del traduttore:
papiello = testo, lettera, documento molto lungo (puro dialetto
napoletano..)
ByeBye
A.

 > Scusandomi per il papiello e ringraziando tutti per l'interessamento
Ho tracciato l'esecuzione quando si bloccava anche nell'IDE, ed ho scoperto
che
apparentemente si chiude (il form principale sparisce), però resta ancora il
processo attivo a consumare risorse... :( ... e vai di Task Manager :(
Altra osservazione è che dopo il primo blocco descritto, se termio il
processo con Task Manager e rilancio l'applicazione, detto blocco non
si manifesta!
Come è possibile? Può dipendere dal fatto che carico la dll per mezzo di
LoadLibrary, e
quindi l'attaching dei thread/processi ad essa è soggetto a delle
restrizioni (come da
documentazione M$)?
Adesso provo con:
1) Semplificazione della dll.
    Rimuovo l'accesso al database, e ci inserisco qualche operazione su
componenti visuali.
2) Introduzione della Critical Section prima di fare qualsiasi operazione
nella funzione chiamata
3) Reintroduzione dell'accesso al database assieme alla Critical Section
Grazie per l'attenzione e per l'aiuto.
Saluti

 3) Reintroduzione dell'accesso al database assieme alla Critical Section
La critical section impedisce l'utlizzo della memoria condivisa a tutti i
thread che sono tagliati fuori
dalla acquire.
Ora, siccome ogni chiamata alla funzione della dll crea un nuovo DataModule,
avrei una Critical Section
per ogni Thread chiamante. Fatto, questo, che mi sembra poco utile allo
scopo prefissato.
Quello che a me serve è poter realizzare un parallelismo tra tutti i thread
che chiamano la/le funzioni
esportate dalla dll.
Ogni thread chiamante deve avere la possibilità di accedere ad una funzione
esportata dalla dll,
in maniera indipendente da quello che un altro thread potrebbe fare.
Nel caso specifico la funzione usata, per ora, non fa altro che accedere ad
una singola tabella paradox,
leggere alcuni campi di un record ben definito e scriverli in una String
List passata come parametro.
Visto che non riesco ancora a venirne fuori, Vi chiedo:
E' possibile far si che N thread creati da un eseguibile accedano a questa
dll, richiamino tutti ed N
più o meno (*) contemporaneamente la stessa funzione, la quale deve accedere
N volte alla stessa
tabella procurandosi le informazioni contenute in, al più, N record diversi?
(*) "più o meno" significa considerando il dovuto delay necessario per
creare/eseguire il nucleo del thread.
Sono ben accetti suggerimenti.
Grazie per l'aiuto!
Saluti