使用dll中的函数

时间:2009-06-19 12:26:39

标签: delphi 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第一次正常工作,但第二次它给出了访问冲突错误。

我在哪里弄错了?

6 个答案:

答案 0 :(得分:3)

更改应用程序中回调函数的类型不会神奇地改变DLL期望接收的类型。 DLL期望接收普通的函数指针,因此这是您需要提供的。您自己的代码中的声明需要匹配编译时DLL代码中出现的声明,但没有什么可以强制执行的。当您的Delphi代码不匹配时,编译器无法推断DLL的声明并打印错误。

方法指针(将of object添加到声明时得到的)与普通函数指针不同。它实际上是一个闭包,它包含指向方法的指针和对象的方法的引用。 Delphi知道如何通过获取引用的对象并在该对象上调用指向的方法来调用方法指针。

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

您无法为DLL提供类的方法。但是,有一些技术允许您间接解决问题:

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

答案 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可能不是线程安全的......