Пример 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 «Работает само собой» (IJW).
Этот документ демонстрирует два разных способа интеграции обычного кода C++ (который использует Qt) с управляемым кодом .NET. Во-первых, представлен ручной способ, включающий использование тонкого класса MC++-оболочки вокруг обычного класса Qt/C++.
Затем представлен автоматический способ, который использует фреймворк ActiveQt в качестве универсального моста. Преимущество первого метода заключается в том, что он предоставляет разработчику приложения полный контроль, в то время как второй метод требует меньше кодирования и избавляет разработчика от необходимости работы с преобразованием между управляемыми и обычными объектами данных.
Нетерпеливому читателю, который сразу хочет увидеть кнопку QPushButton и пользовательский виджет Qt (QAxWidget2) в приложении GUI .NET, рекомендуем обратиться к каталогу примеров 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, когда клиент .NET Framework активирует объект COM. Это обеспечивает универсальный способ повторного использования объектов 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, например, установив привязку кнопки resetButton к «Левой, Верхней, Правой, Нижней», а привязку circleWidget к «Нижней, Правой».
Теперь мы можем скомпилировать и запустить проект, что откроет пользовательский интерфейс с нашими двумя виджетами Qt. Если мы можем изменить размер диалогового окна, виджеты изменят размер соответствующим образом.
Обработка сигналов Qt
Теперь мы реализуем обработчики событий для виджетов. Выберите circleWidget и выберите вкладку «События» в редакторе свойств. Виджет предоставляет события, потому что класс QAxWidget2 имеет атрибут «StockEvents» в своём определении класса. Мы реализуем обработчик событий circleClicked для circleWidget, чтобы увеличить ширину линии на единицу при каждом щелчке:
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.
При использовании метода «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-6.2/activeqt-dotnet.html