Итак в чем суть проблемы и когда она возникает.
Внимание! Автор использует устаревший фреймворк 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;
Я не стал пытаться разжевать, что там внутри происходит, думаю кому интересно сможет без проблем повторить. И честно говоря мне лень, скажу только что достаточно несколько раз пройтись отладчиком и все там будет понятно, что происходит.
Главное, что это будет работать и у нас решило проблему с подключением кассовых аппаратов Штрих к нашей программулине на С++.
Вот и все правки в общем-то.
Думаю кому-то это может показаться полезным.
