La fonction de chaîne DLL ne fonctionne pas sur le Build 600

 

Salut les codeurs !

J'ai écrit une fonction DLL en Delphi. Ce code renvoie seulement un texte PChar à l'ex4.

ibrary get_text;

uses
  SysUtils,
  Math,
  idHttp;

{$R *.res}

var

function get_text(): PChar; stdcall;
begin
  result := PChar('Hello world!');
end;


exports
  get_text;

begin
end.

Mon code MQ4 :

#import "get_text.dll"
   string get_text();
#import

int init(){

   Print("DLL says: ",get_text());

}

Cette solution fonctionne sur Build 509, mais sur Build 600. Sur Build 600, la sortie Experts est : "DLL says : ???????????x ??????????????".

Pourquoi ne fonctionne-t-il pas sur B600 ? Avez-vous une idée pour faire fonctionner mon code correctement ?

Merci d'avance.

Relative

 
Relative:

Cette solution fonctionne sur la Build 509, mais sur la Build 600. Sur la version 600, la sortie des experts est la suivante : "DLL says : ???????????x ??????????????".

La v600 utilise des chaînes Unicode, pas des chaînes Ansi. Soit vous devez renvoyer une chaîne Unicode à partir de votre DLL (voie la plus facile), soit vous devez faire des appels assez complexes dans le code MQL4 pour manipuler la valeur de retour de la chaîne (voie la plus difficile).

Dans tous les cas, cela va provoquer une fuite de mémoire car vous allouez un bloc de mémoire pour la chaîne de caractères que MT4 ne sait pas comment libérer et ne libérera pas. Il est préférable de transmettre un tampon à la DLL, et de faire en sorte que la DLL copie la chaîne dans ce tampon. En d'autres termes, la meilleure façon de renvoyer des valeurs de chaîne à MQL4 est d'utiliser le même type de méthode que les appels de l'API Windows tels que GetWindowsDirectory() et GetTempPath().

 
gchrmt4:

soit vous devez faire des appels assez complexes dans le code MQL4 pour manipuler la valeur de retour de la chaîne (voie la plus difficile).

Pour mémoire, il est toujours possible d'utiliser des DLL qui renvoient des chaînes Ansi ; c'est une sorte de suivi de https://www.mql5.com/en/forum/149321. Le problème est que le retour de chaînes de caractères à partir de DLL entraîne une fuite de mémoire, à moins que le code MQL4 ne sache comment la mémoire a été allouée et ne soit capable de la libérer. L'exemple suivant suppose que la mémoire de la chaîne a été allouée dans la DLL en utilisant LocalAlloc(), ou un équivalent indirect qui correspond à LocalAlloc().

#import "SomeDllWhichReturnsAnsiStrings.dll"
   // Declare the Ansi function as returning int rather than string 
   int Test();  // NOT  string Test();
#import

#import "kernel32.dll"
   int lstrlenA(int);
   void RtlMoveMemory(uchar & arr[], int, int);
   int LocalFree(int); // May need to be changed depending on how the DLL allocates memory
#import

void OnStart()
{
   // Call the DLL function and get its block of string memory as an int pointer to the
   // memory rather than as a string 
   int ptrStringMemory = Test();
   
   // Get the length of the string 
   int szString = lstrlenA(ptrStringMemory);
   
   // Create a uchar[] array whose size is the string length (plus null terminator)
   uchar ucValue[];
   ArrayResize(ucValue, szString + 1);
   
   // Use the Win32 API to copy the string from the block returned by the DLL
   // into the uchar[] array
   RtlMoveMemory(ucValue, ptrStringMemory, szString + 1);
   
   // Convert the uchar[] array to a MQL string
   string strValue = CharArrayToString(ucValue);

   // Free the string memory returned by the DLL. This step can be removed but, without it,
   // there will be a memory leak.
   // The correct method for freeing the string *depends on how the DLL allocated the memory*
   // The following assumes that the DLL has used LocalAlloc (or an indirect equivalent). If not,
   // then the following line may not fix the leak, and may even cause a crash.
   LocalFree(ptrStringMemory);   
   
   // Done...
   Print(strValue);
}
 
Merci gchrmt4 !
 

gchrmt4
:

Pour mémoire, il est toujours possible d'utiliser des DLL qui renvoient des chaînes Ansi ; c'est une sorte de suivi de https://www.mql5.com/en/forum/149321. Le problème est que le fait de renvoyer des chaînes à partir de DLL entraîne une fuite de mémoire, à moins que le code MQL4 ne sache comment la mémoire a été allouée et ne soit capable de la libérer. L'exemple suivant suppose que la mémoire de la chaîne a été allouée dans la DLL en utilisant LocalAlloc(), ou un équivalent indirect qui correspond à LocalAlloc().

Merci ! Cela a également fonctionné pour ma libmysql.dll. Mon MQL4 ne peut plus parler à MySQL depuis la version 600.

Enfin, je peux au moins voir ce que la dll MySql retourne...

Pourriez-vous nous donner un indice sur la façon d'implémenter la communication inverse, c'est-à-dire de passer une chaîne de caractères de MQL4 (build 600) à une dll qui ne supporte que l'ANSI ? J'ai essayé la fonction UNICODE2ANSI() trouvée ici et ici mais cela ne fonctionne pas pour moi malheureusement.
 
lukins:

Pourriez-vous nous donner un indice sur la manière d'implémenter la communication inverse, c'est-à-dire de passer une chaîne de caractères de MQL4 (build 600) à une dll qui ne supporte que l'ANSI ?

C'est plus loin sur la même page ! Voir https://www.mql5.com/en/forum/149321
 
gchrmt4:
C'est plus bas sur la même page ! Voir https://www.mql5.com/en/forum/149321


Merci ! Cela fonctionne parfaitement pour les valeurs de chaîne simples. Au moins, MQL se connecte à MySQL maintenant.

Cependant, il semble que je ne puisse pas obtenir un tableau de chaînes de caractères à partir d'une dll. Voici la fonction qui m'est fournie (il s'agit d'un wrapper MySql MQL4) :

#import "mysql_wrapper.dll"
void     MT4_mysql_fetch_row   (int result, string& row[]);

Actuellement, je reçois l'erreur "Access violation read to 0x0..." lorsque je passe le tableau de chaînes de caractères habituel de MQL.

Toute aide est la bienvenue.

 
Une erreur similaire (violation d'accès) se produit lorsque je passe "int & row[]" et que j'essaie de convertir chaque élément qu'il contient après qu'il ait été rempli.
 
lukins:

Cependant, il semble que je ne puisse pas obtenir un tableau de chaînes de caractères à partir d'une dll. Voici la fonction qui m'est fournie (il s'agit d'un wrapper MySql MQL4) :

Simuler les anciens tableaux de chaînes Ansi est un peu compliqué, mais c'est possible. (Cela va dépendre du bon comportement de la DLL, en particulier si elle renvoie des données à MQL4 en modifiant le contenu du tableau. Je n'ai testé cela que contre l'exemple de code C++ en bas de page, pas contre quelque chose de plus réaliste comme la bibliothèque MySql).

#import "SomeDllWhichTakesAStringArray.dll"
   // Old Ansi function which takes a string array plus a parameter telling it 
   // the size of the array
   void Test(int & arr[], int);  // NOT  void Test(string & arr[], int)
#import

#import "kernel32.dll"
   int LocalAlloc(int,int);
   int LocalFree(int);
   int lstrcpyA(int,uchar & arr[]);
   int lstrlenA(int);
   void RtlMoveMemory(uchar & arr[], int, int);
#import

void OnStart()
{
   // Example array of strings...
   string MyStringArray[] = {"String 1", "String 2"};
   int SizeOfStringArray = ArraySize(MyStringArray);
   
   
   
   // A string array (received by the DLL as an array of MqlStr structures) corresponds
   // to an int array where the even-numbered members are string lengths, and the odd-numbered
   // members are pointers to string memory.
   // We start by creating an int array, which needs to have twice as many members as the
   // string array
   int i;
   int arrMqlStr[];
   ArrayResize(arrMqlStr, SizeOfStringArray * 2);
   
   // Populate the array which simulates MqlStr[]. For each string, we allocate 
   // a block of memory using LocalAlloc() and copy the MQL4 string into that
   for (i = 0; i < SizeOfStringArray; i++) {
      // Get the length of the string and store it
      int szString = StringLen(MyStringArray[i]);
      arrMqlStr[i * 2] = szString;
   
      // Allocate a block of memory to hold the string 
      int ptrMem = LocalAlloc(0x40 /* LPTR */, szString + 1);
      arrMqlStr[(i * 2) + 1] = ptrMem;

      // Convert the MQL4 string to a uchar[] array
      uchar ucString[];
      StringToCharArray(MyStringArray[i], ucString);
 
      // Copy the uchar[] array into the block of memory allocated above
      lstrcpyA(ptrMem, ucString);     
   }

   // Call the DLL function
   Test(arrMqlStr, SizeOfStringArray);

   // We now need to free the memory which was allocated above to hold
   // a copy of each string. In addition, DLLs can alter strings which
   // are passed to them in arrays. Therefore, for completeness, we
   // should read the strings back and put them into the original
   // array
   for (i = 0; i < SizeOfStringArray; i++) {
      // Get the size of the string now contained in the memory block
      int NewSizeofString = lstrlenA(arrMqlStr[(i * 2) + 1]);
      
      // Copy the contents of the memory block into a uchar[] array
      uchar ucReceive[];
      ArrayResize(ucReceive, NewSizeofString + 1);
      RtlMoveMemory(ucReceive, arrMqlStr[(i * 2) + 1], NewSizeofString + 1);

      // Put the uchar[] back into the original string array
      MyStringArray[i] = CharArrayToString(ucReceive);
      
      // Free the memory for the string allocated above
      LocalFree(arrMqlStr[(i * 2) + 1]);
   }
}

Par exemple, le code ci-dessus fonctionne avec la DLL suivante qui fait une boîte de message pour chaque chaîne dans un tableau et ensuite inverse la chaîne avant de retourner à MT4 :

__declspec(dllexport) void WINAPI Test(MqlStr * arr, int sz)
{
        for (int i = 0; i < sz; i++) {
                MessageBoxA(NULL, arr->data, "String", 48);
                strrev(arr->data);
                arr++;
        }
}
 
gchrmt4:

La simulation des anciens tableaux de chaînes de caractères Ansi est compliquée, mais toujours possible.

(Le code ci-dessus peut être simplifié si le MQL4 passe le tableau de chaînes à la DLL uniquement pour lui donner un tampon de chaînes dans lequel la DLL peut écrire. Le code ci-dessus devrait gérer ce scénario, mais il est inutilement compliqué pour un appel en réception seulement à une DLL).
 
gchrmt4:
(Le code ci-dessus peut être simplifié si le MQL4 passe le tableau de chaînes à la DLL uniquement pour lui donner un tampon de chaînes dans lequel la DLL peut écrire. Le code ci-dessus devrait gérer ce scénario, mais il est inutilement compliqué pour un appel en réception seulement à une DLL).

Si une DLL Ansi prend un paramètre de tableau de chaînes uniquement pour pouvoir renvoyer une valeur dans arr[0], alors le code peut être beaucoup plus simple. Par exemple :

#import "AnsiDllWhichPassesBackAStringValueViaAnArray.dll"
   // Old Ansi function which takes a string array parameter **solely** so that 
   // it can **pass back** a value in arr[0]
   void Test(int & arr[]);  // NOT  void Test(string & arr[])
#import

#import "kernel32.dll"
   int LocalAlloc(int,int);
   int LocalFree(int);
   void RtlFillMemory(int, int, int);
   int lstrlenA(int);
   void RtlMoveMemory(uchar & arr[], int, int);
#import

void OnStart()
{
   // Maximum size of string which the DLL is expected to return in arr[0]
   int MaxReturnValueLength = 10000;
   
   // Allocate a block of memory of the desired size
   int ptrMem = LocalAlloc(0x40 /* LPTR */, MaxReturnValueLength + 1);
   
   // Fill the memory with spaces so that the length is <whatever> if 
   // the DLL checks it by doing strlen()
   RtlFillMemory(ptrMem, MaxReturnValueLength, 32);

   // Create a pseudo-MqlStr array which corresponds to a string array with one member
   int arrMqlStr[2];
   arrMqlStr[0] = MaxReturnValueLength;
   arrMqlStr[1] = ptrMem;
   
   // Call the DLL
   Test(arrMqlStr);

   // Get the size of the string contained in the memory block
   int NewSizeofString = lstrlenA(ptrMem);
      
   // Copy the contents of the memory block into a uchar[] array
   uchar ucReceive[];
   ArrayResize(ucReceive, NewSizeofString + 1);
   RtlMoveMemory(ucReceive, ptrMem, NewSizeofString + 1);
   
   // Free the memory for the string allocated above
   LocalFree(ptrMem);
   
   // Convert the uchar[] array to a string 
   string strReturnValueFromDLL = CharArrayToString(ucReceive);
}

Cela fonctionne avec une DLL qui utilise simplement array[0] comme un tampon dans lequel elle peut écrire, par exemple :

__declspec(dllexport) void WINAPI Test(MqlStr * arr)
{
        lstrcpyA(arr->data, "The string return value from the DLL");
}