Spec-Zone.ru › Qt 5.14

Пример 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, когда клиент .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, например, установив привязки 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.14/activeqt-dotnet.html

Spec-Zone.ru

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