Spec-Zone.ru › Qt 6.1

Пример 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» кнопки QPushButton в «Reset», а свойство «lineWidth» QAxWidget2 в 5. Мы также можем поместить эти объекты в систему макета, которую предоставляет редактор диалогов Visual Studio, например, задав привязку кнопки QPushButton к «Лево, Верх, Право, Низ», а привязку QAxWidget2 к «Низ, Право».

Теперь мы можем скомпилировать и запустить проект, который откроет пользовательский интерфейс с нашими двумя виджетами Qt. Если мы можем изменить размер диалога, виджеты будут масштабироваться соответственно.

Обработка сигналов Qt

Теперь мы реализуем обработчики событий для виджетов. Выберите QAxWidget2 и выберите вкладку «События» в редакторе свойств. Виджет экспонирует события, поскольку у класса 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, где кнопка получает фокус при нажатии (чтобы вы могли нажать её снова с помощью пробела).

Если теперь мы скомпилируем и запустим проект, мы можем нажать на виджет QAxWidget2, чтобы увеличить его ширину линии, и нажать на кнопку reset, чтобы вернуть ширину линии к 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.1/activeqt-dotnet.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API