image С задачей вызова неуправляемого кода из управляемого мы сталкиваемся довольно часто, и эта задача имеет простое решение в виде одного атрибута [DllImport] и небольшого набора дополнительных правил, которые хорошо изложены в MSDN. Обратная же задача встречается гораздо реже. В данной статье мы и рассмотрим небольшой пример, как это можно сделать. Его не стоит рассматривать как исчерпывающий, скорее лишь, как направление хода мыслей и концепцию. Итак, начнем.


Наш пример будет состоять из трех проектов:
  1. MixedLibrary — C++/CLI
  2. SimpleLibrary — C#
  3. Win32App — C++

image

Начнем с самого простого — SimpleLibrary. Эта библиотека содержит один простой сервис, который складывает два числа и выводит результат в консоль:

public class Service
    {
        public void Add(Int32 a, Int32 b)
        {
            Console.WriteLine("Hello from Managed Code!");
            Console.WriteLine(String.Format("Result: {0}", a + b));
        }
    }


Теперь перейдем к библиотеке MixedLibrary. Эта библиотека содержит в себе класс-обертку над нашим SimpleService. Содержимое файла CppService.h:

// Директивы препроцессора нужны, чтобы компилятор сгенерировал записи
// об экспорте класса из библиотеки
#ifdef INSIDE_MANAGED_CODE
#    define DECLSPECIFIER __declspec(dllexport)
#    define EXPIMP_TEMPLATE
#else
#    define DECLSPECIFIER __declspec(dllimport)
#    define EXPIMP_TEMPLATE extern
#endif


namespace MixedLibrary
{

	class DECLSPECIFIER CppService
	{
	public:
		CppService();
		virtual ~CppService();

	public:
		void Add(int a, int b);

	private:
		void * m_impl;
	};
}

И содержимое файла CppService.cpp:

#include "CppService.h"

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace SimpleLibrary;

namespace MixedLibrary
{
	CppService::CppService()
	{
		Service^ service = gcnew Service();
		m_impl = GCHandle::ToIntPtr(GCHandle::Alloc(service)).ToPointer();
	}

	CppService::~CppService()
	{
		GCHandle handle = GCHandle::FromIntPtr(IntPtr(m_impl));
		handle.Free();
	}

	void CppService::Add(int a, int b)
	{
		GCHandle handle = GCHandle::FromIntPtr(IntPtr(m_impl));
		Service^ service = safe_cast<Service^>(handle.Target);
		service->Add(a, b);
	}
}


Также для компилируемости необходимо добавить директиву препроцессора INSIDE_MANAGED_CODE:

image

И последний штрих — наше обычное неуправляемое приложение:

#include "stdafx.h"

#pragma comment(lib, "../Debug/MixedLibrary.lib")

#include <iostream>
#include "../MixedLibrary/CppService.h"


using namespace std;
using namespace MixedLibrary;


int main()
{
	CppService* service = new CppService();
	service->Add(5, 6);

	cout << "press any key..." << endl;
	getchar();
}

И, конечно же, результат:

image

Автор: nikitam

Комментарии (44)


  1. nckma
    14.08.2017 21:04
    -4

    У меня от фразы «неуправляемое приложение» мурашки.


    1. jehy
      15.08.2017 11:29

      Аналогично. Managed и unmanaged гораздо понятнее. А если русифицировать, тогда уж вместо


      Также для компилируемости необходимо добавить директиву препроцессора INSIDE_MANAGED_CODE:

      Так же для для преобразования в машинный нулёво-еденичковый код необходимо добавить указание преобработчика В_УПРАВЛЯЕМОМ_КОДЕ.


      1. nckma
        15.08.2017 11:34

        Именно это я и хотел сказать.


  1. Videoman
    14.08.2017 23:04
    +1

    А можно поинтересоваться, а где удаляется вот этот объект?

    CppService* service = new CppService();
    


    1. VioletGiraffe
      14.08.2017 23:35
      +1

      А всего-то нужно было его там сконструировать на стеке.


    1. nikitam
      15.08.2017 09:22
      -1

      В данном примере явно нигде. Но это всего лишь пример, а не рабочее приложение. При освобождении ссылку на управляемый объект также освобождаем:
      CppService::~CppService()
      {
      GCHandle handle = GCHandle::FromIntPtr(IntPtr(m_impl));
      handle.Free();
      }


  1. maisvendoo
    15.08.2017 07:13

    Вот этот кусок

    // Директивы препроцессора нужны, чтобы компилятор сгенерировал записи
    // об экспорте класса из библиотеки
    #ifdef INSIDE_MANAGED_CODE
    #    define DECLSPECIFIER __declspec(dllexport)
    #    define EXPIMP_TEMPLATE
    #else
    #    define DECLSPECIFIER __declspec(dllimport)
    #    define EXPIMP_TEMPLATE extern
    #endif
    

    выглядит не слишком кроссплатформенно


    1. mayorovp
      15.08.2017 08:40

      О какой кроссплатформенности может идти речь, когда один из проектов написан на c++/cli?


  1. vasily-v-ryabov
    15.08.2017 09:51
    +1

    Честно говоря, напоминает студенческое упражнение (даже на лабу не тянет). И ради этого писать статью? Ладно, если бы .NET движок поднять в соседнем процессе, который сугубо нативный, и в нем запустить какой-то код.


    1. nikitam
      15.08.2017 10:23
      +1

      При написании статьи никто и не претендовал на диссертацию! Это просто отправная точка одного из подходов (о чем и сказано в начале), и зачастую именно простейших примеров и не хватает людям, которые только начали разбираться


  1. Xitsa
    15.08.2017 11:35
    +1

    Пара советов из моего опыта поддержки большого смешанного проекта:


    • в области C++/CLI надо находиться как можно меньше, слой совместимости между нативным и управляемым кодом должен быть как можно тоньше
    • если объектная система или процесс взаимодействия не тривиален, то COM — оправданный выбор для организации взаимодействия. Хотя я бы воздержался от автоматической активации через реестр или манифесты, они приносят гораздо больше проблем, чем кажущейся пользы.
    • вопрос управления ресурсами должен быть обязательно продуман, чтобы большие куски памяти или того хуже соединения не зависали непонятно где.