9

関数テーブルにあるプロシージャまたは関数を動的に呼び出す機能を試しています。特定のアプリケーションは、関数テーブルへのポインターを引数の数と型に関する情報と共にエクスポートする DLL です。ホスト アプリケーションは、DLL に問い合わせて関数を呼び出すことができます。それらがオブジェクト メソッドである場合、Rtti を使用してそれらを呼び出すことができますが、それらは通常の手順と関数です。DLL は C、Delphi などを含む任意の言語で記述できるため、DLL はオブジェクトではなく通常の関数ポインタをエクスポートする必要があります。

たとえば、DLL で宣言および入力されたレコードがあります。

TAPI = record
        add  : function (var a, b : double) : double;
        mult : function (var a, b : double) : double;
end;
PAPI = ^TAPI;

次のように宣言されたこのレコードへのポインターを取得します。

apiPtr : PAPI;

プロシージャの名前、引数の数、およびレコード内の各エントリの引数の型にもアクセスできるとします。

add 関数を呼び出したいとします。追加する関数ポインタは次のようになります。

@apiPtr^.add  // I assume this  will give me a pointer to the add function

asm を使用して必要な引数をスタックにプッシュし、結果を取得する以外に方法はないと思いますか?

最初の質問です。プロシージャを cdecl として宣言するのに最適な呼び出し規約は何ですか? 呼び出しの前にスタックを組み立てるのが最も簡単なようです。

2 番目の質問ですが、実際にこれを行うオンラインの例はありますか? http://www.swissdelphicenter.ch/torry/showcode.php?id=1745 (DynamicDllCall)に遭遇しましたが、これは私が望むものに近いものですが、以下のように単純化すると、結果へのポインター (EAX) が返されるようになりました。

function DynamicDllCall(proc : pointer; const Parameters: array of Pointer): pointer;
var x, n: Integer;
    p: Pointer;
begin
n := High(Parameters);
if n > -1 then begin
   x := n;
   repeat
     p := Parameters[x];
     asm
       PUSH p
     end;
     Dec(x);
   until x = -1;
end;
asm
  CALL proc
  MOV p, EAX  <- must be changed to "FST result" if return value is double
end;
result := p;

終わり;

結果ではなく、最初のパラメーターの値を返します。呼び出し規約が間違っているか、EAX で結果を取得する方法を誤解している可能性があります。

次のように DynamicDllCall を呼び出します。

var proc : pointer;
    parameters: array of Pointer;
    x, y, z : double;
    p : pointer;
begin
  x:= 2.3; y := 6.7;
  SetLength(parameters, 2);
  parameters[0] := @x;  parameters[1] := @y;
  proc := @apiPtr^.add;
  p := DynamicDllCall(proc, Parameters);
  z := double (p^);

どんなアドバイスもありがたく受け取った。これを行うべき方法ではないと感じる人もいるかもしれませんが、少なくともそれが可能かどうかはまだ知りません.

更新 1 add 関数が加算を行うための正しい値を取得していることを確認できます。

更新 2 add の署名を次のように変更した場合:

add  : function (var a, b, c : double) : double;

その結果を add 内の c に代入すると、parameters 配列で正しい答えを取得できます (2 ではなく 3 の要素をもう 1 つ追加すると仮定します)。したがって、問題は、関数から値が返される方法を誤解していることです。関数が値を返す方法と、それらを取得する最善の方法を誰か説明できますか?

更新 3答えがあります。私は推測したはずです。Delphi は、さまざまなレジスタを介してさまざまな型を返します。たとえば、整数は EAX を介して返されますが、double は ST(0) を介して返されます。ST(0) を結果変数にコピーするには、「MOV p, EAX」ではなく「FST 結果」を使用する必要があります。少なくとも、原則としてこれが可能であることはわかっています。それが賢明なことかどうかは、私が今考えなければならない別の問題です.

4

2 に答える 2

9

これは XY 問題です。あなたはXを実行したいのですが、何らかの理由でYが解決策であると判断しましたが、 Yを機能させるのに問題があります。あなたの場合、Xポインターを介して外部関数を呼び出し、Yスタックにパラメーターを手動でプッシュします。しかし、Xを達成するためにYを実行する必要はありません。

@apiPtr^.addは、関数へのポインターを提供しません。レコードのaddフィールドへのポインタを提供します。TAPI(addはレコードの最初のメンバーであるため、そのフィールドのアドレスはapiPtr; コードの に保持されているアドレスと等しくなりますAssert(CompareMem(@apiPtr, @apiPtr^.add, SizeOf(Pointer))。)addフィールドは関数へのポインターを保持apiPtr^.addするため、それが必要な場合は、 (と注意してください) を使用します。は^Delphi ではオプションです)。

使用する最適な呼び出し規約はstdcall. DLL 関数のエクスポートをサポートするすべての言語は、その呼び出し規約をサポートします。

関数を呼び出すために、アセンブラーやその他のトリッキーなスタック操作は必要ありません。を宣言するために使用したため、関数の型は既にわかっていますadd。そのフィールドが指す関数を呼び出すには、通常の関数を呼び出す場合と同じ構文を使用します。

z := apiPtr.add(x, y);

コンパイラはaddフィールドの宣言された型を認識しているため、スタックを調整します。

于 2013-06-04T21:57:27.813 に答える