从 DLL 中调用 DLL 函数的调用方

Calling a caller to a DLL function from within the DLL

是否可以调用在Delphi中编写的DLL中调用函数的函数?加载 DLL 的调用程序只能访问我的 DLL 的导出函数,不能导出它自己的函数(它是 Easylanguge 编程语言,没有导出命令或传递指针的能力)。我从DLL调用时不需要传递任何参数,只需在return地址点后再次执行代码即可。 那么如果Easylanguage中的一个函数调用了DLL中的一个函数,是否可以在DLL中使用Easylanguage函数中的return地址,以便稍后在return地址处调用Easylanguage函数?即使是黑客也可以。 在我尝试将它应用到实际的 DLL 和 Easylanguage 平台之前,我想让我编写的这个概念代码正常工作。我有时会遇到访问冲突。

Delphi模拟DLL与Easylanguage交互的demo:

type   
    Tra_func = function: Integer;

var
  Form9: TForm9;
  ra: pointer;
  ra_func: Tra_func;

implementation

{$R *.dfm}

function dll_func: integer;
begin
  ra := System.ReturnAddress;
  Form9.ListBox1.Items.Add(Format('RA to "easylanguage_func": %p', [ra]));
  Form9.ListBox1.Items.Add('END of "dll" function');
  result := 1;
end;

function easylanguage_func: integer; // temp stand-in function for Easylanguage
begin
  Form9.ListBox1.Items.Add('Call "dll" to get return address...');
  dll_func();
  Form9.ListBox1.Items.Add('END of "easylanguage_func" function');
  result := 1;
end;

procedure TForm9.Button1Click(Sender: TObject);
begin
  
  easylanguage_func; // * this call would be from Easylanguage to the DLL 
  ListBox1.Items.Add('Calling RA address of "easylanguage_func"');
  ra_func := Tra_func(ra);
  ra_func; // * this call would be located in the DLL
end;

end.

调用 DLL 函数的 Easylanguage 例程可能如下所示:

external: "ra_test_dll.dll", INT, "GetRAFunction";

method void ReturnFunction() // * can not export this *
begin 
    Print("GetRAFunction");
    GetRAFunction(); // calls function in DLL
    // *** returns here, start execution here when call from the DLL later 
    Print("*RA - next line*");
end;

作为参数传递的字符串和 return 双向..

易语言:

external: "ts_dll_str_test.dll", String, "StringTest", String; //  Delphi DLL function def
    
method void StrFunction(String ss) 
variables: 
   String ss2;
begin 
    ss2 = StringTest(ss+"abc");
    Print(ss2); // Output = ABCD5FGHIJKLM
end;

Call: StrFunction("0123456789")

Delphi DLL:

var
  ss: AnsiString;
  myCharPtr: PAnsiChar;

function StringTest(StrIn: PAnsiChar): PAnsiChar; stdcall;  // called by EL
begin
  ss  := 'ABCDEFGHIJKLM';
  myCharPtr := @ss[1];
  myCharPtr[4] := StrIn[5];
  result := myCharPtr;
end;

exports StringTest;

谢谢。

我设计了一个在调用应用程序和 DLL 中都使用 Delphi 的演示。您必须在 EasyLanguage 编程中应用相同的“技巧”。

这个想法是,当 DLL 需要调用可执行文件中的函数时 - 无论如何都不会导出的函数 - 它只是 return 带有一个特殊值,可以传输调用任何 EasyLanguage 所需的所有信息(此处Delphi)函数。

这意味着在调用者和 DLL 中,函数都是循环。 EXE 调用 DLL 传递初始参数,DLL 获取它和 return 描述它需要的函数调用的特殊值。 EXE 认识到,在他的代码中调用所需的函数,然后再次调用 DLL 中的相同函数,这次传递函数调用的结果。并且该过程循环一秒、三秒,依此类推。最后,DLL 能够生成最终结果,并且 return 它没有指示函数调用的标记。

因为 EasyLaguage 不支持指针,所以一切都使用 AnsiString 处理。

下面的代码已最大限度地简化,以使其更具可读性。在实际应用程序中,最好验证许多事情以避免意外行为。

这是可执行代码:

unit CallingCallerDemoMain;

interface

uses
    Winapi.Windows, Winapi.Messages,
    System.SysUtils, System.Variants, System.Classes,
    Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
    ParamParsing;

type
    TCallingCallerForm = class(TForm)
        Button1: TButton;
        Memo1: TMemo;
        procedure Button1Click(Sender: TObject);
    public
        function CallDll(Value: Integer): String;
        function DemoSquare(Arg1: Integer): Integer;
        function DemoSum(Arg1: Integer): Integer;
    end;

// Declaration for function in DLL
// In this demo, the function takes one integer argument and return a string
// looking like "Value=4  Square=16  Sum=8". The value is the argument, the
// square and the sum are computed by THIS executable: the DLL somehow call
// this executable.
// The use of AnsiChar is required for this demo because it is normally not
// written in Delphi but in EasyLanguage which has only ANSI strings.
function DllFunc(
    StrIn : PAnsiChar
    ) : PAnsiChar; stdcall; external 'CallingCallerDemoDll.dll';

var
  CallingCallerForm: TCallingCallerForm;

implementation

{$R *.dfm}

function TCallingCallerForm.DemoSquare(Arg1 : Integer) : Integer;
begin
    Result := Arg1 * Arg1;
    Memo1.Lines.Add('DemoSquare called');
end;

function TCallingCallerForm.DemoSum(Arg1 : Integer) : Integer;
begin
    Result := Arg1 + Arg1;
    Memo1.Lines.Add('DemoSum called');
end;

function TCallingCallerForm.CallDll(Value : Integer) : String;
var
    S         : String;
    DllFctPrm : AnsiString;
    Params    : String;
    FctName   : String;
    Arg1      : Integer;
    Status    : Boolean;
    State     : String;
    Value1    : Integer;
    Value2    : Integer;
begin
    DllFctPrm := '4';
    while TRUE do begin
        S := String(DllFunc(PAnsiChar(DllFctPrm)));
        if not ((S <> '') and (S[1] = '[') and (S[Length(S)] = ']')) then begin
            Result := S;
            Exit;
        end
        else begin
            Params  := Trim(Copy(S, 2, Length(S) - 2));
            FctName := ParamByNameAsString(Params, 'FctName', Status, '');
            State   := ParamByNameAsString(Params, 'State',   Status, '');
            Memo1.Lines.Add('Callback="' + Params + '"');
            if SameText(FctName, 'DemoSquare') then begin
                Arg1    := ParamByNameAsInteger(Params, 'Arg1', Status, 0);
                Value1  := DemoSquare(Arg1);
                DllFctPrm := AnsiString('[' +
                                         'State=' + State +';' +
                                         'Value=' + IntToStr(Value1) +
                                        ']');
                continue;
            end
            else if SameText(FctName, 'DemoSum') then begin
                Arg1    := ParamByNameAsInteger(Params, 'Arg1', Status, 0);
                Value2  := DemoSum(Arg1);
                DllFctPrm := AnsiString('[' +
                                         'State=' + State +';' +
                                         'Value=' + IntToStr(Value2) +
                                        ']');
                continue;
            end
            else
                raise Exception.Create('Unexpected function name');
        end;
    end;
end;

procedure TCallingCallerForm.Button1Click(Sender: TObject);
begin
    Memo1.Lines.Add('Result: ' + CallDll(4));
end;

end.

这是 DLL 的代码:

library CallingCallerDemoDll;

uses
  System.SysUtils,
  System.Classes,
  ParamParsing in '..\DirectCompute\Mandel\Delphi\ParamParsing.pas';

{$R *.res}

var
    GBuffer : AnsiString;
    Value  : Integer;
    Value1 : Integer;
    Value2 : Integer;

function DllFunc(StrIn : PAnsiChar) : PAnsiChar; stdcall;
var
    S      : String;
    Params : String;
    State  : Integer;
    Status : Boolean;
begin
    S := String(StrIn);
    if not ((S <> '') and (S[1] = '[') and (S[Length(S)] = ']')) then begin
        // Normal call
        State  := 1;
        Value  := StrToInt(S);
        Value1 := 0;
        Value2 := 0;
    end;
    while TRUE do begin
        if not ((S <> '') and (S[1] = '[') and (S[Length(S)] = ']')) then begin
            // Call caller
            {$WARN USE_BEFORE_DEF OFF}
            case State of
            1: GBuffer := '[FctName=' + '"DemoSquare";' +
                           'Arg1='    + AnsiString(IntToStr(Value)) + ';' +
                           'State='   + AnsiString(IntToStr(State)) + ']';
            2: GBuffer := '[FctName=' + '"DemoSum";' +
                           'Arg1='    + AnsiString(IntToStr(Value)) + ';' +
                           'State='   + AnsiString(IntToStr(State)) + ']';
            end;
            Result := PAnsiChar(GBuffer);
            Exit;
        end
        else begin
            // Return from function
            Params := Trim(Copy(S, 2, Length(S) - 2));
            State  := StrToInt(ParamByNameAsString(Params, 'State', Status, ''));
            case State of
            1:  begin
                    Value1 := ParamByNameAsInteger(Params, 'Value', Status, 0);
                    State  := 2;
                    S   := '';
                    continue;
                end;
            2:  begin
                    Value2 := ParamByNameAsInteger(Params, 'Value', Status, 0);
                    GBuffer := AnsiString(Format('Value=%d Square=%d  Sum=%d',
                                                 [Value, Value1, Value2]));
                    Result := PAnsiChar(GBuffer);
                    Exit;
                end;
            end;
        end;
    end;
end;

exports
    DllFunc;
begin
end.

最后是解析值的支持单元:

unit ParamParsing;

interface

uses
    SysUtils;

function ParamByNameAsString(
    const Params     : String;
    const ParamName  : String;
    var   Status     : Boolean;
    const DefValue   : String) : String;
function ParamByNameAsInteger(
    const Params     : String;
    const ParamName  : String;
    var   Status     : Boolean;
    const DefValue   : Integer) : Integer;

implementation

// Parameters format = 'name1="value";name2="value2";....;nameN="valueN"
function ParamByNameAsString(
    const Params     : String;
    const ParamName  : String;
    var   Status     : Boolean;
    const DefValue   : String) : String;
var
    I, J  : Integer;
    Ch    : Char;
begin
    Status := FALSE;
    I := 1;
    while I <= Length(Params) do begin
        J := I;
        while (I <= Length(Params)) and (Params[I] <> '=')  do
            Inc(I);
        if I > Length(Params) then begin
            Result := DefValue;
            Exit;                  // Not found
        end;
        if SameText(ParamName, Trim(Copy(Params, J, I - J))) then begin
            // Found parameter name, extract value
            Inc(I); // Skip '='
            // Skip spaces
            J := I;
            while (J < Length(Params)) and (Params[J] = ' ') do
                Inc(J);
            if (J <= Length(Params)) and (Params[J] = '"') then begin
                // Value is between double quotes
                // Embedded double quotes and backslashes are prefixed
                // by backslash
                I      := J;
                Status := TRUE;
                Result := '';
                Inc(I);        // Skip starting delimiter
                while I <= Length(Params) do begin
                    Ch := Params[I];
                    if Ch = '\' then begin
                        Inc(I);          // Skip escape character
                        if I > Length(Params) then
                            break;
                        Ch := Params[I];
                    end
                    else if Ch = '"' then
                        break;
                    Result := Result + Ch;
                    Inc(I);
                end;
            end
            else begin
                // Value is up to semicolon or end of string
                J := I;
                while (I <= Length(Params)) and (Params[I] <> ';') do
                    Inc(I);
                Result := Trim(Copy(Params, J, I - J));
                Status := TRUE;
            end;
            Exit;
        end;
        // Not good parameter name, skip to next
        Inc(I); // Skip '='
        if (I <= Length(Params)) and (Params[I] = '"') then begin
            Inc(I);        // Skip starting delimiter
            while I <= Length(Params) do begin
                Ch := Params[I];
                if Ch = '\' then begin
                    Inc(I);          // Skip escape character
                    if I > Length(Params) then
                        break;
                end
                else if Ch = '"' then
                    break;
                Inc(I);
            end;
            Inc(I);        // Skip ending delimiter
        end;
        // Param ends with ';'
        while (I <= Length(Params)) and (Params[I] <> ';')  do
            Inc(I);
        Inc(I);  // Skip semicolon
    end;
    Result := DefValue;
end;

function ParamByNameAsInteger(
    const Params     : String;
    const ParamName  : String;
    var   Status     : Boolean;
    const DefValue   : Integer) : Integer;
begin
    Result := StrToInt(ParamByNameAsString(Params, ParamName, Status, IntToStr(DefValue)));
end;

end.

使用 Delphi 10.4.2 测试一切正常(应该适用于任何其他最近的 Delphi)。