Важная информация
RSS лента

The trick

Inline assembler в VB6.

Рейтинг: 4.00. Голосов: 4.
Всем привет!

Бывают ситуации когда в VB нужно использовать ассемблер. Обычно для этого используют предварительно скомпилированный код размещенный в памяти и запускают его одним из миллиона способов. Очевидным недостатком этого метода является то, что любая модификация ассемблерного кода требует изменения в процедурах размещения кода в памяти. К тому же это является довольно медленной процедурой. Я написал Add-in, который делает вышеописанные процедуры автоматически, а также после компиляции никакие дополнительные действия по размещению кода не выполняются - ассемблерный код влинковывается в EXE. Ассемблерный код работает и в IDE и в скомпилированном (только Native!) коде.

Как этим пользоваться?

Для начала нужно установить Add-in (установщик доступен по ссылке ниже). После установки нужно запустить его из VB (Add-Ins -> Add-in Manager -> Inline assembler). После этого в меню добавится одноименный пункт. Если проект еще не использовал функционал Add-in'а то при первой активации добавится стандартный модуль, в который нужно будет добавить прототипы ассемблерных функций для VB6. Сам модуль можно переименовывать, добавлять процедуры/функции, но размещать в них какой-либо функционал запрещено. После создания модуля можно уже открыть сам редактор ассемблерного кода. В нем доступен раскрывающийся список с именами функций, определенных в добавленном модуле. Для каждой функции можно переопределить код используя синтаксис ассемблера NASM, однако если кода нет, то функция не модифицируется (т.е. получается обычный вызов пустой функции). С каждым проектом (если использовался функционал Add-in'а) связывается еще один файл с расширением *ia в папке с файлом проекта, в котором хранятся ассемблерные процедуры для проекта. Add-in работает "прозрачно", т.е. если Add-in отключен, то проект также будет работать и компилироваться, просто "функции-пустышки" будут работать как обычные. Файл *ia не является жизненно-необходимым для работы проекта, если его не будет, то соответственно "функции-пустышки" останутся нетронутыми.

Давайте посмотрим работу Add-in'а на простом примере. К примеру нам необходимо сложить два Integer-массива без переполнения, т.е. если результат сложения больше 32767 он и останется 32767, а если меньше -32768 он останется -32768. К тому же нам нужно сделать это как можно быстрее. Для этого очень хорошо подходит расширение MMX, в нем есть инструкции для работы с векторными данными с насыщением. Перейдем к реализации. Создадим новый проект, откроем Add-in. Это добавит новый модуль, переименуем его в modInlineAsm. Теперь определим прототип функции:

VB Code:
  1. Public Function MMXAdd( _
  2.                 ByRef dest As Integer, _
  3.                 ByRef src As Integer, _
  4.                 ByVal count As Long) As Long
  5. End Function


Первым параметром передаем первый элемент массива, он же результирующий; вторым параметром первый элемент второго массива; наконец третьим параметром передаем количество элементов. Кстати размер массивов должен быть кратен 8 байтам, т.к. мы будем использовать векторные инструкции которые одновременно работают с 8-ю байтами. Теперь определим процедуру в форме, которая будет вызывать эту функцию:

VB Code:
  1. Private Sub Form_Load()
  2.     Dim src()   As Integer
  3.     Dim dst()   As Integer
  4.     Dim size    As Long
  5.     Dim index   As Long
  6.     
  7.     size = 1024
  8.     
  9.     ReDim src(size - 1)
  10.     ReDim dst(size - 1)
  11.     
  12.     For index = 0 To size - 1
  13.         ' // Fill arrays with sine
  14.         src(index) = Sin(index / 40) * 20000
  15.         dst(index) = Sin(index / 23) * 20000
  16.     Next
  17.     
  18.     ' // Add with saturation
  19.     MMXAdd dst(0), src(0), size
  20.     
  21.     '// Draw result
  22.     AutoRedraw = True
  23.     
  24.     Scale (0, 32767)-(index, -32768)
  25.     
  26.     For index = 0 To size - 1
  27.         If index Then
  28.             Me.Line -(index, dst(index))
  29.         Else
  30.             Me.PSet (index, dst(index))
  31.         End If
  32.     Next
  33.     
  34. End Sub

Как видно здесь просто заполняются 2 массива синусоидой, а потом мы их складываем с помощью нашей функции, и выводим результат на форму в виде графика. Теперь нам нужно переопределить процедуру, для этого активируем Add-in. Откроется окно с редактором кода. В нем мы выбираем нашу функцию и добавляем следующий ассемблерный код:

Assembler Code:
  1. BITS 32
  2.  
  3. ; Addition of two arrays using saturation
  4. ; Size of arrays should be a multiple of 8
  5.  
  6. push    EBP
  7. mov EBP, ESP
  8. push    EBX
  9. push    ESI
  10. push    EDI
  11. mov ESI,DWORD [EBP+0x8]
  12. mov EDI,DWORD [EBP+0x0C]
  13. mov ECX,DWORD [EBP+0x10]
  14. shr ECX,2
  15.  
  16. test    ECX,ECX
  17. je  EXIT_PROC
  18. emms    ; Initialize MMX
  19.  
  20. CYCLE:
  21.     movq    MM0,QWORD [EDI]
  22.     movq    MM1,QWORD [ESI]
  23.     paddsw  MM1,MM0
  24.     movq    QWORD [ESI],MM1
  25.     add ESI,0x8
  26.     add EDI,0x8
  27. loop    CYCLE
  28.  
  29. emms
  30.  
  31. EXIT_PROC:
  32. pop     EDI
  33. pop ESI
  34. pop EBX
  35. mov esp, ebp
  36. pop ebp
  37.  
  38. ret 0x0c

Здесь все просто, если знать инструкции. Самая главная paddsw складывает два четырехмерных вектора 16-битных значений со знаком за одну операцию. Теперь сохраняем проект и запускаем:



Как видно из скриншота, две синусоиды сложились причем с насыщением, это можно заметить по пикам. Теперь давайте попробуем скомпилировать EXE файл и посмотреть что у нас вызывается и во что компилируется:



Как видим код уже находится внутри EXE, без всяких выделений буферов и т.п. ненужных вещей.

Как это работает?

На самом деле все довольно просто. В самом начале ставятся обработчики на ключевые события вроде компиляции, запуска кода, закрытия/сохранения проекта и т.п. При запуске из IDE компилируются все коды заданные пользователем, а также вычисляются адреса переопределяемых функций. Затем код переписывается на код скомпилированный через NASM. И при вызове кода, вызывается наш код. При остановке, код восстанавливается. При компиляции (а точнее перед линковкой) ищется OBJ файл переопределяемого модуля и код функций пустышек переписывается на ассемблерный код, и файл пересохраняется. Для этого я специально писал COFF парсер. Вообще это открывает кучу разных возможностей от замены функций рантайма на свои до шифрования кода. Вообщем много чего можно придумать.

Проект, можно сказать, совсем не тестировался, т.к. у меня в ближайшее время не будет такой возможности, поэтому скорее-всего будет много багов, учитывая то, что половина функционала Add-in'а использует недокументированные трюки, которые возможно работают не так как я думаю. В проекте нет даже подсветки синтаксиса, у меня есть мой самодельный текстбокс с подсветкой написанный с нуля, но его нужно дебажить еще и дописывать, поэтому пока я использовать стандартный текстбокс. Если кого-нибудь заинтересует, и будут баги, пишите сюда.
Кстати, я редко стал писать сюда, да и вообще бывать на форуме, но это не значит что я перестал заниматься VB, у меня еще много проектов которые наполовину реализованы.
̶К̶ ̶п̶р̶и̶м̶е̶р̶у̶ ̶г̶о̶т̶о̶в̶и̶т̶с̶я̶ ̶п̶р̶о̶е̶к̶т̶ ̶д̶л̶я̶ ̶и̶с̶п̶о̶л̶ь̶з̶о̶в̶а̶н̶и̶я̶ ̶о̶п̶т̶и̶м̶и̶з̶а̶ц̶и̶й̶ ̶к̶о̶м̶п̶и̶л̶я̶ц̶и̶и̶ ̶в̶ ̶I̶D̶E̶,̶ ̶т̶.̶е̶.̶ ̶о̶т̶к̶л̶ю̶ч̶и̶т̶ь̶ ̶п̶е̶р̶е̶п̶о̶л̶н̶е̶н̶и̶я̶ ̶п̶р̶и̶ ̶а̶р̶и̶ф̶м̶е̶т̶и̶ч̶е̶с̶к̶и̶х̶ ̶о̶п̶е̶р̶а̶ц̶и̶я̶х̶ ̶д̶л̶я̶ ̶и̶с̶п̶о̶л̶ь̶з̶о̶в̶а̶н̶и̶я̶ ̶ц̶и̶к̶л̶и̶ч̶е̶с̶к̶о̶й̶ ̶а̶р̶и̶ф̶м̶е̶т̶и̶к̶и̶ ̶в̶ ̶I̶D̶E̶,̶ ̶а̶ ̶т̶а̶к̶ж̶е̶ ̶п̶р̶о̶в̶е̶р̶к̶у̶ ̶г̶р̶а̶н̶и̶ц̶ ̶м̶а̶с̶с̶и̶в̶о̶в̶ ̶в̶ ̶I̶D̶E̶.̶ (уже реализовано)
Также к примеру вот:



-нативная загрузка PNG а альфаканалом в IDE в любые контролы. Так что ждите).
Всем спасибо за внимание.


Скачать Add-in.
Метки: Нет Добавить / редактировать метки
Категории
Visual Basic 6.0

Комментарии

  1. Аватар для krotan
    А нет ли примера, как на этом инлайн-ассемблере вызывать функцию из DLL, подключенной динамически?
  2. Аватар для Андрей Балуевский
    Анатолий, блестяще!
    Есть идея таким же образом линковать OBJ-файлы, полученные с помощью Visual C++.
    Спасибо за проект!
  3. Аватар для The trick
    А нет ли примера, как на этом инлайн-ассемблере вызывать функцию из DLL, подключенной динамически?
    В чем сложности? Первый параметр адрес функции.
    Assembler Code:
    1. pop eax
    2. pop ecx
    3. push eax
    4. jmp ecx


    Есть идея таким же образом линковать OBJ-файлы, полученные с помощью Visual C++.
    Спасибо за отзыв. По статической линковке. http://www.vbforums.com/showthread.p...=1#post5257873