I am having a problem trying to create a procedure for my component that uses a procedure contained in a DLL (TExecute) which also needs a declaration in the current code. So this procedure has a pointer as parameter to know what to do with the evaluation. The following code works fine but I need that the procedure eval to be inside the component to use the private variables from this component. The working code is the following, note that the eval procedure is global in this case.
TExecute = procedure(eval: pointer, var variableArray: double);cdecl
TMyComponent = Class(TComponent)
public
FHandle: THandle;
FExecute: TExecute;
procedure Calculate;
var
n: integer;
x: array of double;
procedure eval(var x: double);
implementation
procedure eval(var x:double);
var
mx: Array[0..200] of double absolute x;
begin
mx[0]:= 2*mx[0];
end;
TMyComponent.Calculate;
begin
FHandle:= LoadLibrary(.....);
FExecute:= GetProcAddress(FHandle, 'main');
n:=2;
setlength(x,n);
FExecute(@eval,x[0]);
end;
I got a problem when I put the procedure eval inside TMyComponent like that:
TExecute = procedure(eval: pointer, var variableArray: double);cdecl
TMyComponent = Class(TComponent)
public
FHandle: THandle;
FExecute: TExecute;
procedure Calculate;
procedure eval(var x: double);
var
n: integer;
x: array of double;
implementation
procedure TMyComponent.eval(var x:double);
var
mx: Array[0..200] of double absolute x;
begin
mx[0]:= 2*mx[0];
end;
TMyComponent.Calculate;
begin
FHandle:= LoadLibrary(.....);
FExecute:= GetProcAddress(FHandle, 'main');
n:=2;
setlength(fx,n);
FExecute(@TMyComponent.eval,x[0]);
end;
I get that the project faulted with message: access violation at 0x65900381f: write of address 0x0000005c. Process Stopped. Use Setp of Run to continue.
And I don't have no clue about this problem, I already tried to change almost everything and I did't get a solution. If someone could help me I would appreciate.
A global procedure and a class method are not the same thing. A class method has a hidden Self
parameter, which your DLL does not take into account when the class method is passed to the DLL. That is why your code crashes - the call stack is not set up correctly.
Given your "working" code, your component code needs to look like this:
TExecute = procedure(eval: pointer; var variableArray: double); cdecl;
TMyComponent = Class(TComponent)
public
FHandle: THandle;
FExecute: TExecute;
procedure Calculate;
class procedure eval(var x: double); static;
end;
var
n: integer;
x: array of double;
implementation
class procedure TMyComponent.eval(var x:double);
var
mx: Array[0..200] of double absolute x;
begin
mx[0]:= 2*mx[0];
end;
procedure TMyComponent.Calculate;
begin
FHandle:= LoadLibrary(.....);
FExecute:= GetProcAddress(FHandle, 'main');
n:=2;
setlength(fx,n);
FExecute(@eval,x[0]);
end;
Since your eval()
method only accesses global variable, this works fine. But if it needs to access members of the component, you have a problem since the static
directive eliminates the Self
parameter. In which case, you have three options.
If you can, change the DLL function to accept an additional parameter that the component can pass its Self
value in, and then have the DLL pass that value to eval()
as a parameter, eg:
TExecute = procedure(eval: pointer, var variableArray: double; userdata: pointer); cdecl;
TMyComponent = Class(TComponent)
public
FHandle: THandle;
FExecute: TExecute;
procedure Calculate;
class procedure eval(var x: double; userdata: pointer); static;
end;
var
n: integer;
x: array of double;
implementation
class procedure TMyComponent.eval(var x: double; userdata: pointer);
begin
// use TMyComponent(userdata) as needed...
end;
procedure TMyComponent.Calculate;
begin
FHandle:= LoadLibrary(.....);
FExecute:= GetProcAddress(FHandle, 'main');
n:=2;
setlength(fx,n);
FExecute(@eval, x[0], Self);
end;
If #1 is not possible, and if there is only one instance of your component calling the DLL function at a time, then use a global pointer to your component, eg:
TExecute = procedure(eval: pointer, var variableArray: double); cdecl;
TMyComponent = Class(TComponent)
public
FHandle: THandle;
FExecute: TExecute;
procedure Calculate;
class procedure eval(var x: double); static;
end;
var
n: integer;
x: array of double;
implementation
var
MyComp: TMyComponent;
class procedure TMyComponent.eval(var x: double);
begin
// use MyComp as needed...
end;
procedure TMyComponent.Calculate;
begin
FHandle:= LoadLibrary(.....);
FExecute:= GetProcAddress(FHandle, 'main');
n:=2;
setlength(fx,n);
MyComp := Self;
FExecute(@eval, x[0]);
end;
If #2 is not possible because multiple component instances need to call into the DLL at the same time, then the only option left is to use a dynamic proxy. Allocate a block of executable memory and store special stub code in it along with the component Self
pointer, then pass that memory block to the DLL as if it were a normal procedure. When the DLL calls the "procedure", it's stub code is called, which can extract the component pointer from the proxy and use it as needed. This is the approach that the VCL itself uses for assigning the non-static TWinControl.WndProc()
method as Win32 API window procedure callbacks. I can't provide that code here right now, but look in the VCL's source code at the Classes.MakeObjectInstance()
function for an example.
User contributions licensed under CC BY-SA 3.0