P / Invoke Shell_NotifyIcon支持不同的NOTIFYICONDATA版本

时间:2012-08-02 08:38:35

标签: c# pinvoke marshalling notifyicon

我正在尝试从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_SIZENOTIFYICONDATA_V2_SIZENOTIFYICONDATA_V3_SIZEsizeof(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动态类型来编组数据。任何人都可以确认吗?或者任何人都可以向我指出编组如何与继承一起工作的信息?

1 个答案:

答案 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}}