Eine DLL für MQL5 in 10 Minuten (Teil II): Erstellen mit Visual Studio 201

22 Mai 2019, 08:52
Andrei Novichkov
0
144

Einführung

Dieser Artikel wurde als eine Entwicklung von Ideen aus dem zuvor veröffentlichten Artikel zur DLL-Erstellung mit Visual Studio 2005/2008 erstellt. Der ursprüngliche Basisartikel hat seine Relevanz nicht verloren. Daher, wenn Sie an diesem Thema interessiert sind, sollten Sie unbedingt den ersten Artikel lesen. Es ist viel Zeit seitdem vergangen, und Visual Studio 2017 verfügt mittlerweile über eine aktualisierte Oberfläche. Auch der MetaTrader 5 hat neue Funktionen erhalten. Natürlich ist es notwendig, die Informationen zu aktualisieren und einige neue Funktionen zu berücksichtigen. In diesem Artikel werden wir alle Schritte von der Entwicklung des DLL-Projekts in Visual Studio 2017 bis zur Anbindung einer fertigen DLL an das Terminal und deren Nutzung durchführen.

Der Artikel richtet sich an Anfänger, die lernen möchten, wie man C++-Bibliotheken erstellt und mit dem Terminal verbindet.

Warum DLL mit dem Terminal verbinden

Einige Entwickler sind der Meinung, dass keine Bibliotheken mit dem Terminal verbunden werden sollten, da es keine Aufgaben gibt, für die eine solche Verbindung notwendig ist, während die erforderlichen Funktionen mit MQL-Mitteln implementiert werden kann. Diese Meinung gilt bis zu einem gewissen Grad. Es gibt nur sehr wenige Aufgaben, die Bibliotheken erfordern. Die meisten der erforderlichen Aufgaben können mit MQL gelöst werden. Außerdem sollte man beim Anschluss einer Bibliothek verstehen, dass der Expert Advisor oder Indikator, mit dem diese Bibliothek verwendet wird, ohne diese DLL nicht funktionieren kann. Wenn Sie eine solche Anwendung an einen Dritten übertragen müssen, müssen Sie zwei Dateien übertragen, nämlich die Anwendung selbst und die Bibliothek. Manchmal kann dies sehr unangenehm oder sogar unmöglich sein. Ein weiterer Schwachpunkt ist, dass Bibliotheken unsicher sein können und schädlichen Code enthalten können.

Bibliotheken haben jedoch ihre Vorteile, die die Nachteile definitiv überwiegen. Beispiele:

  • Bibliotheken können helfen, die Probleme zu lösen, die nicht mit MQL-Mitteln gelöst werden können. Zum Beispiel, wenn Sie E-Mails mit Anhängen an Mailinglisten senden müssen. DLLs können an Skype schreiben etc.
  • Einige Aufgaben, die in MQL implementiert werden können, können mit den Bibliotheken schneller und effizienter durchgeführt werden. Dazu gehören das Parsen von HTML-Seiten und die Verwendung von regulären Ausdrücken.

Wenn Sie solche komplexen Aufgaben lösen wollen, sollten Sie Ihre Fähigkeiten kennen und richtig lernen, wie man Bibliotheken erstellt und verbindet.

Wir haben die "Vor-" und "Nachteile" der DLL-Nutzung in unseren Projekten berücksichtigt. Betrachten wir nun Schritt für Schritt den Prozess der DLL-Erstellung mit Visual Studio 2017.

Erstellen einer einfachen DLL

Der gesamte Prozess wurde bereits im Originalartikel beschrieben. Nun werden wir ihn unter Berücksichtigung von Software-Updates und -Änderungen wiederholen.

Öffnen Sie Visual Studio 2017 und navigieren Sie zu File -> New -> Project. Erweitern Sie im linken Teil des neuen Projektfensters die Liste Visual C++ und wählen Sie Windows Desktop aus. Wählen Sie die Zeile Windows Desktop Wizard im mittleren Teil aus. Mit Hilfe von Eingabefeldern im unteren Teil können Sie den Projektnamen bearbeiten (es ist ratsam, einen eigenen aussagekräftigen Namen zu vergeben) und den Projektstandort festlegen (es wird empfohlen, ihn wie vorgeschlagen zu behalten). Klicken Sie auf OK und fahren Sie mit dem nächsten Fenster fort:


Wählen Sie Dynamic Link Library (.dll) aus der Dropdown-Liste und aktivieren Sie "Export Symbols". Das Überprüfen dieses Elements ist optional, aber Anfängern wird empfohlen, dies zu tun. In diesem Fall wird den Projektdateien ein Demo-Code hinzugefügt. Dieser Code kann eingesehen und dann gelöscht oder kommentiert werden. Mit einem Klick auf "OK" werden Projektdateien erstellt, die dann bearbeitet werden können. Allerdings müssen wir zunächst die Projekteinstellungen berücksichtigen. Erstens, denken Sie daran, dass MetaTrader 5 nur mit 64-Bit-Bibliotheken funktioniert. Wenn Sie versuchen, eine 32-Bit-DLL zu verbinden, erhalten Sie die folgenden Meldungen:

'E:\...\MQL5\Libraries\Project2.dll' is not 64-bit version
Cannot load 'E:\MetaTrader 5\MQL5\Libraries\Project2.dll' [193]

Daher können Sie solche Bibliothek nicht verwenden.

Eine umgekehrte Einschränkung gilt für MetaTrader 4 DLLs: Nur 32-Bit-Bibliotheken sind erlaubt, während 64-Bit-DLLs nicht verbunden werden können. Berücksichtigen Sie dies und erstellen Sie eine passende Version für Ihre Plattform.

Fahren Sie nun mit den Projekteinstellungen fort. Wählen Sie aus dem Menü "Projekt" "Name Eigenschaften...", wobei "Name" der vom Entwickler bei der Erstellung angegebene Projektname ist. Es öffnet sich ein Fenster mit einer Vielzahl von verschiedenen Einstellungen. Zuerst sollten Sie Unicode aktivieren. Wählen Sie im linken Teil des Fensters "General". Wählen Sie im rechten Teil die Kopfzeile in der ersten Spalte aus: "Character Set". In der zweiten Spalte wird eine Dropdown-Liste angezeigt. Wählen Sie aus dieser Liste "Unicode-Zeichensatz verwenden". In einigen Fällen ist keine Unicode-Unterstützung erforderlich. Wir werden solche Fälle später besprechen.

Eine weitere sehr nützliche (aber nicht notwendige) Änderung der Projekteigenschaften: Kopieren Sie die fertige Bibliothek in den Ordner "Library" des Terminals. Im ursprünglichen Artikel wurde dies durch Ändern des Parameters "Output Directory" erreicht, der sich im gleichen Fenster wie das Element "General" des Projekts befindet. Dies ist in Visual Studio 2017 nicht erforderlich. Ändern Sie diesen Parameter nicht. Achten Sie jedoch auf den Punkt "Build Events": Sie sollten das Unterelement "Post Build Events" auswählen. Der Parameter "Command Line" erscheint in der ersten Spalte des rechten Fensters. Wählen Sie diese Option, um eine bearbeitbare Liste in der zweiten Spalte zu öffnen. Dies sollte eine Liste von Aktionen sein, die Visual Studio 2017 nach dem Erstellen der Bibliothek ausführen wird. Füge die folgende Zeile zu dieser Liste hinzu:

xcopy "$(TargetDir)$(TargetFileName)" "E:\...\MQL5\Libraries\" /s /i /y

Hier sollten Sie anstelle von ... den vollständigen Pfad zum entsprechenden Terminalordner angeben. Nach erfolgreicher Bibliothekserstellung wird Ihre DLL in den angegebenen Ordner kopiert. Alle Dateien im "Output Directory" bleiben in diesem Fall erhalten, was für die weitere versionsgeführte Entwicklung wichtig sein kann.

Der letzte und sehr wichtige Projektaufbauschritt ist der folgende. Stellen Sie sich vor, die Bibliothek ist bereits aufgebaut und enthält eine Funktion, die vom Terminal genutzt werden kann. Angenommen, diese Funktion hat den folgenden einfachen Prototyp:

int fnExport(wchar_t* t);
Diese Funktion kann wie folgt aus dem Terminal aufgerufen werden:
#import "Project2.dll"
int fnExport(string str);
#import

In diesem Fall wird jedoch die folgende Fehlermeldung ausgegeben:

Wie kann man diese Situation lösen? Bei der Generierung von Bibliothekscode hat Visual Studio 2017 das folgende Makro gebildet:

#ifdef PROJECT2_EXPORTS
#define PROJECT2_API __declspec(dllexport)
#else
#define PROJECT2_API __declspec(dllimport)
#endif

Der vollständige Prototyp der gewünschten Funktion sieht wie folgt aus:

PROJECT2_API int fnExport(wchar_t* t);

Betrachten Sie die Exporttabelle nach der Bibliothekskompilierung:


Um sie anzuzeigen, wählen Sie die Bibliotheksdatei im Fenster Total Commander aus und drücken Sie F3. Achten Sie auf den Namen der exportierten Funktion. Lassen Sie uns nun das obige Makro bearbeiten (so wurde es im Originalartikel gemacht):

#ifdef PROJECT2_EXPORTS
#define PROJECT2_API extern "C" __declspec(dllexport)
#else
#define PROJECT2_API __declspec(dllimport)
#endif

Hier

extern "C"

bedeutet die Verwendung einer einfachen Funktionssignaturgenerierung (im C-Sprachenstil) beim Empfangen von Objektdateien. Dies verbietet es dem C++-Compiler insbesondere, den Funktionsnamen beim Export in eine DLL mit zusätzlichen Zeichen zu "dekorieren". Neu kompilieren und die Exporttabelle anzeigen:

Änderungen in der Exporttabelle sind offensichtlich und es tritt nun kein Fehler beim Aufruf der Funktion aus dem Skript auf. Die Methode hat jedoch einen Nachteil: Sie müssen das Skript bearbeiten, das vom Compiler erstellt wurde. Es gibt einen sichereren Weg, dasselbe zu tun, der jedoch etwas länger ist:

Definitionsdatei

Dies ist eine reine Textdatei mit der Erweiterung .def, normalerweise mit einem Namen, der dem Projektnamen entspricht. In unserem Fall ist dies die Datei Project2.def. Die Datei wird in dem normalen Notepad erstellt. Verwenden Sie niemals Word oder ähnliche Editoren. Der Inhalt der Datei sieht wie folgt aus:

; PROJECT2.def : Declares the module parameters for the DLL.

LIBRARY      "PROJECT2"
DESCRIPTION  'PROJECT2 Windows Dynamic Link Library'

EXPORTS
    ; Explicit exports can go here
        fnExport @1
        fnExport2 @2
        fnExport3 @3
        ....

Nach der Kopfzeile folgt die Liste der exportierten Funktionen. Die Zeichen @1, @2 usw. geben die gewünschte Reihenfolge der Funktionen in der Bibliothek an. Speichern Sie diese Datei im Projektordner.

Nun erstellen wir diese Datei und verbinden uns mit dem Projekt. Wählen Sie im linken Teil des Fensters der Projekteigenschaften das Element "Linker" und sein Sub-Element "Input" aus. Wählen Sie dann im rechten Teil den Parameter "Module Definition File". Wie in früheren Fällen erhalten Sie Zugriff auf die editierbare Liste und fügen den Dateinamen "Project2.def" hinzu. Klicken Sie auf OK und wiederholen Sie die Kompilierung. Das Ergebnis ist das gleiche wie im vorherigen Screenshot. Der Name ist nicht dekoriert und es werden keine Fehler beim Aufruf der Funktion durch das Skript festgestellt. Wir haben die Projekteinstellungen analysiert. Nun fangen wir an, den Bibliothekscode zu schreiben.

Erstellen einer Bibliothek und DllMain

Der Originalartikel bietet eine umfassende Beschreibung der Probleme im Zusammenhang mit dem Datenaustausch und verschiedenen Funktionsaufrufen aus DLLs, auf die wir daher nicht näher eingehen werden. Lassen Sie uns einen einfachen Code in der Bibliothek erstellen, um einige spezifische Funktionen anzuzeigen:

1. Hinzufügen der folgenden zu exportierenden Funktion (die Änderung der Definitionsdatei nicht vergessen):

PROJECT2_API int fnExport1(void) {
        return GetSomeParam();
}

2. Erstellen und Hinzufügen der Header-Datei Header1.h zum Projekt und eine eigene, weitere Funktion:

const int GetSomeParam();
3. Editieren der Datei dllmain.cpp:
#include "stdafx.h"
#include "Header1.h"

int iParam;

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
                iParam = 7;
                break;
    case DLL_THREAD_ATTACH:
                iParam += 1;
                break;
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

const int GetSomeParam() {
        return iParam;
}

Der Zweck des Codes sollte klar sein: Eine Variable wird der Bibliothek hinzugefügt. Ihr Wert wird in der Funktion DllMain berechnet und ist über die Funktion fnExport1 verfügbar. Rufen wir die Funktion im Skript auf:

#import "Project2.dll"
int fnExport1(void);
#import
...
void OnStart() {
Print("fnExport1: ",fnExport1() );

Der folgende Eintrag wird ausgegeben:

fnExport1: 7

Das bedeutet, dass dieser Teil des Codes von DllMain nicht ausgeführt wird:

    case DLL_THREAD_ATTACH:
                iParam += 1;
                break;

Ist das wichtig? Meiner Meinung nach ist es entscheidend, denn wenn ein Entwickler hier einen Teil des Bibliotheksinitialisierungscodes hinzufügt und erwartet, dass er beim Verbinden der Bibliothek mit dem Stream ausgeführt wird, wird der Vorgang fehlschlagen. Es wird jedoch kein Fehler zurückgegeben und somit ist es schwierig, das Problem zu erkennen.

Strings

Die Verwendung von Zeichenketten ist im Originalartikel beschrieben. Diese Arbeit ist nicht schwierig. Ich möchte jedoch den folgenden besonderen Punkt klarstellen.

Lassen Sie uns eine einfache Funktion in der Bibliothek erstellen (und die Definitionsdatei bearbeiten):

PROJECT2_API void SamplesW(wchar_t* pChar) {
        size_t len = wcslen(pChar);
        wcscpy_s(pChar + len, 255, L" Hello from C++");
}
Aufrufen der Funktion im Skript:
#import "Project2.dll"
void SamplesW(string& pChar);
#import

void OnStart() {

string t = "Hello from MQL5";
SamplesW(t);
Print("SamplesW(): ", t);

Wir erhalten folgende erwartete Nachricht:

SamplesW(): Hello from MQL5 Hello from C++

Bearbeiten wir nun den Funktionsaufruf:

#import "Project2.dll"
void SamplesW(string& pChar);
#import

void OnStart() {

string t;
SamplesW(t);
Print("SamplesW(): ", t);

Diesmal erhalten wir eine Fehlermeldung:

Access violation at 0x00007FF96B322B1F read to 0x0000000000000008

Initialisieren wir die Zeichenkette, die der Bibliotheksfunktion übergeben wird, und wiederholen die Skriptausführung:

string t="";

Es wird keine Fehlermeldung empfangen, so dass wir die erwartete Ausgabe erhalten:

SamplesW():  Hello from C++

Der obige Code schlägt folgendes vor: Die Zeichenketten, die von der Bibliothek den exportierten Funktionen übergeben werden, müssen initialisiert werden!

Es ist an der Zeit, wieder auf die Verwendung von Unicode zurückzukommen. Wenn Sie nicht planen, Zeichenketten an die DLL zu übergeben (wie im letzten Beispiel gezeigt), dann ist Unicode-Unterstützung nicht erforderlich. Ich empfehle jedoch, die Unterstützung von Unicode in jedem Fall zu aktivieren, da sich exportierte Funktionssignaturen ändern können, neue Funktionen hinzugefügt werden können und der Entwickler das Fehlen einer Unterstützung von Unicode vergessen kann.

Symbol-Arrays werden auf eine gemeinsame Weise übergeben und empfangen, die im Originalartikel beschrieben wurde. Deshalb brauchen wir sie nicht noch einmal zu diskutieren.

Strukturen

Definieren wir die einfachste Struktur in der Bibliothek und im Skript:

//In der dll:
typedef struct E_STRUCT {
        int val1;
        int val2;
}ESTRUCT, *PESTRUCT;

//Im MQL-Skript:
struct ESTRUCT {
   int val1;
   int val2;
};

Hinzufügen einer Funktion zur Bibliothek für die Bearbeitung von Strukturen:

PROJECT2_API void SamplesStruct(PESTRUCT s) {
        int t;
        t = s->val2;
        s->val2 = s->val1;
        s->val1 = t;
}

Wie aus dem Code ersichtlich, vertauscht die Funktion einfach nur ihre eigenen Felder.

Rufen wir diese Funktion aus dem Skript auf:

#import "Project2.dll"
void SamplesStruct(ESTRUCT& s);
#import
....
ESTRUCT e;
e.val1 = 1;
e.val2 = 2;
SamplesStruct(e);
Print("SamplesStruct: val1: ",e.val1," val2: ",e.val2);

Führen Sie das Skript aus und Sie erhalten das erwartete Ergebnis:

SamplesStruct: val1: 2 val2: 1

Das Objekt wurde per Referenz an die aufgerufene Funktion übergeben. Die Funktion hat das Objekt bearbeitet und an den aufrufenden Code zurückgegeben.

Allerdings brauchen wir oft komplexere Strukturen. Lassen Sie uns die Aufgabe verkomplizieren: Fügen wir ein weiteres Feld mit einem anderen Typ zur Struktur hinzu:

typedef struct E_STRUCT1 {
        int val1;
        char cval;
        int val2;
}ESTRUCT1, *PESTRUCT1;

Fügen wir auch eine Funktion hinzu, um damit zu arbeiten:

PROJECT2_API void SamplesStruct1(PESTRUCT1 s) {
        int t;
        t = s->val2;
        s->val2 = s->val1;
        s->val1 = t;
        s->cval = 'A';
}

Wie im vorherigen Fall vertauscht die Funktion ihre Felder vom Typ int und weist einem Feld vom Typ 'char' einen Wert zu. Rufen Sie diese Funktion im Skript auf (genau wie bei der vorherigen Funktion). Diesmal ist das Ergebnis jedoch wie folgt:

SamplesStruct1: val1: -2144992512 cval: A val2: 33554435

Die Strukturfelder vom Typ int enthalten falsche Daten. Es handelt sich nicht um eine Ausnahme, sondern um zufällige, falsche Daten. Was ist passiert? Der Grund liegt im Alignment (Ausrichtung)! Die Ausrichtung ist kein sehr kompliziertes Konzept. Der Dokumentenabschnitt pack zu Strukturen bietet eine detaillierte Beschreibung der Ausrichtung. Visual Studio C++ bietet auch umfangreiche Materialien zum Thema Ausrichtung.

In unserem Beispiel ist der Fehler aufgetreten, weil die Bibliothek und das Skript unterschiedliche Ausrichtungen haben. Es gibt zwei Möglichkeiten, das Problem zu lösen:

  1. Geben Sie eine neue Ausrichtung im Skript an. Dies kann mit dem Attribut pack(n) geschehen. Versuchen wir, die Struktur nach dem größten Feld auszurichten, d.h. int:
    struct ESTRUCT1 pack(sizeof(int)){
            int val1;
            char cval;
            int val2;
    };
    
    Wiederholen wir die Skriptausführung. Der Ausdruck im Log hat sich geändert: SamplesStruct1: val1: 3 cval: A val2: 2. Damit ist der Fehler behoben.

  2. Geben Sie eine neue Ausrichtung der Bibliothek an. Die Standardausrichtung von MQL-Strukturen ist pack(1). Wenden Sie dasselbe auf die Bibliothek an:
    #pragma pack(1)
    typedef struct E_STRUCT1 {
            int val1;
            char cval;
            int val2;
    }ESTRUCT1, *PESTRUCT1;
    #pragma pack()
    
    Erstellen wir die Bibliothek und führen dann das Skript aus: Das Ergebnis ist korrekt und dasselbe wie bei der ersten Methode.
Überprüfen wir noch eine weitere Sache. Was passiert, wenn eine Struktur neben den Datenfeldern auch Methoden enthält? Das ist durchaus möglich. Auch Programmierer können einen Konstruktor (der keine Methode ist), einen Destruktor oder etwas anderes hinzufügen. Lassen Sie uns diese Fälle in der folgenden Bibliotheksstruktur überprüfen:
#pragma pack(1)
typedef struct E_STRUCT2 {
        E_STRUCT2() {
                val2 = 15;
        }
        int val1;
        char cval;
        int val2;
}ESTRUCT2, *PESTRUCT2;
#pragma pack()
Die Struktur wird von der folgenden Funktion verwendet:
PROJECT2_API void SamplesStruct2(PESTRUCT2 s) {
        int t;
        t = s->val2;
        s->val2 = s->val1;
        s->val1 = t;
        s->cval = 'B';
}
Machen wir die entsprechenden Änderungen im Skript:
struct ESTRUCT2 pack(1){
        ESTRUCT2 () {
           val1 = -1;
           val2 = 10;
        }
        int val1;
        char cval;
        int f() { int val3 = val1 + val2; return (val3);}
        int val2;
};

#import "Project2.dll" 
void SamplesStruct2(ESTRUCT2& s); 
#import
...
ESTRUCT2 e2;
e2.val1 = 4;
e2.val2 = 5;
SamplesStruct2(e2);
t = CharToString(e2.cval);
Print("SamplesStruct2: val1: ",e2.val1," cval: ",t," val2: ",e2.val2);

Beachten Sie, dass die Methode f() der Struktur hinzugefügt wurde, wodurch mehr Unterschiede zur Struktur in der Bibliothek entstehen. Starten wir das Skript. Der folgende Eintrag wird in das Journal geschrieben: SamplesStruct2:  val1: 5 cval: B val2: 4 Die Ausführung ist korrekt! Die Anwesenheit eines Konstruktors und einer zusätzlichen Methode in unserer Struktur hatte keinen Einfluss auf das Ergebnis.

Das letzte Experiment. Entfernen wir den Konstruktor und die Methode aus der Struktur im Skript, so dass nur die Datenfelder übrig bleiben. Die Struktur in der Bibliothek bleibt unverändert. Auch hier erzeugt die Skriptausführung ein korrektes Ergebnis. Dies ermöglicht es, eine endgültige Schlussfolgerung zu ziehen: Das Vorhandensein zusätzlicher Methoden in Strukturen hat keinen Einfluss auf das Ergebnis.

Dieses Bibliotheksprojekt für Visual Studio 2017 und ein MetaTrader 5-Skript sind unten angehängt.

Was Sie nicht erreichen können

Es gibt bestimmte Einschränkungen für den Betrieb mit DLLs, die in der zugehörigen Dokumentation beschrieben sind. Wir werden sie hier nicht wiederholen. Hier ist ein Beispiel:

struct BAD_STRUCT {
   string simple_str;
};

Diese Struktur kann nicht an eine DLL übergeben werden. Dies ist eine Zeichenkette, die in eine Struktur eingebettet ist. Komplexere Objekte können nicht ohne Ausnahme an eine DLL übergeben werden.

Was tun, wenn etwas nicht möglich ist?

Oft müssen wir an DLLs die Objekte übergeben, die nicht erlaubt sind. Dazu gehören Strukturen mit dynamischen Objekten, Zahnradarrays etc. Was kann man in diesem Fall tun? Ohne Zugriff auf den Bibliothekscode kann diese Lösung nicht verwendet werden. Der Zugriff auf den Code kann zur Lösung des Problems beitragen.

Wir werden Änderungen im Datendesign nicht berücksichtigen, da wir versuchen sollten, es mit den verfügbaren Mitteln zu lösen und eine Ausnahme zu vermeiden. Eine gewisse Klärung ist erforderlich. Der Artikel ist nicht für erfahrene Anwender gedacht, daher werden wir nur mögliche Lösungen für das Problem aufzeigen.

  1. Die Verwendung der Funktion StructToCharArray(). Dies scheint eine gute Gelegenheit zu sein, die die Verwendung des folgenden Codes im Skript ermöglicht:
    struct Str 
      {
         ...
      };
    
    Str s;
    uchar ch[];
    StructToCharArray(s,ch);
    
    SomeExportFunc(ch);
    
    Der Code in der cpp-Bibliotheksdatei:
    #pragma pack(1)
    typedef struct D_a {
    ...
    }Da, *PDa;
    #pragma pack()
    
    void SomeExportFunc(char* pA)
      {
            PDa = (PDa)pA;
            ......
      }
    
    Neben Sicherheits- und Qualitätsfragen ist die Idee selbst nutzlos: StructToCharArray() funktioniert nur mit POD-Strukturen, die ohne zusätzliche Konvertierungen an Bibliotheken übergeben werden können. Ich habe diese Funktionsweise nicht mit dem aktuellen Code getestet.

  2. Erstellen Sie Ihren eigenen Packer/Entpacker von Strukturen in ein Objekt, das an die Bibliothek übergeben werden kann. Diese Methode ist möglich, aber sehr kompliziert und ressourcen- und zeitintensiv. Diese Methode schlägt jedoch eine völlig akzeptable Lösung vor:

  3. Alle Objekte, die nicht direkt an die Bibliothek übergeben werden können, sollten in einen JSON-String im Skript gepackt und in Strukturen in der Bibliothek entpackt werden. Und umgekehrt. Dafür stehen Werkzeuge zur Verfügung: Parser für JSON sind verfügbar für C++, C# und für MQL. Diese Methode kann verwendet werden, wenn Sie bereit sind, einige Zeit für das Ein- und Auspacken der Objekte aufzuwenden. Neben den offensichtlichen Zeitverlusten gibt es jedoch auch Vorteile. Das Verfahren ermöglicht das Arbeiten mit sehr komplexen Strukturen (und anderen Objekten). Außerdem können Sie bestehende Packer/Entpacker verfeinern, anstatt sie von Grund auf neu zu schreiben.

Beachten Sie also, dass es eine Möglichkeit gibt, ein komplexes Objekt an die Bibliothek zu übergeben (und zu empfangen).

Praktische Verwendung

Lassen Sie uns nun versuchen, eine nützliche Bibliothek zu erstellen. Diese Bibliothek sendet E-Mails. Bitte beachten Sie die folgenden Momente:

  • Die Bibliothek kann nicht für Spam-Mails verwendet werden.
  • Die Bibliothek kann E-Mails von der Adresse und dem Server senden, die nicht in den Terminaleinstellungen angegeben sind. Darüber hinaus kann die Verwendung von E-Mails in den Terminaleinstellungen deaktiviert werden, was jedoch den Bibliotheksbetrieb nicht beeinträchtigt.

Und die letzte Sache. Der größte Teil des C++-Codes gehört nicht mir, sondern wurde aus den Microsoft-Foren heruntergeladen. Dies ist ein altes und bewährtes Beispiel, seine Varianten sind auch auf VBS verfügbar.

Fangen wir an. Erstellen wir ein Projekt in Visual Studio 2017 und ändern dessen Einstellungen wie am Anfang des Artikels beschrieben. Erstellen wir eine Definitionsdatei und verbinden sie mit dem Projekt. Es gibt nur eine exportierte Funktion:

SENDSOMEMAIL_API bool  SendSomeMail(LPCWSTR addr_from,
        LPCWSTR addr_to,
        LPCWSTR subject,
        LPCWSTR text_body,

        LPCWSTR smtp_server,
        LPCWSTR smtp_user,
        LPCWSTR smtp_password);

Die Bedeutung ihrer Argumente ist klar, daher hier eine kurze Erklärung:

  • addr_from, addr_to — Absender- und Empfänger-E-Mail-Adressen.
  • Betreff, Text_Body — Betreff und E-Mail-Body.
  • smtp_server, smtp_user, smtp_password — die SMTP-Serveradresse, die Benutzeranmeldung und das Passwort für den Server.

Achten Sie auf die folgenden Aspekte:

  • Wie aus der Beschreibung der Argumente hervorgeht, müssen Sie zum Senden von E-Mails ein Konto auf dem Mailserver haben und dessen Adresse kennen. Daher darf der Absender nicht anonym sein.
  • Die Portnummer ist in der Bibliothek fest kodiert. Dies ist die Standard-Portnummer 25.
  • Die Bibliothek erhält die erforderlichen Daten, verbindet sich mit dem Server und sendet ihm die E-Mails. In einem Anruf kann eine E-Mail nur an eine Adresse gesendet werden. Um mehr zu senden, wiederholen Sie den Funktionsaufruf mit der neuen Adresse.

Ich werde den C++-Code hier nicht angeben. Dieser Code sowie das gesamte Projekt sind unten im angehängten Projekt SendSomeMail.zip verfügbar. Das verwendete Objekt CDO hat viele Funktionen und sollte für die Weiterentwicklung und Verbesserung der Bibliothek verwendet werden.

Zusätzlich zu diesem Projekt schreiben wir ein einfaches Skript, um die Bibliotheksfunktion aufzurufen (es befindet sich in der angehängten SendSomeMail.mq5-Datei):

#import "SendSomeMail.dll"
bool  SendSomeMail(string addr_from,string addr_to,string subject,string text_body,string smtp_server,string smtp_user,string smtp_password);
#import

//+------------------------------------------------------------------+
//| Script Programm Start Funktion                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
   bool b = SendSomeMail("XXX@XXX.XX", "XXXXXX@XXXXX.XX", "hello", "hello from me to you","smtp.XXX.XX", "XXXX@XXXX.XXX", "XXXXXXXXX");
   Print("Send mail: ", b);
   
  }

Fügen Sie Ihre eigenen Kontodaten anstelle der X-Zeichen hinzu. Damit ist die Entwicklung abgeschlossen. Fügen Sie Ihre eigenen Daten hinzu, ergänzen Sie den Code, den Sie benötigen, und die Bibliothek ist einsatzbereit.

Schlussfolgerung

Mit dem Originalartikel und unter Berücksichtigung der in diesem Artikel enthaltenen Updates kann jeder schnell die Grundlagen erlernen und zu komplexeren und interessanteren Projekten übergehen.

Ich möchte noch auf eine weitere interessante Tatsache eingehen, die in bestimmten Situationen sehr wichtig sein kann. Wie kann man den DLL-Code schützen? Die Standardlösung ist der Einsatz eines Packers. Es gibt viele verschiedene Packer, von denen viele ein gutes Schutzniveau bieten können. Ich habe zwei Packer: Themida 2.4.6.0 und VMProtect Ultimate v. 3.0.9. Lassen Sie uns damit unsere erste einfache Project2.dll in zwei Varianten für jeden Packer packen. Rufen Sie anschließend die exportierten Funktionen im Terminal mit dem vorhandenen Skript auf. Alles funktioniert einwandfrei! Das Terminal kann mit solchen Bibliotheken arbeiten. Der normale Betrieb von Bibliotheken, die durch andere Packer geschützt sind, ist jedoch nicht gewährleistet. Die Project2.dll, gepackt durch zwei Methoden, ist in Project2_Pack.zip verfügbar.

Das war's dann auch schon. Viel Glück bei der Weiterentwicklung.

Programme, die im diesem Artikel verwendet werden

 # Name
Typ
 Beschreibung
1 Project2.zip Archive
Einfaches DLL-Projekt
2
Project2.mq5
Skript
Skript für die Verwendung einer DLL
3 SendSomeMail.zip Archive Projekt-DLL zum Senden von E-Mails.
4 SendSomeMail.mq5 Skript
Das Skript für die Arbeit mit der Dll-Bibliothek SendSomeMail
5 Project2_Pack.zip Archive Die Project2.dll, gepackt von Themida und VMProtect.




Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/5798

Beigefügte Dateien |
Project2.mq5 (3.42 KB)
SendSomeMail.mq5 (1.15 KB)
SendSomeMail.zip (16.62 KB)
Project2_Pack.zip (4645.25 KB)
Project2.zip (18.65 KB)
Untersuchung von Techniken zur Analyse der Kerzen (Teil IV): Aktualisierungen und Ergänzungen des Pattern Analyzers Untersuchung von Techniken zur Analyse der Kerzen (Teil IV): Aktualisierungen und Ergänzungen des Pattern Analyzers

Der Artikel stellt eine neue Version des Pattern Analyzers vor. Diese Version enthält Bugfixes und neue Funktionen sowie eine überarbeitete Benutzeroberfläche. Kommentare und Anregungen aus dem vorherigen Artikel wurden bei der Entwicklung der neuen Version berücksichtigt. Die resultierende Anwendung wird in diesem Artikel beschrieben.

Bondrenditen aus dem Web kratzen Bondrenditen aus dem Web kratzen

Automatisiertes Erfassen von Zinsdaten, um die Leistung eines Expert Advisors zu verbessern.

Wie man den Handelsverlauf mehrerer Währungen basierend auf HTML- und CSV-Berichten visualisiert. Wie man den Handelsverlauf mehrerer Währungen basierend auf HTML- und CSV-Berichten visualisiert.

Seit seiner Einführung bietet MetaTrader 5 die Möglichkeit, mehrere Währungen zu testen. Diese Möglichkeit wird von Händlern oft genutzt. Die Funktion ist jedoch nicht universell einsetzbar. Der Artikel stellt mehrere Programme zum Zeichnen von grafischen Objekten in Diagrammen vor, die auf den Berichten der Handelshistorie im HTML- und CSV-Format basieren. Der Mehrwährungshandel kann parallel, in mehreren Unterfenstern sowie in einem Fenster mit dem dynamischen Schaltbefehl analysiert werden.

Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil IV). Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil IV).

In den vorherigen Artikeln haben wir begonnen, eine große plattformübergreifende Bibliothek zu erstellen, die die Entwicklung von Programmen für MetaTrader 5 und MetaTrader 4 Plattformen vereinfacht. Wir verfügen bereits über Sammlungen historischer Orders und Deals, Market Orders und Positionen sowie über die Klasse zur komfortablen Auswahl und Sortierung der Aufträge. In diesem Teil werden wir die Entwicklung des Basisobjekts fortsetzen und die Engine Library lehren, Handelsereignisse auf dem Konto zu verfolgen.