How do i get the EXCEPTION_POINTERS, i.e. both:
PEXCEPTION_RECORD and
PCONTEXT
data during an EExternal exception?
Background
When Windows throws an exception, it passes a PEXCEPTION_POINTERS; a pointer to the exception information:
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
When Delphi throws me an EExternal exception, it only contains half that information, the PEXCEPTION_RECORD only:
EExternal = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
How, during an EExternal exception, do i get both?
Example Usage
i am trying to write a Minidump using MiniDumpWriteDump function from Delphi.
The function has a few optional parameters:
function MiniDumpWriteDump(
hProcess: THandle; //A handle to the process for which the information is to be generated.
ProcessID: DWORD; //The identifier of the process for which the information is to be generated.
hFile: THandle; //A handle to the file in which the information is to be written.
DumpType: MINIDUMP_TYPE; //The type of information to be generated.
{in, optional}ExceptionParam: PMinidumpExceptionInformation; //A pointer to a MINIDUMP_EXCEPTION_INFORMATION structure describing the client exception that caused the minidump to be generated.
{in, optional}UserStreamParam: PMinidumpUserStreamInformation;
{in, optional}CallbackParam: PMinidumpCallbackInformation): Boolean;
At a basic level i can omit the three optional parameters:
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFileHandle,
nil, //PMinidumpExceptionInformation
nil,
nil);
and it succeeds. The downside is that the minidump is missing the exception information. That information is (optionally) passed using the 4th miniExceptionInfo parameter:
TMinidumpExceptionInformation = record
ThreadId: DWORD;
ExceptionPointers: PExceptionPointers;
ClientPointers: BOOL;
end;
PMinidumpExceptionInformation = ^TMinidumpExceptionInformation;
This is good, except i need a way to get at the EXCEPTION_POINTERS that is supplied by Windows when an exception happens.
The TExceptionPointers structure contains two members:
EXCEPTION_POINTERS = record
ExceptionRecord : PExceptionRecord;
ContextRecord : PContext;
end;
i know that Delphi's EExternal exception is the base of all "Windows" exceptions, and it contains the needed PExceptionRecord:
EExternal = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
But it doesn't contain the associated ContextRecord.
Isn't PEXCEPTION_RECORD good enough?
If i try to pass the EXCEPTION_POINTERS to MiniDumpWriteDump, leaving ContextRecord nil:
procedure TDataModule1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
var
ei: TExceptionPointers;
begin
if (E is EExternal) then
begin
ei.ExceptionRecord := EExternal(E).ExceptionRecord;
ei.ContextRecord := nil;
GenerateDump(#ei);
end;
...
end;
function GenerateDump(exceptionInfo: PExceptionPointers): Boolean;
var
miniEI: TMinidumpExceptionInformation;
begin
...
miniEI.ThreadID := GetCurrentThreadID();
miniEI.ExceptionPointers := exceptionInfo;
miniEI.ClientPointers := True;
MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFileHandle,
#miniEI, //PMinidumpExceptionInformation
nil,
nil);
end;
Then the function fails with error 0x8007021B
Only part of a ReadProcessMemory or WriteProcessMemory request was completed
What about SetUnhandledExceptionFilter?
Why don't you just use SetUnhandledExceptionFilter and get the pointer you need?
SetUnhandledExceptionFilter(#DebugHelpExceptionFilter);
function DebugHelpExceptionFilter(const ExceptionInfo: TExceptionPointers): Longint; stdcall;
begin
GenerateDump(#ExceptionInfo);
Result := 1; //1 = EXCEPTION_EXECUTE_HANDLER
end;
Problem with that is that the unfiltered exception handler only kicks in if the exception is unfiltered. Because this is Delphi, and because because i handle the exception:
procedure DataModule1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
var
ei: TExceptionPointers;
begin
if (E is EExternal) then
begin
//If it's EXCEPTION_IN_PAGE_ERROR then we have to terminate *now*
if EExternal(E).ExceptionRecord.ExceptionCode = EXCEPTION_IN_PAGE_ERROR then
begin
ExitProcess(1);
Exit;
end;
//Write minidump
...
end;
{$IFDEF SaveExceptionsToDatabase}
SaveExceptionToDatabase(Sender, E);
{$ENDIF}
{$IFDEF ShowExceptionForm}
ShowExceptionForm(Sender, E);
{$ENDIF}
end;
The application doesn't, nor do i want it to, terminate with a WER fault.
How do i get the EXCEPTION_POINTERS during an EExternal?
Note: You can ignore everything from Background on. It's unnecessarily filler designed to make me look smarter.
Pre-emptive snarky Heffernan comment: You should stop using Delphi 5.
Bonus Reading
MSDN: Crash Dump Analysis (Windows)
(detailed example of how to call MiniDumpWriteDump)
CodeProject: Post-Mortem Debugging Your Application with Minidumps and Visual Studio .NET
(General talk about the concepts, virtues, and how to generate and use minidumps)
Stackoverflow: How to create minidump for my process when it crashes?
(initial introduction to the world of mini dumps)
Stackoverflow: Can one prevent Microsoft Error Reporting for a single app?
(setting up the unfiltered handler in Delphi)
Since the Delphi RTL doesn't expose the context pointer directly but only extracts the exception pointer and does so in the bowels of System, the solution is going to be somewhat specific to the version of Delphi you are using.
It's been a while since I've had Delphi 5 installed, but I do have Delphi 2007 and I believe that the concepts between Delphi 5 and Delphi 2007 have remained largely unchanged as far as this goes.
With that in mind, here's an example of how it can be done for Delphi 2007:
program Sample;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils;
var
SaveGetExceptionObject : function(P: PExceptionRecord):Exception;
// we show just the content of the general purpose registers in this example
procedure DumpContext(Context: PContext);
begin
writeln('eip:', IntToHex(Context.Eip, 8));
writeln('eax:', IntToHex(Context.Eax, 8));
writeln('ebx:', IntToHex(Context.Ebx, 8));
writeln('ecx:', IntToHex(Context.Ecx, 8));
writeln('edx:', IntToHex(Context.Edx, 8));
writeln('esi:', IntToHex(Context.Esi, 8));
writeln('edi:', IntToHex(Context.Edi, 8));
writeln('ebp:', IntToHex(Context.Ebp, 8));
writeln('esp:', IntToHex(Context.Esp, 8));
end;
// Below, we redirect the ExceptObjProc ptr to point to here
// When control reaches here we locate the context ptr on
// stack, call the dump procedure, and then call the original ptr
function HookGetExceptionObject(P: PExceptionRecord):Exception;
var
Context: PContext;
begin
asm
// This +44 value is likely to differ on a Delphi 5 setup, but probably
// not by a lot. To figure out what value you should use, set a
// break-point here, then look in the stack in the CPU window for the
// P argument value on stack, and the Context pointer should be 8 bytes
// (2 entries) above that on stack.
// Note also that the 44 is sensitive to compiler switches, calling
// conventions, and so on.
mov eax, [esp+44]
mov Context, eax
end;
DumpContext(Context);
Result := SaveGetExceptionObject(P);
end;
var
dvd, dvs, res: double; // used to force a div-by-zero error
begin
dvd := 1; dvs := 0;
SaveGetExceptionObject := ExceptObjProc;
ExceptObjProc := #HookGetExceptionObject;
try
asm
// this is just for register context verification
// - don't do this in production
mov esi, $BADF00D5;
end;
// cause a crash
res := dvd / dvs;
writeln(res);
except
on E:Exception do begin
Writeln(E.Classname, ': ', E.Message);
Readln;
end;
end;
end.
Related
Found that exception handling in Delphi Tokyo behaves a little different than in previous Delphi versions.
function FuncTest: integer;
begin
Result := 1;
try
raise Exception.Create('Error Message');
finally
Result := 2;
end;
end;
function Test:integer;
begin
Result:=0;
try
Result:=FuncTest;
finally
ShowMessage(Result.ToString);
end;
end;
In earlier Delphi versions the message box shows here "2", Tokyo - "0".
Is this a Tokyo bug or the exceptions should not be handled like this?
The Tokyo behaviour is correct. A function that raises an exception does not return a value. You have hitherto been relying on implementation detail.
Consider this code:
Result:=FuncTest;
This executes as follows:
FuncTest is called.
Result is assigned.
Now, because step 1 raises an exception, step 2 does not execute.
If anything, I would say that the behaviour you report from earlier versions is dubious. In this function:
function Test:integer;
begin
Result:=0;
try
Result:=FuncTest;
finally
ShowMessage(Result.ToString);
end;
end;
The statement Result:=FuncTest raises an exception and so Result should not be modified by that statement. Another way to think of it is that the function is called but the assignment is not executed.
One of the problems with the Delphi ABI is that function return values are sometimes implemented as implicit var parameters. Which means that the assignment may or may not happen. To demonstrate:
{$APPTYPE CONSOLE}
uses
System.SysUtils;
type
TRec1 = record
X1: NativeInt;
end;
TRec2 = record
X1: NativeInt;
X2: NativeInt;
end;
function GetRec1: TRec1;
begin
Result.X1 := 1;
raise Exception.Create('');
end;
function GetRec2: TRec2;
begin
Result.X1 := 1;
raise Exception.Create('');
end;
procedure Main;
var
Rec1: TRec1;
Rec2: TRec2;
begin
Rec1 := Default(TRec1);
Writeln(Rec1.X1);
try
Rec1 := GetRec1;
except
end;
Writeln(Rec1.X1);
Rec2 := Default(TRec2);
Writeln(Rec2.X1);
try
Rec2 := GetRec2;
except
end;
Writeln(Rec2.X1);
end;
begin
Main;
Readln;
end.
This outputs:
0
0
0
1
Which is rather disappointing. It should not be possible to modify the caller's variable, but the use of an implicit var parameter rather than a value return allows this leakage. In my view this is a serious flaw in the design of the Delphi ABI, a flaw that you will not find in most other languages.
In your code, there is no var parameter because the return type is transferred in a register. In which case any Delphi version that outputs 2 is broken.
Fundamentally though, your code is mistaken in its expectations. If a function raises an exception then you must assume that the return value is ill-defined.
Finally, your code outputs 0 in XE3 and XE7, so I wonder how far back you need to go to see a value of 2.
Consider this:
function FuncTest: integer;
begin
Result := 1;
try
try
raise Exception.Create('Error Message');
except
{ do nothing }
end
finally
Result := 2;
end;
end;
Don't think that "finally" handles an exception locally, rather than "returning an exception". To handle it locally requires a LOCAL try-except clause.
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 finally bit the bullet and bought XE6 and as expected, the Unicode conversion is turning into a bit of a nightmare. So if anyone can enlighten me on why this simple Windows API call fails, it would be most appreciated. The function does not return an error, the first call gets the correct buffer length, the second call fills the record with garbage.
This works fine under Delphi 2007 but fails on XE6 with unicode garbage in the pAdapterinfo return record even though it is explicitly declared with AnsiString in IpTypes.pas
System is Win7(64) but compiling for 32 bits.
uses iphlpapi, IpTypes;
function GetFirstAdapterMacAddress:AnsiString;
var pAdapterInfo:PIP_ADAPTER_INFO;
BufLen,Status:cardinal; i:Integer;
begin
result:='';
BufLen:= sizeof(IP_ADAPTER_INFO);
GetAdaptersInfo(nil, BufLen);
pAdapterInfo:= AllocMem(BufLen);
try
Status:= GetAdaptersInfo(pAdapterInfo,BufLen);
if (Status <> ERROR_SUCCESS) then
begin
case Status of
ERROR_NOT_SUPPORTED: raise exception.create('GetAdaptersInfo is not supported by the operating ' +
'system running on the local computer.');
ERROR_NO_DATA: raise exception.create('No network adapter on the local computer.');
else
raiselastOSerror;
end;
Exit;
end;
while (pAdapterInfo^.AddressLength=0) and (pAdapterInfo^.next<>nil) do
pAdapterInfo:=pAdapterInfo.next;
if pAdapterInfo^.AddressLength>0 then
for i := 0 to pAdapterInfo^.AddressLength - 1 do
result := result + IntToHex(pAdapterInfo^.Address[I], 2);
finally
Freemem(pAdapterInfo);
end;
end;
UPDATE:
I did some more checking. I created a new simple application with one form and a button and called the routine when the button was pressed and it worked.
The differences are...in the working form the size of IP_ADAPTER_INFO is 640 bytes.
When this routine is used in a more complex application it fails and the size of IP_ADAPTER_INFO displays as 1192 bytes.
At this point, it seems the complier is unilaterally deciding to change the type of the ansi chars in the structures to unicode chars. The debugger is showing AdapterName and description fields in unicode form. I did a grep of the system source code, there are no other versions of this data type declared in the library code apart from in the Indy library and that is just a duplicate.
Here is the data structure definition in IPtypes
PIP_ADAPTER_INFO = ^IP_ADAPTER_INFO;
{$EXTERNALSYM PIP_ADAPTER_INFO}
_IP_ADAPTER_INFO = record
Next: PIP_ADAPTER_INFO;
ComboIndex: DWORD;
AdapterName: array [0..MAX_ADAPTER_NAME_LENGTH + 3] of AnsiChar;
Description: array [0..MAX_ADAPTER_DESCRIPTION_LENGTH + 3] of AnsiChar;
AddressLength: UINT;
Address: array [0..MAX_ADAPTER_ADDRESS_LENGTH - 1] of BYTE;
Index: DWORD;
Type_: UINT;
DhcpEnabled: UINT;
CurrentIpAddress: PIP_ADDR_STRING;
IpAddressList: IP_ADDR_STRING;
GatewayList: IP_ADDR_STRING;
DhcpServer: IP_ADDR_STRING;
HaveWins: BOOL;
PrimaryWinsServer: IP_ADDR_STRING;
SecondaryWinsServer: IP_ADDR_STRING;
LeaseObtained: time_t;
LeaseExpires: time_t;
end;
Looks like a compiler bug.
There are several problems with your code:
You are not doing any error handling at all on the first call that calculates the buffer length. You don't even need that call, so get rid of it.
You are not doing adequate error handling on subsequent calls, in particular you are not handling the ERROR_BUFFER_OVERFLOW condition when GetAdaptersInfo() needs you to allocate more memory than you already have. Your are allocating only enough memory for one adapter, but GetAdaptersInfo() returns info for all adapters and thus needs a sufficient buffer to hold all of them at one time.
GetAdaptersInfo() does not use GetLastError(), so you need to call SetLastError() before you call RaiseLastOSError().
You are looping through the adapter list using the original pointer that you used to allocate the list, so you are causing a memory leak if the first adapter does not have a MAC address. You need to use a separate variable as the loop iterator so the original pointer is preserved so it can be freed correctly.
You are not taking into account the possibility that none of the adapters has a MAC address, so you will end up accessing a nil pointer after your while loop exits.
You appear to have multiple versions of the IpTypes unit on your machine, and the compiler is finding one that happens to use Char instead of AnsiChar in the IP_ADAPTER_INFO record so its size and field offsets are wrong.
With that said, try this instead:
uses
Winapi.iphlpapi, Winapi.IpTypes;
function GetFirstAdapterMacAddress: String;
var
pAdapterList, pAdapter: PIP_ADAPTER_INFO;
BufLen, Status: DWORD;
I: Integer;
begin
Result := '';
BufLen := 1024*15;
GetMem(pAdapterList, BufLen);
try
repeat
Status := GetAdaptersInfo(pAdapterList, BufLen);
case Status of
ERROR_SUCCESS:
begin
// some versions of Windows return ERROR_SUCCESS with
// BufLen=0 instead of returning ERROR_NO_DATA as documented...
if BufLen = 0 then begin
raise Exception.Create('No network adapter on the local computer.');
end;
Break;
end;
ERROR_NOT_SUPPORTED:
begin
raise Exception.Create('GetAdaptersInfo is not supported by the operating system running on the local computer.');
end;
ERROR_NO_DATA:
begin
raise Exception.Create('No network adapter on the local computer.');
end;
ERROR_BUFFER_OVERFLOW:
begin
ReallocMem(pAdapterList, BufLen);
end;
else
SetLastError(Status);
RaiseLastOSError;
end;
until False;
pAdapter := pAdapterList;
while pAdapter <> nil do
begin
if pAdapter^.AddressLength > 0 then
begin
for I := 0 to pAdapter^.AddressLength - 1 do begin
Result := Result + IntToHex(pAdapter^.Address[I], 2);
end;
Exit;
end;
pAdapter := pAdapter^.next;
end;
finally
FreeMem(pAdapterList);
end;
end;
The explanation is that the types declared in your third party IpTypes unit use Char. This is an alias to AnsiChar in pre-Unicode Delphi, and an alias to WideChar in Unicode Delphi. That would explain the fact that you see non-ANSI text when you inspect the content of the record.
The solution is to fix IpTypes to use AnsiChar in place of Char where appropriate. The best way to do that is to use the IpTypes shipped with Delphi rather than your third party version.
On top of that, the first call to GetAdaptersInfo is wrong. Not only do you fail to check the return value, but you pass nil for the buffer and yet also pass a non-zero length. I think it should go like this:
BufLen := 0;
if GetAdaptersInfo(nil, BufLen) <> ERROR_BUFFER_OVERFLOW then
raise ....
Of course, you way will work, but I'm just being a little pedantic here. Always check for errors when you call an API function.
Just to conclude this topic.
Changing IPtypes to winapi.IPtypes fixed the problem for me.
I think a third party component is doing something to confuse the compiler and giving the full link fixes it.
There is method:
function Test.get_Param(out a : BOOL): HRESULT; stdcall;
begin
a := b;
Result := T_Result;
end;
Now the exception happening on a := b; , happening Access violation Exception.
Ofcourse I can try and catch it. but I don't want to do that....
So Is there any way can determine use some way and skip the assignment. like:
if (! now I know it will happening that Exception){
a := b; // so I can skip
}
Result := T_Result;
Maybe it's very easy, but because I don't know use delphi, So hope your guys can help me. thanks.
Update1:
b: Boolean;//Some friend need to know what is the b param type.
Update2:
I'm try to use :
if b<> nil then Enabled := b;
but I can't build it , it will display: E2008 Incompatible types
Update3:
I'm trying to debug it, and when I'm debug, on the Local variables panel display:
a Inaccessible value
I'm use .NET called it. there is metadata:
bool get_Param{ [param: In, MarshalAs(UnmanagedType.Bool)] [PreserveSig] set; }
actually I'm not use .NET access it. I'm use .NET access a DirectShow filter, and the directshow filter is current method(write by delphi)
Update4:
this is partial C# code
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), SuppressUnmanagedCodeSecurity, Guid("hidden")]
public interface IDCDSPFilterInterface{
bool get_Param{ [param: In, MarshalAs(UnmanagedType.Bool)] [PreserveSig] set; }
.. hidden other ..
}}
I'm try to use :
if b<> nil then Enabled := b;
but I can't build it , it will display: E2008 Incompatible types
Pointer variables are ABC of Pascal. http://en.wikipedia.org/wiki/Pascal_(programming_language)#Pointer_types
So the proper way to write that check would be
function Test.get_Param(out a : BOOL): HRESULT; stdcall;
var ptr: ^BOOL;
begin
ptr := #a;
if nil = ptr then ....
a := b;
Result := T_Result;
end;
That is the basic question to you explicit questions.
Now, in reality that check does not help. It would only protect your from nil/NULL pointers, but that is not what probably happens. What happens is probably a random garbage pointer instead of nil. Due to error in the calling code.
Again, you can check that via var ptr: Pointer {untyped}; ptr := #Self; if ptr = nil then ... or just if nil <> Self or just if Assigned(Self) - but that would only protect you from NIL pointers, not from RANDOM GARBAGE pointers.
More so, i think that actual garbage is not in pointer to the variable a, but to the pointer to Self and b being a member of TEST classm, thus the real statement is a := Self.b;.
Since you use stdcall i think you're trying to make a DLL for using from an EXE made in a in non-Delphi language. Most probably you either made a wrong definition for function in that client app code. Actually, you just can make a proper declaration is you Test is a class. You only can make a proper if get_Param is a method of RECORD Test or perhaps if it is STATIC CLASS method of Test class. So the proper way to write your function would be like following
function Test.get_Param(out a : BOOL): HRESULT;
begin
a := b;
Result := T_Result;
end;
function DLL_get_Param(const TestObject: pointer; out a : BOOL): HRESULT; stdcall;
var MyTest: Test;
begin
pointer(MyTest) := TestObject;
Result := MyTest.DLL_get_Param(a);
end;
export DLL_get_Param;
Read Delphi documentation what you can get/put to/from DLL functions.
Integers, floats, pointers, IInterface. You cannot pass into DLL complex and behaving objects like stings, dynamic arrays, object instances. And since you cannot pass an object instance, you cannot pass a Self variable and you cannot call a method.
One very expensive way to catch it would be like
{global} var TestInstances: TList;
type
TEST = class...
procedure AfterConstructon; override;
procedure BeforeConstructon; override;
....
procedure Test.AfterConstructon;
begin
inherited;
TestInstances.Add(Self); // single-thread assumption here
end;
procedure Test.BeforeConstructon;
begin
TestInstances.Remove(Self); // single-thread assumption here
inherited;
end;
function Test.get_Param(out a : BOOL): HRESULT; stdcall;
begin
if not ( TestInstances.IndexOf(Self) >= 0 {found!} ) // single-thread assumption here
then ... WTF ???
...
....
initialization
TestInstances := TList.Create;
finalization
TestInstances.Free;
end;
If your DLL can be used by multi-threaded application you should also wrap the marked calls into http://docwiki.embarcadero.com/Libraries/XE2/en/System.SyncObjs.TCriticalSection
There is a gross mismatch across the two sides of your interop boundary. Your Delphi function does not match the C# declaration.
The solution is not to test for parameter validity. Your Delphi code, given the declaration of the function in the question, is correct. The solution is to make both sides of the interop boundary match. I cannot tell you more than that until you show both sides of the interop boundary.
Since I can't see where you've decalred b, I'm going to assume it's a member of Test.
So one strong possibility is that you have an invalid instance of Test, and you get an Access Violation trying to read b in order to assign it to a. As an example the following use of get_Param would raise an exception.
var
LTest: Test;
LA: Boolean;
begin
LTest := nil;
LTest.get_Param(LA);
end;
The point is that you need a valid instance of Test in order to use it. E.g.
var
LTest: Test;
LA: Boolean;
begin
LTest := Test.Create;
try
LTest.get_Param(LA);
finally
LTest.Free;
end;
end;
I have a threaded application and for some purpose I want to pass call stack information of a catched exception to a new custom exception:
try
//here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
raise EMyException.Create(E, CallStackOfExceptionEAsString);
end;
end;
What is the best way to do this, preferably using EurekaLog? I am using Delphi 2006 btw.
EurekaLog exposes several event handlers like OnExceptionNotify.
You can implement these in your code. For example: procedure EurekaLogExceptionNotify(
EurekaExceptionRecord: TEurekaExceptionRecord; var Handled: Boolean);
Here you can see a TEurekaExceptionRecord which is defined in ExceptionLog.pas. But you maybe just own the non-source version which works just fine.
The record has a EurekaExceptionRecord.CallStack list. This proprietary list can be converted to TStringsusing the CallStackToStrings method which is also defined in the ExceptionLog unit.
Here is an example where I write the CallStack into a StringList.
CallStackList := TStringList.Create;
try
CallStackToStrings(EurekaExceptionRecord.CallStack, CallStackList);
LogMessage := 'An unhandled exception occured. Here is the CallStack.' + #13#10
+ CallStackList.Text;
finally
CallStackList.Free;
end;
At least from this starting point you should be able to investigate the exposed functions, records etc.. All information is accessible.
EurekaLog provides a function GetLastExceptionCallStack() (defined in unit ExceptionLog.pas).
Using this I have written the following function (based on example code here):
function GetLastEurekalogCallStackAsString(): string;
{$IFDEF EUREKALOG}
var
Stack: TEurekaStackList;
Str: TStringList;
{$ENDIF}
begin
{$IFDEF EUREKALOG}
Stack := GetLastExceptionCallStack();
try
Str := TStringList.Create;
try
CallStackToStrings(Stack, Str);
Result := Str.Text;
finally
FreeAndNil(Str);
end;
finally
FreeAndNil(Stack);
end;
{$ELSE}
Result := '';
{$ENDIF}
end;
So you can write:
try
//here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
raise EMyException.Create(E, GetLastEurekalogCallStackAsString());
end;
end;
EurekaLog 7 has Chained Exception support, which is specifically designed for this task. Just enable it in options (it is enabled by default) and use:
try
// here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
Exception.RaiseOuterException(EMyException.Create(E.Message));
// for old IDEs:
// raise EMyException.Create(E.Message);
end;
end;