如果结构在没有自定义处理的情况下是可编组的,我非常喜欢后一种方法,在这种方法中,您将 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
调用GlobalAlloc
kernel32.dll,它返回一个HGLOBAL
. 这曾经不同于16 位时代HLOCAL
返回的, 但在 32 位 Windows 中它们是相同的。LocalAlloc
使用该术语HGLOBAL
来指代(本机)用户空间内存块有点卡住了,我猜,设计这个Marshal
类的人一定没有花时间去思考这对大多数.NET来说是多么不直观开发商。另一方面,大多数 .NET 开发人员不需要分配非托管内存,所以....
</pedantry>
编辑
您提到在使用类而不是结构时遇到 TypeLoadException,并要求提供示例。我使用 进行了快速测试CHARFORMAT2
,因为看起来这就是您要使用的。
首先是 ABC 1:
[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
isSystem.UInt32
和WPARAM
is的别名System.UIntPtr
。
[In, Out]
需要属性 onlParam
才能正常工作,否则,它似乎不会双向编组(在调用本机代码之前和之后)。
我称之为:
CHARFORMAT2 cf = new CHARFORMAT2();
SendMessage(rtfControl.Handle, (MSG)EM.GETCHARFORMAT, (WPARAM)SCF.DEFAULT, cf);
EM
并且为了(SCF
相对enum
)简洁,我再次被遗漏了。
我通过以下方式检查成功:
Console.WriteLine(cf.szFaceName);
我得到:
微软无衬线字体
奇迹般有效!
嗯,或者不,取决于你有多少睡眠,以及你想一次做多少事情,我想。
如果是blittable类型,这将起作用。(blittable 类型是一种在托管内存中与在非托管内存中具有相同表示的类型。)例如,该类型确实如所描述的那样工作。CHARFORMAT2
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 分配非托管内存并在托管对象和它的非托管表示之间复制数据,在执行过程中在格式之间进行必要的转换。
由于成员,该CHARFORMAT2
结构是不可blittable string
。CLR 不能只将一个指针传递给一个string
预期为固定长度字符数组的 .NET 对象。所以CHARFORMAT2
必须对结构进行编组。
看起来,要进行正确的封送处理,必须使用要封送处理的类型声明互操作函数。换句话说,给定上述定义,CLR 必须根据NativeStruct
. 我猜它正确地检测到对象需要被封送,但随后只“封送”一个零字节对象,即其NativeStruct
自身的大小。
因此,为了使您的代码适用于CHARFORMAT2
(以及您可能使用的任何其他非 blittable 类型),您必须返回声明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抽象基类