Pull to refresh

Взаимодействие managed и unmanaged кода

Reading time4 min
Views7K
В данной статье рассматривается вызов управляемого кода (managed) из неуправлявляемого (unmanaged) и их взаимодействие (без использования COM и PInvoke).
Приведенный ниже способ можно рассматривать как альтернативу методу, описанному пользователем klsaymon.

Ситуация взята из личного опыта. Существует некое приложение (ArchiCAD), взаимодействие с которым осуществляется благодаря библиотекам (plugin'ам). Библиотеку (plugin) можно реализовать только на C\C++. Есть собственное приложение (WindowForms) с бизнес логикой и UI, написанное на C# (managed). Необходимо реализовать возможность запуска приложения (WindowsForms) из библиотеки (plugin'а) и их взаимодейсвия. В данном случае, библиотека на C\C++ (plugin) выступает в роли моста между сторонним приложением (ArchiCAD) и нашим (WindowsForms).

Для простоты, отбросим стороннее приложение и предположим, что вызов осуществляется прямо из unmanaged кода. Т.е. заменим связку приложение-plugin одним unmanaged приложением.

Итоговая схема взаимодействия представлена на данной схеме:


Начнем с WindowsForms приложения.
Предположим, что от стороннего приложения нам нужно получить структуру с данными. Создадим данную структуру, назовем ее Entity. Выставим ComVisible и StructLayout аттрибуты.
    [ComVisible(true)]
    [StructLayout(LayoutKind.Sequential)]
    public struct Entity {
		// entity external id
        public int id;
        [MarshalAs(UnmanagedType.BStr)]
		// entity name
        public string name;
    }

С помощью команды "%SYSTEMROOT%\Microsoft.NET\Framework\v2.0.50727\regasm.exe" "$(TargetPath)" /tlb:$(TargetName).tlb /codebase (можно добавить в PostBuild Events) создадим type library (*.tlb) и заимпортим ее в unmanaged C++ приложение.
#import ".\\..\\Managed.Csharp\\bin\\Debug\\Managed.Csharp.tlb" no_namespace

Создадим интерфейс для взаимодействия со сторонним приложением.
    public interface IExternalApplication {
        /// <summary>
        /// Returns the <see cref="Entity"/> instance.
        /// </summary>
        /// <returns></returns>
        Entity GetEntity(int id);
    }

Добавим класс с методом SetExternalApplication с помощью которого можно установить созданный интерфейс для дальнейшей работы с программой (и тут же реализуем небольшой тест результата).
	public class Program {

        private static IExternalApplication _externalApplication;

        /// <summary>
        /// Sets the <see cref="IExternalApplication"/> interface.
        /// </summary>
        /// <param name="externalApplication"></param>
        /// <returns></returns>
        public static void SetExternalApplication(IExternalApplication externalApplication) {
            _externalApplication = externalApplication;

            // test
            var entity = _externalApplication.GetEntity(1234);
            MessageBox.Show(string.Format("Id={0}; Name={1}", entity.id, entity.name));
        }
    }

В unmanages/managed C++ библиотеке (bridge) добавим ссылку (reference) на WindowsForms приложение.

Теперь в нашей bridge библиотеке необходимо реализовать интерфейс IExternalApplication. Для этого создадим класс ExternalApplication и реализуем интерфейс.
В конструктор передаем handle unmanaged C\C++ библиотеки (plugin'a).
typedef void* (__cdecl *GetEntityFunc)(int);

public ref class ExternalApplication: public IExternalApplication
{	

public:
	// constructor
	ExternalApplication(HMODULE win32Handle);
	virtual Entity GetEntity(int id);
private:
	GetEntityFunc m_GetEntity;
};

ExternalApplication::ExternalApplication(HMODULE win32Handle) {
	// initialize functions
	m_GetEntity = (GetEntityFunc)GetProcAddress(win32Handle, "GetEntity");
}

Entity ExternalApplication::GetEntity(int id) {	
	void *entityPtr = m_GetEntity(id);
	Entity entity = (Entity)Marshal::PtrToStructure((IntPtr)entityPtr, Entity::typeid);
	return entity;
}

Реализовываем метод, необходимый для запуска managed приложения и экспортируем функцию StartApplication.

// exported functions
extern "C" __declspec(dllexport) void StartApplication(HMODULE);

void StartApplication(HMODULE win32Handle) {
	Program::SetExternalApplication(gcnew ExternalApplication(win32Handle));
}


В plugin'е реализовываем функицю для получения структуры Entity и экспортируем ее.
// exported functions
extern "C" __declspec(dllexport) void* GetEntity(int);

void* GetEntity(int id) {
	Entity *entity = new Entity();
	entity->id = id;
	entity->name = _bstr_t("I'm entity!").copy();
	return entity;
}

Загружаем bridge библиотеку и запускаем приложение.
typedef void (__cdecl *StartApplicationFunc)(HMODULE);

int _tmain(int argc, _TCHAR* argv[])
{
	HMODULE bridgeHandle = LoadLibrary(L"UnmanagedManaged.C++.dll");			
	StartApplicationFunc startApplication = (StartApplicationFunc)GetProcAddress(bridgeHandle, "StartApplication");	
	HMODULE handle = GetCurrentModule();
	startApplication(handle);
	return 0;
}

Запускаем и проверяем результат.

Результат достигнул.

Исходники на Coogle Code

p.s. исходный код далек от красоты и идеала и лишь отражает логику взаимодействия

все это чудо вертится в одном процессе со всеми вытекающими плюсами
Tags:
Hubs:
Total votes 28: ↑24 and ↓4+20
Comments17

Articles