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

от автора

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

Внимание! Автор использует устаревший фреймворк 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;

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

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

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

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


ссылка на оригинал статьи https://habr.com/ru/articles/894824/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *