Bonjour,
Voici une petite unité qui peut s'averer tres pratique lorsque l'on a besoin de creer des threads dans une DLL. En effet, dans une DLL, la synchronisation d'évènements (Synchronize(MaMethode);) ne fonctionne pas, car la variable SyncList de l'unité Classes.pas reste locale a un module (comme n'importe quelle variable globale, d'ailleurs). Du coup, le CheckSynchronize effectué par le thread principal de l'application, dans le module principal, ne "voit" pas les évènements a synchroniser.
Cette unité permet a une DLL de demander une synchronisation au module principal, qui redispatche la synchronisation aux DLL qui l'ont demandée.
Voir source pour les détails.
Source / Exemple :
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : DLLThreadSynchronize.pas //
// Auteur : S.Mazuir //
// Date : 10/03/2010 //
// Révision : 1 //
// Version : Delphi6 //
// Plateformes : Win32 //
// //
// Notes : //
// Unité permettant de gérér sans modification du code la synchronisation //
// des évènements générés par des threads créés dans une ou plusieurs DLL. //
// //
// Utilisation : Ajouter cette unité au projet de l'application et de //
// chaque DLL dans laquelle sont créés des threads. Aucune modification de //
// code nécessaire. //
// //
// Principe de fonctionnement : Utilisation d'un FileMapping pour partager //
// les pointeurs d'objets necessaires entre l'application et les DLL //
// chargeant cette unité. Un objet est créé pour chaque DLL, et surcharge //
// la variable globale de procedure WakeMainThread. Lorsqu'une //
// synchronisation d'évènement est demandée dans une DLL, cet objet ajoute //
// son pointeur dans une liste globale (protégée par une section critique), //
// et active le thread de surveillance du module pricipal de l'application, //
// qui synchronise un évènement dans lequel il propage la synchronisation //
// a toutes les DLL qui l'ont demandé a travers les objets placés dans la //
// liste globale. //
// //
// Change log : //
// 10/03/2010 - SMA : Création //
// //
////////////////////////////////////////////////////////////////////////////////
unit DllThreadsSynchronize;
interface
implementation
uses Forms, Classes, Windows, SyncObjs, SysUtils;
Type
TCheckSynchronizeFunc = function: Boolean;
// Objet créé dans chaque DLL permettant de surcharger la variable de procedure globale
// WakeMainThread et de redispatcher la méthode CheckSynchronize (Classes.pas)
TSyncObj = Class
private
FLocalCheckSynchronize : TCheckSynchronizeFunc;
public
Procedure DLLWakeMainThread(Sender : TObject);
Procedure DLLCheckSynchronize;
Property LocalCheckSynchronize : TCheckSynchronizeFunc read FLocalCheckSynchronize write FLocalCheckSynchronize;
end;
// Thread de surveillance éxecuté dans le module principal (exe)
// C'est lui qui redistribue la synchronisation depuis le thread
// principal de l'application vers chaque DLL qui l'a demandé
// via un synchronize()
TSyncThread = Class(TThread)
private
Procedure DLLSynchronize;
public
procedure Execute; override;
end;
// Structure partagée entre tous les modules de l'application qui utilisent cette unité
// via un FileMapping. Cette structure partage les pointeurs des objets communs
// entre tous les modules.
TSharedStruct = Packed record
SyncList : TList; // Liste globale des TSyncObj necessitant une synchronisation
DllSyncThread : TSyncThread; // Pointeur vers le thread de controle
CriticalSection : TCriticalSection; // Pointeur vers la section critique protégeant la SyncList
Signal : THandle; // Handle du signal (event) permettant de controler le thread de controle
end;
PSharedStruct = ^TSharedStruct;
var DllThreadSyncObj : TSyncObj;
DllSyncThread : TSyncThread;
FSyncList : TList;
FCriticalSection : TCriticalSection;
FSignal : THandle;
MappingHandle : THandle;
SharedStruct : PSharedStruct;
OldDllProc : TDLLProc;
//****************************************************************************//
{ TSyncObj }
// Objet créé dans chaque DLL permettant de surcharger la variable de procedure globale
// WakeMainThread et de redispatcher la méthode CheckSynchronize (Classes.pas)
procedure TSyncObj.DLLCheckSynchronize;
begin
// On appelle le CheckSynchronize de la DLL dans laquelle a été créé l'objet
FLocalCheckSynchronize;
end;
procedure TSyncObj.DLLWakeMainThread(Sender : TObject);
begin
FCriticalSection.Enter; // Protection de la liste globale
try
try
FSyncList.Add(Self); // Ajout de l'objet dans la liste globale : la DLL necessite une synchronisation
finally
SetEvent(FSignal); // Activation du thread de controle
end;
finally
FCriticalSection.Leave;
end;
end;
//****************************************************************************//
{ TSyncThread }
// Thread de surveillance éxecuté dans le module principal (exe)
// C'est lui qui redistribue la synchronisation depuis le thread
// principal de l'application vers chaque DLL qui l'a demandé
// via un synchronize()
// Méthode synchronisée par le thread de controle
procedure TSyncThread.DLLSynchronize;
var FLocalSyncList : TList;
begin
// On utilise une liste locale pour pouvoir traiter
// tranquillement les objets a traiter en dehors de la section
// critique de protection de la liste globale,
// car cette section critique peut provoquer des interbloquages
// avec le ThreadLock de l'unité Classes.pas, qui est vérouillé pendant
// le CheckSynchronize. Ainsi, le CheckSynchronize de chaque DLL est appelé
// en dehors de notre section critique.
FLocalSyncList := TList.Create;
try
FCriticalSection.Enter; // Protection de la liste globale
try
if FSyncList <> nil then
begin
// Transfert des éléments de la liste globale vers la liste locale
while FSyncList.Count > 0 do
begin
FLocalSyncList.Add(FSyncList[0]);
FSyncList.Delete(0);
end;
end;
finally
FCriticalSection.Leave;
end;
// Synchronisation des DLL en dehors de notre section critique :
// pas de risques d'interblocages
while FLocalSyncList.Count > 0 do
begin
TSyncObj(FLocalSyncList[0]).DLLCheckSynchronize; // Appel du CheckSynchronize de la DLL dans laquelle a été créé l'objet
FLocalSyncList.Delete(0);
end;
finally
FLocalSyncList.Free;
end;
end;
// Boucle principale du thread de controle
procedure TSyncThread.Execute;
begin
while not Terminated do
begin
WaitForSingleObject(FSignal, INFINITE); // Attente du signal demandant la synchronisation d'une DLL
ResetEvent(FSignal);
if Terminated then
Exit;
// Synchronisation d'une méthode locale dans laquelle la synchronisation
// sera propagée aux DLL qui l'ont demandé
Synchronize(DLLSynchronize);
end;
end;
// Initialisation du module principal (exe)
Procedure InitMainModule;
var MappingName : String;
begin
MappingName := ExtractFileName(Application.Title) + '::' + IntToStr(GetCurrentProcessId);
// Création d'un zone mémoire partagée pour l'échange des pointeurs d'objets communs avec les DLL
MappingHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, SizeOf(TSharedStruct), PChar(MappingName));
if MappingHandle <> 0 then
begin
// Initialisation du pointeur de la structure partagée
SharedStruct := MapViewOfFile(MappingHandle,
FILE_MAP_ALL_ACCESS,
0,
0,
SizeOf(TSharedStruct));
// Création des objets partagés
FSyncList := TList.Create;
SharedStruct^.SyncList := FSyncList;
DllSyncThread := TSyncThread.Create(True);
SharedStruct^.DllSyncThread := DllSyncThread;
FCriticalSection := TCriticalSection.Create;
SharedStruct^.CriticalSection := FCriticalSection;
FSignal := CreateEvent(nil, True, False, '');
SharedStruct^.Signal := FSignal;
// Initialisation du thread de controle
ResetEvent(FSignal);
DllSyncThread.Resume;
end;
end;
// Initialisation d'une DLL
Procedure InitLib;
var MappingName : String;
begin
MappingName := ExtractFileName(Application.Title) + '::' + IntToStr(GetCurrentProcessId);
// Ouverture d'un handle vers la zone de mémoire partagée (préalablement créée par le module principal)
MappingHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, SizeOf(TSharedStruct), PChar(MappingName));
if MappingHandle <> 0 then
begin
// Initialisation du pointeur de la structure partagée
SharedStruct := MapViewOfFile(MappingHandle,
FILE_MAP_ALL_ACCESS,
0,
0,
SizeOf(TSharedStruct));
// Récupération des pointeurs d'objets partagés
FSyncList := SharedStruct^.SyncList;
DllSyncThread := SharedStruct^.DllSyncThread;
FCriticalSection := SharedStruct^.CriticalSection;
FSignal := SharedStruct^.Signal;
// Création de l'objet de gestion de la synchronisation pour la DLL
DllThreadSyncObj := TSyncObj.Create;
WakeMainThread := DllThreadSyncObj.DLLWakeMainThread; // Surcharge de la variable globale de procédure WakeMainThread de la DLL
DllThreadSyncObj.LocalCheckSynchronize := CheckSynchronize; // Mémorisation du pointeur de procedure CheckSynchronize de la DLL
end;
end;
// Finalisation du module principal
Procedure FinalizeMainModule;
begin
// Arret et libération du thread de controle
SharedStruct^.DllSyncThread.FreeOnTerminate := True;
SharedStruct^.DllSyncThread.Terminate;
SetEvent(SharedStruct^.Signal);
// Libération des objets partagés et fermeture des handles du signal et du FileMapping
SharedStruct^.SyncList.Free;
SharedStruct^.CriticalSection.Free;
CloseHandle(SharedStruct^.Signal);
UnmapViewOfFile(SharedStruct);
CloseHandle(MappingHandle);
end;
// Finalisation d'une DLL
Procedure FinalizeLib;
begin
// fermeture du partage du FileMapping
UnmapViewOfFile(SharedStruct);
// Libération de l'objet de synchronization de la DLL
DllThreadSyncObj.Free;
end;
// Chargement de la DLL
Procedure DLLEntryPoint(Reason: Integer);
begin
InitLib; // On effectue notre initialisation
DllProc := OldDllProc; // puis on réaffecte la procédure de point d'entrée d'origine
end;
// Initialisation de l'unité
initialization
begin
if IsLibrary then // Si c'est une DLL
begin
OldDllProc := DllProc; // On mémorise le point d'entrée de la DLL
DllProc := @DLLEntryPoint; // puis on le remplace par notre propre point d'entrée
end
else
InitMainModule; // Ce n'est pas une DLL, initialisation du module principal (exe)
end;
// Finalisation de l'unité
finalization
begin
if IsLibrary then // Si c'est une DLL
FinalizeLib // Finalisation de la DLL
else
FinalizeMainModule; // Sinon finalisation du module principal
end;
end.
Conclusion :
Pour l'utilisation, rien de plus simple : il suffit d'ajouter cette unité au projet de l'application, ainsi qu'a celui de chaque DLL necessitant des synchronisation d'évenements de threads.
Enjoy :)
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.