12

おそらく初心者の質問ですが、相互運用性はまだ私の長所の 1 つではありません。

オーバーロードの数を制限する以外に、次のように DllImports を宣言する必要がある理由があります。

[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);

そして、次のように使用します。

IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(formatrange));
Marshal.StructureToPtr(formatrange, lParam, false);

int returnValue = User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_FORMATRANGE, wParam, lParam);

Marshal.FreeCoTaskMem(lParam);

ターゲットを絞ったオーバーロードを作成するのではなく:

[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref FORMATRANGE lParam);

そしてそれを次のように使用します:

FORMATRANGE lParam = new FORMATRANGE();
int returnValue = User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_FORMATRANGE, wParam, ref lParam);

by ref オーバーロードは結局使いやすくなりますが、私が気付いていない欠点があるのだろうかと思っています。

編集:

これまでのところ、多くの素晴らしい情報があります。

@P Daddy: 構造体クラスを抽象 (または任意の) クラスに基づいている例はありますか? 署名を次のように変更しました。

[DllImport("user32.dll", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] CHARFORMAT2 lParam);

In、、OutおよびMarshalAsSendMessage (私のテストでは EM_GETCHARFORMAT) がないと失敗します。上記の例はうまく機能しますが、次のように変更すると:

[DllImport("user32.dll", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] NativeStruct lParam);

CHARFORMAT2 形式が無効であることを示す System.TypeLoadException が表示されます (ここでキャプチャしてみます)。

例外:

形式が無効なため、アセンブリ 'CC.Utilities、Version=1.0.9.1212、Culture=neutral、PublicKeyToken=111aac7a42f7965e' から型 'CC.Utilities.WindowsApi.CHARFORMAT2' を読み込めませんでした。

NativeStruct クラス:

public class NativeStruct
{
}

属性のabstract追加などを試しましたが、同じ例外が発生します。StructLayout

[StructLayout(LayoutKind.Sequential)]
public class CHARFORMAT2: NativeStruct
{
    ...
}

編集:

FAQ に従わず、議論できる質問をしましたが、肯定的な回答は得られませんでした。それ以外にも、このスレッドには多くの洞察に満ちた情報がありました。したがって、回答を投票するのは読者に任せます。最初の 1 票から 10 票以上の賛成票が答えになります。2 日 (12/17 PST) にこれを満たす回答がない場合は、スレッド内のおいしい知識をすべてまとめた独自の回答を追加します :-)

再度編集:

私は嘘をついて、P パパの答えを受け入れました。

4

5 に答える 5

15

構造体がカスタム処理なしでマーシャリング可能である場合、私は後者のアプローチを大いに好みます。この方法では、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、間違った を使用しましたAllocSendMessageは COM 関数ではないため、COM アロケータを使用しないでください。とを使用Marshal.AllocHGlobalMarshal.FreeHGlobalます。それらの名前は不十分です。名前は、Windows API プログラミングを行った場合にのみ意味を持ちます。 を返す kernel32.dll でAllocHGlobal呼び出します。これ、16 ビット時代には によって返される とは異なっていましたが、32 ビット Windows では同じです。GlobalAllocHGLOBALHLOCALLocalAlloc

(ネイティブの) ユーザー空間メモリのブロックを参照するためにこの用語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.UInt32as DWORDLCID、 and COLORREF、および alias System.UInt16asをエイリアスしましWORDた。P/Invoke の定義を SDK の仕様にできる限り忠実に保つようにしています。 これらのフィールドのフラグ値が含まれていますCFM。簡潔にするために定義を省略しましたが、必要に応じて追加できます。CFEenums

私は次のように宣言SendMessageしました:

[DllImport("user32.dll", CharSet=CharSet.Auto)]
static extern IntPtr SendMessage(
    HWND hWnd, MSG msg, WPARAM wParam, [In, Out] NativeStruct lParam);

HWNDSystem.IntPtrは、MSGis System.UInt32、is のエイリアスWPARAMですSystem.UIntPtr

[In, Out]これが機能するには属性 onlParamが必要です。そうしないと、両方向 (ネイティブ コードの呼び出しの前後) にマーシャリングされないようです。

私はそれを次のように呼び出します:

CHARFORMAT2 cf = new CHARFORMAT2();
SendMessage(rtfControl.Handle, (MSG)EM.GETCHARFORMAT, (WPARAM)SCF.DEFAULT, cf);

EMまた、(相対的な) 簡潔さのために省きましたSCFenum

私は成功を確認します:

Console.WriteLine(cf.szFaceName);

そして私は得る:

マイクロソフトサンセリフ

魔法のように動作します!


ええと、そうでないかは、どれだけの睡眠をとったか、一度にいくつのことをしようとしているかによると思います.

これCHARFORMAT2blittable型の場合に機能します。(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 ではありませんstringstringCLR は、固定長の文字配列が期待される.NET オブジェクトへのポインターを渡すことはできません。したがって、CHARFORMAT2構造をマーシャリングする必要があります。

ご覧のとおり、正しいマーシャリングを行うには、相互運用機能をマーシャリングする型で宣言する必要があります。つまり、上記の定義を考えると、CLR は の静的な型に基づいて何らかの判断を下す必要がありますNativeStruct。オブジェクトをマーシャリングする必要があることを正しく検出していると思いますが、NativeStructそれ自体のサイズであるゼロバイトオブジェクトのみを「マーシャリング」します。

したがって、コードを(および使用する可能性のあるその他の非 blittable 型) に対して機能させるには、オブジェクトを取るCHARFORMAT2と宣言することに戻る必要があります。申し訳ありませんが、私はこれであなたを迷わせました。SendMessageCHARFORMAT2


前の編集のキャプチャ:

ウィペット

うん、いいムチ!


コーリー

これはトピックから外れていますが、あなたが作成しているように見えるアプリに潜在的な問題があることに気付きました。

リッチ テキスト ボックス コントロールは、標準の 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抽象基本クラス

于 2009-12-15T22:53:42.403 に答える
3

パラメータが次のようなものref Guid parentで、対応するドキュメントに次のような楽しいケースがありました。

「親を指定する GUID へのポインター。null ポインターを渡して[システム定義の項目を挿入]を使用します。」

null(またはパラメータIntPtr.Zeroに対してIntPtr)が本当に無効なパラメータである場合は、パラメータを使用しても問題ありrefません。渡す必要があるものが正確に明確であるため、さらに良いでしょう。

nullが有効なパラメータである場合は、 のClassType代わりに渡すことができますref StructType。参照型 ( class) のオブジェクトはポインターとして渡され、null.

于 2009-12-15T22:44:12.860 に答える
2

いいえ、SendMessage をオーバーロードして wparam 引数を int にすることはできません。これにより、64 ビット バージョンのオペレーティング システムでプログラムが失敗します。IntPtr、blittable 参照、または out または ref 値型のいずれかのポインターである必要があります。それ以外の場合は、out/ref 型をオーバーロードしても問題ありません。


編集:OPが指摘したように、これは実際には問題ではありません。64 ビットの関数呼び出し規則では、最初の 4 つの引数はスタックではなくレジスタを介して渡されます。したがって、wparam および lparam 引数のスタックのミスアラインメントの危険はありません。

于 2009-12-16T02:09:53.733 に答える
1

ポインターを手動で操作するよりも、使用するref方が簡単でエラーが発生しにくいため、使用しない正当な理由はありません...使用するもう1つの利点は、ref管理されていない割り当てられたメモリを解放することを心配する必要がないことです

于 2009-12-15T22:44:29.680 に答える
1

欠点は見当たりません。

単純な型と単純な構造には、多くの場合、参照によって十分です。

構造体が可変サイズの場合、またはカスタム処理を行いたい場合は、IntPtr を優先する必要があります。

于 2009-12-15T22:38:38.737 に答える