Auf einen Artikel wie diesen habe ich schon lange gewartet. Vielen Dank an den Autor.
Ich danke Ihnen. Sie sind nicht der erste, der sich bei mir bedankt. Ich nehme gerne alle Wünsche und kritischen Anmerkungen zum Material des Artikels entgegen.
In Zukunft möchte ich das Thema der Programmierung in Delphi für MT5 weiter ausbauen und neue Informationen auf der Seite hinzufügen.
Ich denke, der Artikel ist für viele Menschen nützlich. Ein paar Kommentare:
1. die Units SysUtils und Classes hätten im Projekt belassen werden sollen. Trotz der Tatsache, dass ihre Anwesenheit das Projekt etwas "aufbläht", haben sie viele kleine, aber wichtige Funktionen. Zum Beispiel fügt das Vorhandensein von SysUtils automatisch die Verarbeitung von Excepcions zum Projekt hinzu. Wie Sie wissen, wird eine Excepcion, die in der Dll nicht verarbeitet wird, an mt5 weitergeleitet, wo sie die Ausführung des mql5-Programms stoppt.
2. Sie sollten nicht alle Arten von Prozeduren innerhalb von DllEntryPoint (auch bekannt als DllMain) verwenden. Wie Microsoft in seinen Dokumenten angibt, hat dies verschiedene unangenehme Auswirkungen. Hier ist eine kleine Liste von Artikeln zu diesem Thema:
Best Practices for Creating DLLs by Microsoft - http://www.microsoft.com/whdc/driver/kernel/DLL_bestprac.mspx.
DllMain und das Leben vor der Entbindung - http://transl-gunsmoker.blogspot.com/2009/01/dllmain.html
DllMain - eine Gute-Nacht-Geschichte - http://transl-gunsmoker.blogspot.com/2009/01/dllmain_04.html
Ein paar Gründe, in Ihrem DllMain nichts Unheimliches zu tun - http://transl-gunsmoker.blogspot.com/2009/01/dllmain_05.html
Weitere Gründe, warum Sie in Ihrer DllMain nichts Unheimliches tun sollten: versehentliches Sperren -
http://transl-gunsmoker.blogspot.com/2009/01/dllmain_7983.htmlIch habe bereits einen Auszug aus einem unvollendeten Artikel irgendwo gegeben, ich glaube im Quad-Forum. Ich werde ihn hier wiederholen.
Anfang...Ende
Bei der Erstellung eines Delphi-Projekts, das für die DLL-Kompilierung vorgesehen ist, erscheint der Abschnitt begin...end in der .DPR-Projektdatei. Dieser Abschnitt wird immer dann ausgeführt, wenn die DLL zum ersten Mal in den Prozessadressraum projiziert wird. Mit anderen Worten, er kann als eine Art Initialisierungsabschnitt betrachtet werden, den alle Units haben. An dieser Stelle können Sie einige Aktionen durchführen, die ganz am Anfang und nur einmal für den aktuellen Prozess ausgeführt werden müssen. Wenn die DLL in den Adressraum eines anderen Prozesses geladen wird, wird dieser Abschnitt dort erneut ausgeführt. Da aber die Adressräume der Prozesse voneinander getrennt sind, hat die Initialisierung in einem Prozess keinerlei Auswirkungen auf den anderen Prozess.
Dieser Abschnitt hat einige Einschränkungen, die Sie beachten sollten. Diese Einschränkungen hängen mit den Feinheiten des Windows-DLL-Lademechanismus zusammen. Wir werden später noch ausführlicher auf sie eingehen.
Initialisierung/Finalisierung
Jede Unit Delphi hat spezielle Abschnitte, die sogenannten Initialisierungs- und Finalisierungsabschnitte. Sobald eine Unit mit dem Projekt verbunden wird, werden diese Abschnitte mit dem speziellen Lade- und Entlademechanismus des Hauptmoduls verbunden. Und diese Abschnitte werden ausgeführt, bevor der Hauptabschnitt begin...end seine Arbeit beginnt und nachdem die Arbeit beendet ist. Dies ist sehr praktisch, weil dadurch die Notwendigkeit entfällt, die Initialisierung und Beendigung im Programm selbst zu schreiben. Gleichzeitig erfolgt das Verbinden und Trennen automatisch, Sie müssen nur das Gerät mit dem Projekt verbinden bzw. von ihm trennen. Und dies geschieht nicht nur in herkömmlichen EXE-Dateien, sondern auch in DLLs. Die Reihenfolge der Initialisierung der DLL, wenn sie in den Speicher "geladen" wird, ist wie folgt, zuerst werden alle Initialisierungsabschnitte der Unit ausgeführt, in der Reihenfolge, in der sie in den Verwendungen des Projekts markiert sind, dann wird der Abschnitt begin...end ausgeführt. Die Finalisierung erfolgt in umgekehrter Reihenfolge, nur dass es in der DLL-Projektdatei keine speziell dafür vorgesehene Abbruchfunktion gibt. Dies ist im Allgemeinen ein weiterer Grund, warum es empfehlenswert ist, DLL-Projekte in eine Projektdatei und eine Verwendungseinheit zu unterteilen.
DllMain
Dies ist der so genannte DLL-Einstiegspunkt. Der Punkt ist, dass Windows gelegentlich jedes Ereignis, das innerhalb des Prozesses auftritt, an die DLL selbst melden muss. Um dies zu tun, gibt es einen Einstiegspunkt. Das heißt, eine speziell vordefinierte Funktion, die jede DLL hat und die Nachrichten verarbeiten kann. Und obwohl wir diese Funktion noch nicht in einer in Delphi geschriebenen DLL gesehen haben, hat sie doch einen solchen Punkt. Nur der Mechanismus ihres Funktionierens ist verschleiert, aber man kann immer an sie herankommen. Die Antwort auf die Frage - ist sie überhaupt notwendig? - ist nicht so offensichtlich, wie sie scheint.
Versuchen wir zunächst einmal zu verstehen, was Windows der DLL mitteilen will. Es gibt insgesamt 4 Meldungen, mit denen sich das Betriebssystem an die DLL wendet. Die erste, die DLL_PROCESS_ATTACH-Benachrichtigung, wird immer dann gesendet, wenn das System eine DLL an den Adressraum des aufrufenden Prozesses anhängt. Im Fall von MQL4handelt es sich um ein implizites Laden. Es spielt keine Rolle, dass diese DLL bereits in den Adressraum eines anderen Prozesses geladen wurde, die Meldung wird trotzdem gesendet. Und es spielt auch keine Rolle, dass Windows eine bestimmte DLL nur einmal in den Speicher lädt, alle Prozesse, die diese DLL in ihren Adressraum laden wollen, erhalten nur ein Spiegelbild dieser DLL. Dies ist derselbe Code, aber die Daten, die die DLL haben kann, sind für jeden Prozess einzigartig (obwohl es möglich ist, dass gemeinsame Daten existieren). Die zweite Meldung, DLL_PROCESS_DETACH, weist die DLL an, sich aus dem Adressraum des aufrufenden Prozesses zu lösen. Tatsächlich wird diese Meldung empfangen, bevor Windows mit dem Entladen der DLL beginnt. Wenn die DLL von anderen Prozessen verwendet wird, findet kein Entladen statt, Windows "vergisst" einfach, dass die DLL im Adressraum des Prozesses existiert. Zwei weitere Benachrichtigungen, DLL_THREAD_ATTACH undDLL_THREAD_DETACH, werden empfangen, wenn der Prozess, der die DLL geladen hat, Threads innerhalb des Prozesses erzeugt oder zerstört. Die Reihenfolge, in der die Thread-Benachrichtigungen empfangen werden, birgt einige Probleme, auf die wir hier jedoch nicht eingehen wollen.
Nun dazu, wie in Delphi geschriebene DLLs angeordnet sind und was den Programmierern normalerweise verborgen bleibt. Nachdem Windows die DLL "in den Adressraum des aufrufenden Prozesses projiziert" hat, oder einfach ausgedrückt, die DLL in den Speicher geladen hat, erfolgt an dieser Stelle ein Aufruf der Funktion, die sich am Einstiegspunkt befindet, und die Übergabe der Benachrichtigung DLL_PROCESS_ATTACH an diese Funktion. In einer in Delphi geschriebenen DLL enthält dieser Einstiegspunkt speziellen Code, der viele verschiedene Dinge tut, einschließlich des Beginns der Initialisierung von Einheiten. Er merkt sich, dass die Initialisierung und der erste Lauf der DLL erfolgt sind, und führt begin...end der Hauptprojektdatei aus. Somit wird dieser anfängliche Ladecode nur einmal ausgeführt, alle anderen Windows-Aufrufe an den Einstiegspunkt erfolgen an eine andere Funktion, die nachfolgende Benachrichtigungen verarbeitet - tatsächlich ignoriert sie diese, mit Ausnahme der DLL_PROCESS_DETACH-Nachricht, die die Einheit abschließt. So sieht der Mechanismus zum Laden einer in Delphi geschriebenen DLL im Allgemeinen aus. In den meisten Fällen reicht es aus, DLLs in MQL4 zu schreiben und zu verwenden.
Wenn Sie dennoch eine DllMain genau wie in C benötigen, ist es nicht schwierig, diese zu organisieren. Es wird wie folgt gemacht. Beim ersten Laden einer DLL wird u.a. durch das Modul System (es ist immer in einem Programm oder einer DLL vorhanden) automatisch eine globale prozedurale Variable DllProc angelegt, die mit nil initialisiert wird. Das bedeutet, dass keine zusätzliche Verarbeitung von DllMain-Benachrichtigungen erforderlich ist, abgesehen von der bereits vorhandenen. Sobald die Adresse der Funktion dieser Variablen zugewiesen ist, werden alle Benachrichtigungen für DLLs von Windows an diese Funktion gehen. Das ist es, was vom Einstiegspunkt verlangt wird. Die DLL_PROCESS_DETACH-Benachrichtigung wird jedoch nach wie vor von der DLL-Beendigungsfunktion verfolgt, um die Beendigung zu ermöglichen.
procedureDllEntryPoint(Reason: DWORD);
begin
case Grund von
DLL_PROCESS_ATTACH : ;//'Verbindung prozess'
DLL_THREAD_ATTACH : ;//'Verbindung mit dem Thread'
DLL_THREAD_DETACH : ;//'Trennen eines Threads'. Stream'
DLL_PROCESS_DETACH : ;//'Trennen der Verbindung Prozess'
end;
end;
beginnen
if not Assigned(DllProc) then begin
DllProc :=@DllEntryPoint;
DllEntryPoint (DLL_PROCESS_ATTACH);
end;
end.
Wenn wir nicht an Thread-Benachrichtigungen interessiert sind, ist dies alles unnötig. Es ist nur notwendig, die Initialisierungs-/Finalisierungsabschnitte in der Einheit zu organisieren, da die Ereignisse der Prozessverbindung und -trennung automatisch verfolgt werden.
Die Perfidie und der Verrat von DllMain
Nun ist es vielleicht an der Zeit, ein Thema anzusprechen, das in der Programmierliteratur erstaunlich wenig behandelt wird. Dieses Thema betrifft nicht nur Delphi oder C, sondern jede Programmiersprache, die DLLs erstellen kann. Dies ist eine Eigenschaft des Windows-DLL-Laders. In der übersetzten seriösen und weit verbreiteten Literatur über die Programmierung in der Windows-Umgebung hat es nur ein Autor geschafft, eine Erwähnung davon zu finden, und das auch nur in sehr vagen Worten. Dieser Autor ist J. Richter, und man verzeiht ihm, denn sein wunderbares Buch wurde 2001 veröffentlicht, als 32-Bit-Windows im Allgemeinen noch nicht so weit verbreitet war.
Interessant ist, dass MS selbst die Existenz des Problems mit DllMain nie verschwiegen hat und sogar ein spezielles Dokument veröffentlicht hat, so etwas wie - "The best way to use DllMain". Darin erklärte er, was in DllMain getan werden kann und was nicht empfohlen wird. Und es wurde darauf hingewiesen, dass nicht empfohlene Dinge zu schwer erkennbaren und inkonsistenten Fehlern führen. Wer dieses Dokument lesen möchte, kann hier nachsehen. Eine populärere Zusammenfassung mehrerer Übersetzungen mehrerer alarmistischer Berichte zu diesem Thema ist hier zu finden.
Die Essenz des Problems ist sehr einfach. Der Punkt ist, dass DllMain, insbesondere beim Laden einer DLL, ein besonderer Ort ist. Ein Ort, an dem Sie nichts Kompliziertes und Außergewöhnliches tun sollten. Zum Beispiel ist es nicht empfehlenswert, CreateProcess oder LoadLibrary andere DLLs zu laden. Es wird auch nicht empfohlen, CreateThread oder CoInitialise COM zu verwenden. Und so weiter.
Sie können die einfachsten Dinge tun. Ansonsten ist nichts garantiert. Fügen Sie daher nichts Unnötiges in DllMain ein, sonst werden Sie überrascht sein, wenn Anwendungen, die Ihre DLL verwenden, abstürzen. Es ist besser, auf Nummer sicher zu gehen und spezielle exportierte Funktionen für die Initialisierung und den Abschluss zu erstellen, die von der Hauptanwendung zum richtigen Zeitpunkt aufgerufen werden. Dies wird zumindest dazu beitragen, Probleme mit DllMain zu vermeiden.
ExitProc, ExitCode,MainInstance,HInstance....
Das System-Modul, das beim Kompilieren immer in Ihre DLL eingebunden wird, verfügt über einige nützliche globale Variablen, die Sie verwenden können.
ExitCode, - eine Variable, in die Sie beim Laden eine andere Zahl als 0 eingeben können, wodurch das Laden der DLL gestoppt wird.
ExitProc, - eine prozedurale Variable, die die Adresse der Funktion speichern kann, die beim Beenden ausgeführt wird. Diese Variable ist ein Relikt der fernen Vergangenheit, sie funktioniert nicht in DLLs und außerdem empfehlen die Delphi-Entwickler nicht, sie in DLLs zu verwenden, da es zu Problemen kommen kann.
HInstance, - eine Variable, in der nach dem Laden der Deskriptor der DLL selbst gespeichert wird. Sie kann sehr nützlich sein.
MainInstance, - Deskriptor der Anwendung, die die DLL in ihren Adressraum geladen hat.
IsMultiThread, - eine Variable, die automatisch auf True gesetzt wird, wenn bei der Kompilierung der DLL die Arbeit mit Threads erkannt wird. Basierend auf dem Wert dieser Variable schaltetder DLL-Speichermanager in den Multithreading-Modus. Im Prinzip ist es möglich, den Speichermanager zu zwingen, in den Multithreading-Modus zu wechseln, auch wenn Threads nicht explizit in der DLL verwendet werden. IsMultiThread:=True; Natürlich ist der Multithreading-Modus langsamer als der Single-Thread-Modus, da die Threads miteinander synchronisiert werden.
MainThreadID, - Deskriptor des Haupt-Threads der Anwendung.
Und so weiter. Im Allgemeinen führt das Modul System ungefähr die gleichen Funktionen aus wie CRT in C. Einschließlich der Speicherverwaltungsfunktionen. Eine Liste aller Funktionen und Variablen, die in der kompilierten DLL vorhanden sind, und zwar nicht nur die exportierten, sondern alle, erhalten Sie, wenn Sie in den Projekteinstellungen die Linker-Option Map file - Detailed einschalten.
Speicherverwaltung
Das nächste, ziemlich ernste Problem, das oft Schwierigkeiten bereitet, ist die Speicherverwaltung in DLLs. Genauer gesagt verursacht die Speicherverwaltung selbst keine Probleme, aber sobald die DLL versucht, aktiv mit dem vom Speichermanager der Anwendung selbst zugewiesenen Speicher zu arbeiten, beginnen die Probleme in der Regel an dieser Stelle.
Das Problem ist, dass Anwendungen normalerweise mit einem integrierten MemoryManager kompiliert werden. Auch die kompilierte DLL enthält ihren eigenen MemoryManager. Dies gilt insbesondere für Anwendungen und DLLs, die in verschiedenen Programmierumgebungen erstellt wurden. Wie in unserem Fall ist das Terminal in MSVC, die DLL in Delphi. Es ist klar, dass es sich um unterschiedliche Manager handelt, aber gleichzeitig sind es physisch unterschiedliche Manager, die jeweils ihren eigenen Speicher innerhalb des gemeinsamen Adressraums des Prozesses verwalten. Im Prinzip stören sie sich nicht gegenseitig, nehmen sich nicht gegenseitig den Speicher weg, existieren parallel zueinander und "wissen" normalerweise nichts von der Existenz von Konkurrenten. Dies ist möglich, weil beide Manager auf den Speicher aus derselben Quelle, dem Windows-Speichermanager, zugreifen.
Probleme beginnen, wenn eine DLL-Funktion und eine Anwendung versuchen, Speicherbereiche zu verwalten, die von einem anderen Speichermanager verteilt werden. Aus diesem Grund gibt es unter Programmierern eine Faustregel, die besagt, dass der Speicher nicht die Grenzen eines Codemoduls überschreiten sollte.
Das ist eine gute Regel, aber sie ist nicht ganz korrekt. Richtiger wäre es, einfach denselben MemoryManager in der DLL zu verwenden, den auch die Anwendung nutzt. Eigentlich gefällt mir die Idee, den MT4-Speichermanagermit dem Delphi-Speichermanager FastMMzu verbinden , aber das ist keine sehr praktikable Idee. Wie auch immer, die Speicherverwaltung sollte eine Sache sein.
In Delphi ist es möglich, den Standard-Speichermanager durch jeden anderen Speichermanager zu ersetzen, der bestimmte Anforderungen erfüllt. So ist es möglich, die DLL und die Anwendung mit einem einzigen Speichermanager auszustatten, und zwar mit dem MT4-Speichermanager.
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Neuer Artikel Leitfaden zum Schreiben einer DLL für MQL5 in Delphi :
Dieser Beitrag befasst sich mit dem Mechanismus zur Erstellung eines DLL-Moduls mithilfe der beliebten Programmiersprache ObjectPascal innerhalb einer Delphi-Programmierumgebung. Die Inhalte dieses Beitrags richten sich mit der Einbindung äußerer DLL-Module vorrangig an Neueinsteiger in der Programmierung, die mit Problemen kämpfen, die die Grenzen der eingebetteten Programmiersprache MQL5 sprengen.
Autor: Andrey Voytenko