64-битный целочисл. тип данных в 32-разрядном VBA. Работает?

Программирование на Visual Basic for Applications
Михаил Орлов
Начинающий
Начинающий
 
Сообщения: 12
Зарегистрирован: 30.09.2021 (Чт) 20:57

64-битный целочисл. тип данных в 32-разрядном VBA. Работает?

Сообщение Михаил Орлов » 16.11.2021 (Вт) 12:37

Приветствую коллеги.

Хочу поделиться наблюдением, что в 32-разрядном VBA нормально работает 64-разрядный целочисленный тип данных (и я не про Currency).
Следующий код нормально выполняется в 32-разрядном VBA (Excel, если это важно):

Код: Выделить всё
Option Explicit

Sub MyTstSub()

Dim vInt64 As Variant

vInt64 = Range("A1").CountLarge
Debug.Print "vInt64 = " & vInt64, "VarType(vInt64) = " & VarType(vInt64)

vInt64 = vInt64 - vInt64 ' зануляем, с сохранением подтипа переменной
vInt64 = ((vInt64 + &H12345678) * &H10000000 + &H9ABCDEF) * &H10 ' vInt64 = &H123456789ABCDEF0
Debug.Print "vInt64 = " & vInt64, "VarType(vInt64) = " & VarType(vInt64) ' работает

vInt64 = vInt64 \ &H10
Debug.Print "vInt64 = " & vInt64, "VarType(vInt64) = " & VarType(vInt64) ' работает

End Sub


Каким-то образом поместить 64-битное целое в контейнер Variant - не фокус.
А вот то, что дальше с ним норамльно работает целочисленная арифметика - фокус, на мой взгляд.

Для 64-разрядном VBA этот код банален и избыточен, но, повторюсь, код работает в 32-разрядном VBA.

Есть какие-нибудь мысли, как объяснить феномен, или это норма, а не феномен вовсе?

Проверить в VB6 не могу - на работе не дают его установаить (а хотелось бы).

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4147
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: 64-битный целочисл. тип данных в 32-разрядном VBA. Работ

Сообщение Mikle » 17.11.2021 (Ср) 10:21

VB6 останавливается на Range.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: 64-битный целочисл. тип данных в 32-разрядном VBA. Работ

Сообщение Хакер » 17.11.2021 (Ср) 10:48

Excel 11 (то есть Office 2003).
Свойства CountLarge нет как такового — поэтому проверить код непосредственным путём невозможно.

То, что множество подптипов Variant-а несколько больше, чем множество «встроенных» типов переменных VB/VBA — не секрет. Гораздо интереснее, что выводят в этом случае VarType() и TypeName(). Первая возвращает 20, то есть VT_I8 — реальной VARIANT-подтип как есть, вторая же попросту сдаётся, потому что такого типа она не знает.

Однако и не секрет, что не все возможные члены VARENUM-а могут быть использованы для типов VARIANT-переменных. В документации есть табличка, в которой указывается, какие константы в каких рамка применимы:
vt_applience.png
vt_applience.png (5.78 Кб) Просмотров: 852

Здесь нет крестика в первой колонке, что означает, что держание 64-битных знаковых целых в VARIANT-контейнерах недопустимо. Тем не менее, в Excel-евской объектной модели есть свойство, которое возвращает VARIANT с таким подтипом. То есть с одной стороны это можно рассматривать как использование недокументированной возможности: то есть всем остальным нельзя, но нам в своём продукте можно. С другой стороны можно подумать, что документацию просто забыли исправить, добавив крестик на пересечении первой колонки и VT_I8 (и других таких типов). А с третьей стороны, что скорее и является правдой, крестик и хотели бы поставить, да не могли, потому что в этом случае нарушается обратная совместимость, потому что то множество возможных подптипов варианта, с которым должен был уметь работать и умел работать VARIANT-friendly софт, внезапно расширялся без предупреждения, и механизмы работы с VARIANT-ом, которые ранее провозглашались (и фактически были) как полностью VARIANT-совместимые, становились частично нефункциональными.

Это хорошо, что VB/VBA полагается на OLEAUT32.DLL, в которой поддержка ранее неподдерживаемых типов уже реализована. Но в каких-то продуктах может быть своя реализация работы с VARIANT-переменными, которая знать не знает про то, что VT_I8 — в числе допустимых и разрешённых подтипов, и и что с этим типом надо уметь работать.

Каким-то образом поместить 64-битное целое в контейнер Variant - не фокус.
А вот то, что дальше с ним норамльно работает целочисленная арифметика - фокус, на мой взгляд.


Фокуса здесь никакого нет. VB/VBA ведь «рассуждает» в духе: о, вот тут у нас переменная, которая может содержать что угодно, но сейчас содержит число, и тут такая же переменная, внутри которой может быть что угодно, но в ней тоже число — и там, и там число, применю-ка я свою встроенную целочисленную арифметику.

С точки зрения VB/VBA переменная содержит неизвестно что, и в данном случае совершается сложение двух «чёрных ящиков». Эту задачу VB/VBA полностью делегирует OLEAUT32.DLL, где для этого есть функция VarAdd.

То есть то, что VB-код успешно проводит разные арифметические операции (не только сложение) с типами, которые, как считалось, не должны поддерживаться — не заслуга VB, а заслуга функций для арифметических операций с Variant-переменными, предоставляемыми библиотекой OLEAUT32.DLL и используемыми для своих целей VB-шным кодом.

Проверить в VB6 не могу - на работе не дают его установаить (а хотелось бы).

А это зависит не от VB6, а от версии OLEAUT32.DLL — реализована ли в ней арифметика над типами, которые раньше не поддерживались.

Как показывает проверка, под Windows XP SP3 (версия билиотеки OLEAUT32.DLL — 5.1.2600.6341) арифметика над VT_I8 поддерживается.
А вот случись той же VB-программе «заехать» под Windows 98 (версия библиотеки — 2.40.4275) — там поддержки арифметики над VT_I8 не будет, на попытке сложить (перемножить или сделать что-то аналогичное) с двумя Variant-переменными, содержащими 64-битное число, мы получим ошибку:


win98_oleaut32_failure.png
win98_oleaut32_failure.png (2.71 Кб) Просмотров: 852


Хотя это будет абсолютно тот же скомпилированный проект, тот же файл и та же MSVBVM60.DLL. Разница кроется в различиях в OLEAUT32.DLL.

Зато я нашёл кое что другое интересное относительно VB6 и этого трюка.

Для начала правильный проверочный код для VB6-проекта (для VBA в офисе его тоже можно использовать):
Код: Выделить всё
Option Explicit
Private Declare Sub PutMem2 Lib "msvbvm60.dll" (ByVal addr As Long, ByVal v As Integer)

Private Sub Command1_Click()
    On Error Resume Next
    Dim w$, foo, bar, result
   
    foo = &H7FFFFFFF
    bar = 10000000
   
    GoSub report_foo_and_bar
    result = Empty
    result = foo * bar
    GoSub report_result
   
    w$ = w$ & "-----------------------" & vbNewLine
   
    PutMem2 VarPtr(foo), 20
    PutMem2 VarPtr(bar), 20
   
    GoSub report_foo_and_bar
    result = Empty
    result = foo * bar
    GoSub report_result
   
    MsgBox w$
   
    Exit Sub
   
report_foo_and_bar:
    w$ = w$ & "foo(vartype=": w$ = w$ & VarType(foo): w$ = w$ & " aka ["
    w$ = w$ & TypeName(foo)
    w$ = w$ & "]) = ": w$ = w$ & foo: w$ = w$ & vbNewLine
   
    w$ = w$ & "bar(vartype=": w$ = w$ & VarType(bar): w$ = w$ & " aka ["
    w$ = w$ & TypeName(bar)
    w$ = w$ & "]) = ": w$ = w$ & bar: w$ = w$ & vbNewLine
    Return
   
report_result:
    w$ = w$ & "result = foo * bar" & vbNewLine
    w$ = w$ & "result(vartype=": w$ = w$ & VarType(result): w$ = w$ & " aka ["
    w$ = w$ & TypeName(result)
    w$ = w$ & "]) = ": w$ = w$ & result: w$ = w$ & vbNewLine
    Return
End Sub



Вот что он показывает при запуске проекта под IDE в Windows XP:
wxp_ide_result__.png
wxp_ide_result__.png (7.95 Кб) Просмотров: 851

(Как видно, результат перемножения двух VT_I4 не умещается в VT_I4, поэтому OLEAUT32.DLL решает расширить тип до VT_R8 aka «Double». А вот в случае VT_I8 результат умещается, поэтому выходный тип не отличается от входного. Результат получается правильным, также OLEAUT32 умеет корректно конвертировать VT_I8 в десятичное текстовое представление (а вот функция Hex не умеет, к слову)).



А вот то же самое под Windows 98, где библиотека OLEAUT32 не умеет ни складывать или перемножать VT_I8 между собой, ни просто конвертировать VT_I8 в текст:
w98_ide_result__.png
w98_ide_result__.png (3.53 Кб) Просмотров: 851


В чём же заключается интересность? Напишу в следующем посте
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: 64-битный целочисл. тип данных в 32-разрядном VBA. Работ

Сообщение Хакер » 17.11.2021 (Ср) 10:59

Mikle писал(а):VB6 останавливается на Range.

Так это Excel-евский глобальный объект, такой же точно, как Ruby-шный глобальный объект App, которого не встретить в VBA-коде. Предполагается, что поведение VBA/EB должно быть одинаковым, а специфику VBA-хоста (будь то Excel, Word или Ruby) каждый подправит в коде под свой VBA-хост.

(Точнее не сам глобальный объект, а мембер безымянного app-global-ного объекта, предоставляемого хостом)

В standalone VB у нас будут App, Printer, Clipboard, а в Excel-е будут экселевские финтифлюшки — Range, Rows, ActiveWorkbook.
Вложения
vba_plus_host.png
vba_plus_host.png (50.73 Кб) Просмотров: 851
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: 64-битный целочисл. тип данных в 32-разрядном VBA. Работ

Сообщение Хакер » 17.11.2021 (Ср) 15:13

Хакер писал(а):В чём же заключается интересность? Напишу в следующем посте

Ладно, более детальная проверка показала, что интересность — это на самом деле не интересность, а банальный глюк по моей вине.

Сперва показалось/оказалось, что проверочный код в скомпилированном виде работает то правильно (если скомпилирован в P-код), то неправильно (если скомпилирован в Native-код). Неправильность заключалась в том, что результат операции над двумя VT_I8 почему-то давал не VT_I8, а VT_R8 (то есть Double), так же, как в первом случае. Я пошёл по ложному следу, предположив, что в Native-кодны бинарниках используется __vbaVarDup, и что она как-то видоизменяет подптип VARIANT-а по своему усмотрению.

На самом деле ошибка вызвана тем, что вот такой способ конвертирования VT_I4 (aka Long) в VT_I8 (aka... эмм... LongLong? Long64?):
Код: Выделить всё
    PutMem2 VarPtr(foo), 20

Слишком наивный и не гарантирует получения правого числа, потому что старшие 4 байта восьмибайтового числового значения VARIANT-а может содержать мусор, который для VT_I4 не играет никакой роли, а для VT_I8 становится частью числа (в итоге у меня образовывалось два гигантских 64-битных числа, результат операции над которыми не умещался даже в VT_I8, так что производилась конвертация в Double). Поскольку мусор был случайным, поведение бага зависело от погоды на Марсе.

Более правильный вариант:
Код: Выделить всё
    PutMem2 VarPtr(foo), 20
    PutMem4 VarPtr(foo)+12, 0&


Да и то, этот подход не учитывает знак, превращая лонговский -1 в четыре миллиарда.

Тем не менее, к 64-битной арифметике и способу компиляции это всё не имеет отношения, __vbaVarDup и стоящий за ним __vbaVarCopy (а за ним стоит OLEAUT32!VariantCopy) никакой самовольщины над числами не допускает.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Михаил Орлов
Начинающий
Начинающий
 
Сообщения: 12
Зарегистрирован: 30.09.2021 (Чт) 20:57

Re: 64-битный целочисл. тип данных в 32-разрядном VBA. Работ

Сообщение Михаил Орлов » 18.11.2021 (Чт) 14:50

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

Свойство Range.CountLarge появилось в Excell 2007, то есть тогда, когда в нём появилась необходимость из-за нового размера рабочих листов (2^20 x 2^14 ячеек).
Из книги Джона Уокенбаха "Профессиональное программирование на VBA" я запомнил тип этого свойства, как Double.
Потом обнаружил, что VBA/Excell 64 возвращает в этом свойстве тип LongLong (точнее - Variant, subtype LongLong),
решил, что VBA/Excell 64 возвращает LongLong, а VBA/Excell 32 - Double, решил проверить и... обнаружил,
что VBA/Excell 32 тоже возвращает LongLong (Variant, subtype LongLong).

Ещё Была мысль, что в VBA/Excell 2007 32 свойство Range.CountLarge возвращало тип Double, и тип изменился с версией MS-Office,
но оказалось, что тип LongLong был у свойства .CountLarge с момента появления, а в примере из книги Джона Уокенбаха использовалась переменная с типом Double для сохранения зтого свойства.

С арифметикой теперь тоже понятно: не фокус, а OLEAUT32.DLL. Интересно, много ли операций такая арифметика поддерживает для универсальных Variant?

Например, где здесь будут обращения к OLEAUT32.DLL, а где - (как правильно сказать? нативный?) код VB/VBA?
Код: Выделить всё
Option Explicit

Sub MyTstSub()

Dim foo as Variant, bar as Variant, result as Variant

result = Sin(foo * foo) + Cos(bar * bar)

End Sub

Если я правильно понял, foo * foo и bar * bar посчитает OLEAUT32.DLL, затем - конверсия в Double, вычисляются Sin, Cos и "+", и, наконец, ещё один вызов OLEAUT32.DLL для записи резултата?
Или (вдруг) Sin и Cos тоже есть в OLEAUT32.DLL?

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: 64-битный целочисл. тип данных в 32-разрядном VBA. Работ

Сообщение Хакер » 18.11.2021 (Чт) 20:08

Михаил Орлов писал(а):Из книги Джона Уокенбаха "Профессиональное программирование на VBA" я запомнил тип этого свойства, как Double.
Потом обнаружил, что VBA/Excell 64 возвращает в этом свойстве тип LongLong (точнее - Variant, subtype LongLong),
решил, что VBA/Excell 64 возвращает LongLong, а VBA/Excell 32 - Double, решил проверить и... обнаружил,
что VBA/Excell 32 тоже возвращает LongLong (Variant, subtype LongLong).

Ещё Была мысль, что в VBA/Excell 2007 32 свойство Range.CountLarge возвращало тип Double, и тип изменился с версией MS-Office,


Ну Double было бы опрометчиво использовать в связи с перспективой потери точности для целых чисел. Для числа порядка 234 это не грозило бы конечно.

Михаил Орлов писал(а):Интересно, много ли операций такая арифметика поддерживает для универсальных Variant?

Так я же привёл в предыдущем посте ссылку на страницу с перечислением всех операций из этой когорты:
  • VarAbs — Returns the absolute value of a variant.
  • VarAdd — Returns the sum of two variants.
  • VarAnd — Performs a bitwise And operation between two variants of any integral type.
  • VarCat — Concatenates two variants and returns the result.
  • VarCmp — Compares two variants.
  • VarDiv — Returns the result from dividing two variants.
  • VarEqv — Performs a bitwise equivalence on two variants.
  • VarFix — Returns the integer portion of a variant.
  • VarIdiv — Converts two variants of any type to integers then returns the result from dividing them.
  • VarImp — Performs a bitwise implication on two variants.
  • VarInt — Returns the integer portion of a variant.
  • VarMod — Divides two variants and returns only the remainder.
  • VarMul — Returns the result from multiplying two variants.
  • VarNeg — Performs logical negation on a variant.
  • VarNot — Performs the bitwise not negation operation on a variant.
  • VarOr — Performs a logical disjunction on two variants.
  • VarPow — Returns the result of performing the power function with two variants.
  • VarR4CmpR8 — Compares two variants of types float and double.
  • VarR8Pow — Performs the power function for variants of type double.
  • VarR8Round — Rounds a variant of type double to the specified number of decimal places.
  • VarRound — Rounds a variant to the specified number of decimal places.
  • VarSub — Subtracts two variants.
  • VarXor — Performs a logical exclusion on two variants.


По сути это все VB-шные унарные и бинарные операторы (кроме Like), плюс псевдофункции Abs(), Int(), Fix().

Если я правильно понял, foo * foo и bar * bar посчитает OLEAUT32.DLL, затем - конверсия в Double, вычисляются Sin, Cos и "+", и, наконец, ещё один вызов OLEAUT32.DLL для записи резултата?


Тут нужно понимать, что в целом/идеологически у VBA есть собственные функции для осуществления всех операций с VARIANT-значениями: __vbaVarAbs, __vbaVarFix, __vbaVarAdd, __vbaVarSub, __vbaVarCat.

Псевдокодное воплощение VBA-кода имеет свои P-code-инструкции для работы с вариантом, которые вызывают эти __vbaVarXxxx-функции. При компиляции VB-проектов в Native-код прямо из кода вызываются соответствующие __vbaVarXxxx-функции.

Поэтому для подсчёта foo * bar будет вызвана __vbaVarMul.
Для приведения типа foo * bar из Variant в Double будет вызвана __vbaR8Var.
Подсчёт синуса и косинуса будут вызваны rtcSin/rtcCos.
Для приведения типа из Double в Variant после вызова триг. функций ничего не вызывается, это тривиальная задача.
Для осуществления операции присваивания Variant-а вызывается __vbaVarMove.

Это как бы общий случай.

Теперь частный случай: в версии VBA для Windows многих из этих функций (__vbaVarXxxxxx) не делают основную часть работы сами, а вызывают соответствующие функции из OLEAUT32.DLL. Например __vbaVarMul вызывает VarMul из OLEAUT32.DLL. Но это билды VBA для Windows. Хочется спросить: а что, существуют варианты сборки VBA не для Windows? Почти что нет, но хорошо известно, что существовали версии VBA (здесь VBA включает и VB, так как самостоятельный VB это Ruby+VBA) для Macintosh. В VBA есть функции MacID и MacScript даже для не-Macintosh-сборок.

Теоретичесик, было бы желание, из исходников VBA могли бы скомпилирвоать и версии VBA под какие-то другие платформы и операционные системы.

Не знаю, как в версии для Macintosh — не щупал, но в версии для Windows, как я уже сказал, многие из этих функций основную часть работы перекладывают на OLEAUT32.DLL. В версии для Mac, надо полагать, всю работу с вариантами VBA делает самостоятельно, ведь никакого OLEAUT32.DLL там нет.

Многие, как я сказал, но не все. И не всю работу.

__vbaVarMul например просто вызывает OLEAUT32!VarMul, а затем только проверает код возврата и «транслирует» его в исключение, если операция сбойнула. То есть это просто тривиальная обёртка.

А вот __vbaVarCat хоть и вызывает OLEAUT32!VarCat, делает очень много предварительных доп. действий сама. То есть это обёртка, но не тривиальная.

А вот __vbaVarMove не является просто обёрткой над какой-то функцией из OLEAUT32.DLL. То есть это вообще не обёртка над какой-то OLEAUT32-функцией.

__vbaR8Var тоже не пользуется услугами OLEAUT32.DLL, всё делает сама.

Но опять-таки, это актуально только для Windows-сборок VBA, хоть других можно сказать, что и нет (почти).

Так что «и, наконец, ещё один вызов OLEAUT32.DLL для записи резултата» — не правда.

Михаил Орлов писал(а):Или (вдруг) Sin и Cos тоже есть в OLEAUT32.DLL?

Нету, да и нет такой нужды. Для операций с вариантами такие функции есть только потому, что там огромное разнообразие опций относительного того, как можно (и нужно) действовать, когда входные операнды имеют разные типы. Например сложение числа и строки, числа и Null, числа и Empty. И главное, что правила приведений типов «к общему знаменателю» и обработки крайних случаев должны быть у всех програм одинаковые.

А синус — с ним всё просто. На входе Double, на выходе всегда Double. Никаких corner cases и никаки приведений типов.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.


Вернуться в VBA

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 11

    TopList