La función de cadena DLL no funciona en la compilación 600

 

¡Hola codificadores!

He escrito una función DLL en Delphi. Este código sólo devuelve un texto PChar al 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.

Mi código MQ4:

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

int init(){

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

}

Esta solución funciona en la Build 509, pero en la Build 600. En la Build 600, la salida de los expertos es: "DLL dice: ???????????x??????????????"

¿Por qué no funciona en la B600? ¿Tienen alguna idea para que mi código funcione correctamente?

Gracias de antemano.

Relativo

 
Relative:

Esta solución funciona en la Build 509, pero en la Build 600. En la Build 600, la salida de los expertos es: "DLL dice: ???????????x??????????????"

v600 utiliza cadenas Unicode, no cadenas Ansi. Usted tiene que devolver una cadena Unicode de su DLL (ruta más fácil), o usted tiene que hacer algunas llamadas bastante complejas en el código MQL4 para manipular el valor de retorno de la cadena (ruta más difícil).

De cualquier manera, esto va a la pérdida de memoria, porque usted está asignando un bloque de memoria para la cadena que MT4 no sabe cómo liberar, y no se libera. Es mejor pasar un buffer a la DLL, y hacer que la DLL copie la cadena en ese buffer. En otras palabras, la mejor manera de devolver valores de cadena a MQL4 es utilizar el mismo tipo de método que las llamadas de la API de Windows como GetWindowsDirectory() y GetTempPath().

 
gchrmt4:

o tienes que hacer algunas llamadas bastante complejas en el código MQL4 para manipular el valor de retorno de la cadena (ruta más difícil).

Para que conste, todavía es posible utilizar DLLs que devuelven cadenas Ansi; esto es una especie de seguimiento de https://www.mql5.com/en/forum/149321. El problema es que al devolver cadenas desde las DLLs se pierde memoria a menos que el código MQL4 sepa como se asignó la memoria y sea capaz de liberarla. El siguiente ejemplo asume que la memoria de la cadena fue asignada en la DLL usando LocalAlloc(), o algún equivalente indirecto que se asigna a 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);
}
 
¡Gracias gchrmt4!
 

gchrmt4
:

Para que conste, todavía es posible utilizar DLLs que devuelven cadenas Ansi; esto es una especie de seguimiento de https://www.mql5.com/en/forum/149321. El problema es que la devolución de cadenas desde DLLs pierde memoria a menos que el código MQL4 sepa cómo se asignó la memoria y sea capaz de liberarla. El siguiente ejemplo asume que la memoria de la cadena fue asignada en la DLL usando LocalAlloc(), o algún equivalente indirecto que se asigne a LocalAlloc().

Gracias. También funcionó para mi libmysql.dll. Mi MQL4 ya no puede hablar con MySQL desde la build 600

Por fin puedo ver lo que devuelve la dll de MySql...

¿Podrías darnos una pista de cómo implementar la comunicación inversa, es decir, pasar una cadena de MQL4 (build 600) a la dll que sólo soporta ANSI? He probado la función UNICODE2ANSI() que se encuentra aquí y aquí pero no me funciona lamentablemente.
 
lukins:

¿Podría darnos una pista sobre cómo implementar la comunicación inversa, es decir, pasar una cadena de MQL4 (build 600) a la dll que sólo soporta ANSI?

¡Está más abajo en la misma página! Ver https://www.mql5.com/en/forum/149321
 
gchrmt4:
Está más abajo en la misma página. Ver https://www.mql5.com/en/forum/149321


¡Gracias! Funciona para los valores de cadena simple a la perfección. Al menos MQL se conecta a MySQL ahora.

Sin embargo parece que no puedo obtener un array de cadenas desde una dll. Aquí está la función que se me proporciona (es esta envoltura MQL4 MySql):

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

Actualmente estoy recibiendo el error "Access violation read to 0x0..." al pasar el array de cadenas habitual de MQL.

Cualquier ayuda es muy apreciada.

 
Un error similar (violación de acceso) se produce cuando paso "int & row[]" e intento convertir cada elemento dentro de él después de que se haya rellenado.
 
lukins:

Sin embargo parece que no puedo obtener un array de cadenas desde una dll. Aquí está la función que me proporcionan (es esta envoltura MQL4 MySql):

La simulación de los viejos arrays de cadenas de Ansi es un lío, pero todavía es posible. (Esto va a depender de que la DLL se comporte bien, particularmente si pasa datos de vuelta a MQL4 alterando el contenido del array. Sólo he probado esto contra el código C++ de ejemplo en la parte inferior, no contra algo más realista como la biblioteca 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]);
   }
}

Por ejemplo, el código anterior funciona con la siguiente DLL que hace un cuadro de mensaje para cada cadena en un array y luego invierte la cadena antes de regresar a 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:

Simular las antiguas matrices de cadenas Ansi es complicado, pero todavía es posible.

(El código de arriba puede ser simplificado si el MQL4 está pasando el array de cadenas a la DLL sólo para darle un buffer de cadenas para que la DLL escriba en él. El código anterior debería manejar ese escenario, pero es innecesariamente complicado para una llamada de sólo recepción a una DLL).
 
gchrmt4:
(El código anterior puede ser simplificado si el MQL4 está pasando el array de cadenas a la DLL sólo para darle un buffer de cadenas para que la DLL escriba en él. El código anterior debería manejar ese escenario, pero es innecesariamente complicado para una llamada de sólo recepción a una DLL).

Si una DLL Ansi toma un parámetro de matriz de cadenas sólo para poder pasar de vuelta un valor en arr[0], entonces el código puede ser mucho más simple. Por ejemplo:

#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);
}

Esto funciona con una DLL que simplemente usa array[0] como un buffer en el que puede escribir, por ejemplo

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