Как стать автором
Обновить

Как добавить в Qt QVariant свой тип данных Currency (он же Decimal)

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров879

Итак в чем суть проблемы и когда она возникает.

Внимание! Автор использует устаревший фреймворк Qt 4.8.1. Работает под Виндоус 10, собираем msvc-2010. Возможно в более новых версиях Qt5,6 и т.д. описанная здесь проблема устранена (возможно... , но автор не проверял).

Происходит это при общении с COM объектом под Виндоус через QAxObject соответственно.

COM объект это черный ящик для вашего приложения, из которого вы дёргаете методы, оперируете свойствами и обращаетесь к ним по наименованию.

Также вы естественно передаете какие-то параметры и у параметров есть их тип, набор которых составила корпорация Майкрософт для обмена с COM объектом.

Так вот есть там такой параметр как VT_CY и по сути это просто целое число 8 байт, значение которого интерпретируется немного по другому нежели чем просто целое число. А именно как бы получается рубли, а последние четыре десятичных знака это копейки.

Да именно последние четыре знака (в десятичном представлении).

Но в С++ и Qt такого типа нет господа! И мне лично понятно почему - зачем придумывать ещё один тип, если он уже есть, целое число 8 байт длиной (qint64, quint64).

А теперь представим ситуацию, что COM объект написан программистами на паскале, с#, .net или другими языками, в которых есть тип данных, аналогичный VT_CY и самое приятное будет, что они его непосредственно используют в интерфейсе своей компоненты.

Оказывается, когда вы из Qt будете передавать им параметр без копеек все будет работать правильно, так как вы будете им толкать qlonglong, который в коде Qt будет конвертироваться в VT_INT8 (VARIANT).

Но вот если вы захотите им передать как бы число с копейками, ну как-то так 12300, предполагая что 1 это рубль, а 2300 это копейки, то передадите 12300 рублей. Потому что в коде Qt вы опять будете конвертировать в VT_INT8.

Все может показаться бредом, если бы не реальный пример - драйвер ккт Штрих, который написан на паскале, по-видимому на Дельфах, против которого я лично ничего не имею, помню сам программировал лет 20 назад, правда не помню чем закончилось, но не суть важно.

Что делать плюсовикам в этой ситуации. Оказывается есть только два варианта:

Либо форкнуть Qt на профи уровне, либо забить на подключение оборудованиz Штрих.

Вру есть ещё третий вариант - попросить штриховцев отказаться от VT_CY в пользу просто VT_INT8, переписать свой протокол и проблем ни у кого не было бы (в будущем), но поломалась у всех бы работа у всех работающих вариантов на текущий момент.

Из реального остаётся только форкнуть Qt и вот об этом здесь будет речь.

Сразу скажу, забегая вперёд, что это реально и заняло пару дней всего.

Что для этого надо: настроенная среда сборки с отладкой для исходных кодов Qt. У нас ещё Qt4.8.1, но здесь это не принципиально.

Далее отладчиком просто ковыряем метаобъектную систему Qt, сам QVariant, смотрим как работают другие типы данных вроде qlonglong и добавляем свой тип данных по аналогии с существующими.

Да, надо отметить, что здесь речь НЕ о пользовательском типе данных QVariant, который возникает при использовании Q_DECLARE_METATYPE, а речь именно о типе Core Type, таких как uint, bool, longlong, QString и.т.д.

Метаобъектная система

Тут надо просто добавить новый тип в файл qmetatype.cpp, думаю понятно куда (можно глобальным поиском найти):

qmetatype.cpp
    QT_ADD_STATIC_METATYPE("QVariantHash", QMetaType::QVariantHash),
    QT_ADD_STATIC_METATYPE("QEasingCurve", QMetaType::QEasingCurve),
    QT_ADD_STATIC_METATYPE("QpCurrency", QMetaType::QpCurrency), // вот он наш

    /* All GUI types */
    QT_ADD_STATIC_METATYPE("QColorGroup", 63),
    QT_ADD_STATIC_METATYPE("QFont", QMetaType::QFont),

Примечание: QpCurrency - Qp это наш префикс, чтобы как-то отличать свой код от нативного Qt-ного .

В файле qmetatype.h, думаю тут тоже понятно:

qmetatype.h
class Q_CORE_EXPORT QMetaType {
public:
    enum Type {
        // these are merged with QVariant
        Void = 0, Bool = 1, Int = 2, UInt = 3, LongLong = 4, ULongLong = 5,
        Double = 6, QChar = 7, QVariantMap = 8, QVariantList = 9,
        QString = 10, QStringList = 11, QByteArray = 12,
        QBitArray = 13, QDate = 14, QTime = 15, QDateTime = 16, QUrl = 17,
        QLocale = 18, QRect = 19, QRectF = 20, QSize = 21, QSizeF = 22,
        QLine = 23, QLineF = 24, QPoint = 25, QPointF = 26, QRegExp = 27,
        QVariantHash = 28,
        QEasingCurve = 29,
        QpCurrency = 30, // вот он наш !!!
        LastCoreType = QpCurrency,

        FirstGuiType = 63 /* QColorGroup */,

Номер в массиве, куда мы добавили свой тип имеет значение.

Теперь правим QVariant

файл qvariant.cpp

qvariant.cpp
static void construct(QVariant::Private *x, const void *copy)
{
.....................
    case QVariant::LongLong:
        x->data.ll = copy ? *static_cast<const qlonglong *>(copy) : Q_INT64_C(0);
        break;

    case QVariant::QpCurrency: // !!!!!!!!!!!!!!!!!!!
        x->data.ll = copy ? *static_cast<const qlonglong *>(copy) : Q_INT64_C(0);
        break;

    case QVariant::ULongLong:
        x->data.ull = copy ? *static_cast<const qulonglong *>(copy) : Q_UINT64_C(0);
        break;

Еще здесь:

qvariant.cpp
static void clear(QVariant::Private *d)
{
................................
    case QVariant::LongLong:

    case QVariant::QpCurrency: // !!!!!!!!!!!!!!!!!!!!!!!

    case QVariant::ULongLong:
    case QVariant::Double:
    case QMetaType::Float:
    case QMetaType::QObjectStar:
        break;
    case QVariant::Invalid:
    case QVariant::UserType:
    case QVariant::Int:
    case QVariant::UInt:
    case QVariant::Bool:
        break;

ещё кое-что поправим:

qvariant.cpp
static bool isNull(const QVariant::Private *d)
{
    switch(d->type) {
.................................................
    case QVariant::Url:
    case QVariant::Locale:
    case QVariant::RegExp:
    case QVariant::StringList:
    case QVariant::Map:
    case QVariant::Hash:
    case QVariant::List:
    case QVariant::Invalid:
    case QVariant::UserType:
    case QVariant::Int:
    case QVariant::UInt:
    case QVariant::LongLong:
    case QVariant::QpCurrency:
    case QVariant::ULongLong:
    case QVariant::Bool:
    case QVariant::Double:
    case QMetaType::Float:
    case QMetaType::QObjectStar:
        break;
    }
    return d->is_null;

и теперь в ещё надо qvariant.h

qvariant.h
class Q_CORE_EXPORT QVariant
{
 public:
    enum Type {
        Invalid = 0,

        Bool = 1,
        Int = 2,
        UInt = 3,
..........................
        RegExp = 27,
        Hash = 28,
        EasingCurve = 29,

        //-----------------------------------------------------------------------------------------------------------------------
        QpCurrency = 30, // МОЙ ТИП ДЛЯ ВИНДОУС (ДЕНЕЖНЫЙ ТИП : ЦЕЛОЕ ЧИСЛО) (АНАЛОГ Currency или Decimal в других языках)
        //                  ГДЕ ПОСЛЕЖНИЕ 4 PАЗPЯДА ОЗНАЧАЮТ ЦЫФPЫ ПОСЛЕ ЗАПЯТОЙ)
        //-----------------------------------------------------------------------------------------------------------------------

        LastCoreType = QpCurrency,

        // value 62 is internally reserved
#ifdef QT3_SUPPORT
        ColorGroup = 63,
#endif
        Font = 64,
        Pixmap = 65,

QAxTypes и QAxBase

qaxtypes.cpp
bool QVariantToVARIANT(const QVariant &var, VARIANT &arg, const QByteArray &typeName, bool out)
{
    QVariant qvar = var;
......................................
    case QVariant::UInt:

        if (out && (arg.vt == (VT_UINT|VT_BYREF) || arg.vt == (VT_I4|VT_BYREF))) {
            *arg.puintVal = qvar.toUInt();
        } else {
            arg.vt = VT_UINT;
            arg.uintVal = qvar.toUInt();
            if (out) {
                arg.puintVal = new uint(arg.uintVal);
                arg.vt |= VT_BYREF;
            }
        }
        break;

    case QVariant::QpCurrency:  // !!!!!!!!!!!!!! We are Added new QVariant Type 

        arg.vt = VT_CY;
        arg.cyVal.int64 = qvar.toLongLong();

        if (out)
        {
            arg.pcyVal = new CY(arg.cyVal);
            arg.vt |= VT_BYREF;
        }

        break;
        
    case QVariant::LongLong:

фунция QVariantToVoidStar

qaxtypes.cpp еще
bool QVariantToVoidStar(const QVariant &var, void *data, const QByteArray &typeName, uint type)
{
    if (!data)
        return true;
.............................
    case QVariant::LongLong:
        *(qint64*)data = var.toLongLong();
        break;

    case QVariant::QpCurrency: // доработка !!!!!!!!!!!!!!!!!!
        *(quint64*)data = var.toLongLong();
        break;

    case QVariant::ULongLong:
        *(quint64*)data = var.toULongLong();
        break;

.Для корректноого отображения в qDebug() << ...

qaxbase.cpp
QByteArray MetaObjectGenerator::guessTypes(const TYPEDESC &tdesc, ITypeInfo *info, const QByteArray &function)
{
    //qDebug() << "guessTypes" << function << "tdesc.vt"<<tdesc.vt;

    QByteArray str;

    switch (tdesc.vt) {
    case VT_EMPTY:
    case VT_VOID:
        break;
....................
    case VT_CARRAY:
        str = guessTypes(tdesc.lpadesc->tdescElem, info, function);
        if (!str.isEmpty()) {
            for (int index = 0; index < tdesc.lpadesc->cDims; ++index)
                str += '[' + QByteArray::number((int)tdesc.lpadesc->rgbounds[index].cElements) + ']';
        }
        break;
    case VT_CY: // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    {
        str = "QpCurrency";
        break;
    }
    case VT_USERDEFINED:
        str = usertypeToString(tdesc, info, function);
        break;

Я не стал пытаться разжевать, что там внутри происходит, думаю кому интересно сможет без проблем повторить. И честно говоря мне лень, скажу только что достаточно несколько раз пройтись отладчиком и все там будет понятно, что происходит.

Главное, что это будет работать и у нас решило проблему с подключением кассовых аппаратов Штрих к нашей программулине на С++.

Вот и все правки в общем-то.

Думаю кому-то это может показаться полезным.

Теги:
Хабы:
+3
Комментарии23

Публикации

Работа

Программист C++
96 вакансий
QT разработчик
5 вакансий

Ближайшие события