.NET Interop IntPtr与ref

Cor*_*ton 10 .net c# winapi interop intptr

可能是一个菜鸟问题,但互操作不是我的优点之一.

除了限制重载次数之外,有任何理由我应该声明我的DllImports:

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

并像这样使用它们:

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);
Run Code Online (Sandbox Code Playgroud)

而不是创建目标重载:

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

使用它像:

FORMATRANGE lParam = new FORMATRANGE();
int returnValue = User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_FORMATRANGE, wParam, ref lParam);
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

如果没有In,OutMarshalAs该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);
Run Code Online (Sandbox Code Playgroud)

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

例外:

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

NativeStruct类:

public class NativeStruct
{
}
Run Code Online (Sandbox Code Playgroud)

我尝试过abstract,添加StructLayout属性等等,我得到了相同的异常.

[StructLayout(LayoutKind.Sequential)]
public class CHARFORMAT2: NativeStruct
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

编辑:

我没有按照常见问题解答我问了一个可以讨论但没有得到积极回答的问题.除此之外,这个帖子中还有很多有见地的信息.所以我会把它留给读者投票给答案.第一个到10个以上的投票将是答案.如果在两天(太平洋标准时间12/17)没有答案符合这一点,我将添加我自己的答案,总结线程中的所有美味知识:-)

再次编辑:

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

P D*_*ddy 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
Run Code Online (Sandbox Code Playgroud)

<pedantry>

顺便说一下,在你的例子中将指针作为一个传递IntPtr,你使用了错误Alloc. SendMessage不是COM函数,因此您不应该使用COM分配器.使用Marshal.AllocHGlobalMarshal.FreeHGlobal.他们名字不好; 如果您已经完成了Windows API编程,这些名称才有意义,甚至可能不是. 在kernel32.dll中AllocHGlobal调用GlobalAlloc,返回一个HGLOBAL.这曾经与16位日期HLOCAL返回的a 不同LocalAlloc,但在32位Windows中它们是相同的.

HGLOBAL我想,使用这个术语来指代(本机)用户空间内存块只是一种卡住了,并且设计Marshal类的人不应该花时间去思考大多数.NET的不直观性.开发人员.另一方面,大多数.NET开发人员不需要分配非托管内存,所以....

</pedantry>


编辑

你提到你在使用类而不是结构时得到一个TypeLoadException,并要求一个样本.我使用了快速测试CHARFORMAT2,因为它看起来就像你正在尝试使用的那样.

首先是ABC 1:

[StructLayout(LayoutKind.Sequential)]
abstract class NativeStruct{} // simple enough
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

我用using语句别名System.UInt32DWORD,LCIDCOLORREF,和别名System.UInt16WORD.我尝试尽可能地将我的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);
Run Code Online (Sandbox Code Playgroud)

HWND是一个别名System.IntPtr,MSGSystem.UInt32WPARAMSystem.UIntPtr.

[In, Out]属性on lParam是必须的,否则,它似乎没有被编组两个方向(调用本机代码之前和之后).

我叫它:

CHARFORMAT2 cf = new CHARFORMAT2();
SendMessage(rtfControl.Handle, (MSG)EM.GETCHARFORMAT, (WPARAM)SCF.DEFAULT, cf);
Run Code Online (Sandbox Code Playgroud)

EM而且SCF,enum我再次因为(相对)简洁而被排除在外.

我检查成功:

Console.WriteLine(cf.szFaceName);
Run Code Online (Sandbox Code Playgroud)

我得到:

Microsoft Sans Serif

奇迹般有效!


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

如果是一个blittable类型,这工作.(blittable类型是在托管内存中与非托管内存中具有相同表示的类型.)例如,类型确实如所描述的那样工作.CHARFORMAT2MINMAXINFO

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

这是因为blittable类型并没有真正封送.它们只是固定在内存中 - 这使GC无法移动它们 - 它们在托管内存中的位置地址被传递给本机函数.

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

CHARFORMAT2由于该string成员,该结构是不可盲目的.CLR不能只传递一个指向.NET 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的一部分RD标尺栏上显示的表观余量突出端.

假设您的设置为标准96 DPI(普通字体).如果您使用120 DPI(大字体),您将看不到此问题,尽管在这种情况下您的控件尺寸似乎不正确.您也不会在打印页面上看到这一点.

这里发生了什么?问题是0.1英寸(一个字符的宽度)是9.6像素(再次,使用96 DPI).GDI不会使用浮点数对空格字符进行空格,因此它最多可将此数字舍入为10像素.所以55个字符占55*10 = 550像素/ 96 DPI = 5.7291666 ...英寸,而我们所期望的是5.5英寸.

虽然在文字处理程序的正常使用情况下这可能不那么明显,但是有可能出现在屏幕上不同位置而不是在页面上发生自动换行的情况,或者一旦打印出来就会出现相同的情况.他们在屏幕上做了.如果这是您正在处理的商业应用程序,这可能会成为您的问题.

不幸的是,解决这个问题并不容易.这意味着你将不得不放弃丰富的文本框控件,这意味着一个很大的麻烦,实现它为你做的一切,这是相当多的.这也意味着您必须实现的文本绘图代码变得相当复杂.我有代码可以做到这一点,但是在这里发布它太复杂了.但是,您可能会发现此示例示例很有用.

祝好运!


1抽象基类