2009-06-19 108 views
1

我有一个dll和一个库文件来使用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在第一时间正常工作,但第二次出现访问冲突错误。

我在哪里犯错?

回答

3

在应用程序中更改回调函数的类型不会奇迹般地改变DLL期望接收的类型。该DLL期望收到一个普通的函数指针,所以这就是你需要提供的。在您自己的代码中的声明需要与编译时DLL代码中出现的声明相匹配,但没有什么可以强制执行该声明。当你的Delphi代码不匹配时,编译器不能推断出DLL的声明并输出错误。

方法指针(这是您将of object添加到声明时得到的内容)与普通函数指针不同。这真的是一个关闭由指向一个方法的指针和对它的方法引用的对象组成。 Delphi知道如何通过引用对象并调用该对象的指向方法来调用方法指针。

您的DLL不知道如何调用方法指针,除非它是用Delphi或C++ Builder编写的。但即使它知道如何,你会被卡住,因为DLL 不知道你给它一个方法指针。它假定你给它一个普通的函数指针,因为这是DLL的代码写入的方式。

你不能给DLL一个你的类的方法。有一些技巧,让您间接地解决问题,但:

  • 如果DLL的回调定义允许的话,你可以通过一个额外的参数到DLL的DLL然后将传回。你可以用它来保存对你的对象的引用,然后在你的回调函数中你可以使用这个对象。不过,它看起来并不像这个特定的DLL支持。
  • 您可以将对象的引用放入全局变量中,然后您可以在独立回调函数中引用该变量。这不是很优雅,如果您可以同时多次调用DLL(通过多线程或递归),该技术就会崩溃。
  • 您可以为一个函数分配自己的内存,然后将对象引用放入您为该方法实时生成的代码中。实际上,可以调用DLL的每个类的实例都有自己的专用回​​调函数。这项技术比我准备在这里解释的要多一点。有人担心你的程序对病毒扫描程序或在某些内存上设置了不执行标志的计算机有疑虑,但每个VCL程序实际上已经使用该技术将窗体和控件与其底层Win32窗口相关联。
0

如果您正在创建多线程应用程序,那么您的代码可能不是线程安全的。 此外,您打电话的DLL可能不是线程安全的...

1

如果您正在访问冲突,可能是函数指针未设置(或不再)。

在调用函数指针之前检查Assigned通常是明智的,除非您100%确定其有效。

例如与断言:

Assert(Assigned(MyPtr)); 

遗憾的是它不检查悬摆指针。

函数指针和方法指针(与对象扩展)有很大的区别。方法指针有一个隐藏的额外参数(指向对象的指针)。

+0

但我想,DLL会自动调用我的函数。那么如何检查指针指针呢? – SimaWB 2009-06-19 14:57:25

2

“对象”指令实际上指示计算机将一个指向对象实例的指针添加到过程调用参数列表的前面。这会修改过程被调用的方式,并且DLL实际上会获得通过将对象pionter添加到前端而移动的参数列表,并且不起作用。

+0

所以;是不是可以在TMainThread类中使用该过程? – SimaWB 2009-06-19 14:53:08

+0

'对象'意味着它是类实例的一部分,因此它需要一个指向该函数的指针以及它所包含的对象。你只是调用一个函数,而不是一个对象的方法。 – 2009-06-19 15:33:53

1

原谅显而易见的问题,但你有没有在两个地方更新函数指针的定义?

此外,根据您对对象所做的操作,将它传递给DLL边界会给您带来问题,除非相同的内存管理器拥有应用程序和DLL的内存。确保你分享它。

2

罗伯肯尼迪已经给你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相当通用,你可以随时用另一种语言重写它,并且你也可以在其他开发环境中使用它 - 没有什么特定的Delphi像T_API_INIT_PARAMS中的事件处理程序那样。

请注意,现在您可以通过注册几个接口指针来几乎免费地实现多播事件。

像这样扩展API也很容易。通过原始代码中的记录,您不能添加回调而不会破坏二进制兼容性。使用RegisterPortNotification()您可以随时查询传入的接口以获取新的扩展接口,并将其注册。