Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 325
  • Last Modified:

Mouse Hook: how to access DLL variables inside the callback proc?

I have a DLL for a system-wide mouse hook, so I can capture right mouse clicks. Everything works, but I want to process the normal click (Result := CallNextHookEx(HookHandle, Code, Msg, MouseHook);
) if some condition isn't met.. like if the current active window contains some tittle.

So I forward a string to the DLL, which is received just fine (tested). The problem is I can't access it within the callback proc, it's empty.

How can I access it?

by callback I mean the proc that is defined here:

SetWindowsHookEx(WH_MOUSE, MouseHookCallBack, HInstance, 0)

I have that proc in the DLL, MouseHookCallBack which checks which button was pressed and posts a message to the application using the DLL.
0
bryan7
Asked:
bryan7
  • 4
  • 3
  • 2
1 Solution
 
bryan7Author Commented:
Just noticed another weird problem.

I added a string var to the init proc of the DLL, which seems to be fine (showmessage(.. ) ) shows it just fine.

so I have a string variable in the DLL, and if I assign that string to the var, when I finish my application it still remains open and I must close it from task manager.

0
 
MerijnBSr. Software EngineerCommented:
Try storing it as a pchar in the DLL rather then a string.


var DataStr: string;
    Data: pchar;
begin
 Data := StrAlloc(Length(DataStr) + 1);
 StrPCopy(Data, DataStr);
 ... // now your string is stored in Data, can be casted to string where needed: ShowMessage(string(Data));
 StrDispose(Data);
end;
0
 
bryan7Author Commented:
Thanks! That solved the 2nd problem.

It still doesn't work from inside the callback procedure tho, the variable is empty there.
0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
ZhaawZSoftware DeveloperCommented:
As far as I know, there's no problem with accessing variable from callback procedure. The problem is that DLL is loaded into every process, and each instance has it's own memory scope (which means that each process has it's own variables). So if you assign value to variable VAR in process PROC1, you will be able to read the same value from VAR in PROC1, but in PROC2 that value will not be accessible (because PROC2 has its own VAR).


I've read that memory mapped files are often used to solve this problem when using hooks; unfortunately I have never used these MMF, so I have no example for you.


Another solution would be to write your values to a HDD in your main application* and then read this file from your hook procedure - in this case each involved process will have to read that file, so I'm not sure if this is good solution. Another problem could be with filename - I think you would have to use absolute path to filename (not sure about this though; if you specify relative filename, app could look for it either in directory with .exe of that process, or in directory with your .dll, I don't know which is used...).

var
  FileLoaded : boolean = false;
  FileContent : pchar; /* or "array of byte" or whatever you need */

function CallbackProc (nCode, wParam, lParam : integer) : integer; stdcall;
begin
if not FileLoaded then begin
  /* read file contents */
  FileLoaded := true;
end;
/* do whatever you want in your callback proc */
end;


One more solution would be to somehow read values directly from main application. I'll give an example of sending data between processes in my next post.

-------
* main application - application that sets hook (SetWindowsHookEx)
0
 
ZhaawZSoftware DeveloperCommented:
MAIN APPLICATION -- the one that sets hook and "hosts" data
-------------------------------------------------
unit Unit1;

interface

uses
  Windows, Messages, Classes, Controls, Forms;

const
  WM_CustomReadSize = WM_USER + 1;
  WM_CustomReadData = WM_USER + 2;

type
  TMainApplication = class(TForm)
    procedure WMCustomReadSize (var msg : TMessage); message WM_CustomReadSize;
    procedure WMCustomReadData (var msg : TMessage); message WM_CustomReadData;
  end;

var
  MainApplication: TMainApplication;
  data : pchar = 'some string';

implementation

{$R *.dfm}

procedure TMainApplication.WMCustomReadSize (var msg : TMessage);
begin
// return size of "data" (length of string + NULL char)
msg.Result := lstrlen(data) + 1;
end;

procedure TMainApplication.WMCustomReadData (var msg : TMessage);
begin
// return pointer to data
// data is pchar, i.e., it already is pointer, so use integer(data)
msg.Result := integer(data);
end;

end.


SECONDARY APPLICATION - the one that "reads" data (reading data should be done in CallbackProc in your case, probably only 1 time per process)
-------------------------------------------------
unit Unit1;

interface

uses
  Windows, Messages, Classes, Controls, Forms;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

const
  WM_CustomReadSize = WM_USER + 1;
  WM_CustomReadData = WM_USER + 2;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  wnd : cardinal;
  size : integer;
  data : pchar;
  pid : cardinal;
  proc : cardinal;
begin
wnd := FindWindow('TMainApplication', 'Window of Main Application');
if wnd = 0 then exit; // couldn't find the window
GetWindowThreadProcessId(wnd, @pid);
proc := OpenProcess(PROCESS_ALL_ACCESS, false, pid);
size := SendMessage(wnd, WM_CustomReadSize, 0, 0);
GetMem(data, size);
ReadProcessMemory(proc, pointer(SendMessage(wnd, WM_CustomReadData, 0, 0)), @data[0], size, cardinal(nil^));
CloseHandle(proc);
MessageBox(0, data, 'Information', MB_ICONINFORMATION);
FreeMem(data);
end;

end.
0
 
MerijnBSr. Software EngineerCommented:
you should make use of the sharemem unit.

See from the help (read this carefully!):

Shared-Memory Manager (Win32 Only)
On Win32, if a DLL exports routines that pass long strings or dynamic arrays as parameters or function results (whether directly or nested in records or objects), then the DLL and its client applications (or DLLs) must all use the ShareMem unit. The same is true if one application or DLL allocates memory with New or GetMem which is deallocated by a call to Dispose or FreeMem in another module. ShareMem should always be the first unit listed in any program or library uses clause where it occurs.
 
ShareMem is the interface unit for the BORLANDMM.DLL memory manager, which allows modules to share dynamically allocated memory. BORLANDMM.DLL must be deployed with applications and DLLs that use ShareMem. When an application or DLL uses ShareMem, its memory manager is replaced by the memory manager in BORLANDMM.DLL.
0
 
bryan7Author Commented:
ZhaawZ: I can't get that to work, I get some weird characters instead... IF I change the value of data. However using a pre-defined string seems to work. How should I change the value of data in the app? I've tried data:= PChar( ...     StrPCopy(data,..

Thanks!

MerijnB: What doesn't work is reading variables from inside the DLL itself, from the mouse hook callback proc.
0
 
ZhaawZSoftware DeveloperCommented:
And here comes a problem... ;)

I don't work with strings (i.e., Delphi type "string") much, therefore I don't know what would be the best way to do that. But you may try this:

procedure TMainApplication.FormCreate(Sender: TObject);
begin
GetMem(data, length(caption) + 1); // don't forget "+1" part - it's needed for NULL char
lstrcpy(data, pchar(caption)); // copy data
end;

procedure TMainApplication.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeMem(data); // release memory
end;

-------------------------------------------------

If you're reading that text from TEdit or TMemo, you may avoid using "string" type completely.

procedure TMainApplication.FormCreate(Sender: TObject);
var
  len : integer;
begin
// get length of text in Memo1
len := SendMessage(Memo1.Handle, WM_GETTEXTLENGTH, 0, 0) + 1; // don't forget the "+ 1" part
// get memory for "data"
GetMem(data, len);
// get contents of Memo1
SendMessage(Memo1.Handle, WM_GETTEXT, len, integer(data)); //
end;

procedure TMainApplication.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// release memory
FreeMem(data);
end;



P.S. I wrote a ``don't forget the "+ 1" part`` there - it's for NULL char. This char terminates the string, so for text "one two three" you would need 14 bytes of memory (13 bytes for "one two three" and 1 byte for NULL char).
0
 
bryan7Author Commented:
Thanks!
0

Featured Post

Get your problem seen by more experts

Be seen. Boost your question’s priority for more expert views and faster solutions

  • 4
  • 3
  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now