tags:

views:

318

answers:

6

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?

A: 

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...

Workshop Alex
+1  A: 

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

Gamecat
But I think, the DLL automatically call my function. So how can I check the fucntion pointer?
SimaWB
+2  A: 

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.

Stijn Sanders
So; is it not possible to use the procedure in TMainThread class?
SimaWB
The 'of Object' means it is part of a class instance so it requires a pointer to the function as well as the object it is part of. You are just calling a function, not a method on an object.
Jeff Cuscutis
SimaWB: is there only one instance of TMainThread? Is there a global variable you can access it with? Then use a global procedure that only contains a call to the TMainThread's instance's method with the same parameter values.
Stijn Sanders
@Stijn Sanders; unfortunately I've lots of instance of TMainThread.
SimaWB
Then I would suggest you keep a global storage of references to them indexed by the port number, and have the one callback method look them up.
Stijn Sanders
+1  A: 

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.

Mason Wheeler
+2  A: 

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.
Rob Kennedy
+2  A: 

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.

mghie
Thanks mghie. I've no chance to modify the dll.
SimaWB