MQL5 - Launching a form embedded in a DLL - delphi

In MetaTrader 4/MQL4 I'm able to display a non-modal form (i.e., simple window with string grid) and pass data to this form from an indicator using the DLL interface. Been doing this for a while now and it works perfectly.
Trying to accomplish the same thing in MetaTrader 5/MQL5 but having some trouble. The form displays but immediately goes into an unresponsive state (application not responding).
I did run a test using the same 64-bit DLL on a different 64-bit platform (eSignal 12) and the form loads just fine.
I am using Delphi XE5 Update 2 to build the DLLs. 32-bit for MT4 and 64-bit for MT5. MT5 build is 1881. I created a very simple MQL5 script and Delphi DLL to test it:
MQL5
#import "TestForm.dll"
int showAForm(int hwnd);
int closeAForm();
#import
void OnStart()
{
//---
int hwnd=ChartGetInteger(0,CHART_WINDOW_HANDLE,0);
showAForm(hwnd);
Sleep(5000);
closeAForm();
//---
}
Delphi
library TestForm;
uses
System.SysUtils,
System.Classes,
Windows,
Vcl.Forms, Vcl.Dialogs;
{$R *.res}
var
myForm: TForm;
function showAForm(handle: HWND): integer; stdCall;
var
myHandle: HWND;
begin
myHandle := FindWindow('MetaQuotes::MetaTrader::5.00', nil);
showMessage(IntToStr(myHandle));
try
myForm := TForm.Create(nil);
// myForm:=TForm.CreateParented(myHandle);
// Windows.SetParent(myForm.Handle, myHandle);
myForm.Show;
result := 1;
except
result := -2;
end;
end;
function closeAForm(): integer; stdCall;
begin
myForm.Close();
FreeAndNil(myForm);
result := 1;
end;
exports
showAForm,
closeAForm;
begin
IsMultiThread := true;
end.
I have tried a few different methods of creating the form (nil, parented) but it makes no difference. If I use ShowModal instead of Show the form displays correctly but of course the MT5 GUI is blocked.
No exceptions are generated, and both functions return correctly...it is just that the form itself will not display. Would be grateful if anyone could shed some light on what may be going on, and any possible workarounds.

This is the problem:
showAForm(hwnd);
Sleep(5000);
closeAForm();
Your Sleep(5000); blocks the GUI thread for full 5 seconds.
So you create the form showAForm(), block the GUI thread for 5 seconds, then close the form closeAForm().
The Delphi form needs a working message pump to be able to work properly,
but the Sleep(5000); blocks the thread and doesn't let the message pump to operate.
In Delphi applications, eg. "mydelphiapp.exe", the Application.ProcessMessage function handles a message, and inside the Application.Run procedure, there is code like:
repeat
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;
To solve the problem, you need to build in Delphi, a message-pumping alternative to Sleep():
procedure MessagePumpingSleep(ADelay:Longword); stdcall;
var
T1, T2, dT : Longword;
begin
T1 := GetTickCount;
repeat
Application.ProcessMessages;
Sleep(1); // this will stop this procedure from consuming 100% CPU.
T2 := GetTickCount;
dT := T2 - T1;
until dT >= ADelay;
end;
exports MessagePumpingSleep;
Then call MessagePumpingSleep(5000) instead of Sleep(5000);
Obviously, to be able to call MessagePumpingSleep(5000), you need to write the proper import code in MQL5 to import the MessagePumpingSleep() -function from the Delphi DLL.

Related

DLL_PROCESS_DETACH won't get called?

A C# program calls a DLL which I have written in Delphi:
[DllImport("ABCDEF.dll")]
private static extern void OpenDrawer();
The DLL in Delphi has the function (among of other things) to open the drawer for an Epson POS printer.
Its DLLMain contains DLL_PROCESS_ATTACH, which loads EpsStmApi.dll, and its DLL_PROCESS_DETACH method frees EpsStmApi.dll again, as well as closes the printer handle, if one exists. The DLL-function OpenDrawer will call the DLL function and save the printer handle as global variable (this is important for the performance).
library Test;
var
hEpsonDLL: cardinal = 0; // DLL handle for EpsStmApi.dll
hMonPrinter: integer = 0; // Printer handle. Stays open while the DLL is loaded, for performance
type
TFuncBiOpenMonPrinter = function (nType: Integer; pName: LPSTR): Integer; stdcall;
TFuncBiOpenDrawer = function (nHandle: Integer; drawer: Byte; pulse: Byte): Integer; stdcall;
TFuncBiCloseMonPrinter = function (nHandle: Integer): Integer; stdcall;
var
funcBiOpenMonPrinter : TFuncBiOpenMonPrinter;
funcBiOpenDrawer : TFuncBiOpenDrawer;
funcBiCloseMonPrinter : TFuncBiCloseMonPrinter;
procedure OpenDrawer; stdcall; export;
begin
if hEpsonDLL <> 0 then
begin
// DLL missing. Probably no Epson printer installed.
exit;
end;
if hMonPrinter = 0 then
begin
// Initialize the printer.
#funcBiOpenMonPrinter := GetProcAddress(hEpsonDLL, 'BiOpenMonPrinter') ;
if Assigned(funcBiOpenMonPrinter) then hMonPrinter := funcBiOpenMonPrinter(TYPE_PRINTER, 'EPSON TM-T88V Receipt');
end;
if hMonPrinter <> 0 then
begin
// Try to open the drawer
#funcBiOpenDrawer := GetProcAddress(hEpsonDLL, 'BiOpenDrawer') ;
if Assigned(funcBiOpenDrawer) then funcBiOpenDrawer(hMonPrinter, EPS_BI_DRAWER_1, EPS_BI_PULSE_400);
end;
end;
procedure DllMain(reason: Integer);
begin
if reason = DLL_PROCESS_ATTACH then
begin
// Note: Calling BiOpenMonPrinter here will cause a deadlock.
hEpsonDLL := LoadLibrary('EpsStmApi.dll') ;
end
else if reason = DLL_PROCESS_DETACH then
begin
// Destroy the printer object
if hMonPrinter <> 0 then
begin
#funcBiCloseMonPrinter := GetProcAddress(hEpsonDLL, 'BiCloseMonPrinter') ;
if Assigned(funcBiCloseMonPrinter) then funcBiCloseMonPrinter(hMonPrinter);
end;
// Free the library
if hEpsonDLL <> 0 then FreeLibrary(hEpsonDLL);
end;
end;
exports
OpenDrawer;
begin
DllProc := DllMain;
DllMain(DLL_PROCESS_ATTACH);
end.
It works so far, but I wonder if it is clean and correct, because I noticed that DLL_PROCESS_DETACH will never be called (I checked it by writing to log files), therefore the printer handle stays open. I do not think it is a big deal, but I am unsure if something is not correct in-memory.
Is it possible that DLL_PROCESS_DETACH does not get called because the DLL itself has another DLL loaded?
(Note: There is a reason why I don't call EpsStmApi.dll via C# directly, which is not relevant to this topic.)
To begin with, what you report cannot be readily reproduced. A simple C# console application that calls a trivial Delphi DLL does indeed fire DLL_PROCESS_DETACH when the process terminates.
library Project1;
uses
Windows;
procedure foo; stdcall;
begin
end;
procedure DllMain(reason: Integer);
begin
if reason = DLL_PROCESS_ATTACH then begin
OutputDebugString('DLL_PROCESS_ATTACH');
end else if reason = DLL_PROCESS_DETACH then begin
OutputDebugString('DLL_PROCESS_DETACH');
end;
end;
exports
foo;
begin
DllProc := DllMain;
DllMain(DLL_PROCESS_ATTACH);
end.
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
[DllImport(#"Project1.dll")]
static extern void foo();
static void Main(string[] args)
{
foo();
}
}
}
Similarly if this DLL is hosted in an executable that loads it with LoadLibrary and then unloads it with FreeLibrary, the DllMain call with reason DLL_PROCESS_DETACH does fire. Most likely you are simply mistaken in your diagnosis.
If you wish to perform tasks when the DLL is loaded and unloaded, then perhaps a simpler way is to add code to a unit initialization and finalization sections. However, that code is still invoked from DllMain which severely limits what you can do, leading me to my next point.
You are breaking the rules for DllMain. The documentation for DllMain says
The entry-point function should perform only simple initialization or termination tasks. It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order.
I guess you are getting away with this, at least for now. But things could easily change. Moving the code to initialization and finalization changes nothing because they are still invoked from DllMain.
I strongly advise you to remove all of your DllMain code. If you wish to delay load the DLL then do so using delayed or move the loading of the DLL into another exported function that performs initialisation.

Updating Inno Setup progress bar from Dephi DLL

I need your help, please. I'm trying to call function from DLL written in Delphi 10 Seattle from Inno Setup (ANSI). But I do not understand what is the problem. If I make application in Delphi and call this function from DLL, it works perfectly! See code listing:
Delphi DLL:
function Process(Pb: TProgressBar): Integer; stdcall;
var
I: integer;
begin
for I := 0 to 1000 do
begin
Pb.Position := I;
Pb.Update;
Sleep(10);
end;
end;
Exports
Process;
Inno Setup (ANSI):
function Count(Progr: TNewProgressBar): integer; external 'Process#files:CallC.dll stdcall delayload';
procedure NewButton1Click(Sender: TObject);
begin
Count(NewProgressBar1);
end;
After call I get Access Violation. But, comment in dpr file i read, ShareMem write first line, but zero effect.
Show me how to correctly update progress bar in Inno Setup from Delphi DLL, please.
You cannot call object methods this way. You may be lucky to get this working, if you use exactly the same version of Delphi as the one Inno Setup is built with, as your tests with Delphi application shows. But it is still wrong and unreliable, do not do it. As you use a different Delphi version, the layout of the progress bar class in memory is different, hence the "Access violation".
For this particular task, you can easily do with just a handle to the progress bar:
function Process(Handle: THandle): Integer;
var
I: Integer;
begin
SendMessage(Handle, PBM_SETRANGE, 0, 1000 shl 16);
for I := 0 to 1000 do
begin
SendMessage(Handle, PBM_SETPOS, I, 0);
UpdateWindow(Handle);
Sleep(10);
end;
end;
In Inno Setup, call the function like:
function Count(Handle: THandle): integer;
external 'Process#files:CallC.dll stdcall delayload';
procedure NewButton1Click(Sender: TObject);
begin
Count(NewProgressBar1.Handle);
end;
For more advanced tasks, you need to use a callback.
See
Using callback to display filenames from external decompression dll (Inno Setup)
Call C# DLL from Inno Setup with callback

How to work arround the high DPI issue?

I need to get the desktop resolution from a Delphi program.
However, if the program is not DPI aware Windows will lie about the real screen resolution so all kind of problems will rise from here.
Since it is too much work to make the program fully DPI aware (and I try to AVOID the WMI solution) I am thinking using a quick dirty trick: I will create a microscopic DPI-aware console program that will read the real resolution.
The main program will use start this little program (hidden) every time it needs the resolution. Seems simple enough to do. Right?
Question 1: Do I have another (better) option?
Question 2: I tried to create that little program. Although is has something like 10 lines of code its EXE size is 2.1MB and its memory footprint is 5.4MB!
Can I make it smaller? If the program is small enough (under 1MB RAM) I could leave it run all the time without pissing off the users.
Question 1: Do I have another (better) option?
You can use WMI as per your earlier question: How to obtain the real screen resolution in a High DPI system?
Question 2: I tried to create that little program. Although is has something like 10 lines of code its EXE size is 2.1MB and its memory footprint is 5.4MB! Can I make it smaller?
The trick is to avoid using any VCL units, and minimising the number of RTL units that you use. Your goal should be to use the Windows unit only. Or even avoid it and create your own Windows API imports for just the functions that you need.
Another option would be to create this program with a different programming language, one that was better able to remove dead code. I'd probably do this with a short C program.
This is 30KB with a plain icon, 15KB if you UPX it, compiled with Delphi 10 Seattle, and takes roughly 150-200ms in my system.
program ScreenSupport;
{$APPTYPE CONSOLE}
{$WEAKLINKRTTI ON}
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
uses
Windows,
Messages;
{$R *.res}
{$SetPEFlags $0200} // IMAGE_FILE_DEBUG_STRIPPED} // $0200
{$SetPEFlags $0004} // IMAGE_FILE_LINE_NUMS_STRIPPED} // $0004
{$SetPEFlags $0008} // IMAGE_FILE_LOCAL_SYMS_STRIPPED} // $0008
{$SetPEFlags $0001} // IMAGE_FILE_RELOCS_STRIPPED} // $0001
Const WM_APP = $8000;
msgSendScreenres = WM_APP+1;
SM_CXVIRTUALSCREEN = 78;
SM_CYVIRTUALSCREEN = 79;
function GetDesktopHeight: Integer;
begin
Result := GetSystemMetrics(SM_CYVIRTUALSCREEN);
end;
function GetDesktopWidth: Integer;
begin
Result := GetSystemMetrics(SM_CXVIRTUALSCREEN);
end;
procedure SendScreenRes(t: THandle);
begin
if t = 0 then Exit;
PostMessage(t,msgSendScreenres,GetDesktopWidth,GetDesktopHeight);
end;
function IsAnyParam(s: string): Boolean;
Var a: Integer;
begin
Result := False;
if ParamCount = 0 then Exit;
for a := 1 to ParamCount do
if ParamStr(a) = s then Exit(True);
end;
function StrToInt(const S: string): Integer;
Var E: Integer;
begin
Val(S, Result, E);
end;
begin
// screen res requested
if IsAnyParam('-screenres') then begin
try
SendScreenRes(StrToInt(ParamStr(2)));
except
Exit;
end;
end;
end.
To use it, call it from your main app:
Const msgSendScreenres = WM_APP+1;
ShellExecute(0,'open','ScreenSupport.exe',PChar('-screenres '+IntToStr(Form1.Handle)),'',SW_HIDE);
then add this on private declarations on the main unit
procedure WMScreenRes(var Msg: TMessage); message msgSendScreenres;
then catch it
procedure TForm1.WMScreenRes(var Msg: TMessage);
begin
ScreenWidth := Msg.WParam;
ScreenHeight := Msg.LParam;
end;

finalization section not being run in a dll

I am trying to create a DLL in Delphi XE2 which will popup a form with a TWebBrowser component in it. When the WebBrowser.Navigate2 method is called the finalization section of the unit (or any unit) is not called when the application ends. If Navigate2 is not called, the finalization section happens just fine.
The dll is being called from C++ (VS 2010 MFC console at the moment) and linked via in import library.
There are other ways of doing this, but I would like to reuse the code we already have written.
Does anyone have any idea what is going on?
Thanks.
Here is a simple recreation of the problem:
library DisplayPatientAlertsIntf;
exports DisplayPatientAlertsA name 'DisplayPatientAlertsA#4';
begin
end.
unit uAlertWindow;
interface
uses
Winapi.ActiveX,
Forms,
SHDocVw,
Graphics, Controls;
function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; export; stdcall;
implementation
var ts : TStringList;
function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; export; stdcall;
var Form1 : TForm;
WebBrowser1 : TWebBrowser;
DidCoInit : Boolean;
begin
DidCoInit := Succeeded(CoInitialize(nil));
try
Form1 := TForm.Create(nil);
try
WebBrowser1 := TWebBrowser.Create(nil);
try
WebBrowser1.ParentWindow := Form1.Handle;
WebBrowser1.Align := alClient;
WebBrowser1.Navigate2('file://c:\temp.html');
Form1.ShowModal;
finally
WebBrowser1.Free;
end;
finally
Form1.Free;
end;
finally
if DidCoInit then
CoUninitialize;
end;
Result := 0;
end;
initialization
ts := TStringList.Create;
finalization
ts.Free;
end.
Update 2013.03.19
While solving another problem (dbExpress drivers in a dll), I changed it from a statically linked dll with an import library to a dynamically loaded dll and everything started working.
Do not call CoInitialize() or CoUninitialize() during the DLL's initialization/finalization. That is a very bad place to do that, and besides, it is not the DLL's responsibility to call them anyway. It is the responsibility of the thread that is calling the DLL functions. If you must call them, then at least do so inside of your exported function instead.
As for the exported function itself, use WebBrowser1.Parent instead of WebBrowser1.ParentWindow, use Form1.Free instead of Form1.Release, and get rid of Application.ProcessMessages altogether.
And lastly, do not export the function using a manually decorated name. That is not the DLL's responsibility to do, either. Let the compiler handle the decorating. If there is a naming mismatch when importing the function, that needs to be addressed in the calling app, not the DLL itself.
Your misuse of both COM and the VCL (especially since the problem only occurs once the exported DLL function is called) are likely leading to deadlocks, preventing the DLL from unloading from memory correctly, and thus none of its finalization sections would be called because its DLL entry point is not able to be called. COM is very sensitive when it comes to its initialization/cleanup, so you have to make sure you do it correctly, and in the correct context.
Try this:
library DisplayPatientAlertsIntf;
uses
uAlertWindow;
exports
DisplayPatientAlertsA;
begin
end.
.
unit uAlertWindow;
interface
uses
Winapi.ActiveX,
Forms,
SHDocVw,
Graphics, Controls;
function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; stdcall;
implementation
function DisplayPatientAlertsA(PatientID : PAnsiChar): Integer; stdcall;
var
Form1 : TForm;
WebBrowser1 : TWebBrowser;
DidCoInit: Boolean;
begin
Result := 0;
try
DidCoInit = Succeeded(CoInitialize(nil));
try
Form1 := TForm.Create(nil);
try
WebBrowser1 := TWebBrowser.Create(Form1);
WebBrowser1.Parent := Form1;
WebBrowser1.Align := alClient;
WebBrowser1.Navigate2('file://c:\temp.html'); //This contains 'ASDF'
Form1.ShowModal;
finally
Form1.Free;
end;
finally
if DidCoInit then
CoUninitialize;
end;
except
Result := -1;
end;
end;
end.
Delphi does not make heavy use of plain DLLs and its support is basic and scarcely documented
While Delphi makes good work for EXE files, intercepting WinMain and bringing its semantics to Turbo Pascal style context, for DLL you have to do it manually.
Start with reading DLL-Main Microsoft documentation and tutorials.
Then you can add into your DLL.dpr something like
begin
DLLProc := #DLLMain;
DLLMain(DLL_PROCESS_ATTACH);
end.
And then in some unit of DLL you can implement it like this:
procedure DLLMain(dwReason: DWORD);
begin
case dwReason of
DLL_PROCESS_ATTACH:
begin
Application.HelpFile := HelpFileName;
dmSelVars := TdmSelVars.Create(nil);
end {= DLL_PROCESS_ATTACH =};
DLL_PROCESS_DETACH:
begin
Application.Handle := 0;
FreeAndNil(dmSelVars);
g_pSvRec := nil;
end {= DLL_PROCESS_DETACH =};
end {= case =};
end {= DLLMain =};
PS. Why using DLL, when you can use Delphi-native (since 1997) BPL instead ?
It solves many problems and it provides much better finalization support:
for manually-loaded packages (via LoadPackage(...) call) finalization called for all units by granted
for manually-loaded packages (via Project Options / Packages / Link with Runtime packages list ) finalization is called for all units, referenced in "uses" sections of your host EXE.
PPS. Starting MSIE for merely displaying one page - doesn't it look overkill ?
Perhaps native HTML support would suffice, even if limited somewhat ? And it is capable of loading page from TStream or from String w/o tinkering with intermediate temporary files. (Well, MSIE is capable as well, though, after some scaffolding).
You might find that an exception is being raised when one of the units is being finalized, preventing other units from being finalized.
I'm not sure about XE2, but older versions of Delphi tended to be very fussy about the ComObj unit being "high up" in the uses/initialization so it would be one of the last to finalize.
The problem was that if ComObj was finalized too soon, it would CoUninitialize too soon - effectively ripping the rug from under other code that still expected COM to be initialized.
If the XE2 version of SHDocVw still uses ComObj in its implementation section, then ComObj will be initialized relatively 'late'. So that could very well be your problem. In which case simply adding it explicitly and high up in your source should do the trick.

target process crash when using remoteThread to inject a function

I'm using this code to inject my function, but it causes the target process to crash. Does anyone know why?
program Sky;
{$IMAGEBASE $13140000}
uses
Unit2 in 'Unit2.pas',
chstrDec in 'chstrDec.pas',Psapi,
unitinject in 'unitinject.pas', ShellAPI,dialogs,registry, Windows, Messages, tlhelp32, SysUtils, Variants, Classes, Graphics, Controls, Forms;
{$R *.res}
function GetProcessID(ProcessName:string):Integer;
var
Handle:tHandle;
Process:tProcessEntry32;
GotProcess:Boolean;
begin
Handle:=CreateToolHelp32SnapShot(TH32CS_SNAPALL,0) ;
Process.dwSize:=SizeOf(Process);
GotProcess := Process32First(Handle,Process);
{$B-}
if GotProcess and (Process.szExeFile<>ProcessName) then
repeat
GotProcess := Process32Next(Handle,Process);
until (not GotProcess) or (Process.szExeFile=ProcessName);
{$B+}
if GotProcess then Result := Process.th32ProcessID
else Result := 0;
CloseHandle(Handle);
end;
{$IMAGEBASE $13140000}
function Main(dwEntryPoint: Pointer): longword; stdcall;
var
s : String;
begin
ShowMessage('hi');
Result := 0;
Sleep(2000);
Main(dwEntryPoint);
end;
var
x:pointer;
Handle:tHandle;
PID:Cardinal;
begin
Pid:=getProcessID('calc.exe');
Handle := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
Inject(Handle,#Main);
CloseHandle(Handle);
end.
//inject
procedure Inject(ProcessHandle: longword; EntryPoint: pointer);
var
Module, NewModule: Pointer;
Size, BytesWritten, TID: longword;
begin
Module := Pointer(GetModuleHandle(nil));
Size := PImageOptionalHeader(Pointer(integer(Module) + PImageDosHeader(Module)._lfanew + SizeOf(dword) + SizeOf(TImageFileHeader))).SizeOfImage;
VirtualFreeEx(ProcessHandle, Module, 0, MEM_RELEASE);
NewModule := VirtualAllocEx(ProcessHandle, Module, Size, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(ProcessHandle, NewModule, Module, Size, BytesWritten);
CreateRemoteThread(ProcessHandle, nil, 0, EntryPoint, Module, 0, TID);
end;
Err, GetModuleHandle(nil) is going to be value for your process, not the target process. Even if the values happen to be the same (or even if they are not) VirtualFreeEx ing that memory out from under the process is a bad idea, it might be you know, in the middle of executing some code there. That's the first place I see that can cause a potential crash. But let's assume that works somehow. So you allocate some new memory to scribble your code in, which you do. But you haven't relocated if you've needed to, and you also directly use EntryPoint, again not relocated. Why don't you use one of the "easy" code injection methods like a window hook?
Here are some examples:
http://www.codeproject.com/KB/threads/winspy.aspx
They are in C++, but you seem capable of "Delphi-ifying" them.
A simplification of what you are doing currently can be achieved by writing a DLL containing the code you want to inject, and using LoadLibrary to load it (by way of CreateRemoteThread). You use VirtualAllocEx to allocate space for the DLL name, WriteProcessMemory to write it over, and GetModuleHandle("kernel32.dll") for the handle to use with CreateRemoteThread and GetProcAddress("LoadLibraryW") (or LoadLibraryA) to pass to CreateRemoteThread. You should definitely never release memory you haven't allocated like you're currently doing. Every process is guaranteed to have kernel32 loaded in the same place (even with ASLR), so by bootstrapping with LoadLibrary you avoid a lot of the issues you'd have to deal with to get something like your current code working reliably.

Resources