Spec-Zone.ru › Qt 5.9

Пример 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 при активации .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.9/activeqt-dotnet.html

Spec-Zone.ru

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