如果结构在没有自定义处理的情况下是可编组的,我非常喜欢后一种方法,在这种方法中,您将 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.AllocHGlobal 和Marshal.FreeHGlobal。他们的名字很糟糕;这些名称只有在您完成过 Windows API 编程时才有意义,甚至可能没有。 AllocHGlobal 在 kernel32.dll 中调用 GlobalAlloc,返回一个 HGLOBAL。这曾经与 HLOCAL 不同,在 16 位时代由 LocalAlloc 返回,但在 32 位 Windows 中它们是相同的。
使用术语HGLOBAL 来指代(本机)用户空间内存块有点卡住了,我猜,设计Marshal 类的人一定没有花时间去思考对于大多数 .NET 开发人员来说,这将是多么不直观。另一方面,大多数 .NET 开发人员不需要分配非托管内存,所以....
</pedantry>
编辑
您提到在使用类而不是结构时遇到 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 别名为DWORD、LCID 和COLORREF,并将System.UInt16 别名为WORD。我尽量让我的 P/Invoke 定义符合 SDK 规范。 CFM 和 CFE 是 enums,它们包含这些字段的标志值。为简洁起见,我省略了它们的定义,但如果需要,可以添加它们。
我已将SendMessage 声明为:
[DllImport("user32.dll", CharSet=CharSet.Auto)]
static extern IntPtr SendMessage(
HWND hWnd, MSG msg, WPARAM wParam, [In, Out] NativeStruct lParam);
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);
EM 和 SCF 是 enums,为了(相对)简洁,我再次省略了。
我检查成功:
Console.WriteLine(cf.szFaceName);
我得到:
微软无衬线字体
像魅力一样工作!
嗯,我想,这取决于你睡了多少觉,以及你想同时做多少事情。
如果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 对象的指针,该对象应该是一个固定长度的字符数组。所以CHARFORMAT2 结构必须被封送。
看起来,为了发生正确的封送处理,互操作函数必须用要封送处理的类型声明。换句话说,给定上述定义,CLR 必须根据NativeStruct 的静态类型做出某种确定。我猜它正确地检测到对象需要被封送,但随后只“封送”一个零字节对象,大小为NativeStruct 本身。
因此,为了使您的代码适用于 CHARFORMAT2(以及您可能使用的任何其他非 blittable 类型),您必须返回将 SendMessage 声明为采用 CHARFORMAT2 对象。抱歉,我在这件事上让你误入歧途。
上一次编辑的验证码:
鞭子
是的,鞭子很好!
科里,
这是题外话,但我注意到您正在制作的应用可能存在问题。
富文本框控件使用标准 GDI 文本测量和文本绘图功能。为什么这是个问题?因为,尽管声称 TrueType 字体在屏幕上看起来与在纸上看起来一样,但 GDI 并没有准确地放置字符。问题是四舍五入。
GDI 使用全整数例程来测量文本和放置字符。每个字符的宽度(以及每行的高度,就此而言)四舍五入到最接近的整数像素,没有错误校正。
在您的测试应用中可以很容易地看到该错误。将字体设置为 Courier New 12 磅。这种固定宽度的字体应该每英寸恰好间隔 10 个字符,或每字符 0.1 英寸。这应该意味着,如果您的起始行宽为 5.5 英寸,那么在换行之前您应该能够在第一行放置 55 个字符。
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123
但如果您尝试一下,您会发现仅在 54 个字符后发生换行。更重要的是,第 54th 个字符和第 53rd 个字符的一部分超出了标尺栏上显示的明显边距。
这假设您的设置为标准 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抽象基类