Delphi:为什么我可以静态链接该函数但不能动态链接?
我目前正在编写一个模块,该模块与支票扫描仪的黑匣子第 3 方 DLL 接口。我需要动态加载 DLL 函数,这适用于除一个函数之外的所有函数。
SetScanParameters 函数有一个记录结构作为参数,我认为这在某种程度上干扰了我用来动态加载它的方法(见下文)。动态加载时,该函数会因访问冲突而中断。
然而,SetScanParameters 在静态加载时确实会加载并正常运行。 我还需要做其他事情来动态加载具有记录结构的函数吗?
为了清晰起见,自行编辑:
记录类型:
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;
静态声明:
function BK_SetScanParameter(var ScanParameter:TBK_ScanParameter):integer; cdecl;
静态实现:
function BK_SetScanParameter(var ScanParameter:TBK_ScanParameter):integer; cdecl; external 'BKV2.dll' name '_BK_SetScanParameter@4';
动态逻辑(或者如果我不必使用静态调用使其工作,那么什么是动态逻辑):
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;
我尝试过使用 stdcall、cdecl 、 safecall、pascal 和动态注册,它们都产生了 AV。我还尝试在结构 [1..15] 而不是 [0..14] 中创建数组。并且在 但我不明白的是,如果我将结构传递到静态版本中,它就会起作用。
另外,OP中有一些错别字,对此我深表歉意。我正在重写 OP 中的代码并犯了一些拼写错误,这可能使线程有点混乱。我已将其替换为当前测试函数的复制/粘贴。
编辑:下面是 DLL 文档中描述的 typedef:
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; // 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;
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; // 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;
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
如上所述,调用约定看起来应该是 cdecl,而不是 stdcall。其次,尝试将加载库更改为,
原来有“ScanDLL.dll”。
As mentioned above, the calling convention looks like it should be cdecl, not stdcall. Second, try changing the load library to be,
The original had 'ScanDLL.dll'.
您确实定义了两种不同的调用约定:
You did define two different calling conventions:
我的建议是检查调用约定,在delphi中默认调用约定是Pascal,但微软编译的dll最有可能是cdecl。因此,尝试像
静态定义一样定义 func 。
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
as you did at static definition.
这很可能是 Delphi 如何存储记录结构的问题,并且这与 DLL 中的记录结构不兼容。我在用 C 语言编写的与 Delphi 连接的 DLL 上也遇到过类似的问题。
引用
因此,当您传递记录时,字段对齐很可能与内部使用的定义不同,并且您会看到访问冲突。由于静态和动态之间的内存分配不同,因此完全有可能出现这种行为,因为静态分配恰好正确对齐。
首先要尝试的是在记录上使用 Packed 关键字。
再次引用手册
假设您的调用约定是正确的,我希望这可以解决问题。
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
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.
Again to quote the manual
Assuming your calling conventions are correct I would expect this to solve the issue.
您说以下内容有效:
然而,另一个无效……首先,请确保您在每个内容中都做相同的事情。目前您还没有,请参阅下文:
更不用说您可能在每次调用中传递不同的值,您可能使用不同的
TParams
声明。如果您想找出为什么一个苹果与另一个苹果不同,您不需要将其与橙子进行比较。正确掌握这些基础知识;那么,如果您仍然有问题:
如果您需要帮助,至少尽力帮助我们帮助您。
You say the following is working:
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:
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:
If you want help, at least put in the effort to help us help you.
如果你有一些加载DLL的演示.exe,看看你是否可以反汇编它,并找到它调用该函数的位置。如果你幸运的话,并且函数不是太复杂,你可能会发现记录应该有多大。
据我所知,对齐对于 C 编译器来说是典型的(所有内容要么是短的,要么是短的复合体,并且都在自然边界上对齐),但尝试在一些更大的边界(4,8,16 字节)上对齐数组和/或填充用一些额外的假人进行记录也可能有帮助。
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.
你能从头文件中发布 C 函数声明吗?
还要检查函数编译为静态时 DLL 的加载地址与动态加载 DLL 时的地址。 DLL 本身可能有一些特定于位置的代码,当您静态链接它时,Windows 加载程序会将 DLL 加载到内存中的正确位置,但当您动态加载它时,该位置可能已经有一些代码,因此 Windows 加载程序会将 DLL 重新定位到新的位置。位置,但 DLL 内的代码可能没有正确编写,并且它可能仍在尝试访问某些静态内存地址。
另一件要尝试的事情是在函数调用之外加载和卸载 DLL,并在函数外部声明动态函数类型,当您在函数内部声明所有内容时,Delphi 编译器可能会以不同的方式优化某些内容。
最后确保您使用正确的调用约定(应该在 C 头文件中可用),因为它们非常不同,如果使用不正确的约定调用,DLL 函数将导致 A/V 或堆栈问题。
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.
谢谢你们的帮助。事实证明这不再是问题。扫描仪公司推出的 DLL 有点问题,他们推出了一个新的 DLL 来解决这个问题。
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.