【问题标题】:When defining a Windows API interface in C#, do I have to define all members? Can I only define the methods I'm going to use?在 C# 中定义 Windows API 接口时,是否必须定义所有成员?我可以只定义我将要使用的方法吗?
【发布时间】:2010-11-11 13:06:23
【问题描述】:

例如,这是IFileOpenDialog 接口的完整定义,一个Windows Shell 接口,取自Pinvoke 站点:

[ComImport, Guid ( "d57c7288-d4ad-4768-be02-9d969532d960" ), InterfaceType ( ComInterfaceType.InterfaceIsIUnknown )]
interface IFileOpenDialog : IFileDialog
{
// Defined on IModalWindow - repeated here due to requirements of COM interop layer
// --------------------------------------------------------------------------------
[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime ), PreserveSig]
int Show ( [In] IntPtr parent );

// Defined on IFileDialog - repeated here due to requirements of COM interop layer
[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetFileTypes ( [In] uint cFileTypes, [In] COMDLG_FILTERSPEC[] rgFilterSpec );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetFileTypeIndex ( [In] uint iFileType );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void GetFileTypeIndex ( out uint piFileType );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void Advise ( [In, MarshalAs ( UnmanagedType.Interface )] IFileDialogEvents pfde, out uint pdwCookie );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void Unadvise ( [In] uint dwCookie );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetOptions ( [In] FOS fos );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void GetOptions ( out FOS pfos );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetDefaultFolder ( [In, MarshalAs ( UnmanagedType.Interface )] IShellItem psi );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetFolder ( [In, MarshalAs ( UnmanagedType.Interface )] IShellItem psi );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void GetFolder ( [MarshalAs ( UnmanagedType.Interface )] out IShellItem ppsi );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void GetCurrentSelection ( [MarshalAs ( UnmanagedType.Interface )] out IShellItem ppsi );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetFileName ( [In, MarshalAs ( UnmanagedType.LPWStr )] string pszName );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void GetFileName ( [MarshalAs ( UnmanagedType.LPWStr )] out string pszName );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetTitle ( [In, MarshalAs ( UnmanagedType.LPWStr )] string pszTitle );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetOkButtonLabel ( [In, MarshalAs ( UnmanagedType.LPWStr )] string pszText );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetFileNameLabel ( [In, MarshalAs ( UnmanagedType.LPWStr )] string pszLabel );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void GetResult ( [MarshalAs ( UnmanagedType.Interface )] out IShellItem ppsi );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void AddPlace ( [In, MarshalAs ( UnmanagedType.Interface )] IShellItem psi, NativeMethods.FDAP fdap );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetDefaultExtension ( [In, MarshalAs ( UnmanagedType.LPWStr )] string pszDefaultExtension );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void Close ( [MarshalAs ( UnmanagedType.Error )] int hr );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetClientGuid ( [In] ref Guid guid );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void ClearClientData ( );

// Not supported:  IShellItemFilter is not defined, converting to IntPtr
[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetFilter ( [MarshalAs ( UnmanagedType.Interface )] IntPtr pFilter );

// Defined by IFileOpenDialog
// ---------------------------------------------------------------------------------
[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void GetResults ( [MarshalAs ( UnmanagedType.Interface )] out IShellItemArray ppenum );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void GetSelectedItems ( [MarshalAs ( UnmanagedType.Interface )] out IShellItemArray ppsai );
}

如果我只打算使用这个接口中的两个方法,我可以这样定义吗:

[ComImport, Guid ( "d57c7288-d4ad-4768-be02-9d969532d960" ), InterfaceType ( ComInterfaceType.InterfaceIsIUnknown )]
interface IFileOpenDialog : IFileDialog
{
// Defined on IModalWindow - repeated here due to requirements of COM interop layer
// --------------------------------------------------------------------------------
[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime ), PreserveSig]
int Show ( [In] IntPtr parent );

[MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
void SetOptions ( [In] FOS fos );
}

它会工作吗?还是我必须用所有方法定义完整的接口?

【问题讨论】:

  • 是的,如果我只调用 Show 方法,它可以工作,如果我尝试调用 SetOptions,我得到一个异常:“尝试读取或写入受保护的内存。这通常表明其他内存已损坏。”

标签: c# winapi interface


【解决方案1】:

不,这行不通。 CLR 根据声明为 COM 接口构建一个调度表。该表中函数指针的顺序由声明中方法定义的顺序设置。 Show() 方法在这两种情况下都将占据第一个插槽,没有问题。然而,SetOptions() 最终会调用第二个,它实际上是 SetFileTypes()。它们有不同的参数,当实现得到垃圾参数并且堆栈变得不平衡时,这会以一种令人讨厌的方式进行。

可以从尾部省略任何声明。另请注意,当您不调用该方法时,该方法的实际声明并不重要。这使您可以撒谎并避免必须声明其参数类型。请确保该方法显然不会真正起作用,将其命名为 void DontCallMe2()

请注意,这些接口已经包含在 Windows API 代码包以及 .NET 4.0 版本的 Microsoft.Win32.OpenFileDialog 和 .NET 3.5 版本的 System.Windows.Forms.OpenFileDialog 中

【讨论】:

  • 这正是我的代码中发生的情况:如果我只调用 Show 方法,它可以工作,如果我尝试调用 SetOptions,我会得到一个异常:“尝试读取或写入受保护的内存。这是通常表明其他内存已损坏。”所以这是它?没有办法作弊吗?我必须定义直到 SetOptions 的所有方法吗?其中一些可能需要额外的 WinAPI 类型定义,因此代码会快速增长和增长...
  • 请注意,我编辑了我的帖子以指出这些东西已经为你完成了。
  • 是的,谢谢,但是我正在尝试在 .NET 2.0 中工作,因为我认为它具有更大的已安装用户群(即使在 Vista 上也必须安装 3.5,并且必须安装 4.0 Windows 7的)。而且我认为更多的 XP 机器拥有 .NET 2.0 而不是 3.5 或 4.0。因此,如果我在 .NET 2.0 中工作,程序直接在 Windows 7、Windows Vista 和许多已经具有 .NET 2.0 的 XP 机器上运行
  • .NET 版本绑定到 Windows 版本,在 XP 上安装 4.0 不是问题。权衡维护此代码与简单更新的成本是只有您能做的事情。
  • 不,没有属性。您可以使用虚拟方法填充插槽,当您不调用它时,它不一定是准确的。这可以让你避免声明他们的参数类型,我想这就是你真正想要的。一定要为它们起一个明显是假的名字,比如“DontCallMe2()”。
【解决方案2】:

如果要定义实现接口的类型,则必须实现接口的所有元素。如果你不这样做,那么最好的情况是你的代码会出现“不稳定”的行为,最坏的情况是你的应用程序会在你尝试对你的实现做任何事情时崩溃。任何事情界面。

请记住,这两个接口不同相同:

[ComImport, Guid ( "d57c7288-d4ad-4768-be02-9d969532d960" ), InterfaceType ( ComInterfaceType.InterfaceIsIUnknown )]
interface IFileOpenDialog : IFileDialog
{
    [MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime ), PreserveSig]
    int Show ( [In] IntPtr parent );
}

[ComImport, Guid ( "d57c7288-d4ad-4768-be02-9d969532d960" ), InterfaceType ( ComInterfaceType.InterfaceIsIUnknown )]
interface IFileOpenDialog : IFileDialog
{
    [MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime ), PreserveSig]
    int Show ( [In] IntPtr parent );

    [MethodImpl ( MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime )]
    void SetOptions ( [In] FOS fos );
}

即使它们具有相同的名称、相同的 Guid 并且都实现了Show,但只有一个实现了SetOptions 的事实使它们不同。

无论如何,从你没有实现的接口的任何方法中抛出一个NotImplemented异常,但要说你*实现了接口ISomeInterface,你实际上必须这样做。

【讨论】:

  • 这不是真的。使用任一声明调用 Show() 方法都可以正常工作。 COM 接口由它们的 Guid 标识,而不是它们的声明类型。这就是使 .NET 4.0 类型等效功能成为可能的原因,通过程序集引用的新“嵌入互操作类型”属性可见。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-07
  • 1970-01-01
相关资源
最近更新 更多