Spec-Zone.ru › Qt 5.15

Пример 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) в приложении .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.15/activeqt-dotnet.html

Spec-Zone.ru

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