Пример "Dot Net" (ActiveQt)
Пример "Dot Net" демонстрирует, как объекты Qt могут быть использованы в окружении .NET, и как объекты .NET можно использовать в окружении Qt. Если вам нужно сочетать виджеты Qt и Win Forms в одном приложении, вы можете захотеть использовать взамен высокоуровневое Решение QtWinForms. Содержание:
Qt vs. .NETQt - библиотека C++ и она скомпилирована в традиционно, "родные" двоичные файлы, которые дают полное использование производительности, предоставляемой окружением времени выполнения. Одной из ключевых концепций .NET является идея "кода на промежуточном языке" (intermediate language code) - исходный код компилируется в формат байт-кода, а во время выполнения этот байт-код выполняется в виртуальной машине - Common Language Runtime (CLR). Другая ключевая концепция - это управляемый код (managed code). Этот код на промежуточном языке по существу написан таким образом, что CLR будет заботиться об управлении памятью, т.е. CLR автоматически производит сборку мусора, поэтому коду приложения не нужно явно освобождать память от неиспользуемых объектов. Компиляторы MS для C# и VB.NET производят только управляемый код. Такие программы не могут непосредственно вызывать нормальные, "родные" функции или классы. С другой стороны, компилятор MS C++ для .NET может производить как нормальный, так и управляемый код. Чтобы написать класс C++, который можно компилировать в управляемый код, разработчик должен отметить класс как управляемый используя ключевое слов __gc, и ограничить использование кодом только подмножество C++, известное как "Управляемые расширения C++" (Managed Extensions for C++), или для краткости MC++. Преимуществом MC++ является то, что код может свободно вызывать и использовать обычные функции и классы C++. А также работаю другие способы обхода: нормальный код C++ вызывает управляемые функции и использует управляемые классы (например, вся библиотека классов каркаса .NET), включая управляемые функции и классы, реализованные на C# или VB.NET. Эта возможность смешивать управляемый и нормальный код C++ весьма облегчает функциональную совместимость с .NET, и является тем, что Microsoft упоминает как возможность "Оно просто работает" (It Just Works, IJW). Этот документ демонстрирует два разных способа интеграции нормального кода C++ (который использует Qt) с управляемым кодом .NET. Во-первых, имеется ручной способ, который включает в себя использование тонкого класса-обёртки MC++ вокруг нормального Qt/C++ класса. Затем, имеется автоматический способ, который использует каркас ActiveQt как общий (generic) мост. Преимущество первого метода в том, что он даёт разработчику приложения полный контроль, в то время как второй метод требует меньше кодировать и помогает разработчику заниматься перекодированием между управляемыми и нормальными объектами данных. Нетерпеливый читатель, который немедленно хочет увидеть QPushButton и пользовательский виджет Qt (QAxWidget2) запустит в .NET приложение с ГПИ обратившись к каталогу примеров ActiveQt. Он содержит результат этого критического анализа используя и C# и VB.NET, созданный с помощью Visual Studio .NET (не 2003). Загружите examples/dotnet/walkthrough/csharp.csproj, examples/dotnet/walkthrough/vb.vbproj или examples/dotnet/wrapper/wrapper.sln в ИСР и запустите решение (solution). Замечание: Вас уведомят о том, что в сгенерированном коде следующая строка закомментирована: ' VB регистронезависимый, а наши элементы управления C++ - нет. ' Me.resetButton.enabled = True Эта строка возобновляется без комментария когда вы изменяете диалог, в этом случае вам нужно снова закомментировать её чтобы получить возможность запустить проект. Это ошибка оригинальной версии Visual Studio.NET, она была исправлена в редакции 2003. Критический анализ: .NET выполняется между операциями с MC++ и IJWОбычные классы и функции C++ могут быть использованы из управляемого кода .NET путем предоставления тонких классов-обёрток, написанных на MC++. Класс-обёртка будет заботиться о пересылке вызовов обычным функциям или методам C++ и конвертированию, в случае необходимости, данных параметров. Поскольку класс-обёртка - управляемый класс, он может быть использован без добавочных хлопот в любом управляемом приложении .NET, написанном на C#, VB.NET, MC++ или на другом управляемом языке программирования. class Worker : public QObject { Q_OBJECT Q_PROPERTY(QString statusString READ statusString WRITE setStatusString) public: Worker(); QString statusString() const; public slots: void setStatusString(const QString &string); signals: void statusStringChanged(const QString &string); private: QString status; }; Класс Qt не содержит ничего необычного для пользователей Qt, и даже такие тонкости Qt как Q_PROPERTY, слоты и сигналы реализованные на чистом C++, они не послужат причиной каких-либо проблем когда этот класс компилируется любым компилятором C++. class Worker;
// .NET class
public __gc class netWorker
{
public:
netWorker();
~netWorker();
__property String *get_StatusString();
__property void set_StatusString(String *string);
__event void statusStringChanged(String *args);
private:
Worker *workerObject;
};
Класс-обёртка .NET использует ключевые слова, являющиеся частью MC++, чтобы обозначить что класс является управляемым/сборщиком мусора (__gc), и что StatusString будет доступен как свойство в языках, которые поддерживают эту концепцию (__property). Мы также объявляем функцию события statusStringChanged(String*) (__event), эквивалент соответствующего сигнала в классе Qt. Перед тем, как мы сможем начать реализацию класса-обёртки нам нужен способ конвертации типов данных Qt (и, потенциально, ваши) в типы данных .NET, например, объекты QString нужно конвертировать в объекты типа String*. При оперировании с управляемыми объектами в обычном коде C++, нужно соблюдать небольшую дополнительную осторожность из-за сборки мусора CLR. Нормальная переменная указателя не будет использоваться для ссылки на управляемый объект. Основанием является то, что сборка мусора может активироваться в любой момент и переместить объект в другое место на куче, оставив с недействительным указателем.Тем не менее, представляются два метода, легко решающих эту проблему. Первый - используйте pinned указатель, т.е. объявите переменную указателя с ключевым словом __pin. Это гарантирует, что объект на который ссылаются, не будет перемещен сборщиком мусора. Не рекомендуется использовать этот метод для сохранения ссылок на управляемые объекты в течение длительного времени, поскольку он уменьшает эффективность сборщика мусора. Второй метод - использовать шаблонный тип интеллектуального указателя gcroot. Это позволит вам создавать безопасные указатели на управляемые объекты. Например, переменная типа gcroot<String> будет всегда указывать на объект типа String, даже если она будет перемещена сборщиком мусора, и её можно использовать так же как и обычный указатель. #include <QString> #using <mscorlib.dll> #include <vcclr.h> using namespace System; String *QStringToString(const QString &qstring) { return new String((const wchar_t *)qstring.utf16()); } QString StringToQString(String *string) { const wchar_t __pin *chars = PtrToStringChars(string); return QString::fromUtf16((const ushort *)chars); } Затем могут быть использованы функции конвертора реализации класса-обёртки для вызова функций в обычном классе C++. #include "networker.h" #include "worker.h" #include "tools.h" netWorker::netWorker() { workerObject = new Worker(); } netWorker::~netWorker() { delete workerObject; } Конструктор и деструктор просто создают и разрушают объект Qt, обёрнутый с использованием операторов C++ new и delete. String *netWorker::get_StatusString() { return QStringToString(workerObject->statusString()); } Класс netWorker делегирует вызовы из кода .NET в "родной" код. Несмотря на то, что переход между этими двумя мирами подразумевает незначительное снижение производительности для каждого вызова функции и для преобразования типов, оно будет незначительным поскольку мы в любом случае собираемся запускать внутри CLR. void netWorker::set_StatusString(String *string) { workerObject->setStatusString(StringToQString(string)); __raise statusStringChanged(string); } Функция-сеттер свойства вызывает "родно" класс Qt перед активизацией события используя ключевое слово __raise. Этот класс-обёртка может быть теперь использован в коде .NET, например, используя C++, C#, Visual Basic или любой другой доступный для .NET язык программирования. using System; namespace WrapperApp { class App { void Run() { netWorker worker = new netWorker(); worker.statusStringChanged += new netWorker.__Delegate_statusStringChanged(onStatusStringChanged); System.Console.Out.WriteLine(worker.StatusString); System.Console.Out.WriteLine("Working cycle begins..."); worker.StatusString = "Working"; worker.StatusString = "Lunch Break"; worker.StatusString = "Working"; worker.StatusString = "Idle"; System.Console.Out.WriteLine("Working cycle ends..."); } private void onStatusStringChanged(string str) { System.Console.Out.WriteLine(str); } [STAThread] static void Main(string[] args) { App app = new App(); app.Run(); } } } Критический анализ: .NET/COM выполняется между операциями с ActiveQtК счастью, .NET предоставялет обобщенную обёртку для COM-объектов, Runtime Callable Wrapper (RCW). Эта RCW является прокси для COM-объекта и генерируется CLR когда клиент .NET Framework активирует COM-объект. Это предоставляет общий способ повторного использования COM-объектов в проекте .NET Framework. Превратив класс QObject в COM-объект легко достигается с помощью ActiveQt и продемонстрировано в примерах QAxServer (например, в примере "Simple"). Критический анализ будет использовать классы Qt, реализованные в этих примерах, поэтому первой вещью которую нужно сделать - убедиться, что эти примеры были правильно собраны, например, открыв demonstration pages в Internet Explorer для проверки того, что элементы управления работают. Начинаем проектЗапустим Visual Studio.NET и создадим новый проект C# для написания приложения Windows. Это представит вам пустую форму в редакторе диалога Visual Studio. Вы увидите панель инструментов, которая представит вам некоторое количество доступных элементов управления и объектов различных категорий. Если вы щелкните правой кнопкой мыши на панели инструментов, это позволит вам добавить новые вкладки. Мы добавим вкладку "Qt". Импортирование виджетов QtКатегория по умолчанию имеет только инструмент указателя, а мы добавим объекты Qt, которые хотим использовать на нашей форме. Щелкаем правой кнопкой мыши на пустом пространстве и выбираем "Customize". Открывается диалог, в котором есть две вкладки, "COM Components" и ".NET Framework Components". Мы используем ActiveQt для обёртывания объектов QWidget в COM-объекты, поэтому мы выбираем страницу "COM Components", и ищем классы, которые собираемся использовать, например, "QPushButton" и "QAxWidget2". Когда мы выбрали эти виджеты и закрыли диалог, два виджета будут доступны из панели инструментов в виде серых квадратиков с их именами рядом .Использование виджетов QtМы можем теперь добавить экземпляры классов QAxWidget2 и QPushButton на форму. Visual Studio автоматически сгенерирует RCW для серверов объектов. Экземпляр класса QAxWidget2 займёт большую часть вверху формы с QPushButton в нижнем правом углу. В редакторе свойств Visual Studio мы можем изменить свойства наших элементов управления - QPushButton открывает API QWidget и обладает многими свойствами, в то время как QAxWidget2 имеет только стандартные свойства Visual Studio в дополнение к своему собственному свойству "lineWidth" в категории "Miscellaneous". Объекты получили имена "axQPushButton1" и "axQAxWidget21", и поскольку особенно последнее имя немного смущает, мы переименуем объекты в "resetButton" и "circleWidget". Мы также можем изменить совйства Qt, например, установить свойство "text" кнопки resetButton в значение "Reset", а свойство "lineWidth" circleWidget - равным 5. Мы также можем поместить эти объекты в систему компоновки, которую предоставляет редактор диалога Visual Studio, например, установив якори circleWidget равными "Left, Top, Right, Bottom", а якори resetButton равными "Bottom, Right". Теперь мы можем скомпилировать и запустить проект, который будет открывать пользовательский интерфейс с двумя нашими виджетами Qt. Если мы изменим размер диалога, то виджеты соответственно изменят свои размеры. Обработка сигналов QtТеперь мы будем реализовывать обработчики событий для виджетов. Выберем circleWidget и выберем страницу "Events" в редакторе свойств. Виджет делает видимыми события потому что класс QAxWidget2 в своём определении класса имеет установленный атрибут "StockEvents". Мы реализуем обработчик события circleClicked для ClickEvent чтобы увеличить толщину линии на единицу каждый щелчок мыши: private void circleClicked(object sender, System.EventArgs e) { this.circleWidget.lineWidth++; } Вообще мы может реализовать обработчик событий по умолчанию дважды щелкнув по виджету в форме, но события по умолчанию для наших виджетов в данный момент не определены. Мы также реализуем обработчик событий для сигнала clicked, испускаемого кнопкой QPushButton. Добавим обработчик событий resetLineWidth для событию clicked и реализуем порождённую функцию: private void resetLineWidth(object sender, System.EventArgs e) { this.circleWidget.lineWidth = 1; this.resetButton.setFocus(); } Мы сбросим свойство в значение 1, а также вызовем слот setFocus() чтобы симулировать пользовательский стиль в Windows, где кнопка захватывает фокус когда вы щелкаете по ней (так что вы можете щелкнуть по ней снова пробелом). Если мы теперь скомпилируем и запустим проект, то мы можем щелкать на виджете окружности для увеличения толщины её линии, а также нажимать кнопку сброса для установки толщины линии снова равной 1. РезюмеИспользовать ActiveQt в качестве универсального моста взаимодействия между миром .NET и миром Qt очень легко, и часто это делает лишним реализацию множества вручную написанных классов-обёрток. Вместо этого, реализация QAxFactory в другом случае полностью кроссплатформенного проекта Qt предоставляет связующий элемент, который необходим .NET для генерации RCW. Если этого недостаточно, мы можем реализовать наши собственные классы-обёртки - спасибо расширениям C++, предоставляемым Microsoft. ОграниченияВсе ограничения при использовании ActiveQt предполагают, когда используют этот приём для взаимодействия с .NET, например, типы данных мы можем использовать в API могут быть только теми, что поддерживаются ActiveQt и COM. Однако, поскольку это включает подклассы QObject и QWidget мы можем обернуть любой из наших типов данных в подкласс QObject чтобы сделать его API доступным для .NET. Это имеет положительный побочный эффект, что тот же API автоматически доступен в QSA, кроссплатформенное решение сценариев для приложений Qt и для клиентов COM в общем. При использовании метода "IJW", в принципе ограничением являются только время, требуемое для написания классов-обёрток и функций преобразования типа данных. Размышления о производительностиКаждый вызов из байт-кода CLR в "родной" код предполагает небольшое снижение производительности и необходимый преобразования типа вносит дополнительную задержку в каждом слое, существующем между двумя библиотеками. Следовательно каждый подход по смешиванию кода .NET и "родного" кода будет пытаться минимизировать необходимую связь между разными мирами. Так как ActiveQt сразу помещает три слоя - RCW, COM и наконец сам ActiveQt - потеря производительности при использовании общего моста Qt/ActiveQt/COM/RCW/.NET больше, чем при использовании вручную созданного класса-обёртки IJW. Скорость выполнения, однако, остаётся достаточной для присоединения и модификации интерактивных элементов пользовательского интерфейса, и, как только появляется выгода от использования Qt и C++ для реализации и компиляции производительности критических алгоритмов в "родном" коде, ActiveQt становится эффективным выбором чтобы сделать даже невизуальные части вашего приложения доступными для .NET. Смотрите также Решение QtWinForms.
|
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |