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:
I manually added entry <RegisterForComInterop>true</RegisterForComInterop>
to my project and visual studio built me .tlb file without any errors.
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
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...
PCL libraries were designed to be just that - portable, so they implemented the the largest set of features that satisfy all target platforms. Unfortunately, this excludes COM (i.e. there is no COM on Mac OS, so having COM dependencies would take the Portable out of PCL).
RegisterForcomInterop only solves half the problem, you still need to deal with ComVisible, possibly the cause of your TypeInitializationException. More info here.
It is a bit of overhead but to save some pain, wrap your PCL in a standard class library implementing the same interface and just calling the PCL methods as a pass through is probably the best bet. Then use the standard ComVisible etc.
Here is some Pseudo code to explain, in the PCL assembly:
IMyPclInterface
{
void DoSomeWork();
}
public class MyPclImplementation : IMyPclInterface
{
public void DoSomeWork()
{
....
....
....
}
}
In a standard class library, reference your PCL Library and:
public class MyComImplementation : IMyPclInterface
{
MyPclInstance myPclInstance;
public MyComImplementation()
{
myPclInstance = new MyPclInstance();
}
public void DoSomeWork()
{
myPclInstance.DoSomeWork();
}
}
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++
User contributions licensed under CC BY-SA 3.0