Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delayed Loaded Libraries? (https://www.delphipraxis.net/171632-delayed-loaded-libraries.html)

himitsu 16. Nov 2012 09:37

Delphi-Version: XE2

Delayed Loaded Libraries?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Verwendet dieses denn schon jemand ausgiebig?

Ich hab es grade so für mich (wieder)gefunden und will es demnächst einsetzen.
Im Prinzip ist es eine wunderschöne Sache (vorallem man man Dieses auch noch komplett nutzt).

Statisches Laden ist ja nicht immer möglich/erwünscht.
Delphi-Quellcode:
procedure MyProc(...);
...

procedure MyProc(...);
external 'My.dll'; external 'My.dll' name 'MyProc';
;

Und dynamisches Laden sieht häßlich aus.
Delphi-Quellcode:
var MyProc = procedure(...);

// und dann irgendwo ein LoadLibrary und GetProcAddress
Jetzt könnte man noch anfangen und Wrapper-Prozeduren drumrumpacken, damit es nach außen wieder ein "schöner" Prozeduraufruf wird
und vorallem damit niemand "ausversehn" diese Variable überschreibt.


Sooooooo, im Prinzip reicht es so schon aus: (LoadLibrary+GetProcAddress)
Delphi-Quellcode:
function MyFunction(...): PChar;

function MyFunction(...): PChar;
external 'my.dll' name 'MyFunction' delayed;
Schon wird die DLL erst beim ersten Aufruf der Funktion geladen.

Und nun nur noch das Endladen (FreeLibrary):
Delphi-Quellcode:
UnloadDelayLoadedDLL('my.dll');
// Hier bitte ganz genau die Dokumentation (MSDN) durchlesen, da es ein/zwei Dinge zu beachten gibt.
Um das jetzt noch ein bissl OOPiger hinzubekommen, hab ich das Ganze in einem Record gekapselt, wo der erste Parameter der Record selber ist.
Man könnte aber auch statische Klassen-Prozeduren in (leeren) Records oder (abstrakten) Klassen nutzen.
Delphi-Quellcode:
procedure Test(Sender: TObject);
var
  F: TTestFunc;
begin
  F.Time := Now;
  ShowMessage(F.ToTime);

  F := Now;
  ShowMessage(F.ToDate + ' ' + F.ToTime);

  F.Unload;
end;
Delphi-Quellcode:
type
  TTestFunc = record
  public const
    TheDLL = 'DelayedLoadDLL.dll';
  public
    Time: TDateTime;
    function ToTime: PChar; // die beiden Funktionen in einem Record verpackt
    function ToDate: PChar; // function DateToStr(Time: PDataTime): PWideChar;
    class procedure Unload; static;
    class operator Implicit(const Rec: TDateTime): TTestFunc;
    class operator Implicit(const Rec: TTestFunc): TDateTime;
  end;

class operator TTestFunc.Implicit(const Rec: TDateTime): TTestFunc;
begin
  Result.Time := Rec;
end;

class operator TTestFunc.Implicit(const Rec: TTestFunc): TDateTime;
begin
  Result := Rec.Time;
end;

function TTestFunc.ToDate: PChar;
external TTestFunc.TheDLL name 'DateToStr' delayed;

function TTestFunc.ToTime: PChar;
external TTestFunc.TheDLL name 'TimeToStr' delayed;

class procedure TTestFunc.Unload;
type
  DLIUProc = function(szDll: PAnsiChar): LongBool; stdcall;
begin
  if not DLIUProc(UnloadDelayLoadedDLLPtr)(TheDLL) then
    OutputDebugString('DLL was not unloaded');
end;
Im Anhang ist uch noch eine weitere "Klasse" (TDelayedLoadLibrary) drin, welche als Hook für die DLI-PI diehnt.
Denn bei der originalen API kann man sich nur einmal registrieren (wie z.B. bei den alten Application.OnMessage) und diese Klasse ist nun mein "TApplicationEvents". :stupid:
(ohne würde es schwerer das Event zu nutzen, wenn man dieses mehrfach im Programm bräuchte, vorallem wenn dann in irgendeiner "Fremdkomponente" sich jemand zwar registriert, aber den Vorgänger vergisst aufzurufen :wall:)

PS: XE3 fehlt immernoch, aber ich gleub das gibt's auch schon in weilchen, also egal, aber irgendwas muß man ja auswählen, damit kein D5 dasteht :angle2:




[edit]
bevor ich's wieder vergesse ... hier noch eine Debugausgabe zum Code in der DelayedLoadProcU.pas :
(ein bissl aufgesplittet nach Funktionen und mit Positionsmarken versehn)
Code:
Prozess DelayedLoad.exe (4568)



Thread-Start: Thread-ID: 8668.
Prozessstart: ...\DelayedLoad.exe. Basisadresse: $00400000.
Modul laden: DelayedLoad.exe. Enthält Debug-Infos. Basisadresse: $00400000.
Modul laden: ntdll.dll. Ohne Debug-Infos. Basisadresse: $77020000.
Modul laden: KERNEL32.dll. Ohne Debug-Infos. Basisadresse: $74990000.
Modul laden: KERNELBASE.dll. Ohne Debug-Infos. Basisadresse: $76710000.
Modul laden: OLEAUT32.dll. Ohne Debug-Infos. Basisadresse: $74B40000.
Modul laden: ole32.dll. Ohne Debug-Infos. Basisadresse: $765B0000.
Modul laden: msvcrt.dll. Ohne Debug-Infos. Basisadresse: $74F80000.
Modul laden: GDI32.dll. Ohne Debug-Infos. Basisadresse: $76200000.
Modul laden: USER32.dll. Ohne Debug-Infos. Basisadresse: $762F0000.
Modul laden: ADVAPI32.dll. Ohne Debug-Infos. Basisadresse: $763F0000.
Modul laden: SECHOST.dll. Ohne Debug-Infos. Basisadresse: $74790000.
Modul laden: RPCRT4.dll. Ohne Debug-Infos. Basisadresse: $74BD0000.
Modul laden: SspiCli.dll. Ohne Debug-Infos. Basisadresse: $74700000.
Modul laden: CRYPTBASE.dll. Ohne Debug-Infos. Basisadresse: $746F0000.
Modul laden: LPK.dll. Ohne Debug-Infos. Basisadresse: $74980000.
Modul laden: USP10.dll. Ohne Debug-Infos. Basisadresse: $75E90000.
Modul laden: VERSION.dll. Ohne Debug-Infos. Basisadresse: $721F0000.
Modul laden: COMCTL32.dll. Ohne Debug-Infos. Basisadresse: $73570000.
Modul laden: SHLWAPI.dll. Ohne Debug-Infos. Basisadresse: $76290000.
Modul laden: SHELL32.dll. Ohne Debug-Infos. Basisadresse: $751F0000.
Modul laden: WINSPOOL.DRV. Ohne Debug-Infos. Basisadresse: $737F0000.
Modul laden: IMM32.dll. Ohne Debug-Infos. Basisadresse: $75190000.
Modul laden: MSCTF.dll. Ohne Debug-Infos. Basisadresse: $75F70000.
Modul laden: UxTheme.dll. Ohne Debug-Infos. Basisadresse: $73950000.
Modul laden: dwmapi.dll. Ohne Debug-Infos. Basisadresse: $74650000.
Modul laden: WTSAPI32.dll. Ohne Debug-Infos. Basisadresse: $741F0000.
Modul laden: WINSTA.dll. Ohne Debug-Infos. Basisadresse: $73C60000.
Thread-Start: Thread-ID: 4148.
Thread-Start: Thread-ID: 13156.



1:
Debug-Ausgabe: ProcAddr: $005A83F8
2:
Debug-Ausgabe: dliStartProcessing: DelayedLoadDLL.dll TimeToStr
Debug-Ausgabe: dliNotePreLoadLibrary: DelayedLoadDLL.dll TimeToStr
Modul laden: DelayedLoadDLL.dll. Ohne Debug-Infos. Basisadresse: $04B60000.
Debug-Ausgabe: Load DLL: DelayedLoadDLL.dll
Debug-Ausgabe: dliNotePreGetProcAddress: DelayedLoadDLL.dll TimeToStr
Debug-Ausgabe: dliNoteEndProcessing: DelayedLoadDLL.dll TimeToStr
Debug-Ausgabe: 06:06:07.165
3:
Debug-Ausgabe: ProcAddr: $005A83F8 <> $04C2D568
4:
Debug-Ausgabe: Unload DLL: DelayedLoadDLL.dll
Modul entladen: DelayedLoadDLL.dll.
5:
Debug-Ausgabe: ProcAddr: $005A83F8 = $005A83F8
6:
Debug-Ausgabe: dliStartProcessing: DelayedLoadDLL.dll TimeToStr
Debug-Ausgabe: dliNotePreLoadLibrary: DelayedLoadDLL.dll TimeToStr
Modul laden: DelayedLoadDLL.dll. Ohne Debug-Infos. Basisadresse: $04B60000.
Debug-Ausgabe: Load DLL: DelayedLoadDLL.dll
Debug-Ausgabe: dliNotePreGetProcAddress: DelayedLoadDLL.dll TimeToStr
Debug-Ausgabe: dliNoteEndProcessing: DelayedLoadDLL.dll TimeToStr
Debug-Ausgabe: 06:07:07.178
7:
Debug-Ausgabe: dliStartProcessing: DelayedLoadDLL.dll DateToStr
Debug-Ausgabe: dliNotePreGetProcAddress: DelayedLoadDLL.dll DateToStr
Debug-Ausgabe: dliNoteEndProcessing: DelayedLoadDLL.dll DateToStr
Debug-Ausgabe: 2012-11-15
8:
Debug-Ausgabe: ProcAddr: $005A83F8 <> $04C2D568 = $04C2D568



Modul laden: ole32.dll. Ohne Debug-Infos. Basisadresse: $05130000.
Modul entladen: ole32.dll.
Debug-Ausgabe: dliStartProcessing: DWMAPI.DLL DwmIsCompositionEnabled
Debug-Ausgabe: dliNotePreLoadLibrary: DWMAPI.DLL DwmIsCompositionEnabled
Debug-Ausgabe: dliNotePreGetProcAddress: DWMAPI.DLL DwmIsCompositionEnabled
Debug-Ausgabe: dliNoteEndProcessing: DWMAPI.DLL DwmIsCompositionEnabled
Debug-Ausgabe: dliStartProcessing: DWMAPI.DLL DwmExtendFrameIntoClientArea
Debug-Ausgabe: dliNotePreGetProcAddress: DWMAPI.DLL DwmExtendFrameIntoClientArea
Debug-Ausgabe: dliNoteEndProcessing: DWMAPI.DLL DwmExtendFrameIntoClientArea
(wie man sieht, nutzt auch Emba selbst diese Technik)

Programm und Fenster sind fertig geladen
(den Rest hab'sch weggelassen)

Tahtu 16. Dez 2016 07:38

AW: Delayed Loaded Libraries?
 
... das hört sich sehr spannend an. Ich hab es auch gleich mal ausprobiert.

Aber soweit ich das erkenne, beziehst du dich bei deiner delayed loaded DLL auf deine eigene DLL.

Wenn ich mein Programm starte werden ca. 100 DLLs geladen.

Was bringt es mir da, wenn eine einzige DLL delayed geladen wird?

Um den Start meines Programmes zu beschleunigen wünsche ich mir eine Lösung, dass ALLE DLLs erst dann geladen werden, wenn sie benötigt werden. Dabei beziehe ich mich also auf alle DLLs, die innerhalb vom Delphi-Quellcode eingebunden werden. Bei den DLLs kann ich kein "delayed" setzen. Oder irre ich?

Gibt es eine Lösung, um alle DLLs erst dann zu laden, wenn sie benötigt werden? Evtl. eine Toolbox, ein Windows API call oder sonst etwas?

himitsu 16. Dez 2016 09:22

AW: Delayed Loaded Libraries?
 
Viele der System-DLLs sind bereits vorgeladen im RAM, durch die 1000 anderen gestarteten Programme.

Vorteil ist vorallem, dass diese DLL im Code wie eine statische Gelinkte behandelt wird, aber sie wird erst und auch nur bei der ersten Verwendung geladen.
Wenn man davon "aktuell" nichts nutzt, dann ist sie auch nicht geladen, aber ohne dass man selber das dynamische Laden implementieren muß.

Das ist ein Feature, was der Compiler anbietet.
Du müsstest also beim Compilieren dem Compiler sagen, was er so laden soll.

Man könnte natürlich hin gehn und die Importe in den Delphi-Quellcodes um das dalayed ergänzen.
Geht nicht immer, weil einige DLLs, ja auch ohne Aufruf schon beim Start etwas initialisieren sollen.
Entweder man koppiert sich diese Units oder man darf dann auch erstmal die RTL/VCL neu kompileren.

Es ist also vorallem für Dinge nützlich, die man selten verwendet.
Dazu zählen auch Schnittstellen DLLs zu irgendwelcher Hardware, die nicht immer vorhanden ist.

Assarbad 16. Dez 2016 09:53

AW: Delayed Loaded Libraries?
 
Also, ich weiß nicht ob das jetzt von neueren Delphi-Versionen nativ unterstützt wird (mangels Willen denen noch mehr Geld in den Rachen zu schmeißen), oder nicht. Compiler- bzw. linkerseitig meine ich. Aber ich benutze Delay-Loading noch für andere Dinge. So ist es eigentlich nicht möglich bestimmte DLLs als verzögert zu laden zu markieren - der Linker verhindert dies.

Aaaaber, durch die Callbacks, kann man natürlich schummeln. So kann man die DLL als "ntdll-delayed.dll" eintragen und dann einfach das korrekte HMODULE für ntdll.dll zurückgeben. Das benutze ich bspw. in ntobjx (delayimp.cpp) um die Funktionen aus der ntdll.dll verzögert zu laden.

Das eignet sich natürlich auch für andere DLLs die auf der schwarzen Liste des Linkers stehen und deshalb nicht als verzögert zu laden markiert werden können.

Zitat:

Zitat von Tahtu (Beitrag 1356293)
Gibt es eine Lösung, um alle DLLs erst dann zu laden, wenn sie benötigt werden? Evtl. eine Toolbox, ein Windows API call oder sonst etwas?

Jain.

Himitsu hatte ja bereits erklärt, warum es naturgemäß mit einigen DLLs nicht geht. Wenn du aber Anwendungs-DLLs hast, sollte es kein Problem sein. Der normale verzögerte Mechanismus lädt die DLL immer erst dann wenn eine Funktion aus der entsprechenden DLL benötigt wird. Allerdings ist das nicht zwangsläufig günstiger, weil Fehler beim Import so von dir zur Laufzeit behandelt werden müssen. Andererseits sieht es vielleicht freundlicher aus als die Fehlermeldung des Loaders, daß eine bestimmte DLL nicht auffindbar sei.

himitsu 16. Dez 2016 11:13

AW: Delayed Loaded Libraries?
 
Seit 'ner Weile gibt es die Starter-Editionen kostenlos, aber mangels Delphi-Quellcodes (RTL/VCL) wird dir diese Version wohl nicht viel bringen?
https://www.embarcadero.com/de/free-tools

Ab Delphi 2010 ist das implementiert.
Embarcadero hat dabei direkt den Loading-Code von Microsoft als OBJ eingebunden. Schade eigentlich, denn so schlimm/aufwändig ist der nicht und man hätte da auch direkt eine "bessere" Fehlerbehandlung implementieren können, inkl. Anzeige der aufgerufenen Funktion, bei welcher es knallt.
http://docwiki.embarcadero.com/RADSt...B6gertes_Laden
http://docwiki.embarcadero.com/CodeE...ading_(Delphi)

Tahtu 16. Dez 2016 11:17

AW: Delayed Loaded Libraries?
 
Danke für die Antwort.

Ihr habt mich überzeugt: Der Delphi-Compiler müsste die Funktion bereitstellen. Also müsste ich mal die Hilfe von Delphi Berlin durchlesen, ob ich dort etwas finde.

Die DLLs, die ich selbst einbinde, habe ich schon mit LoadLibrary versorgt - dort finde ich also kein Potential zur Beschleunigung mehr...

Tahtu 16. Dez 2016 11:26

AW: Delayed Loaded Libraries?
 
[unwichtig]

Assarbad 16. Dez 2016 12:38

AW: Delayed Loaded Libraries?
 
@Himitsu, bei dem einen Link fehlte was, nämlich die Klammer zu.

Zitat:

Zitat von himitsu (Beitrag 1356352)
Embarcadero hat dabei direkt den Loading-Code von Microsoft als OBJ eingebunden. Schade eigentlich, denn so schlimm/aufwändig ist der nicht und man hätte da auch direkt eine "bessere" Fehlerbehandlung implementieren können, inkl. Anzeige der aufgerufenen Funktion, bei welcher es knallt.

Also laut deinen Links ist das doch ziemlich vollständig, inwieweit kannst du denn deine Fehlerbehandlung nicht umsetzen?

himitsu 16. Dez 2016 14:49

AW: Delayed Loaded Libraries?
 
Neee, nicht ich meine ... Embarcadero hätte passende Fehlermeldungen werfen können, anstatt der Standardexception von Microsoft.
Selbst eine "sprechende" Fehlermeldung ohne Nennung der DLL/Function würde doch nett sein?

Hach, auch bei den Abstract-Exceptions hätte es Möglichkeiten gegeben Methodenname+Klassenname der abstrakten Methode in der Exception zu nennen.



Zumindestens in XE sind/waren die Fehlermeldungen nicht wirklich aussagefähig.
Und ich bin mir fast sicher, dass Embarcadero seit der Einführung in 2010 daran nichts mehr verändert hat. :stupid:
Delphi-Quellcode:
function MessageBox2(hWnd: HWND; lpText, lpCaption: PWideChar; uType: UINT): Integer; stdcall;
  external 'user32.dll' name 'MessageBoxW2' delayed;
function MessageBox3(hWnd: HWND; lpText, lpCaption: PWideChar; uType: UINT): Integer; stdcall;
  external 'blubb.dll' name 'MessageBoxW' delayed;

procedure TForm4.FormCreate(Sender: TObject);
begin
  MessageBox(Self.Handle, 'Test', 'MessageBox', 0);
  try
    // normal: Der Prozedureinsprungpunkt "MessageBoxW2" wurde in der DLL "...\Project4.exe" nicht gefunden.
    // delayed: Externe Exception C0FB007F
    MessageBox2(Self.Handle, 'Test', 'MessageBox2', 0);
  except
    on E: Exception do
      ShowMessage('MessageBox2: ' + E.Message);
  end;
  try
    // normal: Das Programm kann nicht gestartet werden, da blubb.dll auf dem Computer fehlt. ...
    // delayed: Externe Exception C0FB007E
    MessageBox3(Self.Handle, 'Test', 'MessageBox3', 0);
  except
    on E: Exception do
      ShowMessage('MessageBox3: ' + E.Message);
  end;
end;

Zacherl 16. Dez 2016 15:56

AW: Delayed Loaded Libraries?
 
Zitat:

Zitat von himitsu (Beitrag 1356388)
Zumindestens in XE sind/waren die Fehlermeldungen nicht wirklich aussagefähig.
Und ich bin mir fast sicher, dass Embarcadero seit der Einführung in 2010 daran nichts mehr verändert hat. :stupid:

Es gibt aber einen ganz netten Workaround:
http://www.drbob42.com/examines/examinc1.htm (der letzte Abschnitt)


Alle Zeitangaben in WEZ +1. Es ist jetzt 08:55 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