使用可移植类库作为 COM 接口

Posted

技术标签:

【中文标题】使用可移植类库作为 COM 接口【英文标题】:Use Portable Class Library as COM interface 【发布时间】:2016-08-04 15:53:46 【问题描述】:

我有适用于 Windows 8.1 和 Windows Phone 8.1 的 C# 可移植类库。 我想编写一个使用这个库的本机(非托管)C++ Windows 应用程序。 据我所知,最佳做法是将我的库公开为 COM 接口。

这似乎是一个简单的解决方案,但是当我创建可移植类库项目时,“使程序集 COM 可见”和“注册 COM 互操作”复选框被禁用。可移植类库不可能吗?

我尝试过的:

    我手动将条目 <RegisterForComInterop>true</RegisterForComInterop> 添加到我的项目中,Visual Studio 构建了我的 .tlb 文件,没有任何错误。

    我将它添加到我的 C++ 项目中:

#include "stdafx.h"
#import "COM\MyLib.tlb"
#include<iostream>
using namespace MyLib;
using namespace std;

int main()

  CoInitialize(NULL);   //Initialize all COM Components

  IPayMeLogicPtr core = IPayMeLogicPtr(_uuidof(ComPayMeLogic));
  LoginStatus res = core->Login("myLogin", "myPassword", false, PayMeSDK::Language::Language_ru, true, "");

  cout << res;
  return 0;

生成的 .tlh 文件看起来相当不错 - here it is in a gist。 但是当我尝试运行此代码时,它会失败并出现异常:

exception at memory location 0x74A5DAE8 (KernelBase.dll) in PayMeDemo.ComConsoleDemo.exe: 0xE0434352 (0x80131534, 0x00000000, 0x00000000, 0x00000000, 0x73A10000).
exception at memory location 0x74A5DAE8 in PayMeDemo.ComConsoleDemo.exe:  Microsoft C++ exception: _com_error at location 0x00BBFADC.

似乎发生在 .tli 的第四行代码上(_hr 失败):

inline enum LoginStatus IPayMeLogic::Login ( _bstr_t Login, _bstr_t password, VARIANT_BOOL testEnviroment, enum Language locale, VARIANT_BOOL savePassword, _bstr_t applicationToken ) 
  enum LoginStatus _result;
  HRESULT _hr = raw_Login(Login, password, testEnviroment, locale, savePassword, applicationToken, &_result);
  if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
  return _result;

_hr 等于0x80131534 error (TypeInitializationException)

但是,我无法想象哪些类型会出错。登录功能如下:

    LoginStatus Login(string login, string password, bool testEnviroment, Language locale, bool savePassword, string applicationToken = null);

我已经试过了

    将程序集添加到 GAC 使用 Regasm.exe 手动注册程序集

一切似乎都很好,但异常仍然相同。真的不可能吗?我应该做一个非便携式图书馆吗?这会浪费时间,因为我的库使用了可移植的类...

【问题讨论】:

这很正常。 #import 指令创建的包装器将 COM 错误代码转换为 C++ 异常。如果您想诊断错误而不是仅仅让程序崩溃,那么您必须编写 try/catch (_com_error& ex) 来报告它。将调试器类型从自动更改为混合,这样您就可以更轻松地解决 C# 异常,TypeInitializationException 需要这种帮助。并编写一个 C# 单元测试,以便在尝试从 C++ 调用它之前解决基本问题。 @HansPassant 谢谢,您的评论为我节省了大量的故障排除时间! 【参考方案1】:

PCL 库被设计成可移植的,因此它们实现了满足所有目标平台的最大功能集。不幸的是,这不包括 COM(即 Mac OS 上没有 COM,因此具有 COM 依赖项会使 Portable 脱离 PCL)。

RegisterForcomInterop 只解决了一半问题,你还需要处理 ComVisible,可能是你的 TypeInitializationException 的原因。手机号ore info here.

这有点开销,但为了节省一些痛苦,将 PCL 包装在实现相同接口的标准类库中,并且只调用 PCL 方法作为传递可能是最好的选择。然后使用标准的 ComVisible 等。

下面是一些伪代码来解释,在 PCL 程序集中:

IMyPclInterface

     void DoSomeWork();


public class MyPclImplementation : IMyPclInterface

     public void DoSomeWork()
     
        ....
        ....
        ....
     

在标准类库中,引用您的 PCL 库并:

public class MyComImplementation : IMyPclInterface

     MyPclInstance myPclInstance;

     public MyComImplementation()
     
          myPclInstance = new MyPclInstance();
     

     public void DoSomeWork()
     
          myPclInstance.DoSomeWork();
     

【讨论】:

我最近试过这个,但我的标准类库要求我的 PCL 库是 COM 可见的。所以无论哪种方式,我都必须将此选项破解到我的 PCL 中(然后这个包装器没有区别)或使用另一种方法...... 糟透了 :( - 也见这里,你也需要解决 ComVisible:***.com/questions/10194988/… 是的,已经这样做了。还有 GUID 和 [ClassInterface(ClassInterfaceType.None)] 如果您可以自己复制粘贴两者之间的接口,以便 Com 类库拥有自己的本地副本,那么它可以正常工作。你只需要与重复和解:) 你试过这个吗:***.com/questions/10194988/…?【参考方案2】:

正如Murray Foxcroft 所指出的,可以制作PCL 库的副本,编译为标准类库。此外,可以为标准类库添加 PCL 依赖项,它会起作用。

但是,您不必这样做。尽管 Visual Studio 没有为您提供 COM 复选框,但您仍然可以按照问题中的指示手动添加属性或手动运行 Regasm.exe smth.dll /tlb,您将获得一个有效的 COM 库。调试起来真的很困难,但是Hans Passant 留下了一些很好的提示:

#import 指令创建的包装器会转换 COM 错误代码 进入 C++ 异常。如果您想诊断错误而不仅仅是 如果程序崩溃,那么您必须编写 try/catch (_com_error& ex) 进行报告。将调试器类型从 Auto 更改为 Mixed 所以 您可以更轻松地解决 C# 异常问题, TypeInitializationException 需要这种帮助。并编写一个 C# 单元测试,以便在尝试调用它之前解决基本问题 来自 C++

【讨论】:

以上是关于使用可移植类库作为 COM 接口的主要内容,如果未能解决你的问题,请参考以下文章

可移植类库:推荐替换 [Serializable]

StreamReader 和可移植类库

Prism 模块引用可移植类库

为啥我应该在 Xamarin 中使用可移植类库?

使用 Xamarin 在可移植类库中保护 WCF 绑定

VS2015中SharedProject与可移植类库(PCL)项目