Пример 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> всегда будет указывать на объект 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.
Заключение
Использование 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/qt-5.12/activeqt-dotnet.html