Are you sure you have 'stdcall' specifier in the correct place? This looks like 'register/fastcall' convention to me ('nil' is sent in the ECX parameter).
I see nothing special in asm here - GeoEngCheckDbAvailability called with fastcall convention, as you requested. You should show disasm of GeoEngCheckDbAvailability where _GeoEngCheckDbAvailability called if you suspect Delphi in srong opcode generation
OK. I have almost the same code (have built a simple DLL with a method having the same signature) but the call sequence looks totally different from yours. Do you have 'stdcall' in the type which defines _GeoEngCheckDbAvailability?
Alexey Petushkov It has the stdcall marker on it, so I assume it is using stdcall, but it appears that somehow the XE3 compiler is treating it differently.
Compare your code with http://www.gabrijelcic.org/dlltest.zip. Archive contains test DLL (containing a method with your signature) and test clients. Tested in D2007 and XE3 - both work correctly. Compare CPU view with yours and you'll see that parameter passing is completely different.
Even weirder here's the (yucky) code with an exception handler. The ShowMessage call doesn't happen, even though an exception is raised and there is an exception handler:
Not yet -- but the debugger shows that "all is well" going in. What kind of logging would you recommend?
The weird part is the except block being ignored. Then stepping into it merely goes to _LStrToPChar and that's it -- I see no other code in the debugger.
Comparing asm is moot, especially when you add additional variables into the routine from where you are calling as Primoz did. Of course the asm changes then because offsets change and the compiler cannot optimize things.
I am pretty sure the errors origin is something different. Also of course this exception is an AV, it even says so.
Debugging the DLL is not impossible (although definitely not pretty) - just F7 on the appropriate CPU instruction that calls the DLL and you'll be in the DLL code.
Alternatively, send me an NDA, grant me remote access, and I'll find the problem. And send you the bill. ;)
The first two offsets to ebp are greater by 4 than in D7. [ebp - $0a] instead of [ebp - $08], and so on. but the third offset is identical in both. Looks as though either the parameters are in different order, or one of them is being handled as 64-bit....
Sounds like the reason you got those AVs is because strings in Delphi are constants in memory, and because you're now allocating them on the stack, or in heap, the AVs disappear.
String assignments are also +1 back to the string constant.
I'm not ASM genius, but I can see that there is a difference of 2 bytes going on there. Hmm.
ReplyDeleteAre you sure you have 'stdcall' specifier in the correct place? This looks like 'register/fastcall' convention to me ('nil' is sent in the ECX parameter).
ReplyDeletePrimož Gabrijelčič Thanks for your help:
ReplyDeleteI have:
type
TGeoEngCheckDbAvailability = function(pSearchPath: pAnsichar; pDbName: PAnsiChar; pUserDictPath: PAnsiChar; pDbFlags: pshort): short; stdcall;
and then
var
_GeoEngCheckDbAvailability: TGeoEngCheckDbAvailability = nil;
and
function GeoEngCheckDbAvailability(pSearchPath: PAnsiChar; pDbName: PAnsiChar; pUserDictPath: PAnsiChar; pDbFlags: pshort): short;
begin result := _GeoEngCheckDbAvailability(pSearchPath, pDbName, pUserDictPath, pDbFlags); end;
I don't know where else to put the calling convention.
This looks fine. I'll take a better look at it in the morning (it's 11:30 pm here) - unless somebody solves the problem before.
ReplyDeleteThanks very much.
ReplyDeleteI see nothing special in asm here - GeoEngCheckDbAvailability called with fastcall convention, as you requested. You should show disasm of GeoEngCheckDbAvailability where _GeoEngCheckDbAvailability called if you suspect Delphi in srong opcode generation
ReplyDeleteIs the variable you are casting to PAnsiChar of string or AnsiString? If it is a string you have to cast it to AnsiString first.
ReplyDelete1. This is definitely not stdcall calling convention. Stdcall looks like this:
ReplyDeletedllUse1.pas.48: retCode := hProc(PAnsiChar(par1), PAnsiChar(par2), nil,@dbFlags);
0045BF61 8D45F6 lea eax,[ebp-$0a]
0045BF64 50 push eax
0045BF65 6A00 push $00
0045BF67 8B45F8 mov eax,[ebp-$08]
0045BF6A E8918DFAFF call @LStrToPChar
0045BF6F 50 push eax
0045BF70 8B45FC mov eax,[ebp-$04]
0045BF73 E8888DFAFF call @LStrToPChar
0045BF78 50 push eax
0045BF79 FFD6 call esi
0045BF7B 8BF0 mov esi,eax
2. How are you linking to this method? Statically?
Stefan --
ReplyDeleteEverything is an AnsiString.
Primoz --
It's dynamic. The code to get the method looks like this:
@_GeoEngCheckDbAvailability:= GetProcAddress(MMPlusHandleID, 'GeoEngCheckDbAvailability');
OK. I have almost the same code (have built a simple DLL with a method having the same signature) but the call sequence looks totally different from yours. Do you have 'stdcall' in the type which defines _GeoEngCheckDbAvailability?
ReplyDeleteAlexey Petushkov It has the stdcall marker on it, so I assume it is using stdcall, but it appears that somehow the XE3 compiler is treating it differently.
ReplyDeleteEven your D2007 call sequence is not stdcall.
ReplyDeleteStefan Glienke Hard casting the parameters to AnsiString results in the same code gen, so they definitely are AnsiString.
ReplyDeleteCompare your code with http://www.gabrijelcic.org/dlltest.zip. Archive contains test DLL (containing a method with your signature) and test clients. Tested in D2007 and XE3 - both work correctly. Compare CPU view with yours and you'll see that parameter passing is completely different.
ReplyDeleteAnd I mispoke -- it's not an AV, it's an exception:
ReplyDelete---------------------------
Debugger Exception Notification
---------------------------
Project HDM.exe raised exception class $C0000005 with message 'access violation at 0x061b205d: read of address 0x04a70100'.
---------------------------
Break Continue Help
---------------------------
Even weirder here's the (yucky) code with an exception handler. The ShowMessage call doesn't happen, even though an exception is raised and there is an exception handler:
ReplyDeletetry
retCode := GeoEngCheckDbAvailability(PAnsiChar(MMPath), PAnsiChar(dbName), nil,@dbFlags);
except
ShowMessageApp('ERROR: Exception calling GeoEngCheckDbAvailability.'); exit;
end;
And again, I see this exception only in the debugger, not when I run the EXE outside the debugger.
Did you do some logging of the arguments before the call and inside the dll routine yet?
ReplyDeleteNot yet -- but the debugger shows that "all is well" going in. What kind of logging would you recommend?
ReplyDeleteThe weird part is the except block being ignored. Then stepping into it merely goes to _LStrToPChar and that's it -- I see no other code in the debugger.
This is really weird.
Comparing asm is moot, especially when you add additional variables into the routine from where you are calling as Primoz did. Of course the asm changes then because offsets change and the compiler cannot optimize things.
ReplyDeleteI am pretty sure the errors origin is something different. Also of course this exception is an AV, it even says so.
Debug the dll if you can.
Stefan Glienke Just wanted to be clear that it was being reported as an exception, not a "pure" AV -- sorry if I wasn't clear about that.
ReplyDeleteAnyway, thanks for your help, everybody. I'm going to leave this one for a while because it is starting to piss me off. ;-)
Let me know if you have any insightful revelations. :-)
Debugging the DLL is not impossible (although definitely not pretty) - just F7 on the appropriate CPU instruction that calls the DLL and you'll be in the DLL code.
ReplyDeleteAlternatively, send me an NDA, grant me remote access, and I'll find the problem. And send you the bill. ;)
No, it's returning a short -- it won't compile with your suggestion.....
ReplyDeleteThe first two offsets to ebp are greater by 4 than in D7. [ebp - $0a] instead of [ebp - $08], and so on. but the third offset is identical in both. Looks as though either the parameters are in different order, or one of them is being handled as 64-bit....
ReplyDeleteOkay, so this made the AV go away:
ReplyDeleteFillChar(MMPathAC255, SizeOf(ArrayOfAnsiChar255), 0);
FillChar(dbNameAC255, SizeOf(ArrayOfAnsiChar255), 0);
StrPCopy(MMPathAC255, MMPath);
StrPCopy(dbNameAC255, dbName);
try
retCode := GeoEngCheckDbAvailability(@MMPathAC255[1], @MMPathAC255[1], nil,@dbFlags);
But now it fails to initialize properly. At least it is returning a known error code.
Nick Hodges Blech! The first char of each array should be at index 0, not 1. Or so it seems to me....
ReplyDeleteSounds like the reason you got those AVs is because strings in Delphi are constants in memory, and because you're now allocating them on the stack, or in heap, the AVs disappear.
ReplyDeleteString assignments are also +1 back to the string constant.