SimaWB
SimaWB

Reputation: 9294

Use a function from dll

I've a dll and a library file to use the Dll. In a file

  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;

when I want to use this function in my program, my code :

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);

That works fine. But I want to use PortStatusChanged procedure in TMainThread class. So I write the procedure in TMainThread class and change TOnPortStatusChanged type like that:

TOnPortStatusChanged = procedure (cardNo:integer; portNo:integer; newPortStatus:byte) of object; stdcall;

When I run my program in this way; PortStatusChanged works correct at the first time, but the second it gives an access violation error.

Where am I making mistake?

Upvotes: 1

Views: 985

Answers (6)

mghie
mghie

Reputation: 32334

Rob Kennedy already gave you an answer with information about possible reasons for the problem you are experiencing, and some ways to code this differently.

There is however another way to do this, which is both straightforward and compatible with other development environments as well: interfaces. Consider the following code:

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;

The DLL just exports two functions to register and unregister interface pointers, and the interface implements the event handlers in your code directly. This makes the DLL fairly generic, you could rewrite it at any time in another language, and you could use it from other dev environments as well - there is nothing Delphi specific like the event handlers in T_API_INIT_PARAMS.

Note that now you can implement multicast events nearly for free, by registering several interface pointers.

It's also much easier like this to extend the API. With the record in your original code you can't add callbacks without breaking binary compatibility. With RegisterPortNotification() you can always query the passed interface for new, extended interfaces, and register them instead.

Upvotes: 2

Rob Kennedy
Rob Kennedy

Reputation: 163357

Changing the type of the callback function in your application won't magically change the type that the DLL expects to receive. The DLL expects to receive an ordinary function pointer, so that's what you need to provide. The declarations in your own code need to match the declarations that appeared in the DLL's code when it was compiled, but there is nothing to enforce that. The compiler can't deduce the DLL's declarations and print an error when your Delphi code doesn't match.

A method pointer (which is what you get when you add of object to a declaration) isn't the same as an ordinary function pointer. It's really a closure consisting of the pointer to a method and a reference to the object whose method it is. Delphi knows how to call method pointers by taking the referenced object and calling the pointed-to method on that object.

Your DLL doesn't know how to call method pointers, unless it was written in Delphi or C++ Builder. But even if it did know how, you'd be stuck since the DLL doesn't know you're giving it a method pointer. It assumes you're giving it an ordinary function pointer since that's how the DLL's code was written.

You can't give the DLL a method of your class. There are some techniques that allow you to solve the problem indirectly, though:

  • If the DLL's callback definition allows it, you can pass an extra parameter to the DLL that the DLL will then pass back. You can use it to hold a reference to your object, and then in your callback function you can use the object. It doesn't look like this particular DLL supports that, though.
  • You can put a reference to your object in a global variable that you can then refer to in your standalone callback function. This isn't very elegant, and the technique falls apart if you can ever have multiple calls to the DLL at the same time (either through multiple threads or through recursion).
  • You can allocate your own memory for a function and then put the object reference into code you generate on the fly for that one method. In effect, each instance of your class that could call into the DLL will have its own private callback function. The technique is a little more involved than I'm prepared to explain right here. There are some concerns about your program looking suspicious to virus scanners or to computers that have the no-execute flag set on certain memory, but every VCL program actually already uses the technique to associate forms and controls with their underlying Win32 windows.

Upvotes: 3

Mason Wheeler
Mason Wheeler

Reputation: 84650

Forgive the obvious question, but have you updated the definition of your function pointer in both places?

Also, depending on what you're doing with the object, passing it across DLL boundaries can give you problems unless the same memory manager owns the memory for both the application and the DLL. Make sure you're sharing it.

Upvotes: 1

Stijn Sanders
Stijn Sanders

Reputation: 36850

The "of object" directive actually instructs the computer to add a pointer to an object instance to the front of the argument list of the procedure call. This modifies how the procedure get's called, and the DLL actually gets a list of arguments that are shifted by having added an object pionter to the front, and won't work.

Upvotes: 2

Toon Krijthe
Toon Krijthe

Reputation: 53436

If you are getting access violations it is likely the function pointers are not set (or not anymore).

It is often wise to check for Assigned before you call a function pointer unless you are 100% sure its valid.

For example with an assert:

Assert(Assigned(MyPtr));

Unfortunately it does not check for dangling pointers.

There is a big difference between function pointers and method pointers (with the of object extention). Method pointers have a hidden extra parameter (the pointer to the object).

Upvotes: 1

Wim ten Brink
Wim ten Brink

Reputation: 26682

If you're creating a multi-threaded application, then your code might not be threadsafe. Also, the DLL you're calling might not be threadsafe...

Upvotes: 0

Related Questions