Spec-Zone.ru › Qt 5.11

Пример 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> всегда будет указывать на строковый объект, даже если он был перемещен сборщиком мусора, и она может использоваться как обычный указатель.

#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/archives/qt-5.11/activeqt-dotnet.html

Spec-Zone.ru

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