OLE Variant 2 ANSI — Архив WASM.RU
Исходники к статьеДля кого эта статья?
Этот материал полезен для начинающих воинов дзена и может поведать о том как:
Здесь также будет рассмотрен исходник автономной процедуры для преобразования типа variant в ANSI строку.
- конвертировать целые числа и float`ы единым алгоритмом
- узнать количество десятичных цифр в целом числе через логарифм
- избавиться от условных переходов при помощи команд cmovXX, bt и модификации кода
OLE Variant
Этот тип данных используется для передачи информации через COM. Переменная такого типа может содержать до 64 бит полезной информации (это могут быть числа, указатели или флаги). Изнутри эта структура выглядит так:
struct VARIANT vt rw 1 ;тип данных wReserved1 rw 1 ;зарезервировано wReserved2 rw 1 ;зарезервировано wReserved3 rw 1 ;зарезервировано value rq 1 ;данные endsЕдинственное поле которое заслуживает подробного описания это vt - поле типа данных. Оно может содержать следующие значения:
нотация Microsoft нотацияDelphi hex значение описание vt_empty varEmpty 00h пустая структура vt_null varNull 01h пустая структура vt_i2 varSmallint 02h целое со знаком 2 байта vt_i4 varInteger 03h целое со знаком 4 байта vt_r4 varSingle 04h число с плавающей точкой 4 байта vt_r8 varDouble 05h число с плавающей точкой 8 байт vt_cy varCurrency 06h х.з. vt_date varDate 07h число с плавающей точкой 8 байт (01.01.1900=2.0) vt_bstr varOleStr 08h указатель на unicode строку vt_dispatch varDispatch 09h указатель на интерфейс IDispatch vt_error varError 0Ah код ошибки 4 байта vt_bool varBoolean 0Bh 1 бит vt_variant varVariant 0Ch указатель на переменную типа variant vt_unknown varUnknown 0Dh х.з. vt_i1 varShortInt 10h целое со знаком 1 байт vt_ui1 varByte 11h целое без знака 1 байт vt_ui2 varWord 12h целое без знака 2 байта vt_ui4 varLongWord 13h целое без знака 4 байта vt_i8 varInt64 14h целое со знаком 8 байт vt_clsid varStrArg 48h указатель на CLSID Единый алгоритм преобразования чисел
Математический сопроцессор аппаратно поддерживает упакованые двоично-десятичные числа, следовательно можно переложить всю работу на инструкцию fbstp. для особо непродвинутых приведу формат упакованого BCD числа.Как видно из таблицы BCD - число содержит 18 тетрад при этом значение тетрады лежит в диапазоне от 0 до 9 (в общем это перевёрнутое текстовое отображение числа).
9 8 7 6 5 4 3 2 1 0 sxxxxxxx 17,16 15,14 13,12 11,10 9,8 7,6 5,4 3,2 1,0
S - это знаковый бит (1 - число отрицательное)Теперь перейдём к числам в формате с плавающей точкой. Для начала нужно преобразовать число с плавающей точкой в число с фиксированной точкой. Допустим у нас есть число:
87954.6465В идеале его нужно привести к 18-значному виду вот так:879546465000000000Напрашивается формула Y=X*10(18-<длина целой части>). Вопрос: как найти длину целой части? Самый быстрый и маленький способ решить эту задачу - использовать логарифмы.Итак определение:
Логарифм данного числа X при основании Y - это показатель степени, в которую нужно возвести число Y, чтобы получить X.Можно проще:
X=Y(logYX)То есть если взять логарифм по онованию 2 мы получим количество значащих битов в целом числе. Если же взять логарифм по основанию 10 - это будет количество десятичных символов в числе. Фактически сопроцессор способен рассчитывать только логарифмы по основанию 2 и для того чтобы найти логарифм с произвольным основанием применяется формула:
logYX=(log2X)/(log2Y)
Заботливые инженеры intel предусмотрели ряд часто используемых констант, их можно получить при помощи инструкций fldXXX (Нас интересует fldl2t - загрузить log210).В результате всего вышесказанного вырисовывается следующий алгоритм:
- Загрузить число в FPU
- Подсчитать длину целой части
- Умножить число на 10(18-<длина целой части>) (при этом лучше всего воспользоваться таблицей значений)
- сохранить BCD
- Если знаковый бит установлен вывести минус
- Вывести целую часть
- Вывести точку
- Вывести оставшиеся цифры
Однако для умножения на 10x необходимо использовать только точные целые значения (иначе будет жуткая погрешность). Сопроцессор оперирует числами с мантиссой в 64 бита и порядком в 15 бит, а этого не достаточно чтобы точно представить число 1018. Умножение на 64 битовое целое число невозможно по той же причине. Поэтому придётся использовать 32 разрядные целые числа с диапазоном от 0 до 109.
Следовательно число:
0.00000123456789Будет урезано до:
1234В связи с этим обстоятельством не получится выровнять число по старшему байту, то есть в большинстве случаев строка будет начинаться нулями. В конце строки также может появиться много нулей.
Оптимизация
Это несомненно самая дзенская часть статьи. Итак рассмотрим техники оптимизации, которые я применил от простого к сложному.
- Инструкции cmovXX или инструкции условной пересылки данных выглядят так:
cmovXX reg16,reg16 cmovXX reg32,reg32Это позволяет копировать данные из одного регистра в другой при исполнении некоторого условия. Например чтобы поместить в eax наибольшее число из eax и edx нужно написатьcmp eax,edx cmovl eax,edx- Маски позволяют обнулить регистр или память при некотором условии. Маска принимает 2 значения: -1 и 0.
Вот стандартный способ применения маски из флага cf:
cmp ax,dx ;если ax>=dx то обнулить ax sbb dx,dx and ax,dxХотя можно получить маску из любого флага используя setXX:
xor ax,ax ;если cx=dx, то ax=-1, иначе ax=0 cmp сx,dx setne al dec ax- Битовые множества могут существенно сократить количество сравнений и условных переходов.
ensemble db 01000110b ;множество чисел 1,2,6 -//- movzx eax,al bt [ensemble],eax jc quit ;если (al=1) или (al=2) или (al=6) то выход- Модификация кода. Самомодифицирующаяся программа похожа на поезд, который сам прокладывает себе рельсы
mov byte[dirrection],0FDh ;загружаем Маш. код инструкции std rcr al,1 ;если al нечётный то sbb byte[dirrection],0 ;std превращается в cld dirrection: rb 1Можно даже например создать процедуру в стеке, которая сама выбросит себя от туда при завершении:push 0004C2ACh ;маш-коды инструкций 0ACh-lodsb 0004C2h-ret 4 call esp ;вызываем процедуру из стекаПроцедура Variant2Str
Процедура понимает следующие типы:
- varSmallint
- varInteger
- varSingle
- varDouble
- varOleStr
- varBoolean
- varByte
- varWord
- varLongWord
- varInt64
иначе выводит NaN
ограничения:
- длина числа не более 18 десятичных цифр
- требуется наличие WideCharToMultiByte в секции импорта
- логический тип представляется как да/нет
Параметры:
value - указатель на переменную типа variant
lpsz - указатель на выходную строкуprocedure Variant2Str(value,lpsz: pointer);assembler; const power10:array[0..9] of longword=(1000000000, //таблица степеней десяти 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1); opcodes:array[0..3] of byte=($df {fild word[ecx]}, //опкоды для загрузки данных $db {fild dword[ecx]}, $d9 {fld dword[ecx]}, $dd {fld qword[ecx]}); NaNSet: integer=229436; //1111000000000111100b множество //поддерживаемых типов asm finit pusha mov edi,lpsz mov ecx,Value movzx eax,word[ecx] //в eax тип переменной cmp al,$0B //если это vt_bool jne @notbool //то выводим да или нет mov edx,'аД' //и выходим mov eax,'теН' test [ecx+8],al cmovne eax,edx mov [edi],eax popa ret @notbool: cmp al,8 //если это vt_bstr jne @notstring //то воспользуемся xor eax,eax //функцией WideCharToMultiByte push eax //и выходим push eax push 65536 push edi push -1 push [ecx+8] push eax push eax call WideCharToMultiByte popa ret @notstring: push ax fstcw [esp] //устанавливаем максимальную точность FPU or word[esp],0000011100000000b //и режим округления в меньшую сторону fldcw [esp] add ecx,8 //теперь ecx указывает на поле данных cmp ax,20 //если тип не поддерживается ja @NaN //выводим NaN и выходим bt [NaNSet],eax jnc @NaN mov edx,$00C30100 //подготавливаем опкоды mov dl,byte[opcodes+eax-2] //для загрузки данных в зависимости от типа. mov bx,$29bf //в bx опкод для fild qword[ecx] cmp al,16 cmova dx,bx push edx call esp //загружаем данные pop edx fld1 //вычисляем длину целой части числа fld st(1) fabs fyl2x fldl2t fdivp fistp dword[esp-4] mov eax,dword[esp-4] add eax,1 //если log(X)=-1, adc eax,0 //то X<1=> dec eax //=>длина числа=1 (eax=0) cmp al,17 //если число слишком длинное то ja @NaN //выводим NaN и выходим mov edx,eax sub al,8 cmc sbb cl,cl //умножаем на 10(18-<длина числа>) and al,cl //при этом если (18-<длина числа>)>9 fimul dword[power10+eax*4] //то умножаем на 109 fbstp [edi] fldcw [esp] //восстанавливаем настройки FPU pop cx neg eax add eax,edx lea esi,[esp-26+eax+9] //Адрес первого символа в буфере push esi push edi mov esi,edi lea edi,[esp-18] mov ecx,9 @unpack:xor ax,ax //распаковываем BCD число в буфер lodsb shl ax,4 shr al,4 add ax,$3030 //и преобразуем его в ASCII stosw loop @unpack pop edi pop esi shl byte[edi+9],1 //если число отрицательное mov byte[edi],'-' //ставим минус adc edi,0 lea eax,[esp-25] //dx - длина целой части mov ecx,esi //ecx - общая длина sub ecx,eax std @copy: lodsb //переворачиваем строку из буфера mov [edi],al //и отделяем целую часть inc edi //от дробной точкой mov byte[edi],'.' sub dl,1 adc edi,0 loop @copy mov [edi],cl //завершающий ноль scasb mov al,'0' //удаляем лишние нули mov cl,18 //в конце строки repe scasb mov byte[edi+2],ch cmp byte[edi+1],47 //если последний символ - это точка cmc //то удаляем её sbb al,al and byte[edi+1],al cld popa ret @NaN: mov dword[edi],'NaN' pop ax popa end;© murder
OLE Variant 2 ANSI
Дата публикации 11 апр 2008