使用 Visual Studio 互操作程序集

Visual Studio 互操作程序集允许托管应用程序访问提供 Visual Studio 扩展性的 COM 接口。 直接 COM 接口与其互操作版本之间存在一些差异。 例如,HRESULT 通常表示为 int 值,需要以与异常相同的方式进行处理,而参数(尤其是 out 参数)则以不同的方式进行处理。

处理从 COM 返回到托管代码的 HRESULT

当从托管代码调用 COM 接口时,请检查 HRESULT 值并根据需要引发异常。 ErrorHandler 类包含 ThrowOnFailure 方法,该方法根据传递给它的 HRESULT 值引发 COM 异常。

默认情况下,每次向 ThrowOnFailure 传递值小于零的 HRESULT 时,它都会引发异常。 如果此类 HRESULT 是可接受的值,不应引发异常,那么,应在测试完其他 HRESULT 的值后,将这些值传递给 ThrowOnFailure。 如果所测试的 HRESULT 与显式传递到 ThrowOnFailure 的任何 HRESULT 值匹配,则不会引发异常。

注意

VSConstants 类包含常见 HRESULTS 的常量,例如 S_OK ,以及 E_NOTIMPLVisual Studio HRESULTS,例如 VS_E_INCOMPATIBLEDOCDATA ,以及 VS_E_UNSUPPORTEDFORMATVSConstants 还提供 SucceededFailed 方法,分别对应于 COM 中的 SUCCEEDED 宏和 FAILED 宏。

例如,在以下函数调用中,E_NOTIMPL 是可接受的返回值,而其他所有小于零的 HRESULT 均表示错误。

int hr = MyInterface.MyFunction(cmdID);
ErrorHandler.ThrowOnFailure(hr, VSConstants.E_NOTIMPL);

如果有多个可接受的返回值,只需在调用 ThrowOnFailure 时将其他 HRESULT 值追加到列表中。

int hr = MyInterface.MyFunction(cmdID);
ErrorHandler.ThrowOnFailure(hr, VSConstants.E_NOINTERFACE, VSConstants.E_NOTIMPL);

从托管代码将 HRESULT 返回到 COM

如果没有发生异常,托管代码会向调用它的 COM 函数返回 S_OK。 COM 互操作支持托管代码中强类型化的常见异常。 例如,收到不可接受的 null 参数的方法会引发 ArgumentNullException

如果不确定要引发哪个异常,但知道要返回到 COM 的 HRESULT,则可以使用 ThrowExceptionForHR 方法引发相应的异常。 即使是非标准错误(例如 VS_E_INCOMPATIBLEDOCDATA),也同样可行。 ThrowExceptionForHR 会尝试将传递给它的 HRESULT 映射到强类型化的异常。 如果无法映射,它会改为引发一般的 COM 异常。 最终结果是,从托管代码传递到 ThrowExceptionForHR 的 HRESULT 返回到调用它的 COM 函数中。

注意

异常会降低性能,它用于指示程序的异常状况。 对于所发生的状况,通常应以内联方式处理,而不是引发异常。

作为 Type void 传递的 IUnknown 参数**

查找在 COM 接口中定义为类型 void **[``iid_is``] 定义为 Visual Studio 互操作程序集方法原型中的 [out] 参数。

有时,COM 接口会生成一个 IUnknown 对象,COM 接口然后将它作为类型 void **传递。 这些接口尤其重要,因为如果变量在 IDL 中定义为 [out],则 IUnknown 使用该方法对对象进行 AddRef 引用计数。 如果对象未正确处理,则会发生内存泄漏。

注意

IUnknown COM 接口创建并在 [out] 变量中返回的对象在未显式释放时会导致内存泄漏。

处理此类对象的托管方法应被视为 IntPtr 指向对象的 IUnknown 指针,并调用 GetObjectForIUnknown 该方法以获取对象。 然后,调用方应将返回值强制转换为适合的任何类型。 不再需要对象时,请调用 Release 以释放它。

下面是调用 QueryViewInterface 方法并正确处理 IUnknown 对象的示例:

MyClass myclass;
Object object;
IntPtr pObj;
Guid iid = Typeof(MyClass).Guid;
int hr = windowFrame.QueryViewInterface(ref iid, out pObj);
if (NativeMethods.Succeeded(hr))
{
    try
    {
        object = Marshal.GetObjectForIUnknown(pObj);
        myclass = object;
    }
    finally
    {
        Marshal.Release(pObj);
    }
}
else
{
    // error calling QueryViewInterface
}

注意

已知以下方法将对象指针作为类型IntPtr传递IUnknown。 按照本节中所述处理它们。

可选 [out] 参数

在 COM 接口中查找定义为 [out] 数据类型(intobject等)的参数,但在 Visual Studio 互操作程序集方法原型中定义为相同数据类型的数组。

某些 COM 接口(例如 GetCfgs)将 [out] 参数视为可选参数。 如果不需要对象,这些 COM 接口将返回一个 null 指针作为该参数的值,而不是创建 [out] 对象。 这是设计的结果。 对于这些接口, null 指针被假定为 VSPackage 的正确行为的一部分,并且不会返回任何错误。

由于 CLR 不允许 [out] 参数 null的值,因此这些接口的设计行为的一部分在托管代码中不直接可用。 受影响的接口的 Visual Studio 互操作程序集方法通过将相关参数定义为数组来解决该问题,因为 CLR 允许传递 null 数组。

当没有返回任何内容时,这些方法的托管实现应将数组放入 null 参数中。 否则,请创建一个正确类型的一个元素数组,并将返回值放在数组中。

从具有可选 [out] 参数的接口接收信息的托管方法接收参数作为数组。 只需检查数组的第一个元素的值。 如果不是 null,请将第一个元素视为原始参数。

在指针参数中传递常量

查找在 COM 接口中定义为 [in] 指针的参数,但在 Visual Studio 互操作程序集方法原型中定义为 IntPtr 类型。

当 COM 接口传递特殊值(如 0、-1 或 -2)而不是对象指针时,会出现类似的问题。 与 Visual C++ 不同,CLR 不允许将常量强制转换为对象。 相反,Visual Studio 互操作程序集将参数定义为类型 IntPtr

这些方法的托管实现应利用 IntPtr 类具有这两 int 者以及 void * 构造函数,以便根据情况从对象或整数常量创建 IntPtr

接收 IntPtr 此类型的参数的托管方法应使用 IntPtr 类型转换运算符来处理结果。 首先对IntPtrint相关整数常量进行转换并对其进行测试。 如果没有匹配的值,请将其转换为所需类型的对象并继续。

有关此示例,请参阅 OpenStandardEditorOpenSpecificEditor

作为 [out] 参数传递的 OLE 返回值

查找在 COM 接口中具有 retval 返回值的方法,但在 Visual Studio 互操作程序集方法原型中具有 int 返回值和其他 [out] 数组参数。 应该清楚的是,这些方法需要特殊处理,因为 Visual Studio 互操作程序集方法原型具有一个比 COM 接口方法更多的参数。

处理 OLE 活动的许多 COM 接口会将有关 OLE 状态的信息发送回接口的返回值中 retval 存储的调用程序。 相应的 Visual Studio 互操作程序集方法将信息发送回 [out] 数组参数中存储的调用程序,而不是使用返回值。

这些方法的托管实现应创建与 [out] 参数相同的类型的单元素数组,并将其放入参数中。 数组元素的值应与相应的 COM retval相同。

调用此类型的接口的托管方法应从 [out] 数组中提取第一个元素。 此元素可以视为 retval 来自相应 COM 接口的返回值。