1

我正在尝试从 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方法的原因是我不想使用幻数,特别是因为填充已完成,因此需要计算结构的正确大小。在字段的实现中还涉及到一个小技巧。该字段可以是 64 个字符的数组(版本 1),也可以是 128个字符的数组(版本 2)。模拟该类包含以下字段定义:NOTIFYICONDATA_V3_SIZEsizeof(NOTIFYICONDATAW)szTipTCHARTCHARNotifyIconData

    [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实例传递给该函数。在编译时这有效(如预期的那样)。但在运行时我得到一个 MDAFatalExecutionEngineError异常。当我将参数类型从更改NotifyIconDataNotifyIconData4调用成功并且它可以工作。

Marshaller 似乎使用静态NotifyIconData类型而不是NotifyIconData4动态类型来编组数据。谁能证实这一点?或者任何人都可以向我指出有关编组如何与继承一起工作的信息?

4

1 回答 1

0

当您声明只ShellNotifyIconFunction接受NotifyIconData基类使用的内存时,调用时会复制到非托管内存中。如果您在非托管端尝试根据成员的内容访问派生结构中的cbSize成员,则您正在访问 P/Invoke 层分配的数据范围之外的数据,这将导致错误。

要解决此问题,您可以根据正在运行的 Windows 版本进行自己的编组。

ShellNotifyIconFunction而不是声明as的第二个参数,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);
}
于 2012-08-09T09:04:19.360 回答