简体   繁体   English

.NET Interop IntPtr与ref

[英].NET Interop IntPtr vs. ref

Probably a noob question but interop isn't one of my strong points yet. 可能是一个菜鸟问题,但互操作不是我的优点之一。

Aside from limiting the number of overloads is there any reason I should declare my DllImports like: 除了限制重载次数之外,还有任何理由我应该声明我的DllImports:

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

And use them like this: 并像这样使用它们:

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);

Rather than creating a targeted overload: 而不是创建目标重载:

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

And using it like: 使用它像:

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

The by ref overload ends up being easier to use but I'm wondering if there is a drawback that I'm not aware of. by ref重载最终更容易使用,但我想知道是否存在我不知道的缺点。

Edit: 编辑:

Lots of great info so far guys. 到目前为止,有很多很棒的信息。

@P Daddy: Do you have an example of basing the struct class off an abstract (or any) class? @P爸爸:你有一个基于抽象(或任何)类的结构类的例子吗? I changed my signature to: 我将签名改为:

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

Without the In , Out , and MarshalAs the SendMessage (EM_GETCHARFORMAT in my test) fail. 如果没有InOut ,和MarshalAs的SendMessage函数(在我的测试EM_GETCHARFORMAT)失败。 The above example works well but if I change it to: 以上示例效果很好,但如果我将其更改为:

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

I get a System.TypeLoadException that says the CHARFORMAT2 format is not valid (I'll try and capture it for here). 我得到一个System.TypeLoadException,表示CHARFORMAT2格式无效(我会尝试捕获它)。

The exception: 例外:

Could not load type 'CC.Utilities.WindowsApi.CHARFORMAT2' from assembly 'CC.Utilities, Version=1.0.9.1212, Culture=neutral, PublicKeyToken=111aac7a42f7965e' because the format is invalid. 无法从程序集“CC.Utilities,Version = 1.0.9.1212,Culture = neutral,PublicKeyToken = 111aac7a42f7965e”加载类型“CC.Utilities.WindowsApi.CHARFORMAT2”,因为格式无效。

The NativeStruct class: NativeStruct类:

public class NativeStruct
{
}

I've tried abstract , adding the StructLayout attribute, etc. and I get the same exception. 我尝试过abstract ,添加StructLayout属性等,我得到了相同的异常。

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

Edit: 编辑:

I didn't follow the FAQ and I asked a question that can be discussed but not positively answered. 我没有按照常见问题解答我问了一个可以讨论但没有得到积极回答的问题。 Aside from that there has been lot's of insightful information in this thread. 除此之外,这个帖子中还有很多有见地的信息。 So I'll leave it up to the readers to vote up an answer. 所以我会把它留给读者投票给答案。 First one to over 10 up-votes will be the answer. 第一个到10个以上的投票将是答案。 If no answer meets this in two days (12/17 PST) I'll add my own answer that summarizes all the yummy knowledge in the thread :-) 如果在两天(太平洋标准时间12/17)没有答案符合这一点,我将添加我自己的答案,总结线程中的所有美味知识:-)

Edit Again: 再次编辑:

I lied, accepting P Daddy's answer because he is the man and has been a great help (he has a cute little monkey too :-P) 我撒了谎,接受了P爸爸的回答,因为他是那个男人并得到了很大的帮助(他也有一只可爱的小猴子:-P)

If the struct is marshalable without custom processing, I greatly prefer the latter approach, where you declare the p/invoke function as taking a ref (pointer to) your type. 如果结构是可编组的而没有自定义处理,我非常喜欢后一种方法,你将p / invoke函数声明为接受你的类型的ref (指针)。 Alternatively, you can declare your types as classes instead of structs, and then you can pass null , as well. 或者,您可以将类型声明为类而不是结构,然后您也可以传递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>

By the way, in your example passing a pointer as an IntPtr , you've used the wrong Alloc . 顺便说一下,在你的例子中将指针作为IntPtr传递,你使用了错误的Alloc SendMessage is not a COM function, so you shouldn't be using the COM allocator. SendMessage不是COM函数,因此您不应该使用COM分配器。 Use Marshal.AllocHGlobal and Marshal.FreeHGlobal . 使用Marshal.AllocHGlobalMarshal.FreeHGlobal They're poorly named; 他们名字不好; the names only make sense if you've done Windows API programming, and maybe not even then. 如果您已经完成了Windows API编程,这些名称才有意义,甚至可能不是。 AllocHGlobal calls GlobalAlloc in kernel32.dll, which returns an HGLOBAL . AllocHGlobal在kernel32.dll中调用GlobalAlloc ,它返回一个HGLOBAL This used to be different from an HLOCAL , returned by LocalAlloc back in the 16-bit days, but in 32-bit Windows they are the same. 曾经与16天内LocalAlloc返回的HLOCAL不同,但在32位Windows中它们是相同的。

The use of the term HGLOBAL to refer to a block of (native) user-space memory just kind of stuck, I guess, and the people designing the Marshal class must not have taken the time to think about how unintuitive that would be for most .NET developers. 我想,使用术语HGLOBAL来指代(本机)用户空间内存块只是一种卡住了,并且设计Marshal类的人不应该花时间思考对于大多数人来说多么不直观.NET开发人员。 On the other hand, most .NET developers don't need to allocate unmanaged memory, so.... 另一方面,大多数.NET开发人员不需要分配非托管内存,所以....

</pedantry>


Edit 编辑

You mention you're getting a TypeLoadException when using a class instead of a struct, and ask for a sample. 你提到你在使用类而不是结构时得到一个TypeLoadException,并要求一个样本。 I did up a quick test using CHARFORMAT2 , since it looks like that's what you're trying to use. 我使用CHARFORMAT2了快速测试,因为它看起来就像你正在尝试使用的那样。

First the ABC 1 : 首先是ABC 1

[StructLayout(LayoutKind.Sequential)]
abstract class NativeStruct{} // simple enough

The StructLayout attribute is required, or you will get a TypeLoadException. StructLayout属性是必需的,否则您获得TypeLoadException。

Now the CHARFORMAT2 class: 现在是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;
}

I've used using statements to alias System.UInt32 as DWORD , LCID , and COLORREF , and alias System.UInt16 as WORD . 我使用using语句将System.UInt32别名为DWORDLCIDCOLORREF ,并将System.UInt16别名为WORD I try to keep my P/Invoke definitions as true to SDK spec as I can. 我尝试尽可能地将我的P / Invoke定义保持为SDK规范。 CFM and CFE are enums that contain the flag values for these fields. CFMCFE是包含这些字段的标志值的enums I've left their definitions out for brevity, but can add them in if needed. 为简洁起见,我将其定义排除在外,但如果需要可以添加它们。

I've declared SendMessage as: 我已将SendMessage声明为:

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

HWND is an alias for System.IntPtr , MSG is System.UInt32 , and WPARAM is System.UIntPtr . HWNDSystem.IntPtr的别名, MSGSystem.UInt32WPARAMSystem.UIntPtr

[In, Out] attribute on lParam is required for this to work, otherwise, it doesn't seem to get marshaled both directions (before and after call to native code). lParam上的[In, Out]属性是必需的,否则,它似乎没有被两个方向编组(在调用本机代码之前和之后)。

I call it with: 我叫它:

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

EM and SCF are enum s I've, again, left out for (relative) brevity. EMSCFenum我再次因为(相对)简洁而被遗漏。

I check success with: 我检查成功:

Console.WriteLine(cf.szFaceName);

and I get: 我得到:

Microsoft Sans Serif

Works like a charm! 奇迹般有效!


Um, or not, depending on how much sleep you've had and how many things you're trying to do at once, I suppose. 嗯,或不是,取决于你有多少睡眠以及你想要一次做多少事情,我想。

This would work if CHARFORMAT2 were a blittable type. 如果CHARFORMAT2是一个blittable类型,这工作。 (A blittable type is a type that has the same representation in managed memory as in unmanaged memory.) For instance, the MINMAXINFO type does work as described. (blittable类型是在托管内存中与非托管内存中具有相同表示的类型。)例如, MINMAXINFO类型的确如所描述的那样工作。

[StructLayout(LayoutKind.Sequential)]
class MINMAXINFO : NativeStruct{
    public Point ptReserved;
    public Point ptMaxSize;
    public Point ptMaxPosition;
    public Point ptMinTrackSize;
    public Point ptMaxTrackSize;
}

This is because blittable types are not really marshaled. 这是因为blittable类型并没有真正封送。 They're just pinned in memory—this keeps the GC from moving them—and the address of their location in managed memory is passed to the native function. 它们只是固定在内存中 - 这使GC无法移动它们 - 它们在托管内存中的位置地址被传递给本机函数。

Non-blittable types have to be marshaled. 非blittable类型必须被封送。 The CLR allocates unmanaged memory and copies the data between the managed object and its unmanaged representation, making the necessary conversions between formats as it goes. CLR分配非托管内存并在托管对象及其非托管表示之间复制数据,从而在格式之间进行必要的转换。

The CHARFORMAT2 structure is non-blittable because of the string member. 由于string成员, CHARFORMAT2结构是非blittable。 The CLR can't just pass a pointer to a .NET string object where a fixed-length character array is expected to be. CLR不能只传递一个指向.NET string对象的指针,在该string对象中需要一个固定长度的字符数组。 So the CHARFORMAT2 structure must be marshaled. 因此必须对CHARFORMAT2结构进行封送处理。

As it would appear, for correct marshaling to occur, the interop function must be declared with the type to be marshaled. 如图所示,为了正确编组,必须使用要编组的类型声明互操作函数。 In other words, given the above definition, the CLR must be making some sort of determination based on the static type of NativeStruct . 换句话说,鉴于上述定义,CLR必须基于NativeStruct的静态类型进行某种确定。 I would guess that it's correctly detecting that the object needs to be marshaled, but then only "marshaling" a zero-byte object, the size of NativeStruct itself. 我猜想它正确地检测到对象需要被编组,但只是“编组”一个零字节对象,即NativeStruct本身的大小。

So in order to get your code working for CHARFORMAT2 (and any other non-blittable types you might use), you'll have to go back to declaring SendMessage as taking a CHARFORMAT2 object. 因此,为了使您的代码适用于CHARFORMAT2 (以及您可能使用的任何其他非blittable类型),您将不得不返回将SendMessage声明为获取CHARFORMAT2对象。 Sorry I led you astray on this one. 对不起,我把你误入歧途。


Captcha for the previous edit: 上一次编辑的Captcha:

the whippet whippet

Yeah, whip it good! 是的,鞭子好!


Cory, 科里

This is off topic, but I notice a potential problem for you in the app it looks like you're making. 这不是主题,但我注意到你在应用程序中看起来像你正在制作的潜在问题。

The rich textbox control uses standard GDI text-measuring and text-drawing functions. 富文本框控件使用标准GDI文本测量和文本绘制功能。 Why is this a problem? 为什么这是个问题? Because, despite claims that a TrueType font looks the same on screen as on paper, GDI does not accurately place characters. 因为尽管声称TrueType字体在屏幕上看起来和纸上一样,但GDI并不能准确地放置字符。 The problem is rounding. 问题在于四舍五入。

GDI uses all-integer routines to measure text and place characters. GDI使用全整数例程来测量文本和放置字符。 The width of each character (and height of each line, for that matter) is rounded to the nearest whole number of pixels, with no error correction. 每个字符的宽度(以及每条线的高度,就此而言)四舍五入到最接近的整数像素,没有纠错。

The error can easily be seen in your test app. 您可以在测试应用中轻松看到错误。 Set the font to Courier New at 12 points. 将字体设置为Courier New 12点。 This fixed-width font should space characters exactly 10 per inch, or 0.1 inches per character. 这种固定宽度的字体应该在每英寸10个字符或每个字符0.1英寸的空格中放置字符。 This should mean that, given your starting line width of 5.5 inches, you should be able to fit 55 characters on the first line before wrap occurs. 这应该意味着,如果您的起始线宽为5.5英寸,您应该能够在换行之前在第一行上放置55个字符。

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123

But if you try, you'll see that wrap occurs after only 54 characters. 但是如果你尝试,你会发现只有54个字符后才会发生换行。 What's more the 54 th character and part of the 53 rd overhang the apparent margin shown on the ruler bar. 更重要的是54 字符和标尺栏上显示的表观缘53 悬垂的一部分。

This assumes you have your settings at standard 96 DPI (normal fonts). 假设您的设置为标准96 DPI(普通字体)。 If you use 120 DPI (large fonts), you won't see this problem, although it appears that you size your control incorrectly in this case. 如果您使用120 DPI(大字体),您将看不到此问题,尽管在这种情况下您的控件尺寸似乎不正确。 You also won't likely see this on the printed page. 您也不会在打印页面上看到这一点。

What's going on here? 这里发生了什么? The problem is that 0.1 inches (the width of one character) is 9.6 pixels (again, using 96 DPI). 问题是0.1英寸(一个字符的宽度)是9.6像素(再次,使用96 DPI)。 GDI doesn't space characters using floating point numbers, so it rounds this up to 10 pixels. GDI不使用浮点数对空格字符进行空格,因此它最多可将此数字舍入为10像素。 So 55 characters takes up 55 * 10 = 550 pixels / 96 DPI = 5.7291666... inches, whereas what we were expecting was 5.5 inches. 所以55个字符占55 * 10 = 550像素/ 96 DPI = 5.7291666 ...英寸,而我们所期望的是5.5英寸。

While this will probably be less noticeable in the normal use case for a word processor program, there is a likelihood of instances where word wrap occurs at different places on screen versus on page, or that things don't line up the same once printed as they did on screen. 虽然在文字处理程序的正常使用情况下这可能不那么明显,但是有可能出现在屏幕上不同位置而不是在页面上发生自动换行的情况,或者一旦打印出来就会出现相同的情况。他们在屏幕上做了。 This could turn out to be a problem for you if this is a commercial application you're working on. 如果这是您正在处理的商业应用程序,这可能会成为您的问题。

Unfortunately, the fix for this problem is not easy. 不幸的是,解决这个问题并不容易。 It means you'll have to dispense with the rich textbox control, which means a huge hassle of implementing yourself everything it does for you, which is quite a lot. 这意味着你将不得不放弃丰富的文本框控件,这意味着一个很大的麻烦,实现它为你做的一切,这是相当多的。 It also means that the text drawing code you'll have to implement becomes fairly complicated. 这也意味着您必须实现的文本绘图代码变得相当复杂。 I've got code that does it, but it's too complex to post here. 我有代码可以做到这一点,但是在这里发布它太复杂了。 You might, however, find this example or this one helpful. 但是,您可能会发现此示例 示例有用。

Good luck! 祝好运!


1 Abstract Base Class 1抽象基类

I've had some fun cases where a parameter is something like ref Guid parent and the corresponding documentation says: 我有一些有趣的案例,其中参数类似于ref Guid parent ,相应的文档说:

"Pointer to a GUID specifying the parent. Pass a null pointer to use [insert some system-defined item] ." “指向父级的GUID的指针。传递空指针以使用 [插入一些系统定义的项目] 。”

If null (or IntPtr.Zero for IntPtr parameters) really is an invalid parameter, then you're fine using a ref parameter - maybe even better off since it's extra clear exactly what you need to pass. 如果null (或IntPtr.Zero for IntPtr参数)确实是一个无效参数,那么你可以使用ref参数 - 可能更好,因为它更清楚你需要通过什么。

If null is a valid parameter, you can pass ClassType instead of ref StructType . 如果null是有效参数,则可以传递ClassType而不是ref StructType Objects of a reference type ( class ) are passed as a pointer, and they allow null . 引用类型( class )的对象作为指针传递,它们允许null

No, you cannot overload SendMessage and make the wparam argument an int. 不,您不能重载SendMessage并使wparam参数成为int。 That will make your program fail on a 64-bit version of the operating system. 这将使您的程序在64位版本的操作系统上失败。 It has to be a pointer, either IntPtr, a blittable reference or an out or ref value type. 它必须是一个指针,IntPtr,blittable引用或out或ref值类型。 Overloading the out/ref type is otherwise fine. 重载out / ref类型也不错。


EDIT: As the OP pointed out, this is not actually a problem. 编辑:正如OP所指出的,这实际上不是问题。 The 64-bit function calling convention passes the first 4 arguments through registers, not the stack. 64位函数调用约定通过寄存器传递前4个参数,而不是堆栈。 There is thus no danger of stack mis-alignment for the wparam and lparam arguments. 因此,对于wparam和lparam参数,没有堆栈未对齐的危险。

I don't see any drawbacks. 我没有看到任何缺点。

By-ref is often enough for simple type and simple structure. 对于简单类型和简单结构,By-ref通常就足够了。

IntPtr should be favored if the structure has a variable-size or if you want to do custom processing. 如果结构具有可变大小或者您想要进行自定义处理,则应该优先使用IntPtr。

使用ref比手动操作指针更简单且更不容易出错,因此我认为没有充分理由不使用它...使用ref另一个好处是你不必担心释放非托管分配的内存

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM