我正在尝试从C#调用Shell_NotifyIcon
函数[1]。该函数的一个参数是指向NOIFYICONDATA
结构[2]的指针。此结构包含TCHAR
数组,指针,还有4个不同的版本(取决于使用的OS / API)。结构中的第一个字段(cbSize
)必须在调用者传递给Shell_NotifyIcon
函数之前设置为结构的大小(以字节为单位)。
我目前的方法是使用4个类:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
[BestFitMapping(false, ThrowOnUnmappableChar = true)]
public class NotifyIconData { ... }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
[BestFitMapping(false, ThrowOnUnmappableChar = true)]
public class NotifyIconData2 : NotifyIconData { ... }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
[BestFitMapping(false, ThrowOnUnmappableChar = true)]
public class NotifyIconData3 : NotifyIconData2 { ... }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
[BestFitMapping(false, ThrowOnUnmappableChar = true)]
public class NotifyIconData4 : NotifyIconData3 { ... }
在NotifyIconData
的构造函数中,我使用Marshal.SizeOf(this.GetType());
来确定结构的大小。结果是正确的(至少它与未管理的Unicode / Ansi / x86 / x64版本中的NOTIFYICONDATA_V1_SIZE
,NOTIFYICONDATA_V2_SIZE
,NOTIFYICONDATA_V3_SIZE
,sizeof(NOTIFYICONDATAW)
相同。原因在于此方法是我不想使用幻数,特别是因为填充已完成,因此需要计算结构的正确大小。
szTip
字段的实现还涉及一个小技巧。此字段可以是64个TCHAR
个字符(版本1)的数组,也可以是128个TCHAR
个字符的数组(版本2)。要模拟此NotifyIconData
类,包含以下字段定义:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
protected string szTip;
在NotifyIconData2
类中,我使用以下定义“扩展”此字段:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
private string szTipExtension;
通过虚拟属性访问该字段,该属性处理将实际值分段为两个字段。
然后将这些类与Shell_NotifyIcon
函数一起使用,声明如下:
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private delegate bool ShellNotifyIconFunction(
[In, MarshalAs(UnmanagedType.U4)] uint dwMessage,
[In] NotifyIconData lpdata);
有人会相信我现在可以将NotifyIconData4
实例传递给该函数。在编译时,这是有效的(如预期的那样)。但是在运行时我得到一个MDA FatalExecutionEngineError
异常。当我将参数类型从NotifyIconData
更改为NotifyIconData4
时,调用成功并且有效。
似乎Marshaller使用静态NotifyIconData
类型而不是NotifyIconData4
动态类型来编组数据。任何人都可以确认吗?或者任何人都可以向我指出编组如何与继承一起工作的信息?
答案 0 :(得分:0)
当您声明ShellNotifyIconFunction
接受NotifyIconData
时,只有在进行调用时,基类使用的内存才会被复制到非托管内存中。如果您在非托管端尝试根据您正在访问P / Invoke层分配的数据范围之外的数据的cbSize
成员的内容从派生结构访问成员,这将导致错误。
要解决此问题,您可以根据您运行的Windows版本进行自己的编组。
您应该将ShellNotifyIconFunction
声明为NotifyIconData
,而不是将IntPtr
的第二个参数声明为var notifyIconData = new NotifyIconData4(); // Or use another version depending on the
... // version of Windows.
var buffer = IntPtr.Zero;
try {
buffer = Marshal.AllocHGlobal(Marshal.SizeOf(notifyIconData));
Marshal.StructureToPtr(notifyIconData, buffer, false);
var result = ShellNotifyIconFunction(message, buffer);
...
}
finally {
if (buffer != IntPtr.Zero)
Marshal.FreeHGlobal(buffer);
}
。然后,您需要分配和管理自己调用函数时提供的缓冲区内容:
{{1}}