How to call a C# library from Native C++ (using C++\CLI and IJW)
You can do this fairly easily.
- Create an .h/.cpp combo
- Enable /clr on the newly create .cpp file. (CPP -> Right click -> Properties)
- Set the search path for "additional #using directories" to point towards your C# dll.
Native.h
void NativeWrapMethod();
Native.cpp
#using <mscorlib.dll>
#using <MyNet.dll>
using namespace MyNetNameSpace;
void NativeWrapMethod()
{
MyNetNameSpace::MyManagedClass::Method(); // static method
}
That's the basics of using a C# lib from C++\CLI with native code. (Just reference Native.h where needed, and call the function.)
Using C# code with managed C++\CLI code is roughly the same.
There is a lot of misinformation on this subject, so, hopefully this saves someone a lot of hassle. :)
I've done this in: VS2010 - VS2012 (It probably works in VS2008 too.)
UPDATE 2018
It seems like as if the solution does not work for Visual Studio 2017 and onwards. Unfortunately I am currently not working with Visual Studio and therefore cannot update this answer by myself. But kaylee posted an updated version of my answer, thank you!
UPDATE END
If you want to use COM, here's my solution for this problem:
C# library
First of all you need a COM compatible library.
You already got one? Perfect, you can skip this part.
-
You have access to the library? Make sure it's COM compatible by following the steps.
- Make sure that you checked the "Register for COM interop" option in the properties of your project. Properties -> Build -> Scroll down -> Register for COM interop
The following screenshots shows where you find this option.
-
All the interfaces and classes that should be available need to have a GUID
namespace NamespaceOfYourProject { [Guid("add a GUID here")] public interface IInterface { void Connect(); void Disconnect(); } } namespace NamespaceOfYourProject { [Guid("add a GUID here")] public class ClassYouWantToUse: IInterface { private bool connected; public void Connect() { //add code here } public void Disconnect() { //add code here } } }
So that's pretty much what you have to do with your C# code. Let's continue with the C++ code.
C++
- First of all we need to import the C# library.
After compiling your C# library there should be a .tlb file.
#import "path\to\the\file.tlb"
If you import this new created file to your file.cpp you can use your object as a local variable.
#import "path\to\the\file.tlb"
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);
NamespaceOfYourProject::IInterfacePtr yourClass(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));
yourClass->Connect();
CoUninitialize();
}
- Using your class as an attribute.
You will noticed that the first step only works with a local variable. The following code shows how to use it as a attribute. Related to this question.
You will need the CComPtr, which is located in atlcomcli.h. Include this file in your header file.
CPlusPlusClass.h
#include <atlcomcli.h>
#import "path\to\the\file.tlb"
class CPlusPlusClass
{
public:
CPlusPlusClass(void);
~CPlusPlusClass(void);
void Connect(void);
private:
CComPtr<NamespaceOfYourProject::IInterface> yourClass;
}
CPlusPlusClass.cpp
CPlusPlusClass::CPlusPlusClass(void)
{
CoInitialize(NULL);
yourClass.CoCreateInstance(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));
}
CPlusPlusClass::~CPlusPlusClass(void)
{
CoUninitialize();
}
void CPlusPlusClass::Connect(void)
{
yourClass->Connect();
}
That's it! Have fun with your C# classes in C++ with COM.
The answer from 0lli.rocks is unfortunately either outdated or incomplete. My co-worker helped me get this working, and to be frank one or two of the implementation details were not remotely obvious. This answer rectifies the gaps and should be directly copyable into Visual Studio 2017 for your own use.
Caveats: I haven't been able to get this working for C++/WinRT, just an FYI. All sorts of compile errors due to ambiguity of the IUnknown
interface. I was also having problems getting this to work for just a library implementation instead of using it in the main of the app. I tried following the instructions from 0lli.rocks for that specifically, but was never able to get it compiling.
Step 01: Create your C# Library
Here's the one we'll be using for the demo:
using System;
using System.Runtime.InteropServices;
namespace MyCSharpClass
{
[ComVisible(true)] // Don't forget
[ClassInterface(ClassInterfaceType.AutoDual)] // these two lines
[Guid("485B98AF-53D4-4148-B2BD-CC3920BF0ADF")] // or this GUID
public class TheClass
{
public String GetTheThing(String arg) // Make sure this is public
{
return arg + "the thing";
}
}
}
Step 02 - Configure your C# library for COM-visibility
Sub-Step A - Register for COM interoperability
Sub-Step B - Make the assembly COM-visible
Step 3 - Build your Library for the .tlb
file
You probably want to just do this as Release
for AnyCPU
unless you really need something more specific.
Step 4 - Copy the .tlb
file into the source location for your C++ project
Step 5 - Import the .tlb
file into your C++ project
#include "pch.h"
#include <iostream>
#include <Windows.h>
#import "MyCSharpClass.tlb" raw_interfaces_only
int wmain() {
return 0;
}
Step 6 - Don't panic when Intellisense fails
It will still build. You're going to see even more red-lined code once we implement the actual class into the C++ project.
Step 7 - Build your C++ project to generate the .tlh
file
This file will go into your intermediate object build directory once you build the first time
Step 8 - Assess the .tlh
file for implementation instructions
This is the .tlh
file that is generated in the intermediate object folder. Don't edit it.
// Created by Microsoft (R) C/C++ Compiler Version 14.15.26730.0 (333f2c26).
//
// c:\users\user name\source\repos\consoleapplication6\consoleapplication6\debug\mycsharpclass.tlh
//
// C++ source equivalent of Win32 type library MyCSharpClass.tlb
// compiler-generated file created 10/26/18 at 14:04:14 - DO NOT EDIT!
//
// Cross-referenced type libraries:
//
// #import "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb"
//
#pragma once
#pragma pack(push, 8)
#include <comdef.h>
namespace MyCSharpClass {
//
// Forward references and typedefs
//
struct __declspec(uuid("48b51671-5200-4e47-8914-eb1bd0200267"))
/* LIBID */ __MyCSharpClass;
struct /* coclass */ TheClass;
struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
/* dual interface */ _TheClass;
//
// Smart pointer typedef declarations
//
_COM_SMARTPTR_TYPEDEF(_TheClass, __uuidof(_TheClass));
//
// Type library items
//
struct __declspec(uuid("485b98af-53d4-4148-b2bd-cc3920bf0adf"))
TheClass;
// [ default ] interface _TheClass
// interface _Object
struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
_TheClass : IDispatch
{
//
// Raw methods provided by interface
//
virtual HRESULT __stdcall get_ToString (
/*[out,retval]*/ BSTR * pRetVal ) = 0;
virtual HRESULT __stdcall Equals (
/*[in]*/ VARIANT obj,
/*[out,retval]*/ VARIANT_BOOL * pRetVal ) = 0;
virtual HRESULT __stdcall GetHashCode (
/*[out,retval]*/ long * pRetVal ) = 0;
virtual HRESULT __stdcall GetType (
/*[out,retval]*/ struct _Type * * pRetVal ) = 0;
virtual HRESULT __stdcall GetTheThing (
/*[in]*/ BSTR arg,
/*[out,retval]*/ BSTR * pRetVal ) = 0;
};
} // namespace MyCSharpClass
#pragma pack(pop)
In that file, we see these lines for the public method we want to use:
virtual HRESULT __stdcall GetTheThing (
/*[in]*/ BSTR arg,
/*[out,retval]*/ BSTR * pRetVal ) = 0;
That means that the imported method will expect an input-string of type BSTR
, and a pointer to a BSTR
for the output string that the imported method will return on success. You can set them up like this, for example:
BSTR thing_to_send = ::SysAllocString(L"My thing, or ... ");
BSTR returned_thing;
Before we can use the imported method, we will have to construct it. From the .tlh
file, we see these lines:
namespace MyCSharpClass {
//
// Forward references and typedefs
//
struct __declspec(uuid("48b51671-5200-4e47-8914-eb1bd0200267"))
/* LIBID */ __MyCSharpClass;
struct /* coclass */ TheClass;
struct __declspec(uuid("1ed1036e-c4ae-31c1-8846-5ac75029cb93"))
/* dual interface */ _TheClass;
//
// Smart pointer typedef declarations
//
_COM_SMARTPTR_TYPEDEF(_TheClass, __uuidof(_TheClass));
//
// Type library items
//
struct __declspec(uuid("485b98af-53d4-4148-b2bd-cc3920bf0adf"))
TheClass;
// [ default ] interface _TheClass
// interface _Object
First, we need to use the namespace of the class, which is MyCSharpClass
Next, we need to determine the the smart pointer from the namespace, which is _TheClass
+ Ptr
; this step is not remotely obvious, as it's nowhere in the .tlh
file.
Last, we need to provide the correct construction parameter for the class, which is __uuidof(MyCSharpClass::TheClass)
Ending up with,
MyCSharpClass::_TheClassPtr obj(__uuidof(MyCSharpClass::TheClass));
Step 9 - Initialize COM and test the imported library
You can do that with CoInitialize(0)
or whatever your specific COM initializer happens to be.
#include "pch.h"
#include <iostream>
#include <Windows.h>
#import "MyCSharpClass.tlb" raw_interfaces_only
int wmain() {
CoInitialize(0); // Init COM
BSTR thing_to_send = ::SysAllocString(L"My thing, or ... ");
BSTR returned_thing;
MyCSharpClass::_TheClassPtr obj(__uuidof(MyCSharpClass::TheClass));
HRESULT hResult = obj->GetTheThing(thing_to_send, &returned_thing);
if (hResult == S_OK) {
std::wcout << returned_thing << std::endl;
return 0;
}
return 1;
}
Once again, don't panic when Intellisense freaks out. You're in Black Magic, Voodoo, & Thar Be Dragons territory, so press onward!
The absolute best way I have found to do this is create a c++/cli bridge that connects the c# code to your native C++. You can do this with 3 different projects.
- First Project: C# library
- Second Project: C++/CLI bridge (this wraps the C# library)
- Third Project: Native C++ application that uses the second project
I recently created a simple GitHub Tutorial for how to do this here. Reading through that code with a little grit and you should be able to hammer out creating a C++/CLI bridge that allows you to use C# code in your native C++.
As a bonus I added how to wrap a C# event down to a function pointer in C++ that you can subscribe to.