3

私は次のような状況になりました:

Pascal プログラムから C 関数を呼び出したい。C 関数は、渡されたポインターに値を入力する必要があります。

C関数は次のとおりです。

DLLEXPORT int dpstate_callPluginFunction(const char* plugin, const char* function, bool synchronous, const char* p0, const char* p1, const char* p2, const char* p3, const char* p4, const char* p5, const char* p6, char** o0, char** o1, char** o2, char** o3, char** o4, char** o5, char** o6)

「p」パラメータは入力パラメータで、「o」パラメータは出力パラメータです。次のように、Pascal プログラムで関数を呼び出そうとしています。

C 関数呼び出し宣言:

var dpstate_callPluginFunction: function(plugin, method: PAnsiChar; synchronous: boolean; p0, p1, p2, p3, p4, p5, p6: PAnsiChar; o0, o1, o2, o3, o4, o5, o7: PPAnsiChar): integer; cdecl;

C 関数呼び出しの読み込み:

@dpstate_callPluginFunction:= GetProcAddress(mConnectorLibrary, 'dpstate_callPluginFunction');                

関数呼び出し宣言:

function callPluginFunction(plugin, method: PAnsiChar; synchronous :boolean; param, returnParam:array of PAnsiChar): integer;

関数を呼び出す関数:

procedure TForm1.btn_pluginFunctionClick(Sender: TObject);
var param, returnParam: array of PAnsiChar;
begin
SetLength(param, 7);
SetLength(returnParam, 7);
param[0]:= 'Param1';
param[1]:= 'Param2';
connector.callPluginFunction('dpserverplugin', 'showconfigdialog', true, param, returnParam);

output.Append(returnParam[0]);
output.Append(returnParam[1]);
end; 

関数:

function PConnect.callPluginFunction(plugin, method: PAnsiChar; synchronous :boolean; param, returnParam:array of PAnsiChar): integer;
  var i, error: integer;
  var p: array[0..6] of PAnsiChar;
  var o: array[0..6] of PPAnsiChar;
begin
  for i:=0 to 6 do
            p[i]:= param[i]; 
  dpstate_callPluginFunction(plugin, method, synchronous, p[0], p[1], p[2], p[3], p[4], p[5], p[6], @o[0], @o[1], @o[2], @o[3], @o[4], @o[5], @o[6]);

  for i:=0 to 6 do
            if o[i] <> Nil then
                returnParam[i]:= o[i]^; 
end;

私の問題は、出力「returnParam」に常に「範囲外のアドレスxxxxxx」が含まれていることです。迅速な回答に満足しています:)

4

2 に答える 2

9

動的配列とオープン配列を混在させたと思います。これは同じ言葉ですが、文脈が異なります。 http://rvelthuis.de/articles/articles-openarr.html

これを試して:

type TPAnsiCharDynArray = array of PAnsiChar;

function callPluginFunction(plugin, method: PAnsiChar; 
         synchronous :boolean; param: array of PAnsiChar;
     out returnParam: TPAnsiCharDynAttay): integer;

また、この行には問題があります。

for i:=0 to 6 do
         p[i]:= param[i]; 

配列が常にあることを確実に知っているか、0..6動的配列を使用しても意味がありません。

type TDLLVectorIndex = 0..6; 
     TDLLPCharArray = array [TDLLVectorIndex] of PAnsiChar;

function callPluginFunction(const plugin, method: PAnsiChar; 
         synchronous :boolean; 
         const param: TDLLPCharArray;
         out returnParam: TDLLPCharArray): integer;

const/var/out呼び出しコントラクトを文書化し、値を複製して値として渡すのではなく、参照によって渡されるようにするための引数修飾子を忘れないでください

または、正確な寸法がわからない場合は、6マジックナンバーを想定して、渡された配列の実際のサイズまで反復する必要はありません。

for i:=0 to High(param) do
        p[i]:= param[i]; 

ただし、これはあなたのケースではないようですが、パラメータ「god-only-knows-what-length-array」を明示的に宣言し、ハードコードされたマジック定数で使用することは非常に印象的でした。


繰り返しになりますが、PAscalブリッジを作成する場合は、C文字ポインターを使用するよりもPAscal文字列を使用する方が適切です。

function callPluginFunction(const plugin, method: AnsiString; 
         synchronous :boolean; 
         .....

boolCタイプとは何かを追跡します。本当に1バイトのブール値ですか?それとも、1、2、または4バイトかかる可能性のあるWindowsブールですか?trueまた、実際には+1または-1のバイナリ非互換性がある場合もあります。

しかし、そのbool不確実性がなければ、引数修飾子を適用した後のfn宣言は次のようになります。

type fn_dpstate_callPluginFunction = 
   function(const plugin, method: PAnsiChar; synchronous: boolean;
   const  p0, p1, p2, p3, p4, p5, p6: PAnsiChar; 
   var o0, o1, o2, o3, o4, o5, o7: PAnsiChar): integer; cdecl;
var dpstate_callPluginFunction: fn_dpstate_callPluginFunction;

C ++では、パラメータを-refで渡すことは一般的ではなく、Cではおそらく存在しません。しかし、平均的なPascalコードでは、パラメーターを-refで渡す方が、ポインターを渡すよりも通常で安全であると考えられています。


これは、ブリッジの再コーディングになります。

type TDLLVectorIndex = 0..6; 
     TDLLPCharArray = array [TDLLVectorIndex] of PAnsiChar;

function PConnect.callPluginFunction(const plugin, method: AnsiString; 
         synchronous :boolean; 
         const p: TDLLPCharArray;
         out o: TDLLPCharArray): integer;

  var Error: integer;
begin
  Error :=
    dpstate_callPluginFunction( PAnsiChar(plugin), PAnsiChar(method), synchronous, 
        p[0], p[1], p[2], p[3], p[4], p[5], p[6],
        @o[0], @o[1], @o[2], @o[3], @o[4], @o[5], @o[6]);
  Result := Error + 10;
// or something like that - you did had the reason
// to declare the var and declare function return type afterall
end;

procedure TForm1.btn_pluginFunctionClick(Sender: TObject);
var param, returnParam: TDLLPCharArray;
begin
  FillChar(param, 0, SizeOf(TDLLPCharArray)); // maybe redundant, but to be on safe side
  param[0]:= 'Param1';
  param[1]:= 'Param2';
  connector.callPluginFunction('dpserverplugin', 'showconfigdialog', true, param, returnParam);
....

PS。最後になりましたが、コネクタには実際のメンバー変数が含まれていますか?私は非静的なもの、いくつかのコネクタインスタンス間で異なるものを意味しますか?そうでない場合は、おそらくコネクタクラスのインスタンスを作成し、作成しないcallPluginFunctionでしょう。class function

  param[0]:= 'Param1';
  param[1]:= 'Param2';
  PConnect.callPluginFunction('dpserverplugin', 'showconfigdialog', true, param, returnParam);

PPS。デビッドは契約について非常に正しいです。特に「誰がメモリを割り当てるのか」について DLLが文字列定数を返すだけの場合でも、巧妙な落とし穴があります。DLLをロードし、文字列へのポインタを取得し、DLLをアンロードし、ポインタを使用してみてください-アクセス違反。したがって、上記のブリッジは正しいインターフェイス変換であると私は信じていますが、Davidが指摘した問題のため、必ずしも全体像で機能しているとは限りません。

PPPS。配列データ型の宣言は冗長に見えるかもしれませんが、Delphinovadaysの愚かな制限を克服しています。「文字列の配列」に対する2つのエイリアスの扱いが異なるのはなぜですか。

PPPPS。outパラメータの使用については議論の余地があります。たとえば、Davidは、Delphiはほとんどのデータ型に対して実際にそれらを実装していないため、ほとんどの場合、var代わりにパラメータを使用する必要があると考えています。個人的には、outパラメーターを使用することは、契約を文書化するのに役立ち、FPCや他のコンパイラーとの互換性に適していると思います。

于 2013-02-20T10:29:41.180 に答える
2

もう1つの小さな落とし穴は、「ブール」タイプです。パスカルブール値は通常、GTK gbooleansとほぼ同じように、false = 0 true = 1と見なされ、未定義のままになります。

Cブール値は通常0=false、その他はtrueと見なします。

最近の無料パスカルには、さまざまなサイズの両方の完全なセットがあります。パスカル側はBoolean8..boolean64、C側はbytebool、wordbool、longboolなど。

于 2013-02-20T12:21:12.510 に答える