Spec-Zone.ru › Qt 5.13

Пример 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, реализованные в этих примерах, поэтому первое, что нужно сделать, это убедиться, что эти примеры построены правильно, например, открыв страницы демонстрации demonstration pages в 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, где кнопка получает фокус при нажатии (так что вы можете нажать её снова клавишей пробела).

END_OF_DOCUMENT_MARKER

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

Spec-Zone.ru

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