.NET 互操作中的隐式方法签名转换

为了保持与编程语言无关,Windows COM 系统和许多 Windows API 返回一种 4 字节整数类型,称为 HRESULT,用来指示 API 是成功还是失败,并提供有关失败的一些信息。 需要传递给调用方的其他值通过充当“out”参数的指针参数“返回”,通常是签名中的最后一个参数。 C# 和 Visual Basic 等语言传统上将故障代码转换为异常,以便与该语言对失败通常的传播方式相匹配,并期望互操作方法签名不包括 HRESULT。 若要将方法签名转换为本机格式的签名,运行时将方法的返回值移至具有更高间接级别的“out”参数(换句话说,使其成为一个指针,指向托管签名的返回类型),并假定一个 HRESULT 返回值作为默认值。 如果托管方法返回 void,则不添加其他参数,并且返回值变为一个 HRESULT。 例如,请参阅以下两种转换为同一本机签名的 C# COM 方法:

int Add(int a, int b);

void Add(int a, int b, out int sum);
HRESULT Add(int a, int b, /* out */ int* sum);

COM 中的 PreserveSig

C# 中的所有 COM 方法都默认使用已转换的签名。 将 HRESULT 添加到 COM 接口方法中,以便在不使用签名转换和处理 PreserveSigAttribute 值的情况下使用和导出方法。 当属性应用于方法时,不会对方法签名执行任何转换,同时也不会因 HRESULT 值失败而抛出异常。 这适用于内置的 COM 和源生成的 COM。 例如,请参阅以下 C# 方法签名,其中包含属性 PreserveSig 及其相应的本机签名。

[PreserveSig]
int Add(int a, int b, out int sum);
HRESULT Add(int a, int b, int* sum);

如果该方法可能返回不同 HRESULT 值(不是失败,但必须以不同的方式处理)则此方法非常有用。 例如,当方法不失败但只返回部分结果时,某些方法可能会返回值 S_FALSE ,当 S_OK 该方法返回所有结果时。

PreserveSig 和 P/Invokes

DllImportAttribute属性还具有与bool PreserveSig类似的PreserveSigAttribute字段,但默认为true。 若要指示运行时转换托管签名并处理返回的 HRESULT,请在 PreserveSig 中将 false 字段设置为 DllImportAttribute。 例如,请参阅以下对同一本机方法的两个 P/Invokes 的签名,其中一个具有 PreserveSig,设置为 false,另一个保留为默认 true 值。

[DllImport("shlwapi.dll", EntryPoint = "SHAutoComplete", ExactSpelling = true, PreserveSig = false)]
public static extern void SHAutoComplete(IntPtr hwndEdit, SHAutoCompleteFlags dwFlags);

[DllImport("shlwapi.dll", EntryPoint = "SHAutoComplete", ExactSpelling = true)]
public static extern int SHAutoCompleteHRESULT(IntPtr hwndEdit, SHAutoCompleteFlags dwFlags);

注释

源生成的 P/Invokes 使用 LibraryImportAttribute,没有 PreserveSig 字段。 生成的代码始终假定本机签名和托管签名相同。 有关详细信息,请参阅源生成的 P/Invokes

手动处理 HRESULT

调用返回PreserveSigHRESULT方法时,可以在ThrowExceptionForHR指示失败时使用HRESULT方法抛出相应的异常。 同样,在实现 PreserveSig 方法时,可以使用 GetHRForException 该方法返回 HRESULT 指示异常的相应值。

将 HRESULT 封送为结构

使用PreserveSig方法时,int应为HRESULT的托管类型。 但是,使用自定义 4 字节结构作为返回类型,可以定义帮助程序方法和属性,这些方法和属性可以简化使用 HRESULT。 在内置封送中,这是自动工作的。 要在源生成的封送中使用结构代替 int 作为 HRESULT 的托管表示,请添加以 MarshalAsAttribute 为参数的 Error 属性。 此属性的存在将 HRESULT 位重新解释为结构。

另请参阅