65

Background: As part of a larger assignment I need to make a C# library accessible to unmanaged C++ and C code. In an attempt to answer this question myself I have been learning C++/CLI the past few days/ weeks.

There seems to be a number of different ways to achieve using a C# dll from unmanaged C++ and C. Some of the answers in brief appear to be: using Interlope services, Using .com. and regasm, Using PInvoke (which appears to go from C# to C++ only), and using IJW in the C++/CLR (which appears to be Interlope services). I am thinking it would be best to set up a library that is perhaps a CLR wrapper that uses IJW to call my C# dll on the behalf of native C++ and C code.

Specifics: I need to pass values of string as well as int to a C# dll from c++ code, and return void.

Relevance: Many companies have many excuses to mix and match C++, C and C#. Performance: unmanaged code is usually faster, interfaces: Managed interfaces are generally easier to maintain, deploy, and are often easier on the eyes, Managers tell us too. Legacy code forces us too. It was there (Like the mountain that we climbed). While examples of how to call a C++ library from C# are abundant. Examples of how to call C# libraries from C++ code are difficult to find via Googling especially if you want to see updated 4.0+ code.

Software: C#, C++/CLR, C++, C, Visual Studio 2010, and .NET 4.0

Question details: OK multi-part question:

  1. Is there an advantage to using com objects? Or the PInvoke? Or some other method? (I feel like the learning curve here will be just as steep, even though I do find more information on the topic in Google Land. IJW seems to promise what I want it to do. Should I give up on looking for an IJW solution and focus on this instead?) (Advantage/ disadvantage?)

  2. Am I correct in imagining that there is a solution where I write a wrapper that that utilizes IJW in the C++/CLR? Where can I find more information on this topic, and don’t say I didn’t Google enough/ or look at MSDN without telling me where you saw it there. (I think I prefer this option, in the effort to write clear and simple code.)

  3. A narrowing of question scope: I feel that my true issue and need is answering the smaller question that follows: How do I set up a C++/CLR library that an unmanaged C++ file can use within visual studio. I think that if I could simply instantiate a managed C++ class in unmanaged C++ code, then I might be able work out the rest (interfacing and wrapping etc.). I expect that my main folly is in trying to set up references/#includes etc. within Visual Studio, thought clearly I could have other misconceptions. Perhaps the answer to this whole thing could be just a link to a tutorial or instructions that help me with this.

Research: I have Googled and Binged over and over with some success. I have found many links that show you how to use an unmanaged library from C# code. And I will admit that there have been some links that show how to do it using com objects. Not many results were targeted at VS 2010.

References: I have read over and over many posts. I have tried to work through the most relevant ones. Some seem tantalizingly close to the answer, but I just can’t seem to get them to work. I suspect that the thing that I am missing is tantalizingly small, such as misusing the keyword ref, or missing a #include or using statement, or a misuse of namespace, or not actually using the IJW feature properly, or missing a setting that VS needs to handle the compilation correctly, etc. So you wonder, why not include the code? Well I feel like I am not at a place where I understand and expect the code I have to work. I want to be in a place where I understand it, when I get there maybe then I'll need help fixing it. I'll randomly include two of the links but I am not permitted to show them all at my current Hitpoint level.

http://www.codeproject.com/Articles/35437/Moving-Data-between-Managed-Code-and-Unmanaged-Cod

This calls code from managed and unmanaged code in both directions going from C++ to Visual Basic and back via C++CLR, and of course I am interested in C#.: http://www.codeproject.com/Articles/9903/Calling-Managed-Code-from-Unmanaged-Code-and-vice

amalgamate
  • 2,200
  • 5
  • 22
  • 44
  • See if this [link](http://blogs.msdn.com/b/nikolad/archive/2007/07/24/using-c-in-c-project-with-visual-studio-2005.aspx) helps – lstern Nov 08 '12 at 17:18
  • 6
    You are on the wrong track with this, you can directly call C# methods from C++/CLI code without any special interop requirements. You use "C++ Interop" (not IJW, that's old) only to have C++/CLI code call native code. COM is good the other way around. Which one to use has a great deal to do with who's in charge. If your EXE is managed then things get simple since the CLR is already loaded and initialized. If your EXE is native then something must be done to load the CLR so you can run managed code. Like COM. – Hans Passant Nov 08 '12 at 18:32
  • Istern- Thank you. I am aware of WCF as a possible IPC solution but I would like to go a more direct way: One where the C# class can be instantiated from C++. – amalgamate Nov 08 '12 at 19:26
  • 1
    Hans- I know I "can directly call C# methods from C++/CLI code without any special interop requirements." I have code that I have written that can do that. My sticking point is calling that C++/CLI code from a native C++ Eventually I will need to make calls from C as well. – amalgamate Nov 08 '12 at 19:43
  • 1
    Since .NET 5.0 it is possible to do that in a cross-platform way, without using C++\CLI or hacks such as reverse P-invoke. Please see my answer here: https://stackoverflow.com/a/63203205/4669135 – Gabriel Devillers Aug 01 '20 at 08:27

6 Answers6

47

You can do this fairly easily.

  1. Create an .h/.cpp combo
  2. Enable /clr on the newly create .cpp file. (CPP -> Right click -> Properties)
  3. 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.)

Smoke
  • 519
  • 3
  • 4
  • I second the misinformation comment. And Thank you for your post. ... removed some of my comment. I just realized I misread your post. I think I am and was really close to getting this to work all allong based on your post. I'll take a look soon to see if I can figure out where I am going astray... ps: +1 ya (still), but am too green. – amalgamate Nov 21 '12 at 18:46
  • 1
    If you get it to build, it may still error out when running unless you have the DLLs actually in the search path for when the executable runs. Easiest way to do this is copy the DLL into your build directory in the project (probably Debug). VS does not automatically copy the referenced DLLs into the exe directory. – AberrantWolf Jul 23 '15 at 05:40
  • 17
    The question said "From Native C++". This answer is from C++/CLI. – Denise Skidmore Aug 20 '15 at 19:14
  • thx alot, from your posting, I learned how to "use" the C# DLL in C++/CLI at all, `#using ` did the trick I didn't find before! But where in Visual Studio project do I find the "additional #using directiories"?? Currently I'm copying the C# DLL to the C++/CLI project directory, which is annoying... – Micka Oct 05 '15 at 10:39
  • Go to Project -> Properties -> C/C++ – M3579 Nov 01 '15 at 23:31
  • Note: You don't have to use #using if you configure the project to add the .net project as a dependency, which is convenient because it will trigger a rebuild, and doesn't require any additional include directory. – meneldal Jul 07 '20 at 08:45
27

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.

    1. 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.

Screenshot Project Properties Build

  1. 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++

  1. 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();
}
  1. 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.

0lli.rocks
  • 1,027
  • 1
  • 18
  • 31
  • why it is needed for the class to be inherited from the interface? I have some C# classes and I don't want them to be inherited from interface, Is it ok? – Mosi Sep 18 '17 at 11:33
  • @Mosi even though I never tested it, there should be no reason why not. You should be fine without an interface. – 0lli.rocks Sep 19 '17 at 08:17
20

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

enter image description here

Sub-Step B - Make the assembly COM-visible

enter image description here

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


enter image description here

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


enter image description here

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

enter image description here

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!

enter image description here

kayleeFrye_onDeck
  • 6,648
  • 5
  • 69
  • 80
  • What about using [Mono](https://www.mono-project.com/docs/advanced/embedding)? – fsinisi90 Mar 19 '20 at 15:57
  • mono is more simple. you can just get the backend MonoClass*,MonoMethod*,MonoType* by mono apis., and invoke them by call mono_method_invoke....apis, which is exported to mono.dll – boo Sep 24 '20 at 02:35
  • Because Mono is its own thing, and adds a lot of unnecessary bulk to a project. It's probably easier to work with, but if you want to know how to do this the "regular way" *cringe* this is how you do it. Importing C# functionality into C++ is kind of backwards as it is. It's usually the other way around. You can tell by all the black-magic hackery required just to get this working that it was never an intended workflow. – kayleeFrye_onDeck Feb 03 '23 at 01:04
19

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.

jsmith
  • 7,198
  • 6
  • 42
  • 59
  • First link to word press is not public. It is possible to copy some content to here? – Ugur Jan 27 '21 at 11:07
  • I don't have access to the original content that was lost in the link. There's a lot more information out there on how to do this still. I stand by my original answer in that I think creating the C++/CLI bridge is the best way to wrap C# code down to native libraries. – jsmith Mar 02 '21 at 21:43
  • 2
    thanks for your update. Yeah, I had alsotested the C++/CLI bridge. Working very good – Ugur Mar 03 '21 at 14:23
  • I did update this solution to VS2022 and .NET 4.8, works like a charm, thank you. – Xcessity Mar 06 '23 at 18:00
  • @xcessity Can you upload a basic project to github with 2022 and c#? – Angelru Jul 20 '23 at 19:25
4

I found something that at least begins to answer my own question. The following two links have wmv files from Microsoft that demonstrate using a C# class in unmanaged C++.

This first one uses a COM object and regasm: http://msdn.microsoft.com/en-us/vstudio/bb892741.

This second one uses the features of C++/CLI to wrap the C# class: http://msdn.microsoft.com/en-us/vstudio/bb892742. I have been able to instantiate a c# class from managed code and retrieve a string as in the video. It has been very helpful but it only answers 2/3rds of my question as I want to instantiate a class with a string perimeter into a c# class. As a proof of concept I altered the code presented in the example for the following method, and achieved this goal. Of course I also added a altered the {public string PickDate(string Name)} method to do something with the name string to prove to myself that it worked.

wchar_t * DatePickerClient::pick(std::wstring nme)
{
    IntPtr temp(ref);// system int pointer from a native int
    String ^date;// tracking handle to a string (managed)
    String ^name;// tracking handle to a string (managed)
    name = gcnew String(nme.c_str());
    wchar_t *ret;// pointer to a c++ string
    GCHandle gch;// garbage collector handle
    DatePicker::DatePicker ^obj;// reference the c# object with tracking handle(^)
    gch = static_cast<GCHandle>(temp);// converted from the int pointer 
    obj = static_cast<DatePicker::DatePicker ^>(gch.Target);
    date = obj->PickDate(name);
    ret = new wchar_t[date->Length +1];
    interior_ptr<const wchar_t> p1 = PtrToStringChars(date);// clr pointer that acts like pointer
    pin_ptr<const wchar_t> p2 = p1;// pin the pointer to a location as clr pointers move around in memory but c++ does not know about that.
    wcscpy_s(ret, date->Length +1, p2);
    return ret;
}

Part of my question was: What is better? From what I have read in many many efforts to research the answer is that COM objects are considered easier to use, and using a wrapper instead allows for greater control. In some cases using a wrapper can (but not always) reduce the size of the thunk, as COM objects automatically have a standard size footprint and wrappers are only as big as they need to be.

The thunk (as I have used above) refers to the space time and resources used in between C# and C++ in the case of the COM object, and in between C++/CLI and native C++ in the case of coding-using a C++/CLI Wrapper. So another part of my answer should include a warning that crossing the thunk boundary more than absolutely necessary is bad practice, accessing the thunk boundary inside a loop is not recommended, and that it is possible to set up a wrapper incorrectly so that it double thunks (crosses the boundary twice where only one thunk is called for) without the code seeming to be incorrect to a novice like me.

Two notes about the wmv's. First: some footage is reused in both, don't be fooled. At first they seem the same but they do cover different topics. Second, there are some bonus features such as marshalling that are now a part of the CLI that are not covered in the wmv's.

Edit:

Note there is a consequence for your installs, your c++ wrapper will not be found by the CLR. You will have to either confirm that the c++ application installs in any/every directory that uses it, or add the library (which will then need to be strongly named) to the GAC at install time. This also means that with either case in development environments you will likely have to copy the library to each directory where applications call it.

amalgamate
  • 2,200
  • 5
  • 22
  • 44
  • Sigh, thanks Microsoft. I knew it was not the best way to answer a question. Should have downloaded the videos when I had the chance :-). – amalgamate Nov 02 '18 at 14:24
  • My last comment was lamenting that the links in my answer went bad. Previous commenters must have deleted their posts. – amalgamate Jan 27 '21 at 13:28
0

I did a bunch of looking around and found a relatively recent article by Microsoft detailing how it can be done (there is a lot of old infomration floating around). From the article itself:

The code sample uses the CLR 4 hosting APIs to host CLR in a native C++ project, load and invoke .NET assemblies

https://code.msdn.microsoft.com/CppHostCLR-e6581ee0

Basically it describes it in two steps:

  1. Load the CLR into a process
  2. Load your assembly.
gremwell
  • 1,419
  • 17
  • 23