Сообщений 1 Оценка 80 Оценить |
Введение 1 Общие принципы 2 Доступ к управляемому коду из неуправляемого 3 Обработка исключений 4 Работа с делегатами 5 Оболочка над log4net Заключение |
Появление .Net Framework значительно упростило создание многих видов приложений. Благодаря богатой библиотеке отпала необходимость в создании большого количества «велосипедов», которые до этого приходилось писать многим из нас. Однако по-прежнему существует огромное количество неуправляемого кода, написанного на «голом» С++. Этот код ничего не знает и знать не может об .Net Framework. Многие из таких приложений переписываются с использованием управляемого кода, и у многих разработчиков появляется необходимость смешивать управляемый и неуправляемый код.
О том, как взаимодействовать с неуправляемым кодом из управляемого, написано достаточно много, и это неудивительно, поскольку именно эта задача является наиболее распространенной в смешанных приложениях. Но встречается и обратная ситуация, когда неуправляемое приложение (консольное приложение, служба или приложение, написанное с использование MFC) сталкивается с необходимостью обратиться к некоторой управляемой библиотеке, написанной под .Net Framework. Как быть? Переписывать заново нет ни времени, ни возможности, перекомпилировать с использованием ключа /clr тоже не получается.
В данной статье я опишу общие принципы решения задачи обращения из неуправляемого кода к управляемому, а также реализую оболочку для работы с распространенной библиотекой log4net.
Реализация смешанных приложений (т.е. содержащих управляемый и неуправляемый код) основана на том, что среда CLR поддерживает конструкции, не входящие в общую систему типов CLR, путем указания того, что некоторый тип является непрозрачным (opaque type). Метаданные непрозрачных типов не содержат никакой дополнительной информации о типе, вместо этого хранится только размер экземпляра в памяти.
Языки C# и Visual Basic .Net, в отличие от C++, не поддерживают создание непрозрачных типов. По умолчанию при компиляции проекта с ключом /clr все классы и структуры С++ создаются как непрозрачные. Среда CLR делает это для сохранения строгой семантики С++, в результате чего любая программа на С++ может быть перекомпилирована в модуль CLR и нормально выполнена. Чтобы указать, что тип не является непрозрачным (т.е. для создания управляемого типа), в определении типа С++ используется дополнительный модификатор (ref class, value class и др.).
Поскольку среда CLR ничего не знает о полях непрозрачных типов, возникают проблемы при работе со ссылками на управляемые объекты.
class OpaqueObject { private: Object^ managed_; }; |
При попытке компиляции этого кода вы получите следующее сообщение об ошибке: “error C3265: cannot declare a managed 'managed_' in an unmanaged OpaqueObject”. Проблема в том, что управляемый объект располагается в управляемой куче, и сборщик мусора никак не может узнать о том, что где-то в неуправляемой памяти осталась ссылка на этот объект. Если бы компиляция и выполнение этого кода были возможны, то в произвольный момент времени (после сборки мусора) в неуправляемой памяти остался бы указатель на удаленный объект. А вы ведь знаете, к чему приводит разыменовывание указателя, который указывает на удаленный объект?
Для решения этой проблемы среда CLR предоставляет тип System::Runtime::InteropServices::GCHandle, который поддерживает работу со ссылками на управляемые объекты из неуправляемой памяти. Для создания нового экземпляра GCHandle применяется статическая функция GCHandle::Alloc. В результате выполнения этой функции создается новая ссылка на управляемый объект, и дескриптор ссылки сохраняется в экземпляре типа GCHandle. Тип GCHandle поддерживает полный перечень типов ссылок на управляемый объект, включая слабые (weak references) и зафиксированные (pinned references) ссылки. Любая программа может преобразовать тип GCHandle в IntPtr (который можно безопасно хранить в непрозрачном типе) и обратно. Экземпляр типа GCHandle (и ссылка на управляемый объект) существуют до тех пор, пока не будет явно вызван метод GCHandle.Free.
Приведенный ниже С++-класс демонстрирует использование типа GCHandle для взаимодействия управляемого и неуправляемого кода.
#include <iostream> #include <stddef.h> #include <gcroot.h> // Компилируется с ключом /clr// "Непрозрачный" или неуправляемый класс, который использует GCHandle// для доступа к управляемому объекту.class OpaqueObject { public: // Конструкторы и деструкторы OpaqueObject(Object^ managed) { GCHandle handle = GCHandle::Alloc(managed); managedHandle_ = (intptr_t)GCHandle::ToIntPtr(handle); } ~OpaqueObject() { GCHandle handle = GCHandle::FromIntPtr((IntPtr)managedHandle_); handle.Free(); } public: // Public-интерфейсint GetHashCode() const { GCHandle handle = GCHandle::FromIntPtr((IntPtr)managedHandle_); Object^ managedObject = handle.Target; return managedObject->GetHashCode(); } private: // Private-поля intptr_t managedHandle_; }; int main(array<System::String ^> ^args) { String^ str = gcnew String("Hello, Managed!"); OpaqueObject opaque(str); std::cout<<"opaque.GetHashCode(): "<<opaque.GetHashCode()<<std::endl; std::cin.get(); return 0; } |
Поскольку работа с типом GCHandle может быть несколько утомительной, более разумным способом доступа к управляемому объекту из неуправляемой памяти является использование класса gcroot<T>, который реализован в заголовочном файле gcroot.h. Класс gcroot<T> является своего рода «умным указателем», работа которого основана на использовании типа GCHandle. В конструкторе класса gcroot<T> вызывается функция GCHandle::Alloc, а в деструкторе вызывается GCHandle::Free. Кроме того, gcroot поддерживает оператор неявного преобразованию к типу T, оператор разыменовывания gcroot->, а также реализует конструктор копирования и оператор присваивания. Используя gcroot<T>, можно переписать предыдущий пример следующим образом:
// Компилируется с ключом /clr // "Непрозрачный" или неуправляемый класс, который использует gcroot<Object^> // для доступа к управляемому объекту. class OpaqueObject { public: // Конструкторы и деструкторы OpaqueObject(Object^ managed) : managed_(managed) {} public: // Public-интерфейсint GetHashCode() const { return managed_->GetHashCode(); } private: // Private-поля gcroot<Object^> managed_; }; |
Как видите, получить доступ к управляемому коду из неуправляемого не так и сложно. Но для того, чтобы этот доступ был возможен при компиляции приложения без ключа /clr, придется немного потрудиться.
В предыдущем разделе описаны основные принципы взаимодействия управляемого и неуправляемого кода. Но все примеры компилировались с ключом компилятора /clr, но как быть, если это невозможно? Что делать, если имеется консольное Win32-приложение, служба или приложение, разработанное с использованием MFC, но нет возможности перекомпилировать его с ключом /clr?
Разработаем dll-оболочку для набора управляемых классов, которая будет экспортировать неуправляемый интерфейс. Это позволит использовать эту библиотеку без ключа компиляции /clr (например, в простом консольном приложении), хотя для выполнения такого приложения .Net Framework, конечно же, понадобится (рисунок 1).
Рисунок 1. Взаимодействие управляемого и неуправляемого кода.
Для начала рассмотрим набор управляемых классов, написанных на языке C#.
// Class1.cs public class Class1 { public Class1(int i, string str) { this.i = i; this.str = str; } publicvoid ShowMessageBox() { MessageBox.Show(ToString(), "Hello from managed world!"); } public override string ToString() { return String.Format("i = {0}, str = {1}", i, str); } privateint i; private string str; } |
Этот класс ничего не делает, это всего лишь пример, с помощью которого можно проверить взаимодействие управляемого и неуправляемого кода.
// Class2.cs public class Class2 { public Class2(Class1 class1) { this.class1 = class1; } publicvoid ShowMessageBox() { System.Windows.Forms.MessageBox.Show("Message from Class2", "Hello from managed world"); class1.ShowMessageBox(); } private Class1 class1; } |
Класс Class2 также не слишком сложен, но он принимает другой управляемый класс в качестве параметра конструктора.
Наиболее простой способ получить доступ к управляемому объекту из неуправляемого класса – воспользоваться интеллектуальным указателем gcroot<T>.
#include <vcclr.h> class ManagedObject { public: ManagedObject(Object^ managed); private: gcroot<Class1 ^> managed_; }; |
Этот код компилируется и прекрасно работает, но для его компиляции необходимо использовать ключ /clr, поэтому он не может быть использован в неуправляемом приложением.
Для решения этой проблемы можно воспользоваться тем, что gcroot<T> является тонкой оболочкой над указателем, и единственным членом этого класса является intptr_t. Поэтому экземпляр класса gcroot<T> и intptr_t гарантированно имеют одинаковое представление в памяти.
Другой особенностью управляемых приложений является то, что при их компиляции константа _MANAGED определена и равна 1.
При всем этом класс ManagedObject будет выглядеть следующим образом.
#ifdef _MANAGED #include <vcclr.h> #define GCROOT(T) gcroot<T^> #else#define GCROOT(T) intptr_t #endif//_MANAGEDclass ManagedObject { public: ManagedObject(Object^ managed); private: GCROOT(Class1) managed_; }; |
Но проблема еще решена не полностью. Дело в том, что когда клиент из неуправляемого кода создает экземпляр класса ManagedObject, компилятор автоматически генерирует встроенные функции конструктора копирования и оператора присваивания, которые реализуют побитовое копирование объекта. Но побитовое копирование intptr_t приведет к тому, что два объекта gcroot<T> будут содержать один и тот же дескриптор управляемого объекта. В таком случае при удалении второго экземпляра gcroot<T> вы получите неопределенное поведение, что в большинстве случаев выражается в нарушении доступа к памяти.
Решение задачи состоит в том, чтобы объявить экспортируемые функции конструктора копирования и оператора присваивания в заголовочном файле, а реализовать их в соответствующем cpp-файле.
//ManagedObject.h #ifdef _MANAGED #using <mscorlib.dll> #using <System.dll> #include <vcclr.h> #endif//_MANAGED#ifdef _MANAGED #define GCROOT(T) gcroot<T^> #else#define GCROOT(T) intptr_t #endif//_MANAGED#ifdef _MANAGED //Экспортировать функции этих классов нужно только из управляемого кода#define DLL_EXPORT _declspec(dllexport) #else#define DLL_EXPORT #endif//_MANAGEDclass ManagedObject { //Нужно запретить все попытки создания объетков этого класса//из неуправляемого кода DLL_EXPORT ManagedObject(); public: //Public-интерфейс#ifdef _MANAGED //Эти функции существуют только в управляемом коде ManagedObject(System::Object^ managed); System::Object^ GetObject() const {return managed_;} #endif//_MANAGED//Объявлены в этом файле, реализованы в ManagedObject.cpp DLL_EXPORT ManagedObject(const ManagedObject& rhs); DLL_EXPORT ManagedObject& operator=(const ManagedObject& rhs); private: //Private-поля//Разворачивается в gcroot<System::Object^> или в intptr_t, которые//имеют одинаковое представление в памяти и в native и в managed коде GCROOT(System::Object) managed_; }; //ManagedObject.cpp//Находится в проекте SampleManagedAssemblyWrapper и компилируется с ключем /clr#include <ManagedObject.h> ManagedObject::ManagedObject(System::Object^ managed) : managed_(managed) {} ManagedObject::ManagedObject(const ManagedObject& rhs) : managed_(rhs.managed_) { } ManagedObject& ManagedObject::operator=(const ManagedObject& rhs) { managed_ = rhs.managed_; return *this; } |
Тогда, если в неуправляемом коде встретится выражение следующего вида:
ManagedObject obj1 = … ManagedObject obj2 = obj1; |
управление будет передано в dll, и в режиме управляемого выполнения будет вызван конструктор копирования класса gcroot<T>.
С помощью класса ManagedWrapper написать оболочку управляемого класса не составит труда. Но лучше создать несколько макросов, ускоряющих этот процесс.
#ifdef _MANAGED //Этот макрос разворачивается только в управляемом коде//В результате получаем gcroot<Object^> и вспомогательный метод, возвращающий//управляемый объект указанного типа#define DECLARE_WRAPPER(MANAGED_TYPE) \ public: \ MANAGED_TYPE^ GetManaged() const \ {\ returnstatic_cast<MANAGED_TYPE^>(managed_.GetObject()); \ }\ private: \ ManagedWrapper::ManagedObject managed_; \ #else//В неуправляемом коде виден только intptr_t#define DECLARE_WRAPPER(MANAGED_TYPE) \ private: \ ManagedWrapper::ManagedObject managed_; \ #endif |
При наличии класса-оболочки управляемого объекта и макроса DECLARE_WRAPPER реализация оболочки над Class1 становится тривиальной.
//Class1Wrapper.h //Оболочка управляемого класса Class1 class Class1Wrapper { // Конструкторы и деструкторы DECLARE_WRAPPER(SampleManagedAssembly::Class1)public: DLL_EXPORT Class1Wrapper(int i, constchar* str); public: //Public-интерфейс DLL_EXPORT void ShowMessageBox(); }; //Class1Wrapper.cpp //Находится в проекте SampleManagedAssemblyWrapper и компилируется с ключем /clr #include"Class1Wrapper.h" Class1Wrapper::Class1Wrapper(int i, constchar* str) : managed_(gcnew SampleManagedAssembly::Class1(i, gcnew System::String(str))) {} void Class1Wrapper::ShowMessageBox() { GetManaged()->ShowMessageBox(); } |
Ненамного сложнее оболочка для класса Class2.
//Class2Wrapper.h //Оболочка управляемого класса Class2 class Class2Wrapper { // Конструкторы и деструкторы DECLARE_WRAPPER(SampleManagedAssembly::Class2)public: DLL_EXPORT Class2Wrapper(const Class1Wrapper &class1); public: //Public-интерфейс DLL_EXPORT void ShowMessageBox(); }; //Class2Wrapper.cpp //Находится в проекте SampleManagedAssemblyWrapper и компилируется с ключем /clr #include"Class2Wrapper.h" Class2Wrapper::Class2Wrapper(const Class1Wrapper &class1) : managed_(gcnew SampleManagedAssembly::Class2(class1.GetManaged())) { } void Class2Wrapper::ShowMessageBox() { GetManaged()->ShowMessageBox(); } |
Использовать классы оболочек достаточно просто.
//UnmanagedTest.cpp //Находится в проекте UnmanagedTest // и компилируется без ключа /clr #include <iostream> #include"..\SampleManagedAssemblyWrapper\Class1Wrapper.h"#include"..\SampleManagedAssemblyWrapper\Class2Wrapper.h"#pragma comment(lib, "SampleManagedAssemblyWrapper") int _tmain(int argc, _TCHAR* argv[]) { Class1Wrapper c1(12, "Empty"); Class2Wrapper c2(c1); c1.ShowMessageBox(); c2.ShowMessageBox(); std::cin.get(); return 0; } |
В результате выполнения этого кода получим следующий результат (рисунок 2).
Рисунок 2. Работа с классами-оболочками из неуправляемого кода.
Во время разработки смешанных приложений я столкнулся с проблемой отладки. Для отладки dll-оболочки из неуправляемого приложения необходимо установить значение DebuggerType в Mixed. Значение по умолчанию (Auto) определяет тип загружаемого отладчика по EXE-файлу, поэтому при запуске неуправляемого EXE-файла IDE запускает неуправляемый отладчик, и отлаживать управляемый код нельзя. А когда вы указываете значение Mixed, IDE загружает оба отладчика, и проблемы с отладкой управляемых модулей не возникает.
При создании оболочек над классами Class1 и Class2 не была учтена одна важная деталь – обработка исключений. А что будет, если операция в управляемом коде завершится неудачно и будет сгенерировано исключение? Ответ простой – приложение «рухнет».
Рассмотрим класс Class3, который определяет свойство Age, причем значение этого свойства проверяется при установке, и, если оно больше 150 или меньше 0, генерируется исключение System::AgrumentException.
// Class3.cs public class Class3 { public Class3() {} publicint Age { get { return age; } set { if (value > 150 || value < 0) thrownew ArgumentException("Age can't be more than 150 or less then 0"); age = value; } } privateint age; } |
Первое, что нужно сделать – это определить неуправляемый класс исключения, который будет в управляемом коде принимать System::ArgumentException в качестве параметра и предоставлять текстовое описание ошибки для неуправляемого кода.
//ArgumentException.h //Оболочка над System::ArgumentException class ArgumentException { public: #ifdef _MANAGED ArgumentException(System::ArgumentException^ e); #endif//_MANAGED DLL_EXPORT ArgumentException( const std::string &message, const std::string &argument); public: DLL_EXPORT std::string What() const; private: std::string message_; std::string paramName_; }; //ArgumentException.cpp //Находится в проекте SampleManagedAssemblyWrapper // и компилируется с ключом /clr #include"ArgumentException.h"//Преобразование System::String в std::string std::string to_native_string(System::String^ source) { if ( source == nullptr ) return std::string(); size_t i; size_t len = (( source->Length + 1) * 2); char *ch = newchar[len]; bool result ; { pin_ptr<const wchar_t> wch = PtrToStringChars( source ); result = wcstombs_s(&i, ch, len, wch, len ) != -1; } std::string res(ch); delete ch; return res; } ArgumentException::ArgumentException(System::ArgumentException^ e) : message_(to_native_string(e->Message)) , paramName_(to_native_string(e->ParamName)) { } ArgumentException::ArgumentException( const std::string &message, const std::string ¶mName) : message_(message) , paramName_(paramName) {} std::string ArgumentException::What() const { return message_ + " " + paramName_; } |
Все, что нужно сделать в Class3Wrapper – в функции SetAge() перехватить управляемое исключение и сгенерировать соответствующее неуправляемое исключение. Если функция в управляемом коде может генерировать несколько исключений, то нужно создать оболочки для всех этих исключений, или просто перехватить исключение System::Exception. Это уже зависит от потребностей вашего приложения.
// Class3Wrapper.h // Оболочка управляемого класса Class3 class Class3Wrapper { // Конструкторы и деструкторы DECLARE_WRAPPER(SampleManagedAssembly::Class3)public: DLL_EXPORT Class3Wrapper(); public: // Public-интерфейс DLL_EXPORT int GetAge() const; DLL_EXPORT void SetAge(int age) /*throw ArgumentException*/; }; // Class3Wrapper.cpp// Находится в проекте SampleManagedAssemblyWrapper // и компилируется с ключем /clr#include"Class3Wrapper.h" Class3Wrapper::Class3Wrapper() : managed_(gcnew SampleManagedAssembly::Class3()) {} int Class3Wrapper::GetAge() const { return GetManaged()->Age; } void Class3Wrapper::SetAge(int age) /*can throw ArgumentException*/ { try { GetManaged()->Age = age; } catch(System::ArgumentException^ e) { throw ArgumentException(e); } } |
Class3Wrapper используется аналогично любому неуправляемому классу, функция которого может генерировать исключение.
// UnmanagedTest.cpp // Находится в проекте UnmanagedTest // и компилируется без ключа /clr try { Class3Wrapper c3; c3.SetAge(10); std::cout<<"Age: "<<c3.GetAge()<<std::endl; c3.SetAge(240); } catch(ArgumentException &e) { std::cout<<"Error: "<<e.What()<<std::endl; } |
Еще одной особенностью .Net Framework, с которой может столкнуться разработчик оболочек, являются делегаты и события.
Рассмотрим класс Class4, который реализует событие SampleEvent.
// Class4.cs public class Class4 { public Class4() {} publicdelegatevoid SampleEventHandler(int i); publicevent SampleEventHandler SampleEvent; publicevent EventHandler<EventArgs> SampleEvent2; publicvoid FireSampleEvent(int i) { if (SampleEvent != null) SampleEvent(i); } publicvoid FireSampleEvent2() { if (SampleEvent2 != null) SampleEvent2(this, EventArgs.Empty); } } |
Там, где в управляемом коде используются делегаты, в неуправляемом коде применяются различные варианты обратного вызова. В С++ это могут быть указатели на функцию, указатели на функцию-член, boost::function (а теперь уже и std::tr1::function, для этого нужно скачать с сайта Microsoft Visual C++ 2008 Feature Pack) или любой другой функциональный объект.
Для простоты я воспользовался указателем на функцию, вы же в реальном приложении можете воспользоваться любым другим вариантом, на ваше усмотрение.
//Class4Wrapper.h //Оболочка управляемого класса Class4 class Class4Wrapper { public: // Я определил указатель на функцию, вы можете воспользоваться// указателем на функцию-член, boost::function, std::tr1::function// или любой другой вариацией на тему обратного вызова в С++typedefvoid (*SampleEventHandler)(int i); DLL_EXPORT Class4Wrapper(SampleEventHandler eventHandler); //... DLL_EXPORT void FireSampleEvent(int i); //...private: SampleEventHandler eventHandler_; }; // Class4Wrapper.cpp// Находится в проекте SampleManagedAssemblyWrapper // и компилируется с ключем /clr#include"Class4Wrapper.h"usingnamespace System; // Вспомогательный класс, без которого не удастся создать обработчик события ref class EventHandlerWrapper { public: EventHandlerWrapper(Class4^ managed, Class4Wrapper *nativeTarget) : nativeTarget_(nativeTarget) { // подписываюсь на событие управляемого класса managed->SampleEvent += gcnew Class4::SampleEventHandler( this, &EventHandlerWrapper::SampleEventHandler); } ~EventHandlerWrapper() {} private: void SampleEventHandler(int i) { nativeTarget_->EventHandler(i); } private: Class4Wrapper* nativeTarget_; }; Class4Wrapper::Class4Wrapper(SampleEventHandler eventHandler) : managed_(gcnew SampleManagedAssembly::Class4()) , eventHandler_(eventHandler){ // переменная-член типа EventHandlerWrapper не нужна, т.к.// указатель на этот объект сохраняется в классе Class4 при подписке// на событие SampeEvent, поэтому объект класса EventHandlerWrapper// будет удален только вместе с объектом класса Class4 gcnew EventHandlerWrapper(GetManaged(), eventHandler); } void Class4Wrapper::EventHandler(int i) { // вызываю неуправляемую функцию обратного вызоваif ( eventHandler_ != NULL ) (eventHandler_)(i); } void Class4Wrapper::FireSampleEvent(int i) { GetManaged()->FireSampleEvent(i); } // UnmanagedTest.cpp// Находится в проекте UnmanagedTest // и компилируется без ключа /clrvoid Class4WrapperEventHandler(int i) { std::cout<<"EventHandler in native code!"<<std::endl <<"i = "<<i<<std::endl; } //... Class4Wrapper c4(Class4WrapperEventHandler); c4.FireSampleEvent(12); |
К сожалению, без определения дополнительного управляемого класса никак не обойтись. Это связано с тем, что при создании делегата нужно передать указатель на функцию-член управляемого класса. Указатель на неуправляемую функцию не может быть передан в качестве аргумента.
В Visual C++ есть заголовочный файл \Msclr\Event.h – универсальное решение этой проблемы. Определения из этого файла можно использовать всякий раз, когда нужно обрабатывать .Net-событие в методе неуправляемого C++-класса. Недостатком этого решения является необходимость компиляции с ключом /clr, что в нашем случае является неприемлемым ограничением.
Я немного исправил этот набор классов и макросов. Это позволило обрабатывать .Net-события в неуправляемом коде, с возможностью компиляции без ключа /clr.
С использованием вспомогательных макросов, Class4Wrapper будет реализован следующим образом.
// Class4Wrapper.h // Оболочка управляемого класса Class4 class Class4Wrapper { DECLARE_WRAPPER(SampleManagedAssembly::Class4) // Начало карты делегатов BEGIN_DELEGATE_MAP_WRAPPER(Class4Wrapper) // Используется для обработки событий с двумя аргументами EVENT_DELEGATE_ENTRY_WRAPPER(EventHandler2, System::Object^, System::EventArgs^) // Используется для обработки событий с одним аргументом EVENT_DELEGATE_ENTRY_WRAPPER1(EventHandler, int) // Окончание карты делегатов END_DELEGATE_MAP_WRAPPER() //...public: #ifdef _MANAGED // Обработчики управляемых событий должны быть видны только// управляемому кодуvoid EventHandler2(System::Object^, System::EventArgs^); void EventHandler(int); #endifprivate: //Private-поля SampleEventHandler eventHandler_; SampleEventHandler2 eventHandler2_; }; // Class4Wrapper.cpp// Находится в проекте SampleManagedAssemblyWrapper // и компилируется с ключем /clr Class4Wrapper::Class4Wrapper( SampleEventHandler eventHandler, SampleEventHandler2 eventHandler2) : managed_(gcnew SampleManagedAssembly::Class4()) , eventHandler_(eventHandler) , eventHandler2_(eventHandler2) { GetManaged()->SampleEvent += MAKE_DELEGATE_WRAPPER(Class4::SampleEventHandler, EventHandler); GetManaged()->SampleEvent2 += MAKE_DELEGATE_WRAPPER(System::EventHandler<System::EventArgs^>, EventHandler2); } void Class4Wrapper::EventHandler2(System::Object^, System::EventArgs^) { if ( eventHandler2_ != NULL ) (eventHandler2_)(); } void Class4Wrapper::EventHandler(int i) { // вызываю неуправляемую функцию обратного вызоваif ( eventHandler_ != NULL ) (eventHandler_)(i); } |
Реализация оболочки для работы с log4net достаточно проста, и не использует все описанные в данной статье возможности. Это связано с тем, что функции работы с логами не генерируют исключения и не реализуют события. Кроме того, для нормальной работы с log4net мне достаточно обернуть всего лишь три класса, причем два из них содержат только статические функции.
Прежде всего, нужно создать оболочку над интерфейсом log4net::ILog.
// ILog.h // Оболочка над log4net::ILog class ILog { DECLARE_WRAPPER(log4net::ILog)public: #ifdef _MANAGED ILog(log4net::ILog^ managed); #endif//_MANAGED DLL_EXPORT bool IsDebugEnabled() const; DLL_EXPORT bool IsInfoEnabled() const; DLL_EXPORT bool IsErrorEnabled() const; DLL_EXPORT bool IsFatalEnabled() const; DLL_EXPORT bool IsWarnEnabled() const; DLL_EXPORT void Debug(constchar* message) const; DLL_EXPORT void Info(constchar* message) const; DLL_EXPORT void Warn(constchar* message) const; DLL_EXPORT void Error(constchar* message) const; DLL_EXPORT void Fatal(constchar* message) const; }; // ILog.cpp // Находится в проекте Log4NetWrapper и компилируется с ключом /clr #include"ILog.h" ILog::ILog(log4net::ILog^ managed) : managed_(managed) {} bool ILog::IsDebugEnabled() const { return GetManaged()->IsDebugEnabled; } //...void ILog::Debug(constchar* message) const { GetManaged()->Debug(gcnew System::String(message)); } //... |
Реализация оболочек над log4net::LogManager и log4net::Config::XmlConfigurator еще проще, т.к. эти классы состоят только из статических функций.
//LogManager.h //Оболочка над log4net::LogManager class LogManager { //Запрещаю создание экземпляров этого класса DLL_EXPORT LogManager(); public: //Puplic Functions DLL_EXPORT static ILog GetRootLogger(); DLL_EXPORT static ILog GetLogger(constchar* name); }; //LogManager.cpp //Находится в проекте Log4NetWrapper и компилируется с ключом /clr#include"LogManager.h"ILog LogManager::GetRootLogger() { return ILog(log4net::LogManager::GetLogger("")); } ILog LogManager::GetLogger(constchar* name) { return ILog(log4net::LogManager::GetLogger(gcnew System::String(name))); } //Оболочка над log4net::Configuration::XmlConfiguratorclass XmlConfigurator { //Запрещаю создание экземпляров этого класса DLL_EXPORT XmlConfigurator(); public: //Public-интерфейс DLL_EXPORT staticvoid Configure(constchar* filename); DLL_EXPORT staticvoid ConfigureAndWatch(constchar* filename); }; //XmlConfigurator.cpp //Находится в проекте Log4NetWrapper и компилируется с ключом /clr#include"XmlConfiguration.h" void XmlConfigurator::Configure(constchar* filename) { log4net::Config::XmlConfigurator::Configure( gcnew System::IO::FileInfo(gcnew System::String(filename))); } void XmlConfigurator::ConfigureAndWatch(constchar* filename) { log4net::Config::XmlConfigurator::ConfigureAndWatch( gcnew System::IO::FileInfo(gcnew System::String(filename))); } |
Применение оболочки над log4net также достаточно простое.
//TestLog4NetWrapper.cpp //Компилируется без ключа /clr #include <iostream> #include"..\Log4NetWrapper\LogManager.h"#include"..\Log4NetWrapper\XmlConfigurator.h"#pragma comment(lib, "Log4NetWrapper") usingnamespace Log4NetWrapper; int _tmain(int argc, _TCHAR* argv[]) { XmlConfigurator::ConfigureAndWatch("config.log4net"); LogManager::GetRootLogger().Debug("Hello, Debug!!!"); LogManager::GetRootLogger().Info("Hello, Info!!!"); LogManager::GetRootLogger().Warn("Hello, Warn!!!"); LogManager::GetRootLogger().Error("Hello, Error!!!"); LogManager::GetRootLogger().Fatal("Hello, Fatal!!!"); std::cin.get(); return 0; } |
Результат выполнения программы показан на рисунке 3.
Рисунок 3. Работа с log4net из неуправляемого кода.
Конечно, это не вся функциональность библиотеки log4net, которая может вам понадобиться. Но с помощью класса ManagedObject достаточно просто создать оболочки над другими классами.
Создание классов-оболочек для работы с управляемым кодом из неуправляемого является не такой уж сложной задачей. Основную роль в создании классов-оболочек играет класс ManagedObject, с помощью которого вы можете создать оболочки над вашими любимыми библиотеками и использовать их в неуправляемых приложениях. Естественно, что такие оболочки предоставляют лишь ограниченную функциональность, и вы не сможете воспользоваться всей мощью .Net Framework, но во многих случаях этого будет вполне достаточно для создания достаточно сложных смешанных приложений. Кроме того, при использовании смешанных приложений нужно всегда помнить о том, что переход из управляемого кода в неуправляемый не обходится бесплатно и требует со стороны CLR дополнительных операций. Поэтому, насколько возможно, старайтесь свести к минимуму такие переходы.
Сообщений 1 Оценка 80 Оценить |