C# · 12月 27, 2021

c# – .NET Interop IntPtr vs. ref

可能是一个noob的问题,但interop不是我的强项之一.

除了限制重载的数量之外,是否有任何原因应该声明我的DllImports:

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

并使用它们:

IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(formatrange));Marshal.StructureToPtr(formatrange,lParam,false);int returnValue = User32.SendMessage(_RichText@R_896_2419@.Handle,ApiConstants.EM_FORMATRANGE,wParam,lParam);Marshal.FreeCoTaskMem(lParam);@H_419_5@

而不是创建有针对性的超载:

[DllImport(“user32.dll”)]public static extern int SendMessage(IntPtr hWnd,ref FORMATRANGE lParam);@H_419_5@

并使用它像:

FORMATRANGE lParam = new FORMATRANGE();int returnValue = User32.SendMessage(_RichText@R_896_2419@.Handle,ref lParam);@H_419_5@

通过ref重载最终更容易使用,但我想知道是否有一个缺点,我不知道.

编辑:

很多伟大的信息到目前为止的家伙.

@P爸爸:你有一个将struct类从抽象(或任何)类中分离出来的例子吗?我改变了我的签名:

[DllImport(“user32.dll”,SetLastError = true)]public static extern int SendMessage(IntPtr hWnd,[In,Out,MarshalAs(UnmanagedType.LPStruct)] CHARFORMAT2 lParam);@H_419_5@

没有In,Out和MarshalAs SendMessage(EM_GETCHARFORMAT在我的测试中)失败.上面的例子很好,但如果我把它改为:

[DllImport(“user32.dll”,MarshalAs(UnmanagedType.LPStruct)] NativeStruct lParam);@H_419_5@

我收到一个System.TypeLoadException,表示CHARFORMAT2格式无效(我会尝试捕获它在这里).

例外:

无法从程序集“CC.Utilities,Version = 1.0.9.1212,Culture = neutral,PublicKeyToken = 111aac7a42f7965e”加载类型“CC.Utilities.WindowsApi.CHARFORMAT2”,因为格式无效.

NativeStruct类:

public class NativeStruct{}@H_419_5@

我已经尝试过抽象,添加了StructLayout属性等等,我得到了同样的异常.

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

编辑:

我没有遵循常见问题,我问了一个可以讨论但没有得到肯定回答的问题.除此之外,在这个线程中有很多有见地的信息.所以我会把它留给读者来投票答案.第一个到十个以上的投票将成为答案.如果没有回答在两天内(PST)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@H_419_5@

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

编辑

你提到你在使用一个类而不是一个结构体时会得到一个TypeLoadException,并且要求一个样例.我使用CHARFORMAT2做了一个快速测试,因为它看起来像是你想要使用的.

首先是ABC1:

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

需要使用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;}@H_419_5@

我使用的语句将System.UInt32别名为DWORD,LCID和COLORREF,并将System.UInt16别名为WORD.我尽量保持我的P / Invoke定义是真实的SDK规范,我可以. CFM和CFE是包含这些字段的标志值的枚举.为了简洁,我已经将其定义放在了位置,但如果需要,可以添加它们.

我已经将SendMessage声明为:

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

HWND是system.intPtr的别名,MSG是System.UInt32,WPARAM是System.UIntPtr.

需要使用lParam上的[In,Out]属性才能正常工作,否则它似乎没有被封送到两个方向(调用本机代码之前和之后).

我称之为:

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

EM和SCF是枚举,我再也没有(相对)简洁.

我检查成功:

Console.WriteLine(cf.szFaceName);@H_419_5@

我得到:

Microsoft Sans Serif@H_419_5@

奇迹般有效!

嗯,不,取决于你有多少睡眠,以及你一次尝试做多少事情,我想.

如果CHARFORMAT2是blittable类型,这将工作. (可变类型是在托管内存中具有与非托管内存中相同的表示形式的类型.)例如,MINMAXINFO类型按照所述进行工作.

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

这是因为可燃类型不是真正封锁的.它们只是被固定在内存中 – 这样可以使GC不会移动它们,并且它们在托管内存中的位置的地址被传递到本地函数.

不可燃类型必须被封送. CLR分配非托管内存并在受管对象与其非托管表示之间复制数据,从而在格式之间进行必要的转换.

由于字符串成员,CHARFORMAT2结构是不可擦除的. CLR不能只传递一个指向一个固定长度的字符数组的.NET字符串对象的指针.所以CHARFORMAT2结构必须被封送.

如将出现的那样,要进行正确的封送处理,必须使用要封送的类型声明互操作功能.换句话说,考虑到上述定义,CLR必须基于NativeStruct的静态类型进行某种确定.我会猜测它正确地检测到对象需要被封送,而只是“封送”一个零字节对象,即NativeStruct本身的大小.

因此,为了使您的代码适用于CHARFORMAT2(以及您可能使用的任何其他非易失性类型),您必须返回将SendMessage声明为使用CHARFORMAT2对象.对不起,我误导了这个.

上一次编辑的验证码:

the whippet

是的,鞭打好!

科里

这是关闭的主题,但我注意到您在应用程序中看起来像您正在制作的潜在问题.

丰富的文本框控件使用标准的GDI文本测量和文本绘制功能.为什么这是一个问题?因为尽管声称TrueType字体在屏幕上与纸上看起来一样,但是GDI并没有准确地放置字符.问题是四舍五入.

GDI使用全整数例程来测量文本和放置字符.每个字符的宽度(和每行的高度,对于该事物)四舍五入到最接近的整数像素,没有错误更正.

您的测试应用程序可以轻松看到错误.将字体设置为Courier New在12点.这个固定宽度的字体应该将每个字符精确地放在10个字符上,或者每个字符0.1个英寸.这应该意味着,考虑到起始行宽度为5.5英寸,您应该能够在wrap发生之前在第一行适合55个字符.

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123@H_419_5@

但是,如果你尝试,你会看到只有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英寸.

虽然在文字处理程序的正常使用情况下这可能不那么明显,但是有可能在屏幕上与页面上的不同位置发生单词换行,或者事情不会排列相同,一旦打印为他们在屏幕上做了如果这是您正在开展的商业应用程序,这可能是您的一个问题.

不幸的是,这个问题的修复并不容易.这意味着你必须免除丰富的文本框控件,这意味着一个巨大的麻烦,实现自己为你做的一切,这是相当多的.这也意味着你必须实现的文本绘图代码变得相当复杂.我有代码可以做到这一点,但是在这里发布太复杂了.但是,您可能会发现this example或this one有帮助.

祝你好运!

1抽象基类