1

Shell_NotifyIcon関数 [ 1 ] を C# から呼び出そうとしています。NOIFYICONDATA関数の 1 つのパラメーターは、構造体へのポインターです[ 2 ]。この構造体にはTCHAR配列、ポインターが含まれており、4 つの異なるバージョンがあります (使用中の OS/API によって異なります)。構造体の最初のフィールド ( cbSize) は、関数に渡す前に、呼び出し元が構造体のサイズ (バイト単位) に設定する必要がありShell_NotifyIconます。

私の現在のアプローチは、4つのクラスを使用することです:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
[BestFitMapping(false, ThrowOnUnmappableChar = true)]
public class  NotifyIconData { ... }

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
[BestFitMapping(false, ThrowOnUnmappableChar = true)]
public class  NotifyIconData2 : NotifyIconData { ... }

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
[BestFitMapping(false, ThrowOnUnmappableChar = true)]
public class  NotifyIconData3 : NotifyIconData2 { ... }

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
[BestFitMapping(false, ThrowOnUnmappableChar = true)]
public class  NotifyIconData4 : NotifyIconData3 { ... }

のコンストラクターで、構造体のサイズを決定するためNotifyIconDataに使用します。Marshal.SizeOf(this.GetType());結果は正しいです (少なくとも、管理されていない Unicode/Ansi/x86/x64 ビルドの , , と同じですNOTIFYICONDATA_V1_SIZENOTIFYICONDATA_V2_SIZEこのアプローチの理由は、特にパディングが行われているため、マジック ナンバーを使用したくないためです。構造体の正しいサイズを計算します. フィールドの実装にはちょっとしたトリックもあります. このフィールドは 64文字の配列 (バージョン 1) または 128文字の配列 (バージョン 2) のいずれかになります.このクラスをシミュレートするには、次のフィールド定義が含まれています。NOTIFYICONDATA_V3_SIZEsizeof(NOTIFYICONDATAW)szTipTCHARTCHARNotifyIconData

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    protected string szTip;

クラスではNotifyIconData2、次の定義でこのフィールドを「拡張」します。

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
    private string szTipExtension;

このフィールドは、実際の値を 2 つのフィールドに分割する仮想プロパティを介してアクセスされます。

これらのクラスは、Shell_NotifyIcon次のように宣言された関数で使用されます。

    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private delegate bool ShellNotifyIconFunction(
        [In, MarshalAs(UnmanagedType.U4)] uint dwMessage,
        [In] NotifyIconData lpdata);

NotifyIconData4インスタンスを関数に渡すことができるようになったと思う人もいるでしょう。コンパイル時にこれは機能します (期待どおり)。しかし、実行時に MDAFatalExecutionEngineError例外が発生します。パラメータのタイプを から に変更するNotifyIconDataNotifyIconData4、呼び出しが成功し、機能します。

NotifyIconDataマーシャラーは動的型ではなく静的型を使用しNotifyIconData4てデータをマーシャリングしているようです。誰でもそれを確認できますか?または、マーシャリングが継承でどのように機能するかについての情報を誰かに教えてもらえますか?

4

1 に答える 1

0

ShellNotifyIconFunction基本クラスによって使用されるメモリのみを受け入れると宣言するNotifyIconDataと、呼び出しが行われたときにアンマネージ メモリにコピーされます。アンマネージ側でメンバーのコンテンツに基づいて派生構造体からメンバーにアクセスしようとするとcbSize、P/Invoke レイヤーによって割り当てられたデータの境界外のデータにアクセスしているため、エラーが発生します。

これを回避するには、実行している Windows のバージョンに基づいて独自のマーシャリングを行うことができます。

ShellNotifyIconFunctionの 2 番目のパラメーターをas として宣言する代わりに、としてNotifyIconData宣言する必要がありますIntPtr。次に、関数を自分で呼び出すときに提供されるバッファーの内容を割り当てて管理する必要があります。

var notifyIconData = new NotifyIconData4(); // Or use another version depending on the
...                                         // version of Windows.
var buffer = IntPtr.Zero;
try {
  buffer = Marshal.AllocHGlobal(Marshal.SizeOf(notifyIconData));
  Marshal.StructureToPtr(notifyIconData, buffer, false);
  var result = ShellNotifyIconFunction(message, buffer);
  ...
}
finally {
  if (buffer != IntPtr.Zero)
    Marshal.FreeHGlobal(buffer);
}
于 2012-08-09T09:04:19.360 に答える