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