Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi DLL und Threads (https://www.delphipraxis.net/180042-dll-und-threads.html)

haentschman 18. Apr 2014 11:16

Delphi-Version: 5

DLL und Threads
 
Hallo alle...:hi:
...schöne Ostern wünsch ich. 8-)

Zur Frage:
Bekannt ist ja, daß eine DLL nur einmal je Prozeß geladen wird. Meine DLL exportiert eine Hand voll Prozeduren. Diese Prozeduren wiederum werden in verschiedenen parallel laufenden Threads verwendet.

Was ist zu beachten? Wo gehört LoadLibrary und FreeLibrary hin? Müssen die Threads synchronisiert auf die DLL Prozeduren zugreifen?

Danke für Info´s.

Sir Rufo 18. Apr 2014 11:29

AW: DLL und Threads
 
Denk beim Multi-Threading immer an folgendes:

Ich kann problemlos zu n Personen sprechen und die gesprochenen Worte kommen dort an.
Umgekehrt funktioniert das mit n>1 aber nicht.

Sorge einfach dafür, dass dieser umgekehrte Fall nicht eintreten kann und dein Code ist threadsafe.

haentschman 18. Apr 2014 11:43

AW: DLL und Threads
 
:( Dann ist mein Konzept für die Katz...

Hintergrund:
Die DLL parst einen HTML Response. Dieser Response wird in verschiedenen paralelen Threads(HTML Anfragen) abgeholt und soll im Thread geparst und das Ergebnis gespeichert werden. Wenn ich pro Thread die gleiche DLL laden könnte... Das soll ja mit Tricks glaube ich funktionieren. Ich guck mal...

Tipps sind willkommen. :P

DeddyH 18. Apr 2014 11:51

AW: DLL und Threads
 
Ohne jetzt großartig nachgedacht zu haben: was ist denn, wenn die DLL die Threads erzeugt? Mit CallBack-Parametern beim Aufruf müsste das doch funktionieren, oder?

haentschman 18. Apr 2014 11:56

AW: DLL und Threads
 
Du meinst innerhalb der DLL im Speicherkontext der DLL?

Hmmm... Denke ich mal drüber nach. :roll:
Der Thread hat ja die "Lade Schleife" als Aufgabe. Das hat eigentlich in der DLL nix verloren. Die soll ausschließlich den Response parsen und die jeweiligen "Connection Strings" für die Anfrage vorhalten.

Sir Rufo 18. Apr 2014 12:12

AW: DLL und Threads
 
Streich mal das DLL Geraffel aus deinen Überlegungen, denn in der DLL ist einfach nur Code ausgelagert.

Ob eine DLL threadsafe ist, hängt nicht davon ab, ob der Code in einer DLL ist, sondern ob der Code threadsafe ist.

Also kann man das aus der Überlegung auch rausnehmen.

Ich baue mal eben eine kleine Demo, um das zu veranschaulichen ... dauert einen kleinen Moment.

haentschman 18. Apr 2014 12:22

AW: DLL und Threads
 
Zitat:

Streich mal das DLL Geraffel aus deinen Überlegungen, denn in der DLL ist einfach nur Code ausgelagert.
... ja klar. Und dieser Code sollte, so die Idee, in mehreren Threads aufrufbar sein.

Nachtrag:
Im Prinzip funktioniert das auch. Ich bin auf die Suche gegangen weil die eine Funktion aus der DLL exakt 13 mal den Text korrekt parst und beim 14 mal mit einer Zugriffsverletzung in der DLL abschmiert. Deswegen dachte ich an einen Grundsatzfehler.

Diese:
Delphi-Quellcode:
procedure ParseDeviceParameters(const aText: PChar; Parameters: TDeviceParameterList); stdcall;
var
  aParameter: TDeviceParameter;
  I, PositionFirst, PositionCount, TempPosition: Integer;
  sl: TStringList;
begin
  sl:= TStringList.Create;
  try
    sl.Text:= aText;
    for I:= 0 to sl.Count - 1 do
    begin
      if AnsiStartsText('dvcRec[', sl[I]) then
      begin
        aParameter:= TDeviceParameter.Create;
        PositionFirst:= PosEx('(',sl[I],1) + 1;
        PositionCount:= PosEx(',',sl[I],PositionFirst) - PositionFirst;
        aParameter.ParameterID:= StrToInt(Copy(sl[I],PositionFirst,PositionCount));
        PositionFirst:= PosEx('"',sl[I],1) + 1;
        TempPosition:= PositionFirst;
        PositionCount:= PosEx('"',sl[I],TempPosition) - PositionFirst;
        aParameter.Name:= Copy(sl[I],PositionFirst,PositionCount);
        TempPosition:= TempPosition + PositionCount + 1;
        PositionFirst:= PosEx(',',sl[I],TempPosition) + 1;
        PositionCount:= PosEx(',',sl[I],PositionFirst) - PositionFirst;
        aParameter.GroupID:= StrToInt(Copy(sl[I],PositionFirst,PositionCount));
        TempPosition:= TempPosition + PositionCount + 2;
        PositionFirst:= PosEx('"',sl[I],TempPosition) + 1;
        PositionCount:= PosEx('"',sl[I],PositionFirst) - PositionFirst;
        aParameter.Measure:= Copy(sl[I],PositionFirst,PositionCount);
        if aParameter.Measure = ' ' then
        begin
          aParameter.ParameterType:= ptDigital;
        end
        else
        begin
          aParameter.ParameterType:= ptAnalog;
        end;
        aParameter.State:= dsLoaded;
        Parameters.Add(aParameter);
      end;
    end;
  finally
    sl.Free;
  end;
end;
Aufgerufen hier: (FParser ist das Objekt welches sich um das Laden der DLL kümmert und die exportierten Geschichten zur Verfügung stellt)
Delphi-Quellcode:
FParser.ParseDeviceParameters(PChar(FResponseText), FDeviceParameterList);
for DeviceParameter in FDeviceParameterList do
begin
  DeviceParameter.DeviceID := FCurrentDevice.ID;
end;
FDatabase.SaveList(FDeviceParameterList);
FDatabase.GetList(FDeviceParameterList, FCurrentDevice);
Zitat:

Erste Gelegenheit für Exception bei $771AC41F. Exception-Klasse EAccessViolation mit Meldung 'Zugriffsverletzung bei Adresse 08D2328B in Modul 'BLA_500_V1_2.dll'. Schreiben von Adresse 00000001'. Prozess DVAServer.exe (3640)

Sir Rufo 18. Apr 2014 12:51

AW: DLL und Threads
 
Parser-Interface:
Delphi-Quellcode:
unit Parser;

interface

type
  IParser = interface
    ['{7082CCBB-2680-4BC4-8B17-7FFE1D602A0A}']
    function Parse( const AString : string ) : TArray<string>;
  end;

implementation

end.
und ein simpler nicht threadsafe Parser:
Delphi-Quellcode:
unit SimpleParser;

interface

uses
  Classes,
  Parser;

type
  TParserState = procedure( AChar : Char ) of object;

  TSimpleParser = class( TInterfacedObject, IParser )
  private
    FState : TParserState;
    FBuffer : string;
    FTokens : TStrings;
    procedure StartState( AChar : Char );
    procedure TokenState( AChar : Char );
  public
    constructor Create;
    destructor Destroy; override;

    function Parse( const AString : string ) : TArray<string>;
  end;

implementation

{ TSimpleParser }

constructor TSimpleParser.Create;
begin
  inherited;
  FTokens := TStringList.Create;
end;

destructor TSimpleParser.Destroy;
begin
  FTokens.Free;
  inherited;
end;

function TSimpleParser.Parse( const AString : string ) : TArray<string>;
var
  LIdx : Integer;
begin
  FTokens.Clear;
  FState := StartState;
  for LIdx := 1 to Length( AString ) do
  begin
    FState( AString[LIdx] );
  end;
  Result := FTokens.ToStringArray;
end;

procedure TSimpleParser.StartState( AChar : Char );
begin
  case AChar of
    ' ' :
      ;
    ',' :
      ;
  else
    FState := TokenState;
    FState(AChar);
  end;
end;

procedure TSimpleParser.TokenState( AChar : Char );
begin
  case AChar of
    ',' :
      begin
        FTokens.Add( FBuffer );
        FBuffer := '';
        FState := StartState;
      end;
  else
    FBuffer := FBuffer + AChar;
  end;
end;

end.
den ich einfach wrappe um mit dem doch threadsafe arbeiten zu können
Delphi-Quellcode:
unit ThreadSafeParser;

interface

uses
  Parser;

type
  TThreadSafeParser = class( TInterfacedObject, IParser )
  public
    function Parse( const AString : string ) : TArray<string>;
  end;

implementation

uses
  SimpleParser;

{ TThreadSafeParser }

function TThreadSafeParser.Parse( const AString : string ) : TArray<string>;
var
  LParser : IParser;
begin
  // TSimpleParser ist nicht threadsafe, aber
  // hier wird bei jedem Aufruf eine eigene Instanz erzeugt, auf die niemand sonst zugreift
  // dadurch wird das jetzt threadsafe
  LParser := TSimpleParser.Create;
  Result := LParser.Parse( AString );
end;

end.
Delphi-Quellcode:
procedure Test;
var
  LParser : IParser;
begin
  LParser := TSimpleParser.Create;
  OutputParseResult( LParser.Parse( '1,2,3,4' ) );

  LParser := TThreadSafeParser.Create;
  OutputParseResult( LParser.Parse( '1,2,3,4' ) );
end;

Sir Rufo 18. Apr 2014 12:52

AW: DLL und Threads
 
Zitat:

Zitat von haentschman (Beitrag 1256144)
Aufgerufen hier: (FParser ist das Objekt welches sich um das Laden der DLL kümmert und die exportierten Geschichten zur Verfügung stellt)
Delphi-Quellcode:
FParser.ParseDeviceParameters(PChar(FResponseText), FDeviceParameterList);
for DeviceParameter in FDeviceParameterList do
begin
  DeviceParameter.DeviceID := FCurrentDevice.ID;
end;
FDatabase.SaveList(FDeviceParameterList);
FDatabase.GetList(FDeviceParameterList, FCurrentDevice);
Zitat:

Erste Gelegenheit für Exception bei $771AC41F. Exception-Klasse EAccessViolation mit Meldung 'Zugriffsverletzung bei Adresse 08D2328B in Modul 'BLA_500_V1_2.dll'. Schreiben von Adresse 00000001'. Prozess DVAServer.exe (3640)

Ist der Zugriff auf
Delphi-Quellcode:
FParser
,
Delphi-Quellcode:
FDatabase
auch insgesamt threadsafe?

haentschman 18. Apr 2014 12:59

AW: DLL und Threads
 
FParser und FDatabase sind jeweils im Create des Threads erzeugt und im Destroy freigegeben. Da sollte das gut sein... :zwinker: Der Thread soll völlig allein die Aufgabe abarbeiten (incl. Datenbankspeicherung). Der Parser hat zwar seine eigene Instanz im Thread aber dann das gleiche DLL Handle wie der nächste bzw. vorhergehende Thread.
Wenn ich die o.g. Funktion auskommentiere läuft das ganze und beendet sich ohne Speicherlöcher und Zugriffverletzungen. Nehme ich sie rein ist bei 13 Schluß. An dem HTML Response ist nix außergewöhnliches zu erkennen.

PS: Ich hatte auch schon an ein Interface gedacht Die DLL gibt ein Interface zurück und es wird nur mit dem Interface gearbeitet statt mit den Funktionen. Macht das einen Unterschied mit dem DLL Handling?

Warum DLL:
Es gibt geschätzt 30 verschiedene Geräteversionen die sich im Response unterscheiden. Die passende DLL (Parser) zur Geräteversion wird dynamisch geladen.

DeddyH 18. Apr 2014 13:03

AW: DLL und Threads
 
Zitat:

Delphi-Quellcode:
FParser.ParseDeviceParameters(PChar(FResponseText), FDeviceParameterList);

Wird der Code aus der Anwendung heraus ausgeführt oder nur innerhalb der DLL?

haentschman 18. Apr 2014 13:12

AW: DLL und Threads
 
.. nicht in der DLL nur im Thread. Der Thread soll die DLL benutzen.

DLL:
Delphi-Quellcode:
procedure ParseDeviceParameters(const aText: PChar; Parameters: TDeviceParameterList)
.
.
exports
  ParseDeviceParameters
.
.
TParser, instanziert im Thread:
Delphi-Quellcode:
FDLL: string;
FMasterDeviceDLLHandle: THandle;
procedure LoadDLL;
.
.
constructor TParser.Create(aDLL: string);
begin
  inherited Create;
  FMasterDeviceDLLHandle:= 0;
  FDLL:= aDLL;
  LoadDLL;
end;

procedure TParser.LoadDLL;
begin
  FMasterDeviceDllHandle:= LoadLibrary(PChar(FDLL));
  if FMasterDeviceDllHandle <> 0 then begin
    FParseDeviceParameters:= GetProcAddress(FMasterDeviceDllHandle, 'ParseDeviceParameters');
.
.
  end
  else
  begin
    raise Exception.Create('DLL ' + QuotedStr(FDLL) + ' konnte nicht geladen werden.');
  end;
end;

procedure TParser.ParseDeviceParameters(aText: PChar; ParameterList: TDeviceParameterList);
begin
  FParseDeviceParameters(aText, ParameterList);
end;
Aus dem Thread:
Delphi-Quellcode:
FParser.ParseDeviceParameters(PChar(FResponseText), FDeviceParameterList);

DeddyH 18. Apr 2014 13:14

AW: DLL und Threads
 
Dir ist aber bewusst, dass man von und zu DLLs keine Objekte übergeben kann, sondern höchstens Interfaces?

Sir Rufo 18. Apr 2014 13:15

AW: DLL und Threads
 
Das "Warum DLL" spielt hier doch keine Rolle (persönlich würde ich das auch mit DLL's/Plugins machen).

Es kommt nur darauf an, wie du das in der DLL umgesetzt hast.

Als Beispiel nehme ich mal
Delphi-Quellcode:
TSimpleParser
und baue den um, so dass der niemals threadsafe benutzt werden kann:
Delphi-Quellcode:
unit SimpleParser;

interface

uses
  Classes,
  Parser;

type
  TParserState = procedure( AChar : Char ) of object;

  TSimpleParser = class( TInterfacedObject, IParser )
  private
    FBuffer : string;
    FTokens : TStrings;
    procedure StartState( AChar : Char );
    procedure TokenState( AChar : Char );
  public
    constructor Create;
    destructor Destroy; override;

    function Parse( const AString : string ) : TArray<string>;
  end;

implementation

// unit-globale Variable und ich kann threadsafe vergessen
var
  FState : TParserState;

{ TSimpleParser }

constructor TSimpleParser.Create;
begin
  inherited;
  FTokens := TStringList.Create;
end;

destructor TSimpleParser.Destroy;
begin
  FTokens.Free;
  inherited;
end;

function TSimpleParser.Parse( const AString : string ) : TArray<string>;
var
  LIdx : Integer;
begin
  FTokens.Clear;
  FState := StartState;
  for LIdx := 1 to Length( AString ) do
  begin
    FState( AString[LIdx] );
  end;
  Result := FTokens.ToStringArray;
end;

procedure TSimpleParser.StartState( AChar : Char );
begin
  case AChar of
    ' ' :
      ;
    ',' :
      ;
  else
    FState := TokenState;
    FState(AChar);
  end;
end;

procedure TSimpleParser.TokenState( AChar : Char );
begin
  case AChar of
    ',' :
      begin
        FTokens.Add( FBuffer );
        FBuffer := '';
        FState := StartState;
      end;
  else
    FBuffer := FBuffer + AChar;
  end;
end;

end.
Hast du das in der DLL so ähnlich gelöst, dann kannst du das threadsafe vergessen.

haentschman 18. Apr 2014 13:16

AW: DLL und Threads
 
Ja, solange die DLL den gleichen Lebenszyklus wie die hier verwendeten Generischen Listen geht das. Die Objekte werden in der DLL erzeugt und sind im Adressraum der DLL.

Und wenn ich mir von der DLL in jedem Thread ein Interface geben lasse? Dann sollte es doch wurscht sein ob das DLL Handle überall gleich ist.

himitsu 18. Apr 2014 14:01

AW: DLL und Threads
 
Die Referenzzählung von Interfaces ist (meistens) Threadsicher, aber der Zugriff auf Methoden ist natprlich nicht vor gleichzeitigen Zugriffen geschützt.

haentschman 19. Apr 2014 07:52

AW: DLL und Threads
 
Moin...

Ich habe dann mal die DLL auf Herausgabe eines Interfaces umgestellt. Ich bekomme immer noch die Zugriffsverletzung beim 14. Durchlauf (siehe oben). :roll: Also Alles beim Alten... :thumb:

Dabei ist mir folgendes aufgefallen. Wenn ich die DLL Innerhalb eines Thread lade wird dann der DLL Code trotzdem im MainThread ausgeführt? Obwohl der Thread gut funktioniert, beim Parsen hängt meine GUI... :shock:

Ich bin verwirrt... :)


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:40 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz