Формулировка задачи.
Предположим, имеется старое хорошее приложение на C++ с исходными кодами. Вполне возможно, с пользовательским интерфейсом и являющееся COM-сервером (хотя все это и не обязательно). Естественно, это приложение реализовано на неуправляемом коде в виде исполняемого файла (ЕХЕ).
Очень хочется, не теряя его существующей функциональности и, по возможности, не перерабатывая старый код, обеспечить возможность использовать бизнес-логику этого приложения по технологии remoting.
Клиент Remoting
Никаких особенностей при создании клиента remoting для рассматриваемого примера нет. В качестве примера приведен код тривиального клиента на C# в виде консольного приложения
//ClientRemoting.cs //Клиент remoting using System; using System.Collections.Generic; using System.Text; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Channels.Tcp; using Rmt_obj; namespace ClientRemoting { class Program { static void Main(string[] args) { CRmtngObj m_class; // регистрация TCP-канала ChannelServices.RegisterChannel(new TcpChannel(),false); m_class=(CRmtngObj)Activator.GetObject( typeof(Rmt_obj.CRmtngObj), "tcp://localhost:8085/RemoteTest"); // Вызов старого метода method_GetStr Console.WriteLine(m_class.mtd_method_GetStr()); Console.ReadLine(); // Вызов старого метода method_PutStr m_class. mtd_method_PutStr("POIUYTR"); // Вызов старого метода method_GetStr Console.WriteLine(m_class. mtd_method_GetStr()); Console.ReadLine(); } } }
Модули на С++/CLI в проекте С++
VS поддерживает особое взаимодействие между родным C++ и C++/CLI в виде смешанного режима. Эта возможность и будет использована для превращения старого приложения на родном C++ в сервер remoting. Для того, чтобы в рассматриваемом случае не повредить код старого приложения на родном C++, удобно новый управляемый код C++/CLI, необходимый для функционирования remoting, включить в старый проект на родном C++ в виде отдельных файлов (h-файлы и cpp-файлы). И указать в свойствах этих cpp-файлов, что они должны быть откомпилированы в управляемом режиме. Чтобы включить этот режим компиляции, требуется навести курсор на имя нужного cpp-файла на C++/CLI в окне Solution Explorer в VS и, нажав правую кнопку мыши, выбрать Properties. В открывшемся окне полезно выполнить следующие типовые настройки:
Группа настроек |
Настройка |
Значение |
General |
Compile with CLR support |
/clr |
General |
Debug Information Format |
Program Database (/Zi) |
Code Generation |
Enable Minimal Rebuild |
No |
Code Generation |
Enable C++ Exception |
/EHa |
Code Generation |
Basic Runtime Checks |
Default |
Code Generation |
Struct Member Alignment |
Default (не повредит, особенно при странной ошибке error LNK2022) |
Можно перед выполнением настроек выбрать в Configuration режим “All Configuration”
Общая архитектура приложения.
Любое remoting-приложение состоит из трех частей: объекта, клиента и сервера.
Объект remoting - это некоторая библиотека классов, унаследованных от MarshalByRefObject. Сама библиотека реализуется на управляемом коде. Обычно в объекте remoting располагается бизнес-логика, предоставляемая клиенту, однако в рассматриваемом случае это не так. В рассматриваемом случае библиотека классов будет использоваться только для организации вызовов из remoting-клиента соответствующих методов бизнес-логики старого приложения на C++, которое станет сервером remoting (хостом).
Вызов методов серверного приложения из объекта remoting может осуществляться только с помощью технологии событий (event), в которой используются callback функции (delegate). Таким образом, в библиотеке классов (объекте remoting) для каждого метода бизнес-логики старого приложения, который может вызываться по технологии remoting, должны быть описаны соответствующие delegate и event.
Remoting-клиент в данной задаче является совершенно обычным (никаких особенностей в его архитектуре нет), он может быть реализован на любом языке .NET
Создание же сервера remoting из программы на неуправляемом (родном) C++, имеет ряд особенностей. Главной из них является то, что все программные блоки в составе любого сервера remoting, выполняющие активацию и регистрацию объекта remoting, должны быть написаны на управляемом коде. Поэтому, сервер remoting в рассматриваемом случае будет представлять собой приложение, состоящее из смеси управляемого и неуправляемого кода. То есть, в старое приложение на неуправляемом коде должны быть встроены программные блоки на управляемом C++, обеспечивающие функционирование remoting.
Общий подход к решению.
Сформулированная задача имеет очень простое архитектурное решение: старое приложение на неуправляемом С++ превращается в сервер remoting, к нему добавляются объект и клиент remoting. И все.
Необходимо, правда, отметить, что старая бизнес-логика, которая должна быть доступна remoting-клиентам, реализована в исполняемом файле (EXE) на неуправляемом коде, а технология remoting полностью базируется платформе .NET, то есть использует управляемый код.
Таким образом, основную сложность представляет создание сервера remotingиз приложения на unmanaged C++, чему и посвящена данная статья.
Регистрация remoting-объекта
Превращение любого приложения в remoting-сервер всегда начинается с подключения remoting-объекта в качестве Reference. Это выполняется следующим образом: необходимо навести курсор на имя проекта будущего remoting-сервера в окне Solution Explorer в VS и, нажав правую кнопку мыши, выбрать Properties. В открывшемся окне нажать кнопку «Add New Reference» и в закладке «Browse» выбрать dll-файл remoting-объекта.
После этого можно приступить к созданию класса регистрации remoting-объекта. Для этого к старому проекту на родном C++ добавляются файлы Rmt_reg.h и Rmt_reg.cpp со следующим кодом на C++/CLI:
//Rmt_reg.h //регистрация remoting-объекта #include "stdafx.h" #include "mngCover.h" // Управляемый класс-обертка для неуправляемых методов #using <mscorlib.dll> #using <System.Dll> #using <System.Runtime.Remoting.Dll> using namespace System; using namespace System::Runtime; using namespace System::Runtime::Remoting; using namespace System::Runtime::Remoting::Channels; using namespace System::Runtime::Remoting::Channels::Tcp; using namespace Rmt_obj; //пространтво имен remoting-объекта namespace Rmt_reg { public ref class CRmtReg { private: TcpChannel^ m_chan; public: CRmtReg(CMngCover^ pMngCover); //конструктор }; }
// Rmt_reg.cpp //регистрация remoting-объекта #include "Rmt_reg.h" using namespace System; using namespace System::Runtime; using namespace System::Runtime::Remoting; using namespace System::Runtime::Remoting::Channels; using namespace System::Runtime::Remoting::Channels::Tcp; namespace Rmt_reg { CRmtReg::CRmtReg(CMngCover^ pMngCover) //конструктор { m_chan = gcnew TcpChannel(8085); //создаем канал ChannelServices::RegisterChannel(m_chan, false); //регистрируем //описание переменной remoting-класса CRmtngObj^ rmClass; rmClass = gcnew CRmtngObj(); //создание remoting-класса // регистрация remoting-класса ObjRef^ refClass = RemotingServices::Marshal(rmClass, "RemoteTest"); // инициализация delegate для метода method_PutStr rmClass->ev_method_PutStr += gcnew CRmtngObj:: dlg_method_PutStr(pMngCover, &(CMngCover::mng_method_PutStr) ); // инициализация delegate для метода method_GetStr rmClass-> ev_method_GetStr += gcnew CRmtngObj:: dlg_method_GetStr(pMngCover, &(CMngCover::mng_method_GetStr) ); }; }
Класс регистрации remoting-объекта должен не только его зарегистрировать, но и создав, выполнить его инициализацию. Для этого используется уже созданный объект управляемого класса-обертки неуправляемых методов, поэтому в заголовочный файл «Класса регистрации remoting-объекта» добавлен #include на описание класса-обертки.
Класс регистрации remoting-объекта реализуется на управляемом коде (порядок включения режима компиляции с поддержкой CLR описан выше в разделе «Модули на С++/CLI в проекте С++»).
Регистрация remoting-объекта
Регистрация remoting-объекта осуществляется при вызове стартовой функции из некоторого места старого кода - точки регистрации. Эта точка регистрации выбирается (добавляется) в неуправляемом коде старого приложения. При ее выборе необходимо учитывать, во-первых, что remoting-взаимодействие возможно только после регистрации remoting-объекта, и, во-вторых, что повторная регистрация remoting-объекта может привести к ошибке.
В модуль кода на родном C++, где она размещается, добавляется стандартный include для h-файла стартовой функции:
#include "StarterRMT.h"
При непосредственном вызове стартовой функции в нее в качестве параметра передается указатель на текущий объект бизнес-логики, например:
void CMFC_2Dlg::OnBnClickedButton1() { // инициализация remoting StarterRMT (this); }
Следует, наверное, отметить, что упоминание в h-файле стартовой функции модулей только на родном C++ дает возможность не менять параметров компиляции модуля с точкой регистрации remoting-объекта, то есть они остаются прежними для родного C++.
После внесения всех приведенных модернизаций в старый проект на неуправляемом коде его можно собрать, и в результате получится полноценный remoting-сервер (хост).
Remoting - объект
Любой remoting-объект должен быть реализован на управляемом коде, поэтому создадим в VS новый проект типа «CLR Class library для С++» и назовем его, например, Rmt_obj.
В старой бизнес-логике рассматриваемого примера имеется два метода, и для каждого из них в remoting-объекте должны быть объявлены по одному
delegate, event, методу, использующему событие (доступен remoting-клиенту).
Кроме того, класс remoting-объекта должен быть наследником MarshalByRefObject, что указывается в его описании.
Таким образом, получается следующий код на C++/CLI для remoting-объекта рассматриваемого примера:
// Rmt_obj.h //объект REMOTING #pragma once using namespace System; namespace Rmt_obj { public ref class CRmtngObj : MarshalByRefObject { public: // Для метода method_PutStr. delegate void dlg_method_PutStr(String^ str); event dlg_method_PutStr^ ev_method_PutStr; void mtd_method_PutStr(String^ str); // Для метода method_GetStr. delegate String^ dlg_method_GetStr(); event dlg_method_GetStr^ ev_method_GetStr; String^ mtd_method_GetStr(); }; }
// Rmt_obj.cpp //объект REMOTING // This is the main DLL file. #include "stdafx.h" #using <mscorlib.dll> #include "Rmt_obj.h" namespace Rmt_obj { //Для метода method_PutStr void CRmtngObj::mtd_method_PutStr(String^ str) { ev_method_PutStr(str); } // Для метода method_GetStr String^ CRmtngObj:: mtd_method_GetStr() { return ev_method_GetStr(); } }
Приведенный код может быть скомпилирован в Rmt_Obj.dll - объект remoting.
Remoting с сервером на Unmanaged C++ или Вторая жизнь старых приложений
Владимир Красавцев
Математик делает то, что можно, так, как нужно.
Программист делает то, что нужно, так, как можно.
Плакат в Галактика-ZOOM
Сервер Remoting
Теоретически, для получения сервера remoting из старого приложения, реализованного на неуправляемом коде, необходимо к старому приложению добавить код на C++/CLI, обеспечивающий функционирование режима remoting, а именно создание, инициализацию и регистрацию remoting-объекта. Инициализация объекта remoting в рассматриваемом случае предполагает подключение старой бизнес-логики для ее использования по технологии remoting.
Однако, наибольший интерес представляет практическая реализация создания сервера remoting на основе приложения на родном C++. На приведенном рисунке представлена блочная архитектура создаваемого сервера remoting:
Таким образом, для создания remoting-сервера из старого приложения, к его коду на родном C++ (блоки выделены серыми тонами на рисунке) надо добавить три программных модуля на C++/CLI (h- и cpp-файлы, отмеченные голубым цветом на картинке), которые должны быть откомпилированы в управляемом режиме:
Класс-обертка CMngCover для вызова неуправляемых методов старой бизнес-логики через их управляемые аналоги (h-файл и cpp-файл). Управляемый класс CRmtReg для создания, инициализации и регистрации remoting-объекта (h-файл и cpp-файл). Стартовая функция StarterRmt (h-файл и cpp-файл) для включения режима remoting. Эта функция на управляемом коде будет вызываться из старого кода (реально это единственное изменение, которое вносится непосредственно в старый код на родном C++).
При этом, как видно, старая бизнес-логика остается нетронутой.
Рассмотрим более подробно новые модули на управляемом коде.
Старая бизнес логика.
Описание процесса построения всех программных компонентов, необходимых для реализации рассматриваемой архитектуры, необходимо начать с анализа бизнес-логики старого приложения на родном С++. Ведь именно ее сохранение и обеспечение возможности ее использования по технологии remoting является главной целью рассматриваемой задачи.
Для простоты изложения выберем (или создадим) в качестве примера приложение на родном C++ с простейшей бизнес-логикой, которая, например, описывается так (h-файл) :
//Бизнес-логика, которая должна быть доступна по технологии remoting class CMFC_2Dlg : public CDialog { public: // Передача строки void method_PutStr(const wchar_t* s); // получение строки wchar_t* method_GetStr(); };
В рамках сделанных предположений о структуре старой бизнес-логики будет вестись все дальнейшее изложение материала.
Примечание
Все изложение материала ведется в предположении, что в рассматриаемой бизнес-логике старого приложения на родном С++ отсутствует работа с пользовательским графическим интерфейсом. Если это не так, то, возможно, потребуется некоторая переработка старой бизнес-логики для сохранения работоспособности графического интерфейса пользователя при доступе к нему по технологии remoting. Однако, обсуждение этого выходит за пределы тематики данной статьи.
Стартовая функция
Основной задачей стартовой функции является запуск процесса регистрации remoting-объекта.
Стартовая функция реализуется на управляемом коде в рамках старого проекта, но она должна вызываться из неуправляемого кода - из точки регистрации remoting-объекта. Чтобы не нарушать старый проект, код стартовой функции разместим в отдельном модуле (файлы StarterRmt.h и StarterRmt.cpp). В h-файле StarterRmt.h будут присутствовать #include только неуправляемых модулей (описание класса методов бизнес-логики из старого приложения на неуправляемом коде), а управляемые модули (h-файлы управляемого класса-обертки и «Класса регистрации remoting-объекта») будут подключены уже в cpp-файле. В итоге код стартовой функции на С++/CLI в рассматриваемом примере будет иметь такой вид:
// StarterRMT.h // стартовая функция #include "MFC_2Dlg.h" //описание бизнес-логики //Стартовая функция. Входной параметр - существующий объект бизнес-логики void StarterRMT(CMFC_2Dlg* pDialog);
//StarterRMT.cpp // стартовая функция #include "StarterRMT.h" #include "mngCover.h" //Управляемый класс-обертка для неуправляемых методов #include "Rmt_reg.h" //Класс регистрации remoting-объекта //Стартовая функция. Входной параметр - существующий объект бизнес-логики void StarterRMT (CMFC_2Dlg* pDialog) { //Управляемый класс-обертка для неуправляемых методов CMngCover^ mm_MngCover; //описание mm_MngCover = gcnew CMngCover(pDialog); //создание //класс регистрации и иницилизации remoting-объекта Rmt_reg::CRmtReg^ mm_RmtReg; //описание mm_RmtReg = gcnew Rmt_reg::CRmtReg(mm_MngCover); //создание и регистрация }
Как было уже сказано, модуль стартовой функции собирается в режиме управляемого кода поэтому для него необходимо выполнить настройки, приведенные в разделе «Модули на С++/CLI в проекте С++», кроме того, для него может потребоваться отключить использование прикомпилированных заголовков.
Управляемый класс-обертка для неуправляемых методов
Управляемый класс-обертка для неуправляемых методов старой бизнес-логики необходим, чтобы объекты delegate из remoting-объекта могли вызывать неуправляемые методы бизнес-логики. Это связано с тем, что невозможно напрямую передать в delegate ссылку на неуправляемый метод.
Основная сложность написания такого управляемого класса-обертки связана с необходимостью корректного преобразования данных неуправляемых и управляемых типов.
Входным параметром при создании экземпляра управляемого класса-обертки для неуправляемых методов является указатель на существующий (неуправляемый) объект, которому принадлежат методы бизнес-логики. Поэтому, в заголовочный файл класса-обертки включаются #include описания старого класса бизнес-логики.
Таким образом, для его реализации в рамках рассматриваемого примера в состав старого проекта на родном C++ добавляются файлы MngCover.h и MngCover.cpp со следующим кодом на C++/CLI:
//MngCover.h //Управляемый класс-обертка для неуправляемых методов #if !defined(AFX_MNGCOVER__INCLUDED_) #define AFX_MNGCOVER__INCLUDED_ #include "stdafx.h" #include "MFC_2Dlg.h" //описание бизнес-логики #include <string> #using <mscorlib.dll> using namespace System; using namespace std; public ref class CMngCover { CMFC_2Dlg* m_pDialog; //неуправляемый объект бизнес-логики public: //конструктор CMngCover(CMFC_2Dlg* pDialog); //обертка метода method_PutStr void mng_method_PutStr(System::String^ str); //обертка метода method_GetStr String^ mng_method_GetStr(); }; #endif // defined(AFX_MNGCOVER__INCLUDED_)
// MngCover.cpp // Управляемый класс-обертка для неуправляемых методов #include "mngCover.h" #include <vcclr.h> #using <mscorlib.dll> using namespace System; using namespace std; //конструктор CMngCover::CMngCover(CMFC_2Dlg* pDialog):m_pDialog(pDialog){}; //обертка метода method_PutStr void CMngCover::mng_method_PutStr(System::String^ str){ pin_ptr <const wchar_t> ptr = PtrToStringChars(str); m_pDialog->method_PutStr(ptr); }; //обертка метода method_GetStr String^ CMngCover::mng_method_GetStr(){ String^ mm_s; mm_s = gcnew String( m_pDialog->method_GetStr() ); return mm_s; };
Класс CMngCover должен быть откомпилирован с поддержкой CLR, поэтому для него необходимо выполнить настройки, описанные в разделе «Модули на С++/CLI в проекте С++».
Вопросы реализации.
Технология создания любого remoting-приложения является довольно сложным многоходовым процессом, детальному описанию которого посвящено большое количество специализированной литературы. Встречается также литераутра, посвященная вопросам создания приложений со смешанным кодом. Однако, создание сервера remoting с использованием смешанного кода имеет ряд малодокументированных особенностей.
Поэтому вопросы практической реализации такого приложения представлены ниже в виде подробного описания технологии его создания. Предполагается, что для разработки используется MS Visual Studio (2003, 2005, 2008).
В приведенном алгоритме создания сервера
В приведенном алгоритме создания сервера remoting из приложения на неуправляемом C++ не затронуты многие вопросы, обычно обсуждаемые при описании remoting-приложений - они, по-моему, выходят за рамки темы этой статьи и могут быть решены при конкретной реализации.
Надеюсь, описанная технология сможет продлить жизнь еще не одному старому хорошему приложению.