A função de cadeia DLL não funciona no Build 600

 

Olá codificadores!

Eu escrevi uma função DLL em Delphi. Este código só retorna um texto PChar para o 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.

Meu código MQ4:

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

int init(){

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

}

Esta solução funciona no Build 509, mas no Build 600. No Build 600, a saída dos especialistas é: "DLL diz: ???????????x??????????????".

Por que não funciona no B600? Você tem alguma idéia para fazer meu código funcionar corretamente?

Agradecemos antecipadamente.

Em relação a

 
Relative:

Esta solução funciona no Build 509, mas no Build 600. No Build 600, a saída dos especialistas é: "DLL diz: ???????????x??????????????".

v600 usa cordas Unicode, não Ansi strings. Ou você precisa retornar uma string Unicode de sua DLL (rota mais fácil), ou você precisa fazer algumas chamadas bastante complexas no código MQL4 para manipular o valor de retorno da string (rota mais difícil).

De qualquer forma, isto vai vazar memória porque você está alocando um bloco de memória para a string que o MT4 não sabe como liberar, e não vai liberar. É melhor passar um buffer para a DLL, e fazer com que a DLL copie a cadeia de caracteres para esse buffer. Em outras palavras, a melhor maneira de retornar valores de string à MQL4 é usar o mesmo tipo de método que as chamadas API do Windows como GetWindowsDirectory() e GetTempPath().

 
gchrmt4:

ou você precisa fazer algumas chamadas bastante complexas no código MQL4 para manipular o valor de retorno da string (rota mais difícil).

Para registro, ainda é possível usar DLLs que retornam strings Ansi; isto é uma espécie de acompanhamento para https://www.mql5.com/en/forum/149321. O problema é que o retorno de strings de DLLs vaza memória, a menos que o código MQL4 saiba como a memória foi alocada e seja capaz de liberá-la. O exemplo a seguir assume que a memória da string foi alocada na DLL usando LocalAlloc(), ou algum equivalente indireto que mapeia para 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);
}
 
Obrigado gchrmt4!
 

gchrmt4
:

Para o registro, ainda é possível utilizar DLLs que retornam cordas Ansi; esta é uma espécie de acompanhamento para https://www.mql5.com/en/forum/149321. O problema é que o retorno de strings das DLLs vaza memória, a menos que o código MQL4 saiba como a memória foi alocada e seja capaz de liberá-la. O exemplo a seguir assume que a memória da string foi alocada na DLL usando LocalAlloc(), ou algum equivalente indireto que mapeia para LocalAlloc().

Obrigado! Funcionou também para minha libmysql.dll. Minha MQL4 não pode mais falar com o MySQL desde que construiu 600

Finalmente posso ao menos ver o que MySql dll retorna...

Você poderia nos dar uma pista sobre como implementar a comunicação oposta, ou seja, passar uma string da MQL4 (build 600) para dll que só suporta ANSI? Eu tentei a função UNICODE2ANSI() encontrada aqui e aqui, mas infelizmente ela não funciona para mim.
 
lukins:

Você poderia nos dar uma pista sobre como implementar a comunicação oposta, ou seja, passar uma string da MQL4 (build 600) para dll que só suporta ANSI?

É mais abaixo na mesma página! Veja https://www.mql5.com/en/forum/149321
 
gchrmt4:
Está mais abaixo na mesma página! Veja https://www.mql5.com/en/forum/149321


Obrigado! Funciona perfeitamente para valores de cordas simples. Pelo menos a MQL se conecta ao MySQL agora.

No entanto, parece que não consigo obter um conjunto de cordas de uma dll. Aqui está a função que me é fornecida (é esta embalagem MQL4 MySql):

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

Atualmente estou recebendo o erro "Access violation read to 0x0..." quando passo o habitual conjunto de strings da MQL.

Qualquer ajuda é altamente apreciada.

 
Erro semelhante (violação de acesso) ocorre quando eu passo "int & row[]" e tento converter cada elemento dentro dele depois de povoado.
 
lukins:

No entanto, parece que não consigo obter um conjunto de cordas de uma dll. Aqui está a função que me é fornecida (é esta embalagem MQL4 MySql):

A simulação das antigas matrizes de cordas Ansi é confusa, mas ainda assim possível. (Isto vai depender do bom comportamento da DLL, particularmente se ela passar os dados de volta para a MQL4 alterando o conteúdo da matriz. Eu só testei isto contra o exemplo do código C++ na parte inferior, não contra algo mais realista como a 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 exemplo, o código acima funciona com a seguinte DLL que faz uma caixa de mensagem para cada string em um array e depois inverte a string antes de retornar ao 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:

A simulação de antigas matrizes de corda Ansi é confusa, mas ainda assim possível.

(O código acima pode ser simplificado se a MQL4 estiver passando a matriz de caracteres para a DLL somente para dar-lhe um buffer de caracteres para a DLL gravar na DLL. O código acima deve lidar com esse cenário, mas é desnecessariamente complicado para uma chamada somente de recepção para uma DLL).
 
gchrmt4:
(O código acima pode ser simplificado se a MQL4 estiver passando o array de string para a DLL somente para dar a ela um buffer de string para que a DLL escreva. O código acima deve lidar com esse cenário, mas é desnecessariamente complicado para uma chamada somente de recepção para uma DLL).

Se uma DLL Ansi toma um parâmetro de matriz de string apenas para que ela possa passar um valor em arr[0], então o código pode ser muito mais simples. Por exemplo, o código pode ser muito mais simples:

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

Isto funciona com uma DLL que está simplesmente usando array[0] como um buffer no qual ele pode escrever, por exemplo:

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