Подтвердить что ты не робот

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

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

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

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

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

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

Сведения о запросе: OK многочастный вопрос:

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Этот второй использует функции С++/CLI для переноса класса С#: http://msdn.microsoft.com/en-us/vstudio/bb892742. Я смог создать экземпляр класса С# из управляемого кода и получить строку, как в видео. Это было очень полезно, но оно отвечает только на 2/3rds моего вопроса, поскольку я хочу создать экземпляр класса с строковым периметром в класс С#. В качестве доказательства концепции я изменил код, представленный в примере для следующего метода, и достиг этой цели. Конечно, я также добавил измененный метод {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-объекты считаются более удобными в использовании, а использование обертки вместо этого позволяет более эффективно управлять. В некоторых случаях использование обертки может (но не всегда) уменьшать размер thunk, поскольку объекты COM автоматически имеют размер стандартного размера, а обертки - настолько велики, насколько это необходимо.

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

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

Edit:

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

Ответ 2

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

  • Создайте комманду .h/.cpp
  • Включить /clr для нового файла .cpp. (CPP → Щелкните правой кнопкой мыши → Свойства)
  • Задайте путь поиска для "дополнительных каталогов #using", чтобы указать на вашу С# dll.

Native.h

void NativeWrapMethod();

Native.cpp

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

using namespace MyNetNameSpace;

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

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

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

На эту тему много дезинформации, поэтому, надеюсь, это избавит кого-то от хлопот.:)


Я сделал это в: VS2010 - VS2012 (возможно, он также работает и в VS2008.)

Ответ 3

ОБНОВЛЕНИЕ 2018

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

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

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

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

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

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

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

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

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

Screenshot Project Properties 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++

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

После компиляции вашей библиотеки С# должен быть файл .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++ с COM.

Ответ 4

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

  • Первый проект: библиотека С#
  • Второй проект: мост c++/CLI (это обертывает библиотеку С#)
  • Третий проект: приложение Native c++, в котором используется второй проект

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

Ответ 5

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

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

Шаг 01: Создайте свою библиотеку С#


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

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

Шаг 02 - Настройте свою библиотеку С# для видимости COM


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

enter image description here

Подэтап B - Сделать сборку COM-видимой

enter image description here

Шаг 3 - Создайте свою библиотеку для файла .tlb


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

Шаг 4. Скопируйте файл .tlb в исходное местоположение для вашего проекта C++.


enter image description here

Шаг 5 - Импорт .tlb файла в C++ проект


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

int wmain() {
    return 0;
}

Шаг 6 - Не паникуйте, когда Intellisense не работает


enter image description here

Он все равно будет построен. Вы увидите еще более красноватый код, когда мы реализуем фактический класс в проекте C++.

Шаг 7. Создайте проект C++ для создания файла .tlh


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

enter image description here

Шаг 8 - Оцените файл .tlh для инструкций по реализации


Это файл .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));

Шаг 9 - Инициализировать COM и протестировать импортированную библиотеку


Вы можете сделать это с помощью 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 выйдет из игры. Вы находитесь на территории Черной Магии, Voodoo и Thar Be Dragons, поэтому нажмите дальше!

enter image description here

Ответ 6

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

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

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

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

  • Загрузите CLR в процесс
  • Загрузите сборку.