I am currently writing a module which interfaces with a black box 3rd party DLL for a check scanner. I need to have the DLL functions loaded dynamically, and this is working for all but one function.
The SetScanParameters function has a record structure as a parameter, which I believe is somehow interfering with the methodology I am using to dynamically load it (see below). When loaded dynamically, the function is interrupted by an access violation. SetScanParameters does load and function properly when statically loaded, however. Is there something else that I need to be doing to dynamically load a function with a record structure?self-edited for clarity:
Record type:
TBK_ScanParameter=packed record
Left:short;
Top:short;
Width:short;
Length:short;
//
xResolution:short;
yResolution:short;
BitsPerPixel:short;
LightControl:short;
MotorControl:short;
//
rGain:short;
gGain:short;
bGain:short;
rOffset:short;
gOffset:short;
bOffset:short;
rExposure:short;
gExposure:short;
bExposure:short;
//
FeedDirection:short;
CropImage:short;
ScanWithMICR:short;
//
Reserved:array [0..14] of short;
end;
Static declaration:
function BK_SetScanParameter(var ScanParameter:TBK_ScanParameter):integer; cdecl;
Static implementation:
function BK_SetScanParameter(var ScanParameter:TBK_ScanParameter):integer; cdecl; external 'BKV2.dll' name '_BK_SetScanParameter@4';
Dynamic logic (or what would be dynamic logic if I didn't have to use the static call to make it work):
function TdmScannerV2.SetScanParameter(pScanParameter: TBK_ScanParameter): string;
type
TBK_SetScanParameter = function (var ScanParameter:TBK_ScanParameter):integer; stdcall;
var
hV2Dll:HMODULE;
func:TBK_SetScanParameter;
begin
hV2Dll:=0;
result := '';
try
hV2Dll:=LoadLibrary('BKV2.dll');
if hV2Dll>0 then
begin
@func:=GetProcAddress(hV2Dll, '_BK_SetScanParameter@4');
if Assigned(@func) then
begin
try
if BK_SetScanParameter(pScanParameter) > 0 then {This one works, but is static}
//if func(pScanParameter) > 0 then {this one gets an AV}
begin
Result := 'Y:Scan Parameters Set';
end
else
Result := 'ERROR:Failure code returned';
{
if func(pScanParameter) > 0 then
Result := 'Y:Scan Parameters Set'
else
Result := 'ERROR:Failure code returned';
}
except
on e:Exception do
begin
Result := 'ERROR:Exception:' + e.Message;
end;
end;
end
else
Result := 'ERROR:Unable to load BK_SetScanParameter';
end
else
Result := 'ERROR:Unable to load BKV2.dll';
finally
if hV2Dll>0 then FreeLibrary(hV2Dll);
end;
end;
And I've tried using stdcall, cdecl, safecall, pascal, and register on the dynamic and they all resulted in the AV. I also tried making the array in the struct [1..15] instead of [0..14]. And in the But what I don't get is, if I pass the struct into the static version, it works.
Also, there were a few typos in the OP, and I apologize for that. I was re-writing the code in the OP and made a few typos, which may've muddied the thread a bit. I've replaced it with a copy/paste of the current test function.
edit: Below is the typedef as described by the documentation for the DLL:
typedef struct ScanParameter
{
short Left; // left start positsion
short Top; // top start positsion
short Width; // scan image width in 1/100 inch
short Length; // scan image length in 1/100 inch
short xResolution; // horizontal resolution
short yResolution; // vertical resolution
short BitsPerPixel; // 24bit color, 8bit gray
short LightControl; // 0 - All lamp Off, 1 - red, 2 - green, 3 - blue, 4 - All lamp On
short MotorControl; // Motor Control, 0 - off, 1 = on
short rGain; // AFE R-Gain
short gGain; // AFE G-Gain
short bGain; // AFE B-Gain
short rOffset; // AFE R-Offset
short gOffset; // AFE G-Offset
short bOffset;开发者_JS百科 // AFE B-Offset
short rExposure; // AFE R-Exposure
short gExposure; // AFE G-Exposure
short bExposure; // AFE B-Exposure
short FeedDirection; // feedout paper direction, 0 –fordward, 1 - backward
short CropImage; // 0 - no trim edge , 1 - trim edge
short ScanWithMICR; // 0 –off, 1 –scan image until paper leave device
short Reserved[15];
} ScanParameter;
As mentioned above, the calling convention looks like it should be cdecl, not stdcall. Second, try changing the load library to be,
hV2Dll := LoadLibrary('Scan.dll');
The original had 'ScanDLL.dll'.
You did define two different calling conventions:
- cdecl in the static declaration
- stdcall in the dynamic declaration.
My suggestion is to check calling convention, In delphi default calling convention was Pascal but a microsoft compiled dll would be cdecl most probably. So try to define func as
TSetScanParameter = function (var ScanParameter:TParams):integer; cdecl;
as you did at static definition.
It's most likely an issue with how Delphi is storing the record structure and this being incompatible with the record structure in the DLL. I've had similar issues with DLLs I've written in C interfacing to Delphi.
To quote
Normally, complex data types, such as records have their elements aligned to 2, 4 or 8 byte boundaries, as appropriate to the data type. For example, a Word field would be aligned to a 4 byte boundary. Records are also padded to ensure that they end on a 4 byte boundary.
Consequently when you pass the record the field alignment will most likely differ from the definition being used internally and you see an access violation. Because the memory allocation differ between static and dynamic it's perfectly possible for the behaviour to see arise because the static allocation coincidentally does align properly.
The first thing to try is to use the Packed keyword on the record.
TPackedRecord = Packed Record
Again to quote the manual
The Packed keyword tells Delphi to minimise the storage taken up by the defined object. The Packed overrides this, compressing the data into the smallest storage, albeit with consequential reduced access performance.
Assuming your calling conventions are correct I would expect this to solve the issue.
You say the following is working:
function SetScanParameter(var Scans:TParams):integer; cdecl; external 'Scan.dll' name '_SetScanParameter@4';
Yet, the other is not working.... For a start, make sure you're doing the same things in each. Currently you are not, see below:
TSetScanParameter = function (var ScanParameter:TParams):integer; stdcall;
hV2Dll:=LoadLibrary('ScanDLL.dll');
Not to mention that you may be passing different passing different values in each call, you may be using different declarations of TParams
. If you want to find out why one apple is different from another apple, you don't go and compare it to an orange.
Get these basics right; then, if you still have problems:
- Provide us with the declaration of TParams.
- Provide us with the relevant extract of this 3rd Party DLL documentation.
- The language used to create the DLL would be useful if you can find out.
- The actual declarations of the method and structure in the above language would be even more useful.
If you want help, at least put in the effort to help us help you.
If you have some demonstration .exe loading the DLL, see if you can disassemble it, and find the spot where it calls this function. If you are lucky, and the function is not too complicated, you might find out how big the record is supposed to be.
Afaik the alignment is typical for C compilers (everything is either short or a complex of short, and all aligned on natural bounderies), but trying to align the array on some bigger boundery (4,8,16 bytes) and/or padding the record with some extra dummy might help also.
Can you post the C function declaration from the header file? Also check the load address of the DLL when the function is compiled as static vs address where the DLL is loaded dynamically. DLL itself could have some code that is location specific and when you link it statically Windows loader will load the DLL to correct location in memory but when you load it dynamically there may already be something at that location so Windows loader will relocate the DLL to new location but the code inside DLL may not have been written correctly and it may still be trying to access some static memory address. Another thing to try is loading and unloading the DLL outside the function call and declaring the dynamic function type outside the function, Delphi compiler could be optimizing something differently when you declare all of it inside the function. Finally make sure you are using the correct calling convention (should be available in C header file) as they are very different and DLL functions will cause A/Vs or stack issues if called with incorrect convention.
Thanks for the help guys. Turns out it's no longer an issue. The DLL the scanner company had out was a bit buggy and they put out a new one which fixes the problem.
精彩评论