Как написать AddIn для WinAsm Studio — Архив WASM.RU

Все статьи

Как написать AddIn для WinAsm Studio — Архив WASM.RU

 

"Дай порулить!"
(Неизвестный автор)

исходные тексты

0. Предисловие.

 Каждому в глубине души хочеться иметь "свой" редактор. Однако добавить в IDE новую возможность значительно проще, чем разработать ее целиком. Основным препятствием для этого (кроме лени) является необходимость изучения работы IDE и интерфейса для AddIn-ов. Сами же разработчики IDE заинтересованы в такой помощи по понятным причинам, и стремятся упростить это узкое место. Данная статья призвана пролить некоторый свет на этот вопрос пользователям WinAsm Studio и базируется на файлах WinAsm.chm, WAAddIn.inc, анализе кода других AddIn-ов, в том числе примеров, а также на небольшом собственном опыте. Естественно, она не является ни полным переводом, ни исчерпывающим пособием.

В настоящее время я использую версию WinAsm-а 4.0.3, однако большинство из написанного действительно и для версий 3.х.х и даже более раних. Необходимо заметить, что Антонис Киприянов, автор WinAsm-а не только постоянно совершенствует свою IDE, но и развивает интерфейс для AddIn-ов, внемля просьбам трудящихся.

Автор WinAsm Studio: Antonis Kyprianou.

Составитель WinAsm.chm: Jim Giordano.

Сайт программы: http://www.winasm.net

1. Что есть AddIn и как его использовать в WinAsm.

 AddIn - это подключаемый код, предназначенный для расширения функций редактора и размещенный в отдельной dll. Для того, чтобы WinAsm его нашел, dll нужно разместить в подкаталоге AddIns домашнего каталога WinAsm-а. Под "подключением" AddIn-а имеется ввиду загрузка и инициализация соответствующей dll. Для того, чтобы загрузить AddIn, необходимо открыть менеджер AddIn-ов (Add-Ins Manager) в меню Add-Ins. Выбрав нужный AddIn в списке, отметить чекбокс "Loaded/Unloaded" чтобы его загрузить. Также можно отметить чекбокс "Load On StartUp", тогда AddIn будет загружаться автоматически при загрузке WinAsm. Для своего функционирования AddIn должен экспортировать функции, часть из которых обязательна. WinAsm, в свою очередь, предоставляет AddIn-у указатели на внутренние структуры с важными переменными, а также выполняет ряд сервисов, обрабатывая дополнительные сообщения в оконной процедуре главного окна. Основной поток сообщений, получаемых окнами WinAsm-а, пропускается вначале через соответствующие процедуры загруженых AddIn-ов, при этом AddIn может не только выполнить свой код, получив соответствующее сообщение, но и прекратить его дальнейшую обработку, вернув TRUE в eax, полностью подменив, таким образом, некоторую встроенную функцию своим кодом.

2. Что нужно для написания AddIn-а.

Исходник AddIn-а представляет из себя обычный проект dll и включает в себя, как минимум:

  • addin.asm - исходный текст.
  • addin.def - файл, в котором определяются имена экспортируемых функций.
  • WAAddin.inc - файл, в котором определены структуры, используемые в WinAsm, и ряд констант, в том числе и дополнительные сообщения, обрабатываемые контролом окна редактора CodeHi.dll.

WAAddin.inc регулярно обновляется вместе с появлением новых функций в WinAsm-е. Он обширно откомментирован и снабжен примерами использования некоторых возможностей. Чтобы быстро создать шаблон нового AddIn-а, можно вызвать мастер новых проектов Ctrl+N, и в разделе "Bare Bone" выбрать тип проекта "AddIn". Можно также использовать исходные тексты примеров или рабочих AddIn-ов, так как практически все они ими снабжены.

Также для написания AddIn-а удобно сразу же в имя результирующего бинарного файла включить путь к папке с AddIn-ами. При компиляции необходимо учитывать, что она возможна только тогда, когда AddIn выгружен. Для обхождения этого неудобства можно использовать другой AddIn - Enchanced Add-Ins Manager - улучшенный менеджер AddIn-ов (автор - Mario Vilas), который при компиляции автоматически выгружает текущий AddIn, а после нее - загружает. Однако он не всегда это делает успешно, если при компиляции произошла ошибка. При написании AddIn-а необходимо быть готовым к тому, что WinAsm из-за него рухнет, поэтому не торопитесь включать для него опцию "Load On StartUp". Если же это произошло при изменении существующего AddIn-а, который загружается вместе с WinAsm-ом и рушит его, отключить его автоматическую загрузку можно убрав строку с названием соответствующей dll из раздела [ADDINS] файла winasm.ini (расположен в домашнем каталоге WinAsm-а). WinAsm позволяет запускать себя в "защищенном от AddIn-ов" режиме при удержании F8, однако это оказалось малоэффективном при старте его из разных файловых менеджеров, которые сами обрабатывают нажатие F8. Возможно, в дальнейшем это будет исправлено. Для отладки AddIn-ов можно использовать как vkdebug, идущий в составе пакета masm32, так и любые другие отладчики, запуская под ними WinAsm, загружая AddIn и переходя к отладке. Для отладки я обычно использую следующий макрос, исключающий сообщение об ошибке при запуске WinAsm-а с AddIn-ом вне отладчика:

  deb     macro
        local nodeb
        push eax
        invoke IsDebuggerPresent
        test eax,eax
        pop eax
        jz nodeb
        int 3
nodeb:	
endm

3. Механизм загрузки, выгрузки и настройки AddIn-а.

 Возможны следующие варианты обращения к dll AddIn-а:

А. Получение сведений об AddIn-е менеджером AddIn-ов

- AddIn не загружен:

  DllEntry  (DLL_PROCESS_ATTACH)
  GetWAAddinData
  DllEntry  (DLL_PROCESS_DETACH)

- AddIn загружен:

GetWAAddInData

Б. Настройка AddIn-а из менеджера AddIn-ов

- AddIn не загружен:

	DllEntry  (DLL_PROCESS_ATTACH)
  WAAddInConfig
  DllEntry  (DLL_PROCESS_DETACH)

- AddIn загружен:

WAAddInConfig

В. Загрузка AddIn-а:

DllEntry  (DLL_PROCESS_ATTACH)
WAAddInLoad

Г. Выгрузка AddIn-а:

WAAddInUnload
DllEntry  (DLL_PROCESS_DETACH)

4. Обязательные процедуры.

 A. DllEntry

Это стандартная процедура DllMain, она вызывается при загрузке/выгрузке динамической библиотеки. В AddIn-е используется для получения хэндла dll при загрузке.

 DllEntry Proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD

hInst - хэндл модуля dll;
reason - параметр, определяющий причину вызова данной функции, в нашем случае может принимать следующие значения:
DLL_PROCESS_ATTACH - выполняется загрузка библиотеки в память процесса,
DLL_PROCESS_DETACH - выполняется выгрузка библиотеки из памяти процесса;
reserved1 - он и в Африке reserved1.

Процедура DllEntry присутствует в шаблоне и обычно не нуждается в модификации.

Б. GetWAAddInData

GetWAAddInData Proc lpFriendlyName:PTR BYTE, lpDescription:PTR BYTE
Invoke lstrcpy, lpDescription, Offset szDescription
Invoke lstrcpy, lpFriendlyName, Offset szFriendlyName
RET
GetWAAddInData EndP

Простая процедура, вызываемая WinAsm для получения удобочитаемых названий и описаний AddIn-ов для последующего их отображения в списке менеджера. Также присутствует в шаблоне. В модификации нуждаются текстовые переменные szFriendlyName и szDescription, содержащие соответственно имя и краткое описание AddIn-а, выводимые менеджером AddIn-ов в списке.

Наример, в AddIn.Inc записываем:

szFriendlyName    DB "Простой AddIn",0
szDescription     DB "Этот AddIn позволяет читать и "
                  DB "вставлять слово под курсором.",0

В. WAAddInLoad

Процедура, вызываемая при инициализации AddIn-а.

WAAddInLoad Proc pWinAsmHandles:DWORD, features:PTR DWORD

В качестве параметров передаются указатели на структуру с важнейшими хэндлами HANDLES (pWinAsmHandles) и на структуру с разными переменными features (содержит номер версии WinAsm-а, указатели на списки процедур загруженых AddIn-ов, на переменную с хэндлом дочернего окна редактора ресурсов и на список меток редактора ресурсов). Эти структуры подробнее описаны в WAAddIn.inc.

В этой процедуре AddIn может скопировать в свои переменные такие важные значения, как хэндл главного окна WinAsm-а (HANDLES.hMain), хэндл клиентской части главного окна (HANDLES.hClient), хэндл главного меню (HANDLES.hMenu), и многие другие. Конечно же, можно запомнить и сам указатель на структуру WinAsmHandles, однако нельзя сказать, что это удобней.

Также в этой процедуре AddIn может произвести собственную инициализацию: выделить память, добавить свои опции в меню и т.п. Значение, возвращаемое этой процедурой, свидетельствует об успешности инициализации AddIn-а, и в случае неудачи (eax=TRUE) AddIn будет выгружен.

Г. WAAddInUnload

Данная процедура не имеет параметров и вызывается при выгрузке AddIn-а. Здесь AddIn должен освободить выделенную ему память, удалить свои опции меню, и т.п. То есть, восстановить status quo.

5. Необязательные процедуры.

Для использования необязательных процедур AddIn-а в WinAsm их названия необходимо вписать (или декомментировать) в разделе EXPORTS def-файла проекта.

А. WAAddInConfig

Процедура настройки AddIn-а. Вызывается кнопкой из менеджера AddIn-ов и имеет те же параметры, что и процедура WAAddInLoad. Удобна для настройки AddIn-ов, не создающих свои опции меню или окна, а также позволяет настраивать AddIn-ы, которые в данный момент не загружены.

Б. Процедуры обработки сообщений окон:

FrameWindowProc - получает все сообщения, посылаемые главному (MDI) окну WinAsm.

ChildWindowProc - получает все сообщения, посылаемые всем дочерним окнам WinAsm.

ProjectExplorerProc - получает все сообщения, посылаемые окну менеджера проекта.

OutWindowProc - получает все сообщения, посыламые окну вывода результатов компиляции.

все эти процедуры имеют одинаковый формат, соответствующий формату процедуре окна:

Procname Proc hWnd:DWORD uMsg:UINT, wParam:WPARAM, lParam:LPARAM

Перед тем, как процедура окна WinAsm обработает сообщение сама, это сообщение будет пропущено через все соответствующие процедуры AddIn-ов. Если какая-либо процедура AddIn-а вернула eax=TRUE, дальнейшая обработка данного сообщения прекращается. При этом следует заметить, что если два AddIn-а обрабатывают одинаковое сообщение, то первенство будет согласно загрузки, а порядок загрузки AddIn-ов пока не регламентировался. После обработки сообщения WM_COMMAND самим WinAsm-ом всем процедурам FrameWindowProc дополнительно посылается сообщение WAE_COMMANDFINISHED с теми же параметрами, что дает возможность выполнить код после обработки команды редактором (а не перед).

6. Работа с меню.

Вероятно, AddIn-у потребуется создать свою опцию меню. Для этого могут понадобиться либо хэндл главного меню, либо хэндл одного из существующих выпадающих меню. Их можно взять из структуры HANDLES и вложеной структуры POPUPMENUS, их соответствие понятно из названий:

POPUPMENUS STRUCT
    hFileMenu           HWND
    hEditMenu           HWND
    hViewMenu           HWND
    hProjectMenu        HWND
    hFormatMenu         HWND
    hDialogMenu         HWND
    hMakeMenu           HWND
    hSetActiveBuildMenu HWND  субменю в меню "Make"
    hToolsMenu          HWND
    hAddInsMenu         HWND
    hWindowMenu         HWND
    hHelpMenu           HWND
    hNewFileMenu        HWND  субменю в меню "File"
    hConvertMenu        HWND  субменю в меню "Format" 
POPUPMENUS ENDS

Например, если мы решили создать новую опцию в выпадающем меню "Add-Ins", то его хэндл мы можем получить так (в процедуре WAAddInLoad):

   mov ebx,pWinAsmHandles   ; загружаем в ebx указатель на структуру с хэндлами
   push [ebx].HANDLES.POPUPMENUS.hAddInsMenu ; получаем хэндл субменю "Add-Ins"
   pop hSubM                ; сохраняем хэндл в нашей переменной

Далее, для создания новой опции меню нам потребуется уникальный идентификатор, который можно получить, послав сообщение WAM_GETNEXTMENUID главному окну:

   invoke SendMessage,hMain,WAM_GETNEXTMENUID,0,0
   mov MenuID,eax

Теперь можно создавать свою опцию меню:

invoke InsertMenu,hSubM,MenuID,MF_BYCOMMAND or MF_OWNERDRAW,MenuID,\
                                                          ADDR menu_caption

При выгрузке AddIn-а (в процедуре WAAddInUnload) следует удалить свою опцию меню:

invoke DeleteMenu,hSubM,MenuID,MF_BYCOMMAND

Хэндл главного окна нужно взять из структуры HANDLES. Там же можно получить и хэндл главного меню:

 ...
   push [ebx].HANDLES.hMain ; получаем хэндл главного окна
   pop hMain
   push [ebx].HANDLES.hMenu ; получаем хэндл меню
   pop hMenu
   ...

Иногда AddIn может обрабатывать существующие команды меню, тогда создавать свои идентификаторы не нужно. Встроенные идентификаторы перечислены в WAAddIn.inc. Обработав таким образом некоторый идентификатор и вернув в eax значене TRUE AddIn может полностью подменить соответствующую функцию WinAsm-а своей. AddIn может также заставлять WinAsm выполнять нужные функции, посылая встроенные идентификаторы главному окну WinAsm-а.

7. Создание плавающих окон (docking windows).

 Для создания такого рода окон необходимо использовать внутренние сообщения главному окну WinAsm-а, описанные в разделе "Docking windows" файла WAAddIns.inc:

WAM_CREATEDOCKINGWINDOW - создать окно
WAM_GETCLIENTRECT - получить размеры клиентской области окна
WAM_DESTROYDOCKINGWINDOW - удалить окно

Также для создания такого окна необходимо будет заполнить структуру DOCKINGDATA, описанную там же.

8. Работа с окном редактора.

 Вероятно, AddIn-у потребуется обратиться к окну редактора для анализа/модификации редактируемого текста. Для этого вначале необходима небольшая рутинная последовательность:

   ...
   ; получаем хэндл активного дочернего окна
   invoke SendMessage,hClient,WM_MDIGETACTIVE,0,0
   ; если таковое отсутствует - выходим...
   test eax,eax
   jz wp_exit
   ; получаем указатель на структуру с данными окна
   invoke GetWindowLong,EAX,0
   ; получаем из структуры хэндл контрола ричедита
   mov eax,[eax].CHILDDATA.hEditor
   mov hEditor,eax
   ...

Также во многих случаях желательно убедиться в том, что окно видимо при помощи функции IsWindowVisible. Теперь можно работать с окном редактора. Сообщения, которые поддерживает контрол, перечислены в WAAddIn.Inc и состоят как из ряда стандартных сообщений, так и дополнительных, которые начинаются на CHM_ (CodeHi Message). Стандартные сообщения в WAAddIn.inc закомментированы, так как они уже описаны в windows.inc. Смысл большинства сообщений понятен из названия.

Например, получим слово, находящееся под курсором:

invoke SendMessage,hEditor,CHM_GETWORD,128,offset BufferForWord

Также в структуре CHILDDATA можно найти другие полезные данные: имя файла с полным путем, внутренний тип файла и некоторые другие. Эта структура также описана в WAAddIn.inc.

9. Работа с акселераторами.

Для добавления своих клавиатурных акселераторов можно воспользоваться хэндлом таблицы акселераторов, указатель на него имеется в структуре HANDLES: HANDLES.phAcceleratorTable. Работа с таблицей акселераторов показана на примере (неидеальном, но работающем) в демонстрационном AddIn-е AccelaratorDemoAddIn.

10. Хранение настроек.

Для настройки AddIn-а можно использовать как процедуру WAAddInConfig, так и другие методы. Преимущество первого способа заключается в том, что процедура настройки конфигурации может быть вызвана у незагруженного AddInа, или в том случае, когда у AddIn-а нет своих окон, что избавляет от необходимости создавать специальные опции меню только для настройки AddIn-а. Соблюдения порядка ради автором WinAsm-а предлагается хранить настройки AddIn-а в файле WAAddIns.ini, находящегося в каталоге AddIns вместе с AddInами. Для хранения своих настроек желательно создать свою секцию, соответствующую названию AddIn-а, где и хранить значения переменных. Запись/чтение переменных производится при помощи функций WritePrivateProfileString и GetPrivateProfileString.

11. Дополнительные возможности для разработки AddIn-ов.

На сайте WinAsm Studio меется пакет WAAddInLib, разработанный Mario Vilas (QvasiModo), в котором часть рутинного кода оформлено в виде процедур, что позволяет ускорить написание AddIn-ов. Пакет также регулярно обновляется.

12. Пожелания.

Удачи всем!

Александр Журенков (aka shoo)

2002-2013 (c) wasm.ru