我正尝试使用新的LibraryImport
属性(与旧的DllImport
相反)实现一些P/Invoke代码。具体来说,我正尝试封送WNDCLASSEXW
结构体以便在RegisterClassEx
中使用。
下面是我的WNDCLASSEXW
托管实现的简化、缩短版本:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WindowClass
{
private uint StructSize;
public WindowClassStyle Style;
[MarshalAs(UnmanagedType.FunctionPtr)]
public Win32API.WindowProcedure? WindowProcedure;
private int ClassAdditionalBytes;
private int WindowAdditionalBytes;
public IntPtr Instance;
public IntPtr Icon;
public IntPtr Cursor;
public IntPtr BackgroundBrush;
[MarshalAs(UnmanagedType.LPWStr)]
public string? ClassMenuResourceName;
[MarshalAs(UnmanagedType.LPWStr)]
public string? ClassName;
public IntPtr SmallIcon;
}
我对Win32API.WindowProcedure
的定义是:
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate nint WindowProcedure(IntPtr windowHandle, MessageID messageID, nuint wParam, nint lParam);
最后是我对RegisterClassEx
的定义:
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
public static partial ushort RegisterClassEx(in WindowClass classDefinition);
但是,这会导致错误:
错误SYSLIB 1051:源生成的P/Invoke不支持类型“xxx.WindowClass”。生成的源将不处理参数“classDefinition”的封送处理。
因此,我认为我需要为WindowClass
结构定制编组。
然而,由于这个系统相对较新,我很难找到正确和最佳的指导,以前,DllImport
会神奇地编组大多数类型,几乎没有指导,但LibraryImport
似乎需要更多的信息,而且有点严格。
我可以通过将类型更改为IntPtr
并要求在程序中的其他地方将delegate
转换为IntPtr
来绕过这个问题,但我更愿意尽可能靠近托管/非托管边界进行转换,并保持结构体和公开的本机函数可用于更具描述性的类型。
我在搜索时找到的一些资源:
- The old P/Invoke documentation regarding delegates/function pointers
- The new information regarding CustomMarshaller
- The design documentation for the new source generator-based system
**主要问题:**如何正确实现WNDPROC和LP(C)WSTR的自定义封送处理?
问题2:
我更喜欢使用readonly struct
,并将所有成员转换为{ get; init; }
属性而不是字段,因为语义更好。但是我注意到MarshalAs
属性不能应用于属性。是否有一个好方法既可以使用readonly struct
属性,同时还提供必要的信息以确保所有内容都正确地编组入/编组出??特别是对于更复杂的类型,如string? <-> LPCWSTR
、delegate? <-> void*
和我可能遇到的其他此类类型。
附加问题:LibraryImport
似乎不再强调指定正确调用约定的重要性。它不再像DllImport
那样是主属性的一部分,而是使用一个辅助属性,如下所示:[UnmanagedCallConv(CallConvs = new[] { typeof(CallConvStdcall) })]
,坦率地说看起来很糟糕。现在指定调用约定是必要的还是有益的?
1条答案
按热度按时间b4lqfgs41#
我能够让它与自定义封送处理一起工作。虽然Simon关于更改结构以包含本机类型的建议在一般情况下是有意义的,但在我的情况下却没有意义,因为这些类型将暴露给其他人使用。
对于更快、更频繁调用的方法,答案可能不同,但在这种情况下,注册一个类和创建一个窗口本质上是一个非常昂贵的操作,因此向/从不同的结构体复制数据的额外开销不值得关注。
封送拆收器是这样实现的:
使用此辅助函数将Win32
LP(C)WSTR
转换为常规.NETstring
:更好的
WindowClass
结构体与以前几乎相同,除了只读,并且所有元素都是{ get; init; }
。成员上的MarshalAs
属性不再需要,因为自定义封送处理处理所有事情。最后,实际的extern函数现在看起来如下所示:
注意这已经被修正了。以前我在参数上使用了
in
关键字,但是这会导致它传递一个指针指向结构数据的指针,这是一个额外的间接层,会导致代码失败。以上是正确工作的更新版本。我已经在常规发布模式和AOT编译中测试并验证了这一点,这也是在本例中使用
LibraryImport
的原因。但是,我的额外问题仍然存在,使用
UnmanagedCallConv
指定stdcall有什么好处吗?