7

Delphiで記述されたネイティブDLLがあり、コールバックメカニズムを積極的に使用しています。コールバック関数は「登録」され、後でDLLの内部から呼び出されます。

function RegisterCallback(CallbackProc: TCallbackProc): Integer; stdcall;

ほとんどのコールバック関数は、次のように、参照によってプレーン構造を渡します。

TCallbackProc = procedure(Struct: PStructType); stdcall;

ここで、PStructTypeは次のように宣言されています

TStructType = packed record 
  Parameter1: array[0..9] of AnsiChar;
  Parameter2: array[0..19] of AnsiChar;
  Parameter3: array[0..29] of AnsiChar;
end;
PStructType = ^TStructType;

このDLLは、C#で記述された.NETアプリケーションによって使用されます。C#コードは非常に怠慢に記述されており、アプリケーションは全体として信頼性が低く、実行ごとに異なる場所で発生する、識別が難しい例外を示しています。

DLLは、他の多くのアプリケーションで使用されている非常に堅牢なソフトウェアであることがすでに証明されているため、疑う理由はありません。私が現在懸念しているのは、これらの構造がC#でどのように使用されるかです。

上記のレコードがC#で次のように再宣言されていると仮定します。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct TStructType
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
    public string Parameter1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
    public string Parameter2;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
    public string Parameter3;
}

コールバックは次のように宣言されます

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void CallbackProc(ref TStructType Struct);

今、何か面白いことが始まります。DLLで、登録されたコールバックが次のように呼び出されると仮定します。

var
  Struct: TStructType;
begin
  // Struct is initialized and filled with values
  CallbackProc(@Struct);
end;

しかし、C#アプリケーションで見たものと、まったく気に入らないものは、マーシャリングされた構造が将来の使用のためのポインターとして脇に保存されていることです。

private void CallbackProc(ref TStructType Struct)
{
    SomeObjectList.Add(Struct); // !!! WTF?
}

私が理解しているように、Struct変数は、DLLの奥深くにあるDelphiのスタック上に作成されたものであり、それへのポインターをクライアントアプリケーションのヒープに格納することはまったくの冒険です。

私はC#の大ファン/専門家ではないので、私の素朴な質問を許してください。マーシャラーは、構造をヒープにコピーするなど、舞台裏で何かをしますか、またはアプリケーションが時々機能するという事実は純粋な問題ですチャンス?

前もって感謝します。

4

1 に答える 1

4

A C# struct is a value type. Which means that

SomeObjectList.Add(Struct)

will make a copy of the struct. So, nothing to get concerned about.

In fact, in CallbackProc you are not operating on the object that was allocated in your Delphi code. That's because the p/invoke marshaller had to take the raw pointer that it received and convert that into a TStructType object. And a TStructType contains C# strings which are most definitely not blittable with those Delphi character arrays. So the marshaller has already added a layer in between your C# code and the Delphi code.

Since the function receives the structure by ref what happens is as follows:

  1. Before calling CallbackProc the marshaller de-serializes the raw unmanaged pointer to a TStructType object.
  2. CallbackProc is then passed that TStructType object by reference.
  3. When CallbackProc returns, the p/invoke marshaller serializes the TStructType object back to the original raw unmanaged pointer.

One of the consequences of this is that changes you make to the TStructType object are not visible to the Delphi code until the callback procedure returns. Contrast that to what happens when you call a Delphi procedure passing a variable as a var parameter. In that case any changes in the procedure are visible immediately outside that procedure.

于 2012-12-06T16:12:30.543 に答える