Procedure in a component that uses another procedure in a DLL

0

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.

delphi
dll
asked on Stack Overflow May 18, 2013 by Felipe • edited May 19, 2013 by Ken White

1 Answer

4

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.

  1. 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;
    
  2. 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;
    
  3. 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.

answered on Stack Overflow May 19, 2013 by Remy Lebeau • edited May 19, 2013 by Rudy Velthuis

User contributions licensed under CC BY-SA 3.0