Пример Dot Net (ActiveQt)
Содержание:
Qt по сравнению с .NET
Qt — это библиотека C++ и компилируется в традиционные, родные двоичные файлы, которые полностью используют производительность, предоставляемую средой выполнения.
Одним из ключевых понятий .NET является идея «кода промежуточного языка» — исходный код компилируется в формат байткода, а во время выполнения этот байт код выполняется в виртуальной машине — Common Language Runtime (CLR).
Другим ключевым понятием является управляемый код. Это по существу код промежуточного языка, написанный таким образом, что CLR может позаботиться об управлении памятью, т.е. CLR выполнит автоматическое сборку мусора, поэтому коду приложения не нужно явно освобождать память для неиспользуемых объектов.
Компиляторы MS для C# и VB.NET производят только управляемый код. Такие программы не могут напрямую вызывать обычные, родные функции или классы.
Компилятор MS C++ для .NET, с другой стороны, может производить как обычный, так и управляемый код. Чтобы написать класс C++, который может быть скомпилирован в управляемый код, разработчик должен пометить класс как управляемый с помощью ключевого слова __gc, и ограничить код только подмножеством C++, известным как «Управляемые расширения для C++», или MC++ для краткости. Преимущество состоит в том, что код MC++ может свободно вызывать и использовать обычные функции и классы C++. И он также работает в обратном направлении: обычный код C++ может вызывать управляемые функции и использовать управляемые классы (например, всю библиотеку классов .NET Framework), включая управляемые функции и классы, реализованные на C# или VB.NET. Эта возможность смешивания управляемого и обычного кода C++ значительно упрощает взаимодействие с .NET и обозначается Microsoft как функция «It Just Works» (IJW).
Этот документ демонстрирует два разных способа интеграции обычного кода C++ (который использует Qt) с управляемым кодом .NET. Во-первых, представлен ручной способ, который включает использование тонкого оберточного класса MC++ вокруг обычного класса Qt/C++. Затем представлен автоматический способ, который использует фреймворк ActiveQt в качестве универсального моста. Преимущество первого метода заключается в том, что он дает разработчику приложения полный контроль, в то время как второй метод требует меньше кодирования и освобождает разработчика от работы с преобразованием между управляемыми и обычными объектами данных.
Нетерпеливый читатель, который сразу хочет увидеть кнопку QPushButton и пользовательский виджет Qt (QAxWidget2) в приложении .NET GUI, обращается к папке примеров ActiveQt. Она содержит результат этого руководства, выполненного с использованием C# и VB.NET, созданных с помощью Visual Studio .NET (не 2003 года). Загрузите examples/dotnet/walkthrough/csharp.csproj, examples/dotnet/walkthrough/vb.vbproj или examples/dotnet/wrapper/wrapper.sln в IDE и запустите решение.
Примечание: Вы заметите, что в сгенерированном коде следующая строка закомментирована:
' VB is case insensitive, but our C++ controls are not. ' 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, slots и signals, реализованные с помощью обычного 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. Обычная переменная указателя не должна
использоваться для ссылки на управляемый объект. Причина в том, что сборка мусора может начаться в любое время и переместить объект в другое место в куче, оставив вас с недействительным указателем.Однако предоставляются два метода, которые легко решают эту проблему. Первый — использовать закрепленный указатель, т. е. объявить переменную указателя с ключевым словом __pin. Это гарантирует, что объект, на который указывает указатель, не будет перемещен сборщиком мусора. Рекомендовано не использовать этот метод для хранения ссылок на управляемые объекты в течение длительного времени, поскольку это снизит эффективность сборщика мусора. Второй способ — использовать шаблон умного указателя gcroot. Это позволяет создавать безопасные указатели на управляемые объекты. Например, переменная типа gcroot<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::fromWCharArray(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 при активации объекта COM клиентом .NET Framework. Это предоставляет универсальный способ повторного использования объектов COM в проекте .NET Framework.
Преобразование класса QObject в объект COM легко достигается с помощью ActiveQt и демонстрируется в примерах QAxServer (например, пример Simple). Пошаговое руководство будет использовать классы Qt, реализованные в этих примерах, поэтому первое, что нужно сделать, — убедиться, что эти примеры были построены правильно, например, путем открытия страниц демонстрации демонстраций в Internet Explorer для проверки функциональности элементов управления.
Начало проекта
Запустите Visual Studio.NET и создайте новый проект C# для написания приложения Windows. Это откроет пустую форму в редакторе диалогов Visual Studio. Вы должны увидеть палитру инструментов, которая предоставляет вам ряд доступных элементов управления и объектов в разных категориях. Если вы щелкнете правой кнопкой мыши по палитре инструментов, вы сможете добавить новые вкладки. Мы добавим вкладку "Qt".
Импорт виджетов Qt
По умолчанию в категории есть только инструмент указателя, и нам нужно добавить объекты Qt, которые мы хотим использовать в нашей форме. Щелкните правой кнопкой мыши по пустому месту и выберите "Настроить". Это откроет диалог с двумя вкладками: "Компоненты COM" и "Компоненты .NET Framework". Мы использовали ActiveQt для обертывания QWidget в объекты COM, поэтому мы выбираем вкладку "Компоненты COM" и ищем классы, которые хотим использовать, например, "QPushButton" и "QAxWidget2".
Когда мы выберем эти виджеты и закроем диалог, два виджета теперь будут доступны в палитре инструментов как серые квадраты с их именем рядом с ними
.Использование виджетов Qt
Теперь мы можем добавить экземпляр QAxWidget2 и QPushButton в форму. Visual Studio автоматически сгенерирует RCW для серверов объектов. Экземпляр QAxWidget2 занимает большую часть верхней части формы, а QPushButton — в правом нижнем углу.
В редакторе свойств Visual Studio мы можем изменить свойства наших элементов управления — QPushButton предоставляет API QWidget и имеет множество свойств, а QAxWidget2 имеет только стандартные свойства Visual Studio в дополнение к своему собственному свойству "lineWidth" в категории "Разное". Объекты называются "axQPushButton1" и "axQAxWidget21", и, учитывая, что особенно последнее имя немного запутанно, мы переименуем объекты в "resetButton" и "circleWidget".
Мы также можем изменить свойства Qt, например, установить свойство "text" для resetButton в "Reset" и свойство "lineWidth" для circleWidget в 5. Мы также можем поместить эти объекты в систему макета, предоставляемую редактором диалогов Visual Studio, например, установив привязки для circleWidget к "Слева, Сверху, Справа, Снизу" и привязки для resetButton к "Снизу, Справа".
Теперь мы можем скомпилировать и запустить проект, который откроет пользовательский интерфейс с нашими двумя виджетами Qt. Если мы можем изменить размер диалога, виджеты будут изменять размер соответственно.
Обработка сигналов Qt
Теперь мы будем реализовывать обработчики событий для виджетов. Выберите circleWidget и выберите вкладку "События" в редакторе свойств. Виджет предоставляет события, потому что класс 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.
END_OF_DOCUMENT_MARKERРезюме
Использование ActiveQt в качестве универсального моста для взаимодействия между миром .NET и родным миром Qt очень просто и часто делает ненужным создание большого количества вручную написанных оберточных классов. Вместо этого, реализация QAxFactory в полностью кроссплатформенном проекте Qt предоставляет необходимый связующий код, который .NET использует для генерации RCW.
Если этого недостаточно, мы можем реализовать собственные оберточные классы благодаря расширениям C++ от Microsoft.
Ограничения
Все ограничения при использовании ActiveQt подразумеваются при использовании этого метода для взаимодействия с .NET, например, типы данных, которые мы можем использовать в API, могут быть только теми, которые поддерживаются ActiveQt и COM. Однако, поскольку это включает подклассы QObject и QWidget, мы можем обернуть любые наши типы данных в подкласс QObject, чтобы сделать его API доступным для .NET. Это имеет положительный побочный эффект, заключающийся в том, что тот же API автоматически доступен при использовании Qt Script для автоматизации приложений Qt и COM-клиентам в целом.
При использовании метода "IJW" единственным ограничением, в принципе, является время, необходимое для написания оберточных классов и функций преобразования типов данных.
Соображения по производительности
Каждый вызов из байткода CLR в нативный код влечёт за собой небольшое снижение производительности, а необходимые преобразования типов вносят дополнительную задержку с каждым слоем, существующим между двумя фреймворками. Поэтому любой подход к смешению .NET и нативного кода должен стремиться минимизировать необходимое взаимодействие между различными мирами.
Поскольку ActiveQt вводит сразу три слоя — RCW, COM и собственно ActiveQt — штраф за производительность при использовании универсального моста Qt/ActiveQt/COM/RCW/.NET больше, чем при использовании вручную написанного оберточного класса IJW. Однако скорость выполнения по-прежнему достаточна для подключения к и модификации интерактивных элементов пользовательского интерфейса, и как только начинает проявляться выгода использования Qt и C++ для реализации и компиляции критически важных для производительности алгоритмов в нативный код, ActiveQt становится приемлемым выбором для предоставления доступа к невизуальным частям вашего приложения для .NET.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/archives/qt-5.6/activeqt-dotnet.html