Synchronisation de threads dans des dll

Contenu du snippet

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 :)

A voir également

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.