.NET Interop IntPtr 与 ref

发布于 2024-08-14 08:47:53 字数 2356 浏览 4 评论 0原文

可能是一个菜鸟问题,但互操作还不是我的强项之一。

除了限制重载的数量之外,还有什么理由我应该像这样声明我的 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);

如果没有 InOutMarshalAs,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
{
    ...
}

编辑:

我没有遵循常见问题解答,我提出了一个可以讨论但没有得到积极回答的问题。除此之外,该线程中还有很多有见地的信息。因此,我将留给读者投票选出答案。第一个到十多个赞成票将是答案。如果两天内(12/17 PST)没有答案满足这个要求,我将添加我自己的答案,总结线程中所有美味的知识:-)

再次编辑:

我撒谎了,接受 P 爸爸的答案因为他是个男人并且给了我很大的帮助(他也有一只可爱的小猴子:-P)

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:

[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.

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? 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. 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).

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.

The NativeStruct class:

public class NativeStruct
{
}

I've tried abstract, adding the StructLayout attribute, etc. and I get the same exception.

[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. 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 :-)

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

爱情眠于流年 2024-08-21 08:47:53

如果该结构无需自定义处理即可编组,我非常喜欢后一种方法,其中您将 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

<迂腐>

顺便说一句,在您将指针作为 IntPtr 传递的示例中,您使用了错误的 AllocSendMessage 不是 COM 函数,因此您不应使用 COM 分配器。使用Marshal.AllocHGlobal 和Marshal.FreeHGlobal。他们的名字不好听;仅当您完成了 Windows API 编程时,这些名称才有意义,甚至可能没有意义。 AllocHGlobal 调用 kernel32.dll 中的 GlobalAlloc,返回一个 HGLOBAL。这曾经HLOCAL 不同,在 16 位时代由 LocalAlloc 返回,但在 32 位 Windows 中它们是一样。

我猜,使用术语HGLOBAL来指代(本机)用户空间内存块有点卡住了,而设计Marshal的人们类一定没有花时间去思考这对于大多数 .NET 开发人员来说是多么不直观。另一方面,大多数 .NET 开发人员不需要分配非托管内存,因此......


编辑

您提到您正在获得一个使用类而不是结构时出现 TypeLoadException,并要求提供示例。我使用 CHARFORMAT2 进行了快速测试,因为它看起来就是您想要使用的。

首先是 ABC1

[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 是包含这些字段的标志值的枚举。为了简洁起见,我省略了它们的定义,但如果需要,可以添加它们。

我已将 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

效果很好!


嗯,或者不,取决于你睡了多少觉以及你想同时做多少件事,我想。

如果 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 移动它们——并且它们在托管内存中的位置地址被传递给本机函数。

必须对不可直接传送的类型进行编组。 CLR 分配非托管内存并在托管对象与其非托管表示形式之间复制数据,从而在格式之间进行必要的转换。

由于 string 成员的原因,CHARFORMAT2 结构是不可直接直接传送的。 CLR 不能只将指针传递给 .NET string 对象(其中应包含固定长度的字符数组)。因此必须对 CHARFORMAT2 结构进行封送。

看起来,为了进行正确的封送,必须使用要封送的类型来声明互操作函数。换句话说,根据上述定义,CLR 必须根据 NativeStruct 的静态类型做出某种确定。我猜它正确地检测到需要对对象进行封送,但随后仅“封送”零字节对象,即 NativeStruct 本身的大小。

因此,为了让您的代码适用于 CHARFORMAT2(以及您可能使用的任何其他不可直接传送的类型),您必须返回将 SendMessage 声明为采用CHARFORMAT2 对象。抱歉,我在这件事上让你误入歧途。


先前编辑的验证码:

惠比特犬

呀,鞭子好啊!


科里,

这是题外话,但我注意到您正在制作的应用程序中存在潜在问题。

富文本框控件使用标准 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 抽象基类

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. Alternatively, you can declare your types as classes instead of structs, and then you can pass null, as well.

[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. SendMessage is not a COM function, so you shouldn't be using the COM allocator. Use Marshal.AllocHGlobal and Marshal.FreeHGlobal. They're poorly named; the names only make sense if you've done Windows API programming, and maybe not even then. AllocHGlobal calls GlobalAlloc in kernel32.dll, which returns an 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.

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. On the other hand, most .NET developers don't need to allocate unmanaged memory, so....

</pedantry>


Edit

You mention you're getting a TypeLoadException when using a class instead of a struct, and ask for a sample. I did up a quick test using CHARFORMAT2, since it looks like that's what you're trying to use.

First the ABC1:

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

The StructLayout attribute is required, or you will get a TypeLoadException.

Now the CHARFORMAT2 class:

[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. I try to keep my P/Invoke definitions as true to SDK spec as I can. CFM and CFE are enums that contain the flag values for these fields. I've left their definitions out for brevity, but can add them in if needed.

I've declared SendMessage as:

[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.

[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).

I call it with:

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

EM and SCF are enums I've, again, left out for (relative) brevity.

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. (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.

[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. 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.

Non-blittable types have to be marshaled. 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.

The CHARFORMAT2 structure is non-blittable because of the string member. The CLR can't just pass a pointer to a .NET string object where a fixed-length character array is expected to be. So the CHARFORMAT2 structure must be marshaled.

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. 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.

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. Sorry I led you astray on this one.


Captcha for the previous edit:

the 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. 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. The problem is rounding.

GDI uses all-integer routines to measure text and place characters. 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. This fixed-width font should space characters exactly 10 per inch, or 0.1 inches per character. 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.

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123

But if you try, you'll see that wrap occurs after only 54 characters. What's more the 54th character and part of the 53rd overhang the apparent margin shown on the ruler bar.

This assumes you have your settings at standard 96 DPI (normal fonts). 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. 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). GDI doesn't space characters using floating point numbers, so it rounds this up to 10 pixels. So 55 characters takes up 55 * 10 = 550 pixels / 96 DPI = 5.7291666... inches, whereas what we were expecting was 5.5 inches.

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

忘羡 2024-08-21 08:47:53

我遇到过一些有趣的情况,其中参数类似于 ref Guid Parent ,相应的文档说:

“指向指定父级的 GUID 的指针。传递一个空指针以使用 [插入一些系统定义的项目]。”

如果 null (或IntPtr.Zero 对于 IntPtr 参数)确实是一个无效参数,那么你可以使用 ref 参数 - 也许更好,因为它是更加明确您需要通过什么。

如果 null 是有效参数,则可以传递 ClassType 而不是 ref StructType。引用类型 (class) 的对象作为指针传递,并且它们允许 null

I've had some fun cases where a parameter is something like ref Guid parent and the corresponding documentation says:

"Pointer to a GUID specifying the parent. Pass a null pointer to use [insert some system-defined item]."

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.

If null is a valid parameter, you can pass ClassType instead of ref StructType. Objects of a reference type (class) are passed as a pointer, and they allow null.

墨洒年华 2024-08-21 08:47:53

不,您不能重载 SendMessage 并使 wparam 参数成为 int。这将使您的程序在 64 位版本的操作系统上失败。它必须是一个指针,可以是 IntPtr、blittable 引用或者 out 或 ref 值类型。否则,重载 out/ref 类型就可以了。


编辑:正如OP指出的,这实际上不是问题。 64 位函数调用约定通过寄存器而不是堆栈传递前 4 个参数。因此,wparam 和 lparam 参数不存在堆栈错位的危险。

No, you cannot overload SendMessage and make the wparam argument an int. That will make your program fail on a 64-bit version of the operating system. It has to be a pointer, either IntPtr, a blittable reference or an out or ref value type. Overloading the out/ref type is otherwise fine.


EDIT: As the OP pointed out, this is not actually a problem. The 64-bit function calling convention passes the first 4 arguments through registers, not the stack. There is thus no danger of stack mis-alignment for the wparam and lparam arguments.

述情 2024-08-21 08:47:53

我没有看到任何缺点。

对于简单类型和简单结构来说,By-ref 通常就足够了。

如果结构具有可变大小或者您想要进行自定义处理,则应优先考虑 IntPtr。

I don't see any drawbacks.

By-ref is often enough for simple type and simple structure.

IntPtr should be favored if the structure has a variable-size or if you want to do custom processing.

聆听风音 2024-08-21 08:47:53

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

Using ref is simpler and less error-prone than manipulating pointers manually, so I see no good reason for not using it... Another benefit of using ref is that you don't have to worry about freeing unmanaged allocated memory

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文