我有一个dll和一个库文件来使用Dll。在文件中
TOnPortStatusChanged = procedure (cardNo:integer; portNo:integer; newPortStatus:byte); stdcall;
TOnPortDataReady = procedure (cardNo:integer; portNo:integer; dataBuff:PByteArray; buffLen:integer); stdcall;
T_API_INIT_PARAMS = record
OnPortStatusChanged :TOnPortStatusChanged;
OnPortDataReady :TOnPortDataReady ;
end;
P_API_INIT_PARAMS = ^T_API_INIT_PARAMS ;
//....
//...
var
PR_Init: function (initParams: P_API_INIT_PARAMS):integer; stdcall;
当我想在我的程序中使用此函数时,我的代码:
procedure PortStatusChanged(cardNo: integer; portNo: integer; newPortStatus: byte); stdcall;
begin
//...
end;
procedure TMainThread.CheckDevices;
var
vCount: integer;
initParams: T_API_INIT_PARAMS;
begin
initParams.OnPortStatusChanged := PortStatusChanged;
initParams.OnPortDataReady := nil;
vCount := PR_Init(@initParams);
工作正常。但我想在TMainThread类中使用PortStatusChanged过程。所以我在TMainThread类中编写过程并更改TOnPortStatusChanged类型:
TOnPortStatusChanged = procedure (cardNo:integer; portNo:integer; newPortStatus:byte) of object; stdcall;
当我以这种方式运行我的程序时; PortStatusChanged第一次正常工作,但第二次它给出了访问冲突错误。
我在哪里弄错了?
答案 0 :(得分:3)
更改应用程序中回调函数的类型不会神奇地改变DLL期望接收的类型。 DLL期望接收普通的函数指针,因此这是您需要提供的。您自己的代码中的声明需要匹配编译时DLL代码中出现的声明,但没有什么可以强制执行的。当您的Delphi代码不匹配时,编译器无法推断DLL的声明并打印错误。
方法指针(将of object
添加到声明时得到的)与普通函数指针不同。它实际上是一个闭包,它包含指向方法的指针和对象的方法的引用。 Delphi知道如何通过获取引用的对象并在该对象上调用指向的方法来调用方法指针。
你的DLL不知道如何调用方法指针,除非它是用Delphi或C ++ Builder编写的。但即使它确实知道如何,你也会被卡住,因为DLL 不知道你给它一个方法指针。它假定你给它一个普通的函数指针,因为这是DLL代码的编写方式。
您无法为DLL提供类的方法。但是,有一些技术允许您间接解决问题:
答案 1 :(得分:2)
“of object”指令实际上指示计算机将指向对象实例的指针添加到过程调用的参数列表的前面。这会修改如何调用过程,并且DLL实际上获取了一个参数列表,这些参数通过在前面添加了一个对象pionter而被移位,并且不起作用。
答案 2 :(得分:2)
Rob Kennedy已经向您an answer提供了有关您遇到问题的可能原因的信息,以及一些以不同方式编码的方法。
然而,还有另一种方法可以做到这一点,它既简单又兼容其他开发环境:接口。请考虑以下代码:
type
IPortNotification = interface
['{7BECA1D9-A6E8-4406-9910-5B36A6B0D564}']
procedure StatusChanged(ACardNo, APortNo: integer; ANewPortStatus: byte);
procedure DataReceived(ACardNo, APortNo: integer; ADataPtr: PByte;
ADataLength: integer);
end;
function RegisterPortNotification(ANotification: IPortNotification): BOOL;
stdcall; external PortDLL;
function UnregisterPortNotification(ANotification: IPortNotification): BOOL;
stdcall; external PortDLL;
DLL只导出两个函数来注册和取消注册接口指针,接口直接在代码中实现事件处理程序。这使得DLL非常通用,您可以随时用另一种语言重写它,并且您也可以在其他开发环境中使用它 - 没有像T_API_INIT_PARAMS
中的事件处理程序那样特有的Delphi。
请注意,现在您可以通过注册多个接口指针来实现几乎免费的多播事件。
扩展API也更容易。使用原始代码中的记录,您无法在不破坏二进制兼容性的情况下添加回调。使用RegisterPortNotification()
,您始终可以查询传递的接口以获取新的扩展接口,并注册它们。
答案 3 :(得分:1)
如果您遇到访问冲突,很可能没有设置(或不再设置)函数指针。
在调用函数指针之前检查Assigned通常是明智的,除非你100%确定它是有效的。
例如使用断言:
Assert(Assigned(MyPtr));
不幸的是,它没有检查悬空指针。
函数指针和方法指针(具有对象扩展)之间存在很大差异。方法指针有一个隐藏的额外参数(指向对象的指针)。
答案 4 :(得分:1)
请原谅这个显而易见的问题,但您是否已在两个地方更新了函数指针的定义?
此外,根据您对对象的操作,将其传递到DLL边界可能会给您带来问题,除非相同的内存管理器拥有应用程序和DLL的内存。确保你正在分享它。
答案 5 :(得分:0)
如果您正在创建多线程应用程序,那么您的代码可能不是线程安全的。 此外,您正在调用的DLL可能不是线程安全的......