Как вызвать библиотеку C # из Native C ++ (используя C ++ \ CLI и IJW)

Фон: В рамках более крупного задания мне нужно сделать библиотеку C # доступной для неуправляемого кода C ++ и C. В попытке ответить на этот вопрос сам я изучал C ++ / CLI последние несколько дней / недель.

Кажется, есть ряд различных способов добиться использования C # dll от неуправляемых C ++ и C. Вкратце, некоторые из ответов, по-видимому, таковы: использование Interlope services, Using .com. и regasm, используя PInvoke (который, кажется, идет от C # до C ++), и используя IJW в C ++ / CLR (который, как представляется, Interlope services). Я думаю, что было бы лучше установить библиотеку, которая, возможно, является оболочкой CLR, которая использует IJW для вызова моей C # dll от имени собственного кода C ++ и C.

Особенности: Мне нужно передать значения строки, а также int в C # dll из кода C ++ и вернуть void.

Актуальность: У многих компаний есть много поводов смешивать и сочетать C ++, C и C #. Производительность: неуправляемый код, как правило, быстрее, интерфейсы. Управляемые интерфейсы, как правило, проще в обслуживании, развертывании и зачастую проще для глаз, говорят нам менеджеры. Наследственный код заставляет нас тоже. Это было там (Как гора, на которую мы поднялись). Хотя примеров того, как вызвать библиотеку C ++ из C #, много. Примеры того, как вызывать библиотеки C # из кода C ++, трудно найти с помощью Googling, особенно если вы хотите увидеть обновленный код 4.0+.

Программного обеспечения: C #, C ++ / CLR, C ++, C, Visual Studio 2010 и .NET 4.0

Детали вопроса: ОК, вопрос из нескольких частей:

  1. Есть ли преимущество в использовании com-объектов? Или PInvoke? Или какой-то другой метод? (Я чувствую, что кривая обучения здесь будет такой же крутой, хотя я нахожу больше информации по этой теме в Google Land. Кажется, IJW обещает то, что я хочу, чтобы он делал. Должен ли я отказаться от поиска решения IJW и сосредоточиться на этом вместо этого?) (Преимущество / недостаток?)

  2. Правильно ли я себе представляю, что есть решение, в котором я пишу оболочку, которая использует IJW в C ++ / CLR? Где я могу найти дополнительную информацию по этой теме и не сказать, что я недостаточно Google / или посмотреть MSDN, не сказав мне, где вы видели его там. (Я думаю, что предпочитаю эту опцию, чтобы написать ясный и простой код.)

  3. Сужение области вопроса: я чувствую, что моя настоящая проблема и потребность в том, чтобы ответить на следующий небольшой вопрос: Как настроить библиотеку C ++ / CLR, которую неуправляемый файл C ++ может использовать в Visual Studio. Я думаю, что если бы я мог просто создать экземпляр управляемого класса C ++ в неуправляемом коде C ++, то я мог бы справиться с остальным (интерфейсом, переносом и т. Д.). Я ожидаю, что моей главной глупостью является попытка настроить ссылки / # включения и т. Д. В Visual Studio, ясно подумав, что у меня могут быть другие заблуждения. Возможно, ответом на все это может быть просто ссылка на учебник или инструкции, которые помогут мне в этом.

Исследование: Я гуглил и Binged снова и снова с некоторым успехом. Я нашел много ссылок, которые показывают вам, как использовать неуправляемую библиотеку из кода C #. И я признаю, что были некоторые ссылки, которые показывают, как это сделать, используя com-объекты. Не многие результаты были ориентированы на VS 2010.

Рекомендации:
Я прочитал много раз много сообщений. Я пытался проработать наиболее подходящие из них. Некоторые кажутся соблазнительно близкими к ответу, но я просто не могу заставить их работать. Я подозреваю, что вещь, которую я пропускаю, является невероятно маленькой, такой как неправильное использование ключевого слова ref, или пропущенное #include или использование оператора, или неправильное использование пространства имен, или фактическое неправильное использование функции IJW, или пропущенная настройка VS необходимо правильно обрабатывать компиляцию и т. д. Итак, вы удивляетесь, почему бы не включить код? Ну, я чувствую, что нахожусь не в том месте, где я понимаю и ожидаю код, с которым мне нужно работать. Я хочу быть в месте, где я понимаю это, когда я доберусь до этого, может быть, тогда мне понадобится помощь, чтобы исправить это. Я случайно добавлю две ссылки, но мне не разрешено показывать их все на текущем уровне Hitpoint.

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

Это вызывает код из управляемого и неуправляемого кода в обоих направлениях, от C ++ до Visual Basic и обратно через C ++ CLR, и, конечно, меня интересует C #: http://www.codeproject.com/Articles/9903/Calling-Managed-Code-from-Unmanaged-Code-and-vice

36

Решение

Вы можете сделать это довольно легко.

  1. Создайте комбо .h / .cpp
  2. Включите / clr для только что созданного файла .cpp. (CPP -> Правый клик -> Свойства)
  3. Задайте путь поиска для «дополнительных каталогов #using», чтобы он указывал на вашу C # dll.

Native.h

void NativeWrapMethod();

Native.cpp

#using <mscorlib.dll>
#using <MyNet.dll>

using namespace MyNetNameSpace;

void NativeWrapMethod()
{
MyNetNameSpace::MyManagedClass::Method(); // static method
}

Это основа использования C # lib из C ++ \ CLI с нативным кодом. (Просто обратитесь к Native.h, где это необходимо, и вызовите функцию.)

Использование кода C # с управляемым кодом C ++ \ CLI примерно одинаково.

Существует много дезинформации по этому вопросу, так что, надеюсь, это избавит кого-то от хлопот. 🙂


Я сделал это в: VS2010 — VS2012 (это, вероятно, работает и в VS2008.)

31

Другие решения

ОБНОВЛЕНИЕ 2018

Похоже, что решение не работает для Visual Studio 2017 и далее. К сожалению, в настоящее время я не работаю с Visual Studio и поэтому не могу обновить этот ответ самостоятельно. Но Кейли выложил обновленную версию моего ответ, благодарю вас!

ОБНОВЛЕНИЕ КОНЕЦ

Если вы хотите использовать COM, вот мое решение для этой проблемы:

Библиотека C #

Прежде всего вам нужна библиотека, совместимая с COM.

  • У вас уже есть один? Отлично, вы можете пропустить эту часть.

  • У вас есть доступ к библиотеке? Убедитесь, что он совместим с COM, следуя инструкциям.

    1. Убедитесь, что вы проверено опция «Зарегистрироваться для COM-взаимодействия» в свойствах вашего проекта. Свойства -> Построить -> Прокрутить вниз -> Зарегистрироваться для взаимодействия COM

На следующих снимках экрана показано, где вы найдете эту опцию.

Скриншот Свойства проекта Build

  1. Все интерфейсы и классы, которые должны быть доступны, должны иметь 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
    }
    }
    }
    

Так что это в значительной степени то, что вы должны делать со своим кодом C #. Давайте продолжим с кодом C ++.

C ++

  1. Прежде всего нам нужно импортировать библиотеку C #.

После компиляции вашей библиотеки C # должен быть файл .tlb.

#import "path\to\the\file.tlb"

Если вы импортируете этот новый созданный файл в файл file.cpp, вы можете использовать свой объект в качестве локальной переменной.

#import "path\to\the\file.tlb"
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);

NamespaceOfYourProject::IInterfacePtr yourClass(__uuidof(NamespaceOfYourProject::ClassYouWantToUse));

yourClass->Connect();

CoUninitialize();
}
  1. Использование вашего класса в качестве атрибута.

Вы заметите, что первый шаг работает только с локальной переменной. Следующий код показывает, как использовать его в качестве атрибута. Относится к этот вопрос.

Вам понадобится CComPtr, который находится в atlcomcli.h. Включите этот файл в ваш заголовочный файл.

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

Это оно! Веселитесь с вашими C # классами в C ++ с COM.

14

Абсолютно лучший способ, который я нашел, — это создать мост c ++ / cli, который соединяет код c # с вашим собственным C ++. Я бы не рекомендовал использовать COM-объекты для решения вашей проблемы. Вы можете сделать это с 3 различными проектами.

  • Первый проект: библиотека C #
  • Второй проект: C ++ / CLI bridge (оборачивает библиотеку C #)
  • Третий проект: родное приложение C ++, которое использует второй проект

Хороший визуальный / учебник по этому можно найти Вот, и лучшее полное прохождение, которое я нашел, чтобы сделать это, может быть найдено Вот! Между этими двумя ссылками и небольшим грифом вы сможете создать мост C ++ / CLI, который позволит вам использовать код C # в вашем родном C ++.

7

Я нашел то, что, по крайней мере, начинает отвечать на мой собственный вопрос. Следующие две ссылки содержат файлы wmv от Microsoft, которые демонстрируют использование класса C # в неуправляемом C ++.

Этот первый использует COM-объект и regasm: http://msdn.microsoft.com/en-us/vstudio/bb892741.

Этот второй использует возможности C ++ / CLI, чтобы обернуть класс C #: http://msdn.microsoft.com/en-us/vstudio/bb892742. Мне удалось создать экземпляр класса C # из управляемого кода и получить строку, как в видео. Это было очень полезно, но оно отвечает только на 2/3 моего вопроса, так как я хочу создать экземпляр класса со строковым периметром в классе c #. В качестве подтверждения концепции я изменил код, представленный в примере для следующего метода, и достиг этой цели. Конечно, я также добавил измененный метод {public string PickDate (string Name)}, чтобы сделать что-то со строкой имени, чтобы доказать себе, что это работает.

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

Часть моего вопроса была: что лучше? Из того, что я прочитал во многих многочисленных исследованиях, ответ заключается в том, что COM-объекты считаются более простыми в использовании, а использование обертки вместо этого обеспечивает больший контроль. В некоторых случаях использование оболочки может (но не всегда) уменьшить размер блока, так как COM-объекты автоматически занимают стандартный размер, а оболочки — настолько большие, насколько это необходимо.

Термин (как я использовал выше) относится к пространству-времени и ресурсам, используемым между C # и C ++ в случае COM-объекта и между C ++ / CLI и собственным C ++ в случае кодирования с использованием C ++ / CLI Обертка. Таким образом, другая часть моего ответа должна включать предупреждение о том, что пересечение границы thunk более чем абсолютно необходимо, является плохой практикой, доступ к границе thunk внутри цикла не рекомендуется, и что можно неправильно настроить оболочку, чтобы она удваивала thunks (пересекает границу дважды, когда требуется только один поток), без кода, который кажется неправильным для новичка, как я.

Две заметки о WMV. Во-первых: некоторые кадры используются в обоих случаях, не обманывайте себя. Сначала они кажутся одинаковыми, но они затрагивают разные темы. Во-вторых, есть некоторые бонусные функции, такие как маршаллинг, которые теперь являются частью CLI, которые не включены в wmv.

Редактировать:

Обратите внимание, что для ваших установок есть следствие: CLR не найдет вашу оболочку c ++. Вам нужно будет либо подтвердить, что приложение c ++ устанавливается в любой / каждый каталог, который его использует, либо добавить библиотеку (которая затем должна иметь строгое имя) в GAC во время установки. Это также означает, что в любом случае в средах разработки вам, вероятно, придется копировать библиотеку в каждый каталог, где ее вызывают приложения.

4

ответ от 0lli.rocks к сожалению, либо устарел, либо неполон. Мой коллега помог мне заставить это работать, и, честно говоря, одна или две детали реализации не были отдаленно очевидны. Этот ответ устраняет пробелы и должен быть непосредственно скопирован в Visual Studio 2017 для собственного использования.

ПредостереженияЯ не смог заставить это работать для C ++ / WinRT, просто для справки. Все виды ошибок компиляции из-за неоднозначности IUnknown интерфейс. У меня также были проблемы с тем, чтобы заставить это работать только для реализации библиотеки вместо использования в основной части приложения. Я пытался следовать инструкциям из 0lli.rocks специально для этого, но так и не смог его скомпилировать.


Вот тот, который мы будем использовать для демонстрации:

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

Подэтап A — Регистрация для взаимодействия COM

введите описание изображения здесь

Подшаг B — сделать сборку видимой

введите описание изображения здесь


Вы, вероятно, хотите просто сделать это как Release за AnyCPU если вам действительно не нужно что-то более конкретное.


введите описание изображения здесь


#include "pch.h"#include <iostream>
#include <Windows.h>
#import "MyCSharpClass.tlb" raw_interfaces_only

int wmain() {
return 0;
}

введите описание изображения здесь

Это все еще будет строить. Вы увидите еще больше кода с красной линией, как только мы внедрим реальный класс в проект C ++.


Этот файл войдет в каталог сборки промежуточного объекта, как только вы соберете его в первый раз

введите описание изображения здесь


Это .tlh файл, который создается в папке промежуточного объекта. Не редактируйте это.

// 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)

В этом файле мы видим эти строки для открытого метода, который мы хотим использовать:

virtual HRESULT __stdcall GetTheThing (
/*[in]*/ BSTR arg,
/*[out,retval]*/ BSTR * pRetVal ) = 0;

Это означает, что импортируемый метод будет ожидать входную строку типа BSTRи указатель на BSTR для выходной строки, которую импортированный метод вернет в случае успеха. Вы можете настроить их так, например:

BSTR thing_to_send = ::SysAllocString(L"My thing, or ... ");
BSTR returned_thing;

Прежде чем мы сможем использовать импортированный метод, мы должны его построить. От .tlh файл, мы видим эти строки:

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

Во-первых, нам нужно использовать пространство имен класса, которое MyCSharpClass

Далее нам нужно определить умный указатель из пространства имен, которое _TheClass + Ptr ; этот шаг не очевиден, так как нигде .tlh файл.

Наконец, нам нужно предоставить правильный параметр конструкции для класса, который __uuidof(MyCSharpClass::TheClass)

Заканчивая с,

MyCSharpClass::_TheClassPtr obj(__uuidof(MyCSharpClass::TheClass));

Вы можете сделать это с CoInitialize(0) или каким бы ни был ваш конкретный инициализатор COM.

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

Еще раз, не паникуйте, когда Intellisense волнуется. Ты в Черной Магии, Вуду, & Территория драконьих драконов, так что давай дальше!

введите описание изображения здесь

2

Я много осмотрелся и обнаружил относительно недавнюю статью Microsoft, подробно описывающую, как это можно сделать (вокруг всплывает много старой информации). Из самой статьи:

В примере кода используются API-интерфейсы хостинга CLR 4 для размещения CLR в собственном проекте C ++, загрузки и запуска сборок .NET.

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

В основном это описывает это в два шага:

  1. Загрузите CLR в процесс
  2. Загрузите вашу сборку.
0