Search code examples
c#c++visual-studio-2015comportable-class-library

Use Portable Class Library as COM interface


I have C# portable class library for Windows 8.1 and Windows Phone 8.1. I want to write a native (unmanaged) C++ Windows application which uses this library. As far as I seen, the best practise is to expose my library as COM interface.

This seems like a simple solution but when I create Portable Class Library project, checkboxes "Make assembly COM visible" and "Register for COM interop" are disabled. Is it impossible for Portable Class Library?

What I have tried:

  1. I manually added entry <RegisterForComInterop>true</RegisterForComInterop> to my project and visual studio built me .tlb file without any errors.

  2. I added it to my C++ project:

#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;
}

Generated .tlh file seems pretty fine - here it is in a gist. But when I try to run this code, it fails with an exceptions:

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.

Seems like it is happening on the fourth line of code in .tli (_hr fails):

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 is equal to 0x80131534 error (TypeInitializationException)

However, I can't imagine what types can be wrong. Login function looks like this:

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

I already tried to

  1. Add assembly to GAC
  2. Manually register assembly with Regasm.exe

Everything seems fine but exception is still the same. Is it really impossible? Should I make a non-portable library? It would be a waste of time because my library uses classes from portable...


Solution

  • As Murray Foxcroft pointed, it is possible to make a copy of PCL library, compiled as standard class library. Furthermore, it is possible to add PCL dependencies for standard class library and it will work.

    However, you don't have to do this. Despite Visual Studio not giving you checkbox for COM, you still can add atribute manually as pointed in the question or run Regasm.exe smth.dll /tlb manually and you will get a valid COM library. It's really difficult to debug but Hans Passant left some good tips:

    The wrappers created by the #import directive turn COM error codes into C++ exceptions. If you want to diagnose the error instead of just having the program crash then you have to write try/catch (_com_error& ex) {} to report it. Change the Debugger Type from Auto to Mixed so you'll have a much easier time trouble-shooting the C# exception, TypeInitializationException requires that kind of help. And write a C# unit test so you get the basic problems out before you try to call it from C++