0

我在尝试为我的组件创建一个过程时遇到问题,该过程使用包含在 DLL (TExecute) 中的过程,该过程也需要在当前代码中进行声明。所以这个过程有一个指针作为参数来知道如何处理评估。以下代码可以正常工作,但我需要将过程 eval 放在组件内部才能使用该组件中的私有变量。工作代码如下,请注意在这种情况下 eval 过程是全局的。

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;

当我像这样将过程 eval 放入 TMyComponent 时遇到问题:

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;

我知道该项目出现错误消息:0x65900381f 的访问冲突:地址 0x0000005c 的写入。进程停止。使用 Setp of Run 继续。

而且我对这个问题一无所知,我已经尝试改变几乎所有东西,但我没有得到解决方案。如果有人可以帮助我,我将不胜感激。

4

1 回答 1

4

全局过程和类方法不是一回事。类方法有一个隐藏Self参数,当类方法传递给 DLL 时,您的 DLL 不会考虑该参数。这就是您的代码崩溃的原因 - 调用堆栈设置不正确。

鉴于您的“工作”代码,您的组件代码需要如下所示:

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;

由于您的eval()方法仅访问全局变量,因此可以正常工作。但是如果它需要访问组件的成员,你就会遇到问题,因为static指令消除了Self参数。在这种情况下,您有三个选择。

  1. 如果可以,更改 DLL 函数以接受组件可以传递其Self值的附加参数,然后让 DLL 将该值eval()作为参数传递给,例如:

    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. 如果 #1 是不可能的,并且如果您的组件一次只有一个实例调用 DLL 函数,则使用指向您的组件的全局指针,例如:

    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. 如果#2 是不可能的,因为多个组件实例需要同时调用 DLL,那么剩下的唯一选择就是使用动态代理。分配一块可执行内存并在其中存储特殊的存根代码以及组件Self指针,然后将该内存块传递给 DLL,就好像它是一个正常的过程一样。当 DLL 调用“过程”时,它的存根代码被调用,它可以从代理中提取组件指针并根据需要使用它。这是 VCL 本身用于将非静态TWinControl.WndProc()方法分配为 Win32 API 窗口过程回调的方法。我现在无法在此处提供该代码,但请查看该Classes.MakeObjectInstance()函数的 VCL 源代码以获取示例。

于 2013-05-19T06:38:17.267 回答