Reputation: 9294
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
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
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:
Upvotes: 3
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
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
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
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