Windows - статьи

         

Формулировка задачи.


Предположим, имеется старое хорошее приложение на 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-приложений - они, по-моему, выходят за рамки темы этой статьи и могут быть решены  при конкретной реализации.
Надеюсь, описанная технология сможет продлить жизнь еще не одному старому хорошему приложению.