Need full control over SendMessageA's LPARAM

 

I need to use SendMessage with a whole array of messages; some of them have the normal use of lparam ; but in other cases I want lparam to be a pointer to a string that has to be sent to target window.

That means that sometimes I need

int SendMessageA(int hWnd,int Msg,int wParam,int lParam);

and other times I need

int SendMessageA(int hWnd,int Msg,int wParam,string lParam);


Now how do I send my pointer to string since I can't obviously import SendMessageA under different internal names?

 
TheEconomist:

[...] Now how do I send my pointer to string since I can't obviously import SendMessageA under different internal names?

My WinFile.mqh library (https://www.mql5.com/en/forum/118999) uses a well-known workaround for this for this sort of thing. See the last section of the overview notes in the code. There are limits to how far you can go with this; there comes a point where there's no alternative to using a custom DLL as a bridge between MQ4 and the Windows API.

 

Problem changed a bit since 20 minutes ago.

I can import SendMessage and use it for numeric messages, and use the string version with SendMessageA.

However, it sends quite nothing. The array pointed seems to contain nothing (output is a series of null). 

   #import "user32.dll"
     int RegisterWindowMessageA(string s);
     int SendMessage(int hWnd,int Msg,int wParam,int lParam);
     int SendMessageA(int hWnd,int Msg,int wParam,string lParam);
   #import


   ....
   string b="                                                                    ";
   b=AccountCompany();
   Print(b);
   Print("111=",SendMessageA(SERVERHANDLE,WM_USER+SPECACCOUNTBROKERMSG,0,b));

target application:





type CharArray=array[0..255] of byte;

type PChar=^CharArray;

....

if (amessage=WM_USER+SPECACCOUNTBROKERMSG) then
      begin
        s:=PChar(lparam);        
        assign(f,'c:\test.txt');
        rewrite(f);
        writeln(f,'Rough:');
        for i:=0 to 5 do write(f,chr(s^[i]));
        close(f);
        WindowProc:=111;
        exit;
      end;
 
TheEconomist:

Problem changed a bit since 20 minutes ago.

[...] target application:

You can't use SendMessage() to give one process the address of some memory within another process. If you could do this, then it would destroy process security, and also some aspects of process stability.


Your simplest alternative mechanism is going to be something like writing to a pre-agreed file on disk, and then sending a message which means "read the new contents from the file".

 

Yeah normally the target app should have blown up with a 216 "General Protection Fault" like in the old good times that it belongs to after all...

It's a good idea. These days I struggled to use GlobalAlloc like in the old times, but the dll that was supposed to work with the main program (dll done in Dev-Pascal too) was unable to get a lock. GlobalLock failed all the time, no matter what flag combination I used. That brought me to message communication..

Ok, will do the file version. Thx!

 
TheEconomist:

These days I struggled to use GlobalAlloc like in the old times [...]

That's quite some "old times". I don't think GlobalAlloc() has been usable in this way since Windows 3.x.


Ok, will do the file version. Thx!

One extra option, if you're dealing with multiple instances of MT4, is to have a pre-agreed directory but allocate an integer ID for each message which gets sent (e.g. just a random number). The filename for the message then includes the ID. And you send the ID as the lparam so that the receiving process knows which file to look at (and then deletes it when finished reading).

 

The target app will work with more EAs. But one of the messages will return a busy status, and if I think better, I should also use an EA magic. When an EA locks in the server will answer "busy" to all the others. The EA magic will be an addon, to also know which EA is allowed to work with at a given time.

About the GlobalAlloc issue, it's very daunting. The server allocated memory with GHND (GMEM_MOVEABLE || GMEM_ZEROINIT) or with GMEM_MOVEABLE alone, could lock in, and write in the pointer. From any other place I tried to lock with GlobalLock, even if it was locked or not from the server, always got a GlobalLock fail ( 0 ), with a GetLastError of 6, invalid handler. Normally invalid handler should happen only after a GlobalFree... But I didn't understand why for two days and dumped it...

 
TheEconomist:

The target app will work with more EAs. But one of the messages will return a busy status, and if I think better, I should also use an EA magic. When an EA locks in the server will answer "busy" to all the others. The EA magic will be an addon, to also know which EA is allowed to work with at a given time.

You've obviously also got the 32 bits of wparam to play with. Basically, you can pass up to 64 bits of information (wparam + lparam) without life becoming complicated.


Another alternative would be to talk to the server application using HTTP. That gives you the potential to scale the whole thing out across multiple computers. There's been some recent discussion of this, from a different perspective, in https://www.mql5.com/en/forum/124014.


About the GlobalAlloc issue, it's very daunting. The server allocated memory with GHND (GMEM_MOVEABLE || GMEM_ZEROINIT) or with GMEM_MOVEABLE alone, could lock in, and write in the pointer. From any other place I tried to lock with GlobalLock, even if it was locked or not from the server, always got a GlobalLock fail ( 0 ), with a GetLastError of 6, invalid handler. Normally invalid handler should happen only after a GlobalFree... But I didn't understand why for two days and dumped it...

I would have guessed I was the only person on this forum who'd ever used GlobalAlloc() in this way. It hasn't worked since the days of 16-bit processes and shared address spaces. See http://msdn.microsoft.com/en-us/library/aa366596(VS.85).aspx

 

Isn't wparam a word (16 bits) ? Thus making 16+32 = 48 bits ?

The server has to work on one computer, so no need to complicate even more...

This are the applications (original versions, Dev-Pas ):

server:

Program OrderCopyMain;
uses Crt;



const

      GMEM_FIXED          =$0000;
      GMEM_MOVEABLE       =$0002;
      GMEM_NOCOMPACT      =$0010;    //obsolete
      GMEM_NODISCARD      =$0020;    //obsolete
      GMEM_ZEROINIT       =$0040;
      GMEM_MODIFY         =$0080;
      GMEM_DISCARDABLE    =$0100;    //obsolete
      GMEM_NOT_BANKED     =$1000;    //obsolete
      GMEM_SHARE          =$2000;    //obsolete
      GMEM_DDESHARE       =$2000;    //obsolete
      GMEM_NOTIFY         =$4000;    //obsolete
      GMEM_LOWER          =GMEM_NOT_BANKED;   //obsolete
      GMEM_DISCARDED      =$4000;
      GMEM_LOCKCOUNT      =$00ff;
      GMEM_INVALID_HANDLE =$8000;

      GHND                =$0042;//GMEM_MOVEABLE or GMEM_ZEROINIT;
      GPTR                =$0040;//GMEM_FIXED or GMEM_ZEROINIT;


type RecordCollectionStruct=record                           
                            Available:boolean;
                            LastUpdate:longint;
                            Data:string;
                            end;

type CharArray=array[0..255] of byte;

type hglobal=longint;

type PCollection=^RecordCollectionStruct;

var Collection:PCollection;
var hmem,hfree:hglobal;
var dereferred:boolean;

function GetTickCount:dword;external 'KERNEL32.DLL';
function GlobalAlloc(uFlags:longint;dwBytes:dword):hglobal; external 'KERNEL32.DLL';
function GlobalFree(ghmem:hglobal):hglobal; external 'KERNEL32.DLL';
function GlobalLock(hmem:hglobal):pointer; external 'KERNEL32.DLL';
function GlobalUnlock(hmem:hglobal):boolean; external 'KERNEL32.DLL';
function GetLastError:dword; external 'KERNEL32.DLL';

begin
Collection:=nil;
ClrScr;
WriteLn('Allocating memory...');
hmem:=GlobalAlloc(GHND + GMEM_MODIFY,sizeof(RecordCollectionStruct));
if (hmem<>0) then begin
                  WriteLn('My handler : ',hmem,' -> THIS IS EA PARAMETER!');
                  end
             else begin
                  WriteLn('GlobalAlloc() failed.');
                  WriteLn('Error code : ',GetLastError);
                  WriteLn('See ya next time!');
                  Readkey;
                  halt;
                  end;
WriteLn('Locking memory...');
Collection:=GlobalLock(hmem);

if (Collection=nil) then begin
                          WriteLn('GlobalLock() failed.');
                          WriteLn('Error code : ',GetLastError);
                          WriteLn('Unallocating memory...');
                          hfree:=GlobalFree(hmem);
                          if (hfree<>0) then begin
                                        WriteLn('F$%!.. couldn`t free memory.');
                                        WriteLn('Error code = ',GetLastError);
                                        WriteLn('Anyway...screw it...');
                                        end;
                          Readkey;
                          halt;
                          end;

WriteLn('Locked pointer ',dword(Collection) );

Collection^.Available:=false;
Collection^.LastUpdate=GetTickCount;
Collection^.Data='xxx';
Collection^.Available:=true;

WriteLn('You can use it now ... or later (it is locked now)');
readkey;

WriteLn('Unlocking memory...');
dereferred:=GlobalUnlock(hmem);
if (dereferred=false) then begin
                           WriteLn('GlobalUnlock() failed.');
                           WriteLn('Error code = ',GetLastError() );
                           end;
WriteLn('waiting for you to use... (it's not locked now)');
readkey;

WriteLn('Unallocating memory...');
hfree:=GlobalFree(hmem);
if (hfree<>0) then begin
                   WriteLn('F$%!.. couldn`t free memory.');
                   WriteLn('Error code = ',GetLastError);
                   Readkey;
                   end;
end.

and the tester

program test;


const

      GMEM_FIXED          =$0000;
      GMEM_MOVEABLE       =$0002;
      GMEM_NOCOMPACT      =$0010;
      GMEM_NODISCARD      =$0020;
      GMEM_ZEROINIT       =$0040;
      GMEM_MODIFY         =$0080;
      GMEM_DISCARDABLE    =$0100;
      GMEM_NOT_BANKED     =$1000;
      GMEM_SHARE          =$2000;
      GMEM_DDESHARE       =$2000;
      GMEM_NOTIFY         =$4000;
      GMEM_LOWER          =GMEM_NOT_BANKED;
      GMEM_DISCARDED      =$4000;
      GMEM_LOCKCOUNT      =$00ff;
      GMEM_INVALID_HANDLE =$8000;

      GHND                = GMEM_MOVEABLE or GMEM_ZEROINIT;
      GPTR                = GMEM_FIXED or GMEM_ZEROINIT;


type RecordCollectionStruct=record
                            Available:boolean;
                            LastUpdate:dword;
                            Data:string;
                            end;


type CharArray=array[0..255] of byte;

type hglobal=longint;casttype=array[0..3] of byte;

type PCollection=^RecordCollectionStruct;

function GetTickCount:dword;external 'KERNEL32.DLL';
function GlobalAlloc(uFlags:longint;dwBytes:dword):hglobal; external 'KERNEL32.DLL';
function GlobalFree(ghmem:hglobal):hglobal; external 'KERNEL32.DLL';
function GetLastError:dword; external 'KERNEL32.DLL';
function GlobalLock(hmem:hglobal):pointer; external 'KERNEL32.DLL';
function GlobalUnlock(hmem:hglobal):boolean; external 'KERNEL32.DLL';

var col:pcollection;hmem:hglobal;
var addr:dword;
var m:integer;
var cast:casttype;


begin
WriteLn('Give method : ');
ReadLn(m);
if m=1
   then begin
   WriteLn('Give memory handler : ');
   Readln(hmem);
   cast:=casttype(hmem);
   WriteLn(cast[0],' ',cast[1],' ',cast[2],' ',cast[3]);
   WriteLn('Attempting with hmem= ',hmem);
   col:=GlobalLock(hmem);
   if col=nil then begin
                   WriteLn('Error= ',GetLastError);
                   Readln;
                   halt;
                   end;
   WriteLn(col^.LastUpdate);
   WriteLn(col^.AccountsNo);
   //col^.Available:=False;
   //col^.LastUpdate:=GetTickCount;
   //col^.Available:=True;
   WriteLn(GlobalUnlock(hmem));
   ReadLn;
   end
   else begin
   WriteLn('Give pointer : ');
   Readln(addr);
   WriteLn('Attempting with hmem= ',addr);
   col:=PCollection(addr);
   WriteLn(col^.LastUpdate);
   WriteLn(col^.AccountsNo);
   //col^.Available:=False;
   //col^.LastUpdate:=GetTickCount;
   //col^.Available:=True;

   Readln;
   end;

end.

Whatever you do, it fails. First method gently. Second one bad. Second one fails even if you attempt GPTR or GMEM_FIXED.

Couldn't find anything to explain this.

 
TheEconomist:

Isn't wparam a word (16 bits) ?

Not since 1995. WPARAM is defined as UINT_PTR; UINT_PTR is a __int3264; and __int3264 depends on whether you're running a 64-bit version of Windows. See, for example, http://blogs.msdn.com/oldnewthing/archive/2003/11/25/55850.aspx

 
Does it depend on Windows or on compiler ? I mean, if a compiler is 32 bit it will expect 32 bit parameters, regardless of Windows' generosity.
Reason: