La funzione DLL string non funziona su Build 600

 

Ciao codificatori!

Ho scritto una funzione DLL in Delphi. Questo codice restituisce solo un testo PChar all'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.

Il mio codice MQ4:

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

int init(){

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

}

Questa soluzione funziona su Build 509, ma su Build 600. Su Build 600, l'output degli esperti è: "DLL dice: ???????????x??????????????"

Perché non funziona su B600? Avete qualche idea per far funzionare correttamente il mio codice?

Grazie in anticipo.

Relativo

 
Relative:

Questa soluzione funziona su Build 509, ma su Build 600. Sulla Build 600, l'output degli esperti è: "DLL says: ???????????x??????????????"

v600 usa stringhe Unicode, non stringhe Ansi. O avete bisogno di restituire una stringa Unicode dalla vostra DLL (via più facile), o dovete fare alcune chiamate abbastanza complesse nel codice MQL4 per manipolare il valore di ritorno della stringa (via più difficile).

In entrambi i casi, questo comporta una perdita di memoria perché si sta allocando un blocco di memoria per la stringa che MT4 non sa come liberare, e non lo farà. E' meglio passare un buffer alla DLL, e lasciare che la DLL copi la stringa in quel buffer. In altre parole, il modo migliore per restituire valori di stringa a MQL4 è usare lo stesso tipo di metodo delle chiamate API di Windows come GetWindowsDirectory() e GetTempPath().

 
gchrmt4:

oppure bisogna fare delle chiamate piuttosto complesse nel codice MQL4 per manipolare il valore di ritorno della stringa (percorso più difficile).

Per la cronaca, è ancora possibile usare DLL che restituiscono stringhe Ansi; questo è una sorta di seguito a https://www.mql5.com/en/forum/149321. Il problema è che restituire stringhe dalle DLL fa perdere memoria, a meno che il codice MQL4 non sappia come è stata allocata la memoria e sia in grado di liberarla. L'esempio seguente presuppone che la memoria della stringa sia stata allocata nella DLL usando LocalAlloc(), o qualche equivalente indiretto che si sovrapponga 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);
}
 
Grazie gchrmt4!
 

gchrmt4
:

Per la cronaca, è ancora possibile usare DLL che restituiscono stringhe Ansi; questo è una sorta di seguito a https://www.mql5.com/en/forum/149321. Il problema è che restituire stringhe dalle DLL fa perdere memoria, a meno che il codice MQL4 non sappia come è stata allocata la memoria e sia in grado di liberarla. Il seguente esempio presuppone che la memoria della stringa sia stata allocata nella DLL usando LocalAlloc(), o qualche equivalente indiretto che mappi su LocalAlloc().

Grazie! Ha funzionato anche per la mia libmysql.dll. Il mio MQL4 non può più parlare con MySQL dalla build 600

Finalmente posso almeno vedere cosa restituisce la dll MySql...

Potresti darci un indizio su come implementare la comunicazione opposta, cioè passare una stringa da MQL4 (build 600) alla dll che supporta solo ANSI? Ho provato la funzione UNICODE2ANSI() che si trova qui e qui, ma purtroppo non mi funziona.
 
lukins:

Potresti darci un indizio su come implementare la comunicazione opposta, cioè passare una stringa da MQL4 (build 600) a dll che supporta solo ANSI?

È più in basso nella stessa pagina! Vedere https://www.mql5.com/en/forum/149321
 
gchrmt4:
È più in basso nella stessa pagina! Vedi https://www.mql5.com/en/forum/149321


Grazie! Funziona perfettamente per semplici valori di stringa. Almeno MQL si connette a MySQL ora.

Tuttavia sembra che io non possa ottenere un array di stringhe da una DLL. Ecco la funzione che mi viene fornita (è questo wrapper MQL4 MySql):

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

Attualmente sto ricevendo l'errore "Access violation read to 0x0..." quando passo l'array di stringhe di MQL.

Qualsiasi aiuto è molto apprezzato.

 
Un errore simile (violazione dell'accesso) si verifica quando passo "int & row[]" e provo a convertire ogni elemento al suo interno dopo che è stato popolato.
 
lukins:

Tuttavia sembra che non possa ottenere un array di stringhe da una dll. Ecco la funzione che mi viene fornita (è questo wrapper MQL4 MySql):

Simulare i vecchi array di stringhe Ansi è disordinato, ma ancora possibile. (Questo dipende dal fatto che la DLL si comporti bene, in particolare se passa i dati indietro a MQL4 alterando il contenuto dell'array. Ho testato questo solo contro l'esempio di codice C++ in basso, non contro qualcosa di più realistico come la libreria 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]);
   }
}

Per esempio, il codice di cui sopra funziona con la seguente DLL che fa una casella di messaggio per ogni stringa in un array e poi inverte la stringa prima di tornare 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:

Simulare i vecchi array di stringhe Ansi è disordinato, ma ancora possibile.

(Il codice di cui sopra può essere semplificato se l'MQL4 sta passando l'array di stringhe alla DLL solo per dargli un buffer di stringhe in cui la DLL possa scrivere. Il codice sopra dovrebbe gestire questo scenario, ma è inutilmente complicato per una chiamata di sola ricezione ad una DLL).
 
gchrmt4:
(Il codice di cui sopra può essere semplificato se MQL4 sta passando l'array di stringhe alla DLL solo per darle un buffer di stringhe in cui la DLL possa scrivere. Il codice sopra dovrebbe gestire questo scenario, ma è inutilmente complicato per una chiamata di sola ricezione ad una DLL).

Se una DLL Ansi prende un parametro array di stringhe solo per poter passare indietro un valore in arr[0], allora il codice può essere molto più semplice. Per esempio:

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

Questo funziona con una DLL che sta semplicemente usando array[0] come un buffer in cui può scrivere, per esempio:

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