I'm writing joke program (makes strange sounds, where user use mouse or keyboard) in Delphi, which uses global hooks to capture mouse and keyboard events.
This is function responsible for handle this hook:
procedure MKHOOK(code: Integer;wp: wParam;lp: lParam); stdcall;
var
hh: HHOOK;
begin
PlaySound('fart.wav');
CallNextHookEx(hh,code,wp,lp);
end;
Hook starts, when Form is creating:
procedure TForm6.FormCreate(Sender: TObject);
begin
MH := SetWindowsHookEx(WH_MOUSE_LL,#MKHOOK,hInstance,0);
KH := SetWindowsHookEx(WH_KEYBOARD_LL,#MKHOOK,hInstance,0);
end;
Where MH and KH are type of HHOOK
Hook is deleted, when program ends:
procedure TForm6.FormDestroy(Sender: TObject);
begin
UnhookWindowsHookEx(MH);
UnhookWindowsHookEx(KH);
end;
Problems starts, when I try to run it there are strange things happends like:
Mouse and keyboard stopped working, I must use ctrl + alt + del to recover it
Some keys didn't working after program execution, when I stoped it (ctrl, alt, tab)
Some keys change their behavior for example, when I used arrows keys, they turn my screen view.
What is wrong with this code? Why it not works? Sorry for my English ;)
There are a few things wrong here. The most obvious:
The call to PlaySound is too expensive to make in a hook. It's liable to bring the system to its knees if you call PlaySound every time your hook executes.
Your hook signatures are wrong. They need to be functions that return LRESULT. The required return value is described in the documentation in both cases as follows: if nCode is less than zero, the hook procedure must return the value returned by CallNextHookEx.
You don't do any error checking. Always check API calls for errors. Error handling is discussed in the documentation for each API call.
The first parameter of CallNextHookEx is ignored so you may as well pass 0. And it is cleaner to use separate hook procedures in my view.
The hook functions have to look like this:
function MouseHook(code: Integer; wp: wParam; lp: lParam): LRESULT; stdcall;
begin
Result := CallNextHookEx(0, code, wp, lp);
end;
function KeyboardHook(code: Integer; wp: wParam; lp: lParam): LRESULT; stdcall;
begin
Result := CallNextHookEx(0, code, wp, lp);
end;
Obviously these hooks don't do anything yet, but let's walk before we try to run.
Install the hooks like this:
MH := SetWindowsHookEx(WH_MOUSE_LL, #MouseHook, hInstance, 0);
if MH = 0 then
// handle error
KH := SetWindowsHookEx(WH_KEYBOARD_LL, #KeyboardHook, hInstance, 0);
if KH = 0 then
// handle error
I think it's clear that you have not read the documentation in sufficient detail. These APIs are tricky to use. You need precision in your coding. I recommend that you read the documentation again more closely.
Related
I'm using the following routine to patch functions in the RTL.
procedure PatchCode(const AddrProc: Pointer; const CodeSize: NativeUInt;
const Code: Pointer);
var
OldProtect: Cardinal;
begin
VirtualProtect(AddrProc, CodeSize, PAGE_EXECUTE_READWRITE, OldProtect);
Move(Code^, AddrProc^, CodeSize);
VirtualProtect(AddrProc, CodeSize, OldProtect, OldProtect);
end;
However when I tweak my patch-methods their size changes causing code like this to break:
//PatchRedirect calls PatchCode internally
PatchRedirect(AddrGetMem,{codesize = }17, #RedirectGetMem, JUMPS_GETMEM);
Is there a way to determine the size of a method at compile-time or runtime? (either one is fine).
I'm hoping for a general solution, but
if it only works for asm routines that's fine for my purposes.
Use case
One use case for this is a faster version of FillChar.
99% of the time FillChar is used as a ZeroMem.
So I patch System.ZeroMem with:
xor r8,r8
jmp FastFillChar;
and I patch System.FillChar with
movzx R8,R8b
mov r9,$0101010101010101
imul r8,r9
jmp FastFillChar
That way I can make the FillChar a tiny bit faster for those 99% of cases.
Or it would if anyone bothered to actually use zeromem
Update
Thanks to Rudy I think I have a solution applicable to a limited subset.
Is there a way to get size of a procedure?
If you have access to the source code, yes.
Delphi puts the generated code of routines in the same order as it is declared in the implementation section.
As long as the destination code you are trying to patch and the source code you're getting your patches from are compiled with the same parameters of {$CODEALIGN n} there is no problem.
For Win32, the default value is 4 and the Win32 RTL is compiled with alignment 4.
The code alignment for the Win64 RTL is {$CodeAlign 16}.
As long as the code alignment in your code and the patch recipient matches it the following code will work fine:
ProcSize:= NativeInt(#Routine2) - NativeInt(#Routine1);
PatchCode(#Routine1, ProcSize, #System.Something);
Any alignment nops will only increase the size up to the next multiple of $CodeAlign and the destination code is aligned the same way so you should be fine.
Obviously Routine1 had better be really short, otherwise you'll run into trouble, perhaps it's a good idea to assert that #dest is not a naked jmp to some other routine before patching if ProcSize > $CodeAlign.
I once wrote a piece of patching code myself, which doesn't overwrite the entire function at all, but just a jump to it at the start of the procedure. The size of the old procedure is of little importance because of that. Additionally, the class remembers the original content, so you can also 'unhook' the procedure by restoring that code.
It's written a long time ago, and I didn't have to use it in a long time, so I hope it still works in a more modern environment.
unit BigProcHook;
interface
uses
Windows, sysUtils;
type
PHack = ^THook;
THook = packed record
OpCodeCall : Byte;
OFFTo : Integer;
OpCodeRet : Byte;
end;
TBackup = THook;
TBigProcHook = class
private
FOldProc, FNewProc: Pointer;
FBackupped: Boolean;
FHooked: Boolean;
FOriginal: TBackup;
procedure SetHooked(const Value: Boolean);
protected
procedure InstallHook(Hook: THook);
procedure OverwriteProc;
public
constructor Create(AOldProc, ANewProc: Pointer; Install: Boolean = True);
property Hooked: Boolean read FHooked write SetHooked;
end;
implementation
{ TBigProcHook }
constructor TBigProcHook.Create(AOldProc, ANewProc: Pointer;
Install: Boolean);
begin
inherited Create;
FOldProc := AOldProc;
FNewProc := ANewProc;
if Install then
SetHooked(True);
end;
procedure TBigProcHook.InstallHook(Hook: THook);
var
OldProtect: Cardinal;
begin
// Change protection of oldproc memory
if VirtualProtect(FOldProc, SizeOf(THook), PAGE_EXECUTE_READWRITE, OldProtect) then
try
if not FBackupped then
begin
Move(FOldProc^, FOriginal, SizeOf(THook));
FBackupped := True;
end;
// Overwrite the old procedure
Move(Hook, FOldProc^, SizeOf(THook));
finally
VirtualProtect(FOldProc, SizeOf(THook), OldProtect, OldProtect);
end
else
begin
RaiseLastOSError;
end;
end;
procedure TBigProcHook.OverwriteProc;
// Overwrites the first few calls of OldProc with a call to NewProc and a Ret.
var
Hook: THook;
begin
// Create a tiny little redirection
with Hook do begin
OpCodeCall := $E8; // = CALL}
OFFTo := PAnsiChar(FNewProc) - PAnsiChar(FOldProc) - 5;
OpCodeRet := $C3; // = RET
end;
InstallHook(Hook);
end;
procedure TBigProcHook.SetHooked(const Value: Boolean);
begin
// Toggle hook.
if FHooked <> Value then
if Value then
OverwriteProc
else
InstallHook(FOriginal);
FHooked := Value;
end;
initialization
end.
Which you can call like this: (in the example it's called in the initialization and finalization of a unit)
var
FHook: TBigProcHook;
initialization
FHook := TBigProcHook.Create(#ProcedureToReplace, #ReplacementProcedure);
finalization
FHook.Hooked := False;
FHook.Free;
Originally posted on the Dutch forum NLDelphi.com.
I have this piece of working code using AsyncCalls 2.99 in the modified version by Zarko Gajic:
function TForm1.DoIt(i:integer):integer;
begin
end;
procedure TForm1.Main;
//-------------------------------------------------------
procedure CallIt;
begin
TAsyncCalls.Invoke(
procedure
var i:integer;
begin
For i := 0 to 10 do
If i < 11
then TAsyncCalls.Invoke<integer>(DoIt,i));
end);
end;
//-------------------------------------------------------
begin
CallIt;
end;
Now I would like to move the function DoIt into Main to be a nested function next to CallIt:
procedure TForm1.Main;
//-------------------------------------------------------
function DoIt(i:integer):integer;
begin
end;
//-------------------------------------------------------
procedure CallIt;
begin
TAsyncCalls.Invoke(
procedure
var i:integer;
begin
For i := 0 to 10 do
If i < 11
then TAsyncCalls.Invoke<integer>(DoIt,i));
end);
end;
//-------------------------------------------------------
begin
CallIt;
end;
The above code (naturally) does not work. As much as I unterstand Invoke requires a method as parameter and a nested function isn't one.
Invoke expects a TAsyncCallArgGenericMethod:
class function Invoke<T>(Event: TAsyncCallArgGenericMethod<T>; const Arg: T): IAsyncCall; overload; static;
TAsyncCallArgGenericMethod<T> = function(Arg: T): Integer of object;
I have already received a hint to convert the TAsyncCallArgGenericMethod into a reference:
TAsyncCallArgGenericMethod<T> = reference to function(Arg: T): Integer;
Although I have the general notion (i.e. illusion) that I understand the concept I have not been able to produce working code.
Now I would like to move the function DoIt into Main to be a nested function next to CallIt:
You can not call nested function from outside the function containing it - because nested functions need to access the outer(containing) function local variables, that only exists while executing code inside that containing function.
Even if the particular nested function does not evaluate their rights of accessing those local variables - it has those rights and the compiler should be able to produce all the lo-level scaffolding for that.
Specifically in your snippet, You can not call TForm1.Main.DoIt from outside of the TForm1.Main itself. So you can not take the reference to it and pass it to some external body like AsyncCall dispatcher.
It does not depend upon whether you would use procedure of object or reference to procedure or any other type - it is the fundamental property of nested function that they "exist" only locally to the containing function and only can be run when the outer function runs. AsyncCall would most probably try to run the function when TForm1.Main would be exited and thus its local variables stack frame required by TForm1.Main.DoIt would not exist.
You have to find some other way to "pack" those functions together, nested functions would not do here.
For example one may try using Advanced Records here.
Try to arrange it somehow like that:
type
TForm1 = class(TForm)
....
private
type Dummy = record
procedure CallIt;
procedure DoIt(const i:integer);
end;
end;
....
//-------------------------------------------------------
procedure TForm1.Dummy.CallIt;
begin
TAsyncCalls.Invoke(
procedure
var i:integer;
begin
For i := 0 to 10 do
If i < 11
then TAsyncCalls.Invoke<integer>(DoIt,i));
end);
end;
procedure TForm1.Dummy.DoIt(const i:integer);
begin
end;
procedure TForm1.Main;
var d: Dummy;
begin
d.CallIt;
end;
Also, I think your approach is wrong here: you would instantly form many-many threads exhausting your OS resources.
I would suggest you using OmniThreadLibrary instead, where there are hi-level Parallel-Loop and Collection-Pipeline concepts. They would give you benefit of automatic threads pool management, so you would only have so many worker threads as your CPU can bear, adapting your program to any hardware it would happen to run on.
I may also have the illusion that I understand these things (i.e. I may be wrong) so take this with a pinch of salt, but this is my take on it.
A nested function has access to all parameters available to the calling function (including self), but has no 'hidden' parameters (it doesn't need any). The class function on the other hand has a hidden parameter (called 'self') that the function accesses to find the object that is actually calling the function. Thus the signatures are totally different.
If you go back to the olden days when C++ was an interpreter, something like Fred.Main( x, y) in C++ would be translated to something like Main( Fred, x, y) in C. I only include this to illustrate how that hidden parameter works.
So the upshot is you can't do what you are trying to do because by moving DoIt inside your Main function, you are completely changing its signature, and indeed how it works.
I just couldn't leave it at that since for some reason I really had sunk my teeth into it. Now, here's a solution. Not a solution I would recommend, but a solution.
There has been a discussion here on stackoverflow some 4 years ago. David quoted the documentation and continued:
If I recall correctly, an extra, hidden, parameter is passed to nested functions with the pointer to the enclosing stack frame. This is omitted in 32 bit code if no reference is made to the enclosing environment.
Sertaç Akyüz apparently poked around in the assembler code and reported:
It's an implicit parameter alright! The compiler assumes it has its thing in 'rcx' and the parameters to the function are at 'rdx' and 'r8', while in fact there's no 'its thing' and the parameters are at 'rcx' and 'rdx'.
This seemed to finish the whole thing.
But then, there is this text: How to pass a nested routine as a procedural parameter (32 bit). A rather surprising title if you consider the documentation. This led to the following code:
{unit AsyncCalls;}
TAsyncCalls = class(TObject)
private
type
…
//TAsyncCallArgGenericMethod<T> = function(Arg: T): Integer of object;
TAsyncCallArgGenericMethod<T> = reference to function(Arg: T): Integer;
uses … ,AsyncCalls,AsyncCallsHelper;
procedure TForm1.Main;
//-------------------------------------------------------
function DoIt(i:integer):integer;
begin
Result := i;
end;
//-------------------------------------------------------
procedure CallIt;
var p:Pointer;
begin
p := #DoIt;
TAsyncCalls.Invoke(
procedure
var i:integer;
begin
For i := 0 to 10 do
If i < 11 then
AsyncHelper.AddTask(TAsyncCalls.Invoke<integer>(p,i));
end);
end;
//-------------------------------------------------------
begin
CallIt;
end;
This code works. As I mentioned before, I wouldn't recommend using it, but it works. I learned a lot in the course of finding a solution which I now consider the main benefit.
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
There's something I don't understand about waitable timers. I searched online and read specs on MSDN plus whatever I could find on stackoverflow (such as link) but my timers fire almost immediately after the PC is suspended.
To focus on the problem I wrote a small test app using XE5 (64-bit) in Windows 7 and tried to duplicate the project found here: http://www.codeproject.com/Articles/49798/Wake-the-PC-from-standby-or-hibernation
I thought the problem was the way I derive time but I can't seem to find the problem.
The test app looks like this (very simple):
I declare the thread type as follows
type
TWakeupThread = class(TThread)
private
FTime: LARGE_INTEGER;
protected
procedure Execute; override;
public
constructor Create(Time: LARGE_INTEGER);
end;
...
constructor TWakeupThread.Create(Time: LARGE_INTEGER);
begin
inherited Create(False);
FreeOnTerminate:=True;
FTime:=Time;
end;
procedure TWakeupThread.Execute;
var
hTimer: THandle;
begin
// Create a waitable timer.
hTimer:=CreateWaitableTimer(nil, True, 'WakeupThread');
if (hTimer <> 0) then
begin
//CancelWaitableTimer(hTimer);
if (SetWaitableTimer(hTimer, FTime.QuadPart, 0, nil, nil, True)) then
begin
WaitForSingleObject(hTimer, INFINITE);
end;
CloseHandle(hTimer);
end;
end;
Now when the "Set timer" button is clicked I calculate file time this way and create the thread.
procedure TForm1.btnSetTimerClick(Sender: TObject);
var
iUTCTime : LARGE_INTEGER;
SysTime : _SystemTime;
FTime : _FileTime;
hHandle : THandle;
dt : TDateTime;
begin
ReplaceDate(dt,uiDate.DateTime);
ReplaceTime(dt,uiTime.DateTime);
DateTimeToSystemTime(dt, SysTime);
SystemTimeToFileTime(SysTime, FTime);
LocalFileTimeToFileTime(FTime, FTime);
iUTCTime.LowPart := FTime.dwLowDateTime;
iUTCTime.HighPart := FTime.dwHighDateTime;
TWakeupThread.Create(iUTCTime);
end;
This does not work. The timer seems to fire less then 2 minutes after the system enters suspend mode regardless of the amount of time selected. Any pointers as to what I am doing wrong would be appreciated.
EDIT
Found this interesting command line tool that let's us inspect the waitable timers. From command you can "see" the state of your waitable timers by typing:
powercfg -waketimers
I can use this to confirm that my timers are being set properly. I can also use this to confirm that my timers are still running when the PCs prematurely wake-up.
Using the same tool you can get a list of devices that are able to wake from hibernation (mouse, keyboard, network in my case):
powercfg -devicequery wake_armed
On all systems tested, the command "powercfg -lastwake" returns the following which I do not know how to decipher:
Wake History Count - 1
Wake History [0]
Wake Source Count - 0
I enabled both sleep and hibernate in Windows and both will wake up after a few seconds. There's no keyboard / mouse activity and we don't have devices sending WOL (wake-on-lan) requests to these PCs.
I'm wondering if there's something special I need to do when calling SetSuspendState; here's my code:
function SetSuspendState(Hibernate, ForceCritical, DisableWakeEvent: Boolean): Boolean;
//Hibernate = False : system suspends
//Hibernate = True : system hibernates
begin
if not Assigned(_SetSuspendState) then
#_SetSuspendState := LinkAPI('POWRPROF.dll', 'SetSuspendState');
if Assigned(_SetSuspendState) then
Result := _SetSuspendState(Hibernate, ForceCritical, DisableWakeEvent)
else
Result := False;
end;
function LinkAPI(const module, functionname: string): Pointer;
var
hLib: HMODULE;
begin
hLib := GetModuleHandle(PChar(module));
if hLib =0 then
hLib := LoadLibrary(PChar(module));
if hLib <> 0 then
Result := getProcAddress(hLib, PChar(functionname))
else
Result := nil;
end;
procedure TForm1.btnSuspendClick(Sender: TObject);
begin
SetSuspendState(True, False, False);
end;
The problem was not related to delphi or the code in any way. The problem was created by a Windows 7 feature that enables more than magic packets when WOL is enabled. Forcing Windows to only listen for magic packets solved the problem.
MS Link: http://support.microsoft.com/kb/941145
Thank you to everyone who tried to help and especially to David Heffernan who alluded to the possibility that something else was waking up the PC.
I'm not quite sure how to even ask this question, since I don't know whether it is related to the execution time, application process.message procedure or anything else.
I'm having (for me) weird situations, where the procedure fails to run and raises system exception on run, while it runs completely flawless if I put "showmessage" there in between (which I put so that I could quickly see what's going on in between. I prefer that way over watches somehow...).
I'm not sure whether the code matters or not, but I'll give it below:
procedure LoadSettings;
var SettingsBuffToLoad: TStringList;
begin
SettingsBuffToLoad:=TStringList.Create;
Encoding:=TEncoding.ANSI;
SettingsBuffToLoad.LoadFromFile('bin/settings.txt', Encoding);
// showmessage(settingsbufftoload.Strings[0]);
SettingsBuffer:=Decode(SettingsBuffToLoad);
// showmessage(settingsbuffer.Strings[0]); //decode
end;
The Decode procedure is declared as external and is read from the dll.
If I just remove those "/" , so that it becomes the code instead of comment, it works just fine. However, set as you see now, it raises exception, but after the procedure is already done. (the debugger last break point is stopped at "end;", after continuing however it raises exception instead of showing the form; this procedure is called as the last thing in FormCreate procedure.
Is there anything that has to do with the timing, which ShowMessage solves, or...? :/
Update:
The decode functions, as asked:
this is how it's declared, right above of the implementation and variables of the form:
function Decode(Buff: TStringList): TStringList; StdCall; external 'bin\settings.txt';
And this is in the dll:
function Decode(Buff: TStringList): TStringList; export;
var
t, u, h: integer;
s: String;
begin
DecodeBuffer.Clear;
DecodeBuffer:=Buff;
for h := 0 to DecodeBuffer.Count-1 do
begin
s := DecodeBuffer.Strings[h];
t := Length(s);
if t > 0 then
begin
for u := 0 to t-1 do
begin
s[u+1] := DecodeChar(s[u+1], (h mod 5) + 1);
end;
DecodeBuffer.Strings[h] := s;
end;
end;
Result:=DecodeBuffer;
end;
This code was discussed in a question at Delphi changing Chars in string - missunderstood behavior - XE3 and is used from Remy's answer. The DecodeChar is, I believe simply unimportant here, or is it?
Also, the same goes with the function to save settings, which is called at FormClose event:
This is:
procedure TScribbles.SaveSettings;
var SettingsBuffToSave: TStringList;
begin
SettingsBuffToSave:=TStringList.Create;
Encoding := TEncoding.ANSI;
// Showmessage(settingsbuffer.Strings[0]);
SettingsBuffToSave:=Encode(SettingsBuffer);
// Showmessage(settingsbufftosave.Strings[0]);
SettingsBuffToSave.SaveToFile('bin/settings.txt', Encoding);
end;
With the first ShowMessage used as code instead of comment, it works, while otherwise in a comment function as it is written above, it calls external exception the same way as on Decode.
Is it possible, that the SettingsBuffToSave is just not yet created when it already calls the function Encode, or what?
At that time, the SettingsBuffer exists and is populated, so it really seems weird that it raises errors, which disappears with simply putting ShowMessage in there.
(Function Encode is basically a mirror of Decode, so the code is not important here...)
This code is VERY VERY VERY dangerous on many levels. Using objects across the DLL boundary in an unsafe manner. Mismanagement of object pointers across function calls. You need a redesign. Try the following as a start:
procedure Decode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; export;
var
u: integer;
begin
for u := 0 to BuffLen-1 do
begin
Buff^ := DecodeChar(Buff^, (ListIndex mod 5) + 1);
Inc(Buff);
end;
end;
procedure Encode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; export;
var
u: integer;
begin
for u := 0 to BuffLen-1 do
begin
Buff^ := EncodeChar(Buff^, (ListIndex mod 5) + 1);
Inc(Buff);
end;
end;
procedure Decode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; external '...';
procedure Encode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; external '...';
procedure LoadSettings;
var
h: Integer;
begin
SettingsBuffer := TStringList.Create;
SettingsBuffer.LoadFromFile('bin/settings.txt', TEncoding.ANSI);
for h := 0 to SettingsBuff.Count-1 do
begin
Decode(PChar(SettingsBuff[h]), Length(SettingsBuff[h]), h);
end;
end;
procedure TScribbles.SaveSettings;
var
h: Integer;
begin
for h := 0 to SettingsBuff.Count-1 do
begin
Encode(PChar(SettingsBuff[h]), Length(SettingsBuff[h]), h);
end;
SettingsBuff.SaveToFile('bin/setpb95enc.dll', TEncoding.ANSI);
end;
The obvious problem here is that the code exists in a DLL. Most likely you didn't arrange for the DLL to share its host's heap. And a Delphi class cannot be passed across a DLL boundary.
If you want to share Delphi classes between modules, you must use packages. Of course, another option is to put all the code in the same module. That is remove the DLL, and compile everything in the executable. The final option is to use valid interop types for DLLs.
Of course, there could be other reasons for the actual error. The code smells bad. For instance, what is this:
DecodeBuffer:=Buff;
Is DecodeBuffer a global variable? If so then it is plausible that you refer to the object after it has been destroyed. Not that I can see evidence of anything being destroyed. Without wishing to seem rude, your code looks like it may have multiple problems. As a matter of urgency you need to:
Deal with the DLL problem described above.
Remove global variables.
Fix lifetime issues. Stop leaking.
Enable range checking to locate buffer overruns.
Add FastMM in debug mode to try to catch heap corruptions.
I think I know what's going on here: I think your stack is getting smashed.
Furthermore, I rather suspect the actual cause is the Decode procedure using an uninitialized variable. Your ShowMessage statement (it would be the first one that matters if I'm right) changes what's on the stack and thus changes the uninitialized variable.
If I'm right this is going to have some heisenbug attributes--anything you do to find out what's going on will change the value of the uninitialized variable.
One thing to try: Declare a large local variable (the idea is to use up stack space) and make sure it's not discarded by the compiler. This will move things in memory and thus likely defuse the blowup. If it works it's pretty conclusive at to what's going on.