Need Help Interfacing to Delphi DLL

 

Does anyone have some sample code for how to call a DLL written in Delphi from MetaTrader? I can't get Var parameters to work.

I wrote the following test function in Delphi and compiled it as a DLL (using Delphi 5):

function AddIntegers( a: integer; b: integer): integer; stdcall;

begin

  result := a + b;

end;

In the mq4 code I used

#import "testdll.DLL"

int AddIntegers( int a, int b);

#import

That worked beautifully, first time out. But if I change one of the function arguments to pass-by-reference rather than pass-by-value, it stops working. All I changed is 

function AddIntegers( a: integer; var b: integer): integer; stdcall;

begin

  result := a + b;

end;

In the mq4 code I used

#import "testdll.DLL"

int AddIntegers( int a, int& b);

#import

I followed the example provided in the MetaTrader samples directory exactly. Changing "int b" to "int& b" causes a memory access violation. When I examine the call stack within the Delphi function, the value on the stack for b is 0. Since this is a memory address, an attempt to read from memory location 0 generates the access violation. The Delphi function is responding to this correctly. The unsolved problem is why MQ4 uses a value of 0 as the address of the variable, which is clearly incorrect. Any clues? Any tips? I'm stumped.

I'm not a C programmer, and I need to get this working with Delphi so my EAs can access 6000 lines of previously developed code. I'd appreciate any help or advice or even a few example lines of code that work for someone else, so I can copy that and do further tests. 

 

"If there is a need to pass data of the int or double type, then the one-dimensional array of the corresponding type should be passed by reference as a parameter."

regards

fx1.net

 
chipdude wrote >>

Does anyone have some sample code for how to call a DLL written in Delphi from MetaTrader? I can't get Var parameters to work.

I wrote the following test function in Delphi and compiled it as a DLL (using Delphi 5):

function AddIntegers( a: integer; b: integer): integer; stdcall;

begin

result := a + b;

end;

In the mq4 code I used

#import "testdll.DLL"

int AddIntegers( int a, int b);

#import

That worked beautifully, first time out. But if I change one of the function arguments to pass-by-reference rather than pass-by-value, it stops working. All I changed is

function AddIntegers( a: integer; var b: integer): integer; stdcall;

begin

result := a + b;

end;

In the mq4 code I used

#import "testdll.DLL"

int AddIntegers( int a, int& b);

#import

I followed the example provided in the MetaTrader samples directory exactly. Changing "int b" to "int& b" causes a memory access violation. When I examine the call stack within the Delphi function, the value on the stack for b is 0. Since this is a memory address, an attempt to read from memory location 0 generates the access violation. The Delphi function is responding to this correctly. The unsolved problem is why MQ4 uses a value of 0 as the address of the variable, which is clearly incorrect. Any clues? Any tips? I'm stumped.

I'm not a C programmer, and I need to get this working with Delphi so my EAs can access 6000 lines of previously developed code. I'd appreciate any help or advice or even a few example lines of code that work for someone else, so I can copy that and do further tests.

Hi,

look here:

http://www.codexterity.com/memmgr.htm

maybe helps, you need to include some memory manager in your dll for handle the parameters....

 

Thanks guys!

I tried passing parameters as a one-dimensional array and that worked.

 
chipdude:

Thanks guys!

I tried passing parameters as a one-dimensional array and that worked.

Hi


I would really appreciate it if you could share your Delphi code you used to do this I am having problems with passing integers and doubles myself to a Delphi DLL


Thanks

 
theorem:

Hi


I would really appreciate it if you could share your Delphi code you used to do this I am having problems with passing integers and doubles myself to a Delphi DLL


Thanks

Certainly. The files are attached. I recompiled and reran the test just before zipping these so I know they work.

Put the TDLL.dpr file into ...\experts\libraries and let Delphi put the DLL file there when it compiles. You can get fancy with configuring directories later; right now you want the simplest possible setup so the fewest things can go wrong.

TestDLL.mq4 is an indicator that goes in ...\experts\indicators. When the indicator is dropped onto a chart, the init() function will call the DLL and display the sum of the two numbers you entered. You'll figure it out.

Files:
testdll.zip  2 kb
 

In the interest of building up some documentation on how to interface MetaTrader to Delphi DLLs, here is a description of some of my recent work. Please feel free to contribute your own helpful information

The most efficient way to pass price, time and volume information to a DLL seems to be to use ArrayCopyRates function, which actually doesn't copy anything but just sets up the references so MetaTrader internal memory can be directly referenced by the DLL. Inside the Delphi DLL the memory structure must be declared in a very particular way to make this work. I had to analyze some assembly language and hex memory dumps to figure this out. Here is the Type declaration that works:

Type
MTRec = packed record
  time : integer;
  Open,
  High,
  Low,
  Close,
  Volume: double;
end;
MTRecArray = array[0..65000] of MTRec;

Then in the function header, you would declare an argument like this: "Var Rates: MTRec", and in the MQ4 function header it would be declared as "double& Rates[][6]". I have no idea how MQL4 gets away with mixing Integer and Double types in an array, they aren't even the same size. But the important thing is this works.


Two important points to observe here: the record must be declared as "packed" to get the fields aligned right, and the array cannot be declared as dynamic, in other words, you cannot simply say "array of MTRec", the index range must be specified. I think you can give any value you like as the upper bound, as long as it is equal to or larger than the number of bars in the chart.

Also, the order of the data is earliest bars first, i.e. the time value at index 0 is a smaller value than the one at index 1. That means that probably the values you are most interested in are at the far end of the array. ArrayCopyRates returns an integer number indicating the number of records that are present. That number needs to be given to the DLL so it knows where to look for the most recent values.



 

Getting the Delphi debugger to actually work when debugging a DLL requires some ducking, dodging and jumping through hoops. These are the hoops for Delphi 5.

  1. Configure the linker to generate a detailed map file: Project|Options...|Linker tab|select "Detailed" radio button
  2. in Run|Parameters, enter the full path to terminal.exe as the host application, e.g. C:\MT4s\ATC\terminal.exe
  3. Start the application with F9 or the Run button
  4. You need to break when the DLL loads; set the Module Load breakpoint in Run|Add Breakpoint, then drop the EA or indicator on a chart and it'll break
  5. To enable line by line breakpoints the symbol table must be correctly loaded in View|Debug Windows|Modules. If the list of entry points isn't visible when the module is highlighted, then right-click and reload the symbol table, browsing to the proper location, e.g. C:\MT4s\ATC\experts\library\Tdll.dll

On my system, the symbol table will only load if the full path to the DLL is given. And, each time I stop and restart terminal.exe I have to repeat step 4 and 5, as Delphi seems to forget the path each time. I'm not 100% sure that step 1 is actually necessary, but it was part of what I originally did to make things work so I left it in.


Windows is a bit funky about giving users permission to read, write and view files in the Program Files folder, so I put my MetaTrader installations in a separate folder, C:\MT4s\.


And, one last tip. If your DLL has enough bugs to crash (it happens often enough in the early stages), MetaTrader may bork on loading, before any breakpoints are reached. The easy fix is to either delete or rename your dll file, e.g. Tdll.dll, then run MetaTrader outside of Delphi and this time it will successfully start. Then delete the EA or indicator from the chart and close MetaTrader so it saves the chart state without the EA/indicator on it. Now restore your DLL file and Delphi will be able to load MetaTrader completely and break on module load when you add the EA or indicator to the chart.

 
chipdude:

Type
 MTRec = packed record
  time  : integer;
  Open,
  High,
  Low,
  Close,
  Volume: double;
 end;
 MTRecArray = array[0..65000] of MTRec;

Then in the function header, you would declare an argument like this: "Var Rates: MTRec"

couldn't the above be written even more elegant with the help of open arrays?

untested:


type
 MTRec = packed record
  time  : integer;
  Open,
  High,
  Low,
  Close,
  Volume: double;
 end;

procedure foo(history: array of MTRec); stdcall;
var
 i : integer;
begin
 // loop over the array
 for i := 0 to High(history) do
 begin
  // do some stuff
 end
end;


exploiting the fact that an open array parameter is in reality two parameters, the pointer and the size?


#import "mydll.dll"
 void foo(double& history[][6], int size);
#import


This would remove the ugly big number constant entirely. I will test this ASAP. (I'm using Lazarus/FPC but from what I have read open array parameters are internally implemented the same way)

 
7bit:

couldn't the above be written even more elegant with the help of open arrays?

untested:



exploiting the fact that an open array parameter is in reality two parameters, the pointer and the size?



This would remove the ugly big number constant entirely. I will test this ASAP. (I'm using Lazarus/FPC but from what I have read open array parameters are internally implemented the same way)

I just saw this, and want to answer it to keep the on-line information as complete as possible. I initially tried it as an open array and it didn't work. Remember that the array is created by MetaTrader, not by Delphi/Lazarus. So if MT is not writing the array in the Delphi "open array" format (and it isn't) then Delphi will not be able to read it that way. Just stick a gigantic constant there (like one million) and forget it. It doesn't matter how large it is made, since Delphi will never allocate it, the array is allocated by MT and will be read-only for Delphi. The actual size is supplied by MT as a separate parameter, which you will use to locate the record for the most recent bar.
Reason: