![]() |
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. |
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. |
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 |
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?
|
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. |
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. |
AW: DLL und Threads
Zitat:
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:
Aufgerufen hier: (FParser ist das Objekt welches sich um das Laden der DLL kümmert und die exportierten Geschichten zur Verfügung stellt)
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;
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:
|
AW: DLL und Threads
Parser-Interface:
Delphi-Quellcode:
und ein simpler nicht threadsafe Parser:
unit Parser;
interface type IParser = interface ['{7082CCBB-2680-4BC4-8B17-7FFE1D602A0A}'] function Parse( const AString : string ) : TArray<string>; end; implementation end.
Delphi-Quellcode:
den ich einfach wrappe um mit dem doch threadsafe arbeiten zu können
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.
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; |
AW: DLL und Threads
Zitat:
Delphi-Quellcode:
,
FParser
Delphi-Quellcode:
auch insgesamt threadsafe?
FDatabase
|
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. |
AW: DLL und Threads
Zitat:
|
AW: DLL und Threads
.. nicht in der DLL nur im Thread. Der Thread soll die DLL benutzen.
DLL:
Delphi-Quellcode:
TParser, instanziert im Thread:
procedure ParseDeviceParameters(const aText: PChar; Parameters: TDeviceParameterList)
. . exports ParseDeviceParameters . .
Delphi-Quellcode:
Aus dem Thread:
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;
Delphi-Quellcode:
FParser.ParseDeviceParameters(PChar(FResponseText), FDeviceParameterList);
|
AW: DLL und Threads
Dir ist aber bewusst, dass man von und zu DLLs keine Objekte übergeben kann, sondern höchstens Interfaces?
|
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:
und baue den um, so dass der niemals threadsafe benutzt werden kann:
TSimpleParser
Delphi-Quellcode:
Hast du das in der DLL so ähnlich gelöst, dann kannst du das threadsafe vergessen.
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. |
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. |
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.
|
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 00:34 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz