構造体がカスタム処理なしでマーシャリング可能である場合、私は後者のアプローチを大いに好みます。この方法では、p/invoke 関数をref
型 (へのポインター) を取るものとして宣言します。または、型を構造体ではなくクラスとして宣言してから、 を渡すこともできnull
ます。
[StructLayout(LayoutKind.Sequential)]
struct NativeType{
...
}
[DllImport("...")]
static extern bool NativeFunction(ref NativeType foo);
// can't pass null to NativeFunction
// unless you also include an overload that takes IntPtr
[DllImport("...")]
static extern bool NativeFunction(IntPtr foo);
// but declaring NativeType as a class works, too
[StructLayout(LayoutKind.Sequential)]
class NativeType2{
...
}
[DllImport("...")]
static extern bool NativeFunction(NativeType2 foo);
// and now you can pass null
<pedantry>
ちなみに、ポインターを として渡す例ではIntPtr
、間違った を使用しましたAlloc
。 SendMessage
は COM 関数ではないため、COM アロケータを使用しないでください。とを使用Marshal.AllocHGlobal
しMarshal.FreeHGlobal
ます。それらの名前は不十分です。名前は、Windows API プログラミングを行った場合にのみ意味を持ちます。 を返す kernel32.dll でAllocHGlobal
呼び出します。これは、16 ビット時代には によって返される とは異なっていましたが、32 ビット Windows では同じです。GlobalAlloc
HGLOBAL
HLOCAL
LocalAlloc
(ネイティブの) ユーザー空間メモリのブロックを参照するためにこの用語HGLOBAL
を使用することは、ちょっと行き詰まっているMarshal
と思います。クラスを設計する人々は、ほとんどの .NET にとってそれがどれほど直感的でないかについて考える時間をとらなかったに違いありません。開発者。一方、ほとんどの .NET 開発者は、アンマネージ メモリを割り当てる必要がないため....
</pedantry>
編集
構造体の代わりにクラスを使用すると TypeLoadException が発生することに言及し、サンプルを求めます。を使用して簡単なテストを行いましたCHARFORMAT2
。それがあなたが使用しようとしているように見えるからです。
最初に ABC 1 :
[StructLayout(LayoutKind.Sequential)]
abstract class NativeStruct{} // simple enough
このStructLayout
属性は必須です。そうしないと、TypeLoadException が発生します。
今CHARFORMAT2
クラス:
[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Auto)]
class CHARFORMAT2 : NativeStruct{
public DWORD cbSize = (DWORD)Marshal.SizeOf(typeof(CHARFORMAT2));
public CFM dwMask;
public CFE dwEffects;
public int yHeight;
public int yOffset;
public COLORREF crTextColor;
public byte bCharSet;
public byte bPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
public string szFaceName;
public WORD wWeight;
public short sSpacing;
public COLORREF crBackColor;
public LCID lcid;
public DWORD dwReserved;
public short sStyle;
public WORD wKerning;
public byte bUnderlineType;
public byte bAnimation;
public byte bRevAuthor;
public byte bReserved1;
}
using
ステートメントを使用して、 System.UInt32
as DWORD
、LCID
、 and COLORREF
、および alias System.UInt16
asをエイリアスしましWORD
た。P/Invoke の定義を SDK の仕様にできる限り忠実に保つようにしています。 これらのフィールドのフラグ値が含まれていますCFM
。簡潔にするために定義を省略しましたが、必要に応じて追加できます。CFE
enums
私は次のように宣言SendMessage
しました:
[DllImport("user32.dll", CharSet=CharSet.Auto)]
static extern IntPtr SendMessage(
HWND hWnd, MSG msg, WPARAM wParam, [In, Out] NativeStruct lParam);
HWND
System.IntPtr
は、MSG
is System.UInt32
、is のエイリアスWPARAM
ですSystem.UIntPtr
。
[In, Out]
これが機能するには属性 onlParam
が必要です。そうしないと、両方向 (ネイティブ コードの呼び出しの前後) にマーシャリングされないようです。
私はそれを次のように呼び出します:
CHARFORMAT2 cf = new CHARFORMAT2();
SendMessage(rtfControl.Handle, (MSG)EM.GETCHARFORMAT, (WPARAM)SCF.DEFAULT, cf);
EM
また、(相対的な) 簡潔さのために省きましたSCF
。enum
私は成功を確認します:
Console.WriteLine(cf.szFaceName);
そして私は得る:
マイクロソフトサンセリフ
魔法のように動作します!
ええと、そうでないかは、どれだけの睡眠をとったか、一度にいくつのことをしようとしているかによると思います.
これはCHARFORMAT2
、blittable型の場合に機能します。(blittable 型は、マネージド メモリとアンマネージド メモリで同じ表現を持つ型です。) たとえば、このMINMAXINFO
型は説明どおりに機能します。
[StructLayout(LayoutKind.Sequential)]
class MINMAXINFO : NativeStruct{
public Point ptReserved;
public Point ptMaxSize;
public Point ptMaxPosition;
public Point ptMinTrackSize;
public Point ptMaxTrackSize;
}
これは、blittable 型が実際にはマーシャリングされていないためです。それらはメモリに固定されているだけであり、GC がそれらを移動するのを防ぎます。マネージ メモリ内のそれらの場所のアドレスがネイティブ関数に渡されます。
blittable でない型はマーシャリングする必要があります。CLR は、アンマネージ メモリを割り当て、マネージ オブジェクトとそのアンマネージ表現の間でデータをコピーし、必要なフォーマット間の変換を行います。
メンバーのCHARFORMAT2
ため、構造体は blittable ではありませんstring
。string
CLR は、固定長の文字配列が期待される.NET オブジェクトへのポインターを渡すことはできません。したがって、CHARFORMAT2
構造をマーシャリングする必要があります。
ご覧のとおり、正しいマーシャリングを行うには、相互運用機能をマーシャリングする型で宣言する必要があります。つまり、上記の定義を考えると、CLR は の静的な型に基づいて何らかの判断を下す必要がありますNativeStruct
。オブジェクトをマーシャリングする必要があることを正しく検出していると思いますが、NativeStruct
それ自体のサイズであるゼロバイトオブジェクトのみを「マーシャリング」します。
したがって、コードを(および使用する可能性のあるその他の非 blittable 型) に対して機能させるには、オブジェクトを取るCHARFORMAT2
と宣言することに戻る必要があります。申し訳ありませんが、私はこれであなたを迷わせました。SendMessage
CHARFORMAT2
前の編集のキャプチャ:
ウィペット
うん、いいムチ!
コーリー
これはトピックから外れていますが、あなたが作成しているように見えるアプリに潜在的な問題があることに気付きました。
リッチ テキスト ボックス コントロールは、標準の GDI テキスト測定機能とテキスト描画機能を使用します。なぜこれが問題なのですか?TrueType フォントは紙の上と画面上で同じように見えるという主張にもかかわらず、GDI は文字を正確に配置しないためです。問題は丸めです。
GDI は、すべて整数ルーチンを使用してテキストを測定し、文字を配置します。各文字の幅 (および各行の高さ) は、エラー修正なしで、最も近い整数のピクセルに丸められます。
エラーは、テスト アプリで簡単に確認できます。フォントを 12 ポイントの Courier New に設定します。この固定幅フォントは、1 インチあたり正確に 10 文字、または 1 文字あたり 0.1 インチの文字間隔を空ける必要があります。これは、最初の行の幅が 5.5 インチの場合、折り返しが発生する前に最初の行に 55 文字を収めることができることを意味します。
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123
しかし、試してみると、わずか 54 文字の後に折り返しが発生することがわかります。さらに、54番目の文字と 53番目の文字の一部が、ルーラー バーに表示されている見かけの余白からはみ出しています。
これは、設定が標準の 96 DPI (通常のフォント) であることを前提としています。120 DPI (大きなフォント) を使用している場合、この問題は発生しませんが、この場合、コントロールのサイズが正しくないように見えます。また、これは印刷されたページにも表示されない可能性があります。
何が起きてる?問題は、0.1 インチ (1 文字の幅) が 9.6 ピクセルであることです (これも 96 DPI を使用)。GDI は浮動小数点数を使用して文字の間隔を空けないため、これを 10 ピクセルに丸めます。したがって、55 文字は 55 * 10 = 550 ピクセル / 96 DPI = 5.7291666... インチを占めますが、予想していたのは 5.5 インチでした。
これは、ワード プロセッサ プログラムの通常の使用例ではおそらくあまり目立たないでしょうが、画面とページの異なる場所でワード ラップが発生する可能性や、印刷後に同じように並べられない可能性があります。彼らは画面上で行いました。これが作業中の商用アプリケーションである場合、これは問題になる可能性があります。
残念ながら、この問題の修正は容易ではありません。これは、リッチ テキスト ボックス コントロールを省略しなければならないことを意味します。これは、リッチ テキスト ボックス コントロールが行うすべてのことを自分で実装するという非常に手間がかかることを意味します。また、実装する必要があるテキスト描画コードがかなり複雑になることも意味します。それを行うコードがありますが、ここに投稿するには複雑すぎます。ただし、この例またはこの例が役立つ場合があります。
幸運を!
1抽象基本クラス