В данной статье рассматривается вызов управляемого кода (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 аттрибуты.
С помощью команды "%SYSTEMROOT%\Microsoft.NET\Framework\v2.0.50727\regasm.exe" "$(TargetPath)" /tlb:$(TargetName).tlb /codebase (можно добавить в PostBuild Events) создадим type library (*.tlb) и заимпортим ее в unmanaged C++ приложение.
Создадим интерфейс для взаимодействия со сторонним приложением.
Добавим класс с методом SetExternalApplication с помощью которого можно установить созданный интерфейс для дальнейшей работы с программой (и тут же реализуем небольшой тест результата).
В unmanages/managed C++ библиотеке (bridge) добавим ссылку (reference) на WindowsForms приложение.
Теперь в нашей bridge библиотеке необходимо реализовать интерфейс IExternalApplication. Для этого создадим класс ExternalApplication и реализуем интерфейс.
В конструктор передаем handle unmanaged C\C++ библиотеки (plugin'a).
Реализовываем метод, необходимый для запуска managed приложения и экспортируем функцию StartApplication.
В plugin'е реализовываем функицю для получения структуры Entity и экспортируем ее.
Загружаем bridge библиотеку и запускаем приложение.
Запускаем и проверяем результат.
Результат достигнул.
Исходники на Coogle Code
p.s. исходный код далек от красоты и идеала и лишь отражает логику взаимодействия
все это чудо вертится в одном процессе со всеми вытекающими плюсами
Приведенный ниже способ можно рассматривать как альтернативу методу, описанному пользователем 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. исходный код далек от красоты и идеала и лишь отражает логику взаимодействия
все это чудо вертится в одном процессе со всеми вытекающими плюсами