簡體   English   中英

.NET Interop IntPtr與ref

[英].NET Interop IntPtr vs. ref

可能是一個菜鳥問題,但互操作不是我的優點之一。

除了限制重載次數之外,還有任何理由我應該聲明我的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爸爸:你有一個基於抽象(或任何)類的結構類的例子嗎? 我將簽名改為:

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

如果沒有InOut ,和MarshalAs的SendMessage函數(在我的測試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);

我得到一個System.TypeLoadException,表示CHARFORMAT2格式無效(我會嘗試捕獲它)。

例外:

無法從程序集“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
{
    ...
}

編輯:

我沒有按照常見問題解答我問了一個可以討論但沒有得到積極回答的問題。 除此之外,這個帖子中還有很多有見地的信息。 所以我會把它留給讀者投票給答案。 第一個到10個以上的投票將是答案。 如果在兩天(太平洋標准時間12/17)沒有答案符合這一點,我將添加我自己的答案,總結線程中的所有美味知識:-)

再次編輯:

我撒了謊,接受了P爸爸的回答,因為他是那個男人並得到了很大的幫助(他也有一只可愛的小猴子:-P)

如果結構是可編組的而沒有自定義處理,我非常喜歡后一種方法,你將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.AllocHGlobalMarshal.FreeHGlobal 他們名字不好; 如果您已經完成了Windows API編程,這些名稱才有意義,甚至可能不是。 AllocHGlobal在kernel32.dll中調用GlobalAlloc ,它返回一個HGLOBAL 曾經與16天內LocalAlloc返回的HLOCAL不同,但在32位Windows中它們是相同的。

我想,使用術語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別名為DWORDLCIDCOLORREF ,並將System.UInt16別名為WORD 我嘗試盡可能地將我的P / Invoke定義保持為SDK規范。 CFMCFE是包含這些字段的標志值的enums 為簡潔起見,我將其定義排除在外,但如果需要可以添加它們。

我已將SendMessage聲明為:

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

HWNDSystem.IntPtr的別名, MSGSystem.UInt32WPARAMSystem.UIntPtr

lParam上的[In, Out]屬性是必需的,否則,它似乎沒有被兩個方向編組(在調用本機代碼之前和之后)。

我叫它:

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

EMSCFenum我再次因為(相對)簡潔而被遺漏。

我檢查成功:

Console.WriteLine(cf.szFaceName);

我得到:

Microsoft Sans Serif

奇跡般有效!


嗯,或不是,取決於你有多少睡眠以及你想要一次做多少事情,我想。

如果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分配非托管內存並在托管對象及其非托管表示之間復制數據,從而在格式之間進行必要的轉換。

由於string成員, CHARFORMAT2結構是非blittable。 CLR不能只傳遞一個指向.NET string對象的指針,在該string對象中需要一個固定長度的字符數組。 因此必須對CHARFORMAT2結構進行封送處理。

如圖所示,為了正確編組,必須使用要編組的類型聲明互操作函數。 換句話說,鑒於上述定義,CLR必須基於NativeStruct的靜態類型進行某種確定。 我猜想它正確地檢測到對象需要被編組,但只是“編組”一個零字節對象,即NativeStruct本身的大小。

因此,為了使您的代碼適用於CHARFORMAT2 (以及您可能使用的任何其他非blittable類型),您將不得不返回將SendMessage聲明為獲取CHARFORMAT2對象。 對不起,我把你誤入歧途。


上一次編輯的Captcha:

whippet

是的,鞭子好!


科里

這不是主題,但我注意到你在應用程序中看起來像你正在制作的潛在問題。

富文本框控件使用標准GDI文本測量和文本繪制功能。 為什么這是個問題? 因為盡管聲稱TrueType字體在屏幕上看起來和紙上一樣,但GDI並不能准確地放置字符。 問題在於四舍五入。

GDI使用全整數例程來測量文本和放置字符。 每個字符的寬度(以及每條線的高度,就此而言)四舍五入到最接近的整數像素,沒有糾錯。

您可以在測試應用中輕松看到錯誤。 將字體設置為Courier New 12點。 這種固定寬度的字體應該在每英寸10個字符或每個字符0.1英寸的空格中放置字符。 這應該意味着,如果您的起始線寬為5.5英寸,您應該能夠在換行之前在第一行上放置55個字符。

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123

但是如果你嘗試,你會發現只有54個字符后才會發生換行。 更重要的是54 字符和標尺欄上顯示的表觀緣53 懸垂的一部分。

假設您的設置為標准96 DPI(普通字體)。 如果您使用120 DPI(大字體),您將看不到此問題,盡管在這種情況下您的控件尺寸似乎不正確。 您也不會在打印頁面上看到這一點。

這里發生了什么? 問題是0.1英寸(一個字符的寬度)是9.6像素(再次,使用96 DPI)。 GDI不使用浮點數對空格字符進行空格,因此它最多可將此數字舍入為10像素。 所以55個字符占55 * 10 = 550像素/ 96 DPI = 5.7291666 ...英寸,而我們所期望的是5.5英寸。

雖然在文字處理程序的正常使用情況下這可能不那么明顯,但是有可能出現在屏幕上不同位置而不是在頁面上發生自動換行的情況,或者一旦打印出來就會出現相同的情況。他們在屏幕上做了。 如果這是您正在處理的商業應用程序,這可能會成為您的問題。

不幸的是,解決這個問題並不容易。 這意味着你將不得不放棄豐富的文本框控件,這意味着一個很大的麻煩,實現它為你做的一切,這是相當多的。 這也意味着您必須實現的文本繪圖代碼變得相當復雜。 我有代碼可以做到這一點,但是在這里發布它太復雜了。 但是,您可能會發現此示例 示例有用。

祝好運!


1抽象基類

我有一些有趣的案例,其中參數類似於ref Guid parent ,相應的文檔說:

“指向父級的GUID的指針。傳遞空指針以使用 [插入一些系統定義的項目] 。”

如果null (或IntPtr.Zero for IntPtr參數)確實是一個無效參數,那么你可以使用ref參數 - 可能更好,因為它更清楚你需要通過什么。

如果null是有效參數,則可以傳遞ClassType而不是ref StructType 引用類型( class )的對象作為指針傳遞,它們允許null

不,您不能重載SendMessage並使wparam參數成為int。 這將使您的程序在64位版本的操作系統上失敗。 它必須是一個指針,IntPtr,blittable引用或out或ref值類型。 重載out / ref類型也不錯。


編輯:正如OP所指出的,這實際上不是問題。 64位函數調用約定通過寄存器傳遞前4個參數,而不是堆棧。 因此,對於wparam和lparam參數,沒有堆棧未對齊的危險。

我沒有看到任何缺點。

對於簡單類型和簡單結構,By-ref通常就足夠了。

如果結構具有可變大小或者您想要進行自定義處理,則應該優先使用IntPtr。

使用ref比手動操作指針更簡單且更不容易出錯,因此我認為沒有充分理由不使用它...使用ref另一個好處是你不必擔心釋放非托管分配的內存

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM