Доброго времени суток, уважаемые подписчики. Сегодня мы рассмотрим программу, которая использует интерфейсы IMalloc, IShellLink и IPersistFile для создания ярлыка на произвольный объект в произвольном месте.
Вначале давайте разберёмся, что нам нужно сделать. Нам надо получить адрес объекта, для создания ярлыка и адрес, по которому этот ярлык будет помещён. Далее, нужно получить новый интерфейс IShellLink, используя его методы, задать нужную нам информацию о ярлыке, и сохранить ярлык. Мне кажется, для сбора информации вполне можно использовать диалоговое окно.
Напомню, что так как создание файлов ресурсов никак не связанно с ассемблером, все (или почти все) такие файлы я создаю с помощью Borland Resource Workshop и отдельного внимания созданию этих файлов в рассылке не уделяется.
Итак, мы имеем диалоговое окно с тремя полями ввода информации. Для облегчения выбора адреса, так-же, создадим две кнопки "Обзора". При нажатии на первую кнопку нам нужно найти адрес сохранения файла - никаких проблем, используем GetSaveFileName. А вот во втором случае нас устроят и файлы и папки (так как ярлык может быть создан как на файл так и на папку). В этом случае, считаю правильным использовать функцию SHBrowseForFolder с установленным флагом BIF_BROWSEINCLUDEFILES. Проблема заключается в том, что это функция относится к функциям Shell-а, что для нас означает то, что при успешном выполнении функции мы получим не адрес выбранного объекта, а его PIDL... Так как программирование Shell-а я хотел бы рассмотреть отдельно и более детально, поэтому пока нам будет достаточно того, что PIDL вполне можно преобразовать в обычный адрес. Для этого используется функция SHGetPathFromIDList. После того как нужная информация была получена нам следует приступить к самому созданию ярлыка. Для этого нам нужно создать новый IShellLink. Если не существует специальной функции для создания определённого интерфейса (а иногда и тогда, когда такая функция существует) нужно использовать функцию CoCreateInstance, которая должна получать следующие параметры: CLSID ассоциированный с создаваемым объектом, указатель на контрольный IUnknown (нам не нужен), флаги создания нового интерфейса, идентификатор интерфейса и адрес двойного слова, в которое будет сохранён наш интерфейс. Опередив вопросы, хочу заметить, что CLSID и идентификатор интерфейса это заранее известные 16-и байтовые константы. По каким-то причинам их принято записывать как одно двойное слово, два слова и 8 байт, но это для нас не так важно. Интерфейс IShellLink имеет следующие методы:
Метод 4 = GetPath Возвращает адрес объекта
Метод 5 = GetIDList Возвращает адрес объекта в формате PIDL-а
Метод 6 = SetIDList Устанавливает адрес объекта из PIDL-а
Метод 7 = GetDescription Возвращает описание объекта
Метод 8 = SetDescription Устанавливает описание объекта
Метод 9 = GetWorkingDirectory Возвращает директорию по умолчанию
Метод 10 = SetWorkingDirectory Устанавливает директорию по умолчанию
Метод 11 = GetArguments Возвращает параметры, указанные в командной строке
Метод 12 = SetArguments Задаёт параметры, указанные в командной строке
Метод 13 = GetHotkey Возвращает горячую клавишу, ассоциированную с ярлыком
Метод 14 = SetHotkey Устанавливает горячую клавишу, ассоциированную с ярлыком
Метод 15 = GetShowCmd Возвращает заданные команды начального отображения окна
Метод 16 = SetShowCmd Задаёт команды начального отображения окна
Метод 17 = GetIconLocation Возвращает адрес иконки ярлыка
Метод 18 = SetIconLocation Устанавливает адрес иконки ярлыка
Метод 19 = SetRelativePath Устанавливает относительный адрес
Метод 20 = Resolve Используется для редактирования уже созданных ярлыков
Метод 21 = SetPath Устанавливает адрес объекта
Из них нас пока интересуют лишь SetPath, SetDescription и QueryInterface (это метод IUnkonwn, не надо забывать, что первые три метода каждого интерфейса - методы IUnknown). Думаю, названия первых двух методов говорят сами за себя, а QueryInterface нам понадобится для получения интерфейса IPersistFile, ассоциированного с нашим IShellLink. IPersistFile используется для сохранения объектов на диск и их загрузки с диска. Он имеет следующие методы:
Метод 4 = GetClassID Возвращает CLSID объекта
Метод 5 = IsDirty Проверяет, изменялся ли объект с момента последнего сохранения
Метод 6 = Load Загружает файл и инициализирует объект
Метод 7 = Save Сохраняет объект на диск
Метод 8 = SaveCompleted пока неважно
Метод 9 = GetCurFile Возвращает имя файла, ассоциированное с объектом
Используя метод Save этого интерфейса, мы и сохраняем наш ярлык на диск. При использовании этого метода нужно обратить внимание на то, что передаваемые ему строки должны быть UNICODE-строками (UNICODE формат предполагает, что каждый символ строки занимает одно слово, а не один байт. Это позволяет хранить символы из разных кодовых страниц даже в одной строке. Подробнее с использованием UNICODE мы познакомимся позже, при обсуждении многоязыковых приложений). Так как мы используем не UNICODE строки нам нужно будет перевести нашу строку в UNICODE. Для этого можно использовать функцию MultiByteToWideChar (её использование смотри в приложении).
Прежде чем приступить к самой программе хочу заметить, что почти все интерфейсы являются частью COM (Component Object Model), поэтому прежде чем работать с ними нам нужно инициализировать COM библиотеку, а после завершения работы с ней деинициализировать её. Это можно сделать при помощи функций CoInitializeEx (можно использовать и функцию CoInitialize, но microsoft советует не использовать её в новых приложениях) и CoUninitialize. Исключением является IMalloc, который можно использовать и не инициализируя COM библиотеку (microsoft даже советует использовать функции CoTaskMemAlloc и CoTaskMemFree вместо методов Alloc и Free интерфейса IMalloc, но в учебных целях можно этот совет оставить без внимания).
Итак, приступим, наконец, к самой программе:
include kernel32.inc ;добавляем описания функций,
include user32.inc ;макросов и констант
include def32.inc
include macros.inc
include ole32.inc
include shell32.inc
include comdlg32.inc
.386 ;386-ой процессор, flat память
.model flat
option casemap:none ;Различать маленькие и большие буквы
;сегмент данных
_DATA SEGMENT public USE32 'data' ;Коды-идентефикаторы интерфейсов и классов
CLSID_ShellLink dd 000021401h
dw 0, 0
db 0c0h, 0, 0, 0, 0, 0, 0, 46h
IID_IShellLinkA dd 0000214EEh
dw 0, 0
db 0c0h, 0, 0, 0, 0, 0, 0, 46h
IID_IPersistFile dd 00000010bh
dw 0, 0
db 0c0h, 0, 0, 0, 0, 0, 0, 46h ;указатели на обработчики WM_COMMAND
Procz dd ExitProc
dd MakeProc
dd SaveProc
dd BrowseProc
COUNT_COMMANDPROC = (($-(offset Procz)) shr 2) - 1 ;фильтр для GetSaveFileName
aFilters db '.lnk files',0,'*.'
aDefExt db 'lnk',0,0
_DATA ENDS
;не инициализированные данные
_BSS SEGMENT public USE32 'bss'
IMalloc dd ?
BrowseInfo BROWSEINFO<>
SaveName OPENFILENAME<>
_BSS ENDS
;сегмент кода
_TEXT SEGMENT public USE32 'code' ;Получаем hInstance и вызываем диалог, после завершаем программу
_start:
null ebx
run GetModuleHandle, ebx
run DialogBoxParam, eax, 01h, ebx, offset DlgProc, ebx
run ExitProcess, ebx ;Процедура диалогового окна
DlgProc proc
null eax
push ebp
mov ebp,esp ;создаём стековой кадр
pusha
mov edi,[ebp+08h] ;заносим handle окна в edi
null ebx ;обнуляем ebx
mov eax,[ebp+0Ch] ;заносим в eax сообщение и обрабатываем его
cmp eax,WM_COMMAND
je command_proc
cmp eax,WM_INITDIALOG
je init_proc
cmp eax,WM_CLOSE
je close_proc
popa
leave
ret 16
SaveProc label dword ;если нажат "обзор" для создаваемого файла
;IMalloc -> Alloc(4096) ;создаём буфер push 4096
mov eax,IMalloc
push eax
mov eax,[eax]
run dword ptr [eax+0Ch]
jeaxz close_proc ;если ошибка, выходим из программы
mov [eax],ebx ;попробуйте убрать эту строку...
mov SaveName.lpstrFile, eax
run GetSaveFileName, offset SaveName
jeaxz EndWndProc ;Если нажата "отмена", то не делать ничего
run SetDlgItemText, edi, 104, SaveName.lpstrFile
;IMalloc -> Free(SaveName.lpstrFile) ;уничтожаем буфер push SaveName.lpstrFile
mov eax,IMalloc
push eax
mov eax,[eax]
run dword ptr [eax+14h]
jmp EndWndProc
BrowseProc label dword ;если нажат "обзор" для объекта
;IMalloc -> Alloc(4096) ;создаём буфер push 4096
mov eax,IMalloc
push eax
mov eax,[eax]
run dword ptr [eax+0Ch]
jeaxz close_proc ;Если ошибка, то выходим
mov BrowseInfo.pszDisplayName, eax
run SHBrowseForFolder, offset BrowseInfo ;Запрашиваем файл или папку
jeaxz EndWndProc ;Если была нажата "Отмена" push eax;для последующего IMalloc->Free
run SHGetPathFromIDList, eax, BrowseInfo.pszDisplayName ;PIDL->Строка
;IMalloc -> Free(eax) ;уничтожаем PIDL, который вернула SHBrowseForFolder mov eax,IMalloc
push eax
mov eax,[eax]
run dword ptr [eax+14h]
run SetDlgItemText, edi, 105, BrowseInfo.pszDisplayName
;IMalloc -> Free(BrowseInfo.pszDisplayName) ;уничтожаем буфер push BrowseInfo.pszDisplayName
mov eax,IMalloc
push eax
mov eax,[eax]
run dword ptr [eax+14h]
jmp EndWndProc
command_proc:
movzx ecx,word ptr [ebp+10h]
sub ecx, 100
js EndWndProc
cmp ecx, COUNT_COMMANDPROC
ja EndWndProc
jmp dword ptr Procz[ecx*4]
EndWndProc:
popa
inc eax
leave
ret 16
ExitProc label dword ;Если нажата кнопка "Закрыть" или Alt+F4
close_proc:
;IMalloc -> Release() ;Уничтожаем наш интерфейс mov eax,IMalloc
push eax
mov eax,[eax]
run dword ptr [eax+08h]
run CoUninitialize ;деинициализируем COM библиотеку
run EndDialog, edi, ebx ;уничтожаем диалог
jmp EndWndProc
MakeProc label dword ;Нажата кнопка "Создать"
run AllocMemAndGetText, 104 ;получаем адрес создаваемого ярлыка
push eax
run AllocMemAndGetText, 105 ;получаем объект, на который указывает ярлык
push eax
run AllocMemAndGetText, 106 ;получаем описание ярлыка
push eax
run CreateLink ;эта функция создаёт ярлык, она получает три параметра, ;но (!) не удаляет их из стека
pop eax ;получаем буфера из стека и уничтожаем их
;IMalloc -> Free(eax) push eax
mov eax,IMalloc
push eax
mov eax,[eax]
run dword ptr [eax+14h]
pop eax
;IMalloc -> Free(eax) push eax
mov eax,IMalloc
push eax
mov eax,[eax]
run dword ptr [eax+14h]
pop eax
;IMalloc -> Free(eax) push eax
mov eax,IMalloc
push eax
mov eax,[eax]
run dword ptr [eax+14h]
run MessageBeep, ebx
jmp EndWndProc
init_proc: ;при создании диалога
run CoInitializeEx, ebx, COINIT_APARTMENTTHREADED ;инициализируем COM библиотеку
test eax,eax
js close_proc ;если ошибка, то выходим
run CoGetMalloc, 01h, offset IMalloc ;получаем наш интерфейс IMalloc
test eax,eax
js close_proc ;если ошибка, то выходим
mov BrowseInfo.hwndOwner,edi ;инициализируем структуры поиска файлов
mov BrowseInfo.ulFlags, BIF_DONTGOBELOWDOMAIN + BIF_NEWDIALOGSTYLE + BIF_BROWSEINCLUDEFILES
mov SaveName.lStructSize,sizeof(OPENFILENAME)
mov SaveName.hwndOwner, edi
mov SaveName.lpstrFilter,offset aFilters
mov SaveName.nMaxFile, 2048
mov SaveName.Flags, OFN_EXPLORER or OFN_OVERWRITEPROMPT or OFN_PATHMUSTEXIST
mov SaveName.lpstrDefExt,offset aDefExt
jmp EndWndProc
DlgProc endp
;Функция создаёт буфер нужного размера и получает
;в него информацию из Edit box-ов диалога
AllocMemAndGetText proc
push edi
run GetDlgItem, edi, dword ptr [esp+08h]
mov esi,eax ;получаем в esi handle EditBox-а
run GetWindowTextLength, eax ;получаем длину текста
inc eax ;Буфер должен содержать место и для завершающего строку нуля
push eax ;дла функции GetWindowText, см. ниже
;IMalloc -> Alloc(eax) ;создаём буфер push eax
mov eax,IMalloc
push eax
mov eax,[eax]
run dword ptr [eax+0Ch]
mov edi,eax ;временно сохраняем адрес буфера в edi
run GetWindowText, esi, eax ;считываем текст
mov eax,edi ;восстанавливаем адрес буфера
pop edi
ret 4
AllocMemAndGetText endp
;Функция создаёт ярлык, она получает три параметра через стек,
;но (!) не удаляет их из стека
CreateLink proc
enter 408h, 00h ;создаём стековой кадр резервируя буфер ;для UNICODE-строки и двух интерфейсов
lea eax,[ebp-04h] ;получаем IShellLink в [ebp-04h]
run CoCreateInstance, offset CLSID_ShellLink, ebx, CLSCTX_INPROC_SERVER, offset IID_IShellLinkA, eax
;[ebp-04h] -> SetPath(dword ptr [ebp+0Ch]) ;устанавливаем адрес объекта push dword ptr [ebp+0Ch]
mov eax,[ebp-04h]
push eax
mov eax,[eax]
run dword ptr [eax+50h]
;[ebp-04h] -> SetDescription(dword ptr [ebp+08h]);устанавливаем описание push dword ptr [ebp+08h]
mov eax,[ebp-04h]
push eax
mov eax,[eax]
run dword ptr [eax+1Ch]
lea eax,[ebp-08h] ;получаем IPersistFile
;[ebp-04h] -> QueryInterface(offset IID_IPersistFile, eax) push eax
push offset IID_IPersistFile
mov eax,[ebp-04h]
push eax
mov eax,[eax]
run dword ptr [eax]
lea esi,[ebp-408h] ;esi = буфер для UNICODE-строки
;Конвертируем строку в UNICODE
run MultiByteToWideChar, ebx, ebx, dword ptr [ebp+10h], -1, esi, 512
;[ebp-08h] -> Save(esi, 01h) ;Сохраняем ярлык на диск push 01h
push esi
mov eax,[ebp-08h]
push eax
mov eax,[eax]
run dword ptr [eax+18h]
;[ebp-08h] -> Release() ;уничтожаем наши интерфейсы mov eax,[ebp-08h]
push eax
mov eax,[eax]
run dword ptr [eax+08h]
;[ebp-04h] -> Release() mov eax,[ebp-04h]
push eax
mov eax,[eax]
run dword ptr [eax+08h]
leave
ret
CreateLink endp
end _start ;конец программы
_TEXT ENDS
Давайте рассмотрим отдельно действие функции SHBrowseForFolder. Единственным её параметром является адрес заполненной структуры BROWSEINFO.
BROWSEINFO STRUCT
hwndOwner dd ? ;окно создатель
pidlRoot dd ? ;PIDL дли инициализации
pszDisplayName dd ? ;буфер для имени выбранного объекта
lpszTitle dd ? ;указатель на нестандартный заголовок окна
ulFlags dd ? ;флаги
lpfn dd ? ;адрес процедуры, вызываемой, если определённое событие произошло
lParam dd ? ;передаваемый функции параметр
iImage dd ? ;сюда возвращается индекс иконки объекта в системном списке иконок
BROWSEINFO ENDS
Однако, возвращаемое этой функцией значение гораздо интереснее. В случае, если юзер нажал на кнопку "отмена", то функция возвращает ноль, но если был выбран объект, то функцией возвращается указатель на блок памяти, содержащий PIDL данного объекта. Причём задача освобождения этого блока памяти ложится на вызвавшую функцию программу. Так как блок памяти был выделен тем-же механизмом, который использует IMalloc, после перевода PIDL в строку мы закрываем его методом Free нашего интерфейса IMalloc.
Все вызовы методов в программе выделены красным цветом. Почему методы вызываются именно так мы достаточно детально рассмотрели в прошлом уроке. Если у вас возникли любые вопросы по этой программе, пишите
Скомпилированную программу и всё необходимое для её компиляции можно скачать по следующим адресам: 0035asm - код программы и файл ресурсов 0035inc - файлы дополнения 0035lib - библиотеки импорта 0035exe - скомпилированная программа compile - необходимые для компиляции программы
Так как личный опыт ни капли не важнее знаний, сегодня я предлагаю вам сделать что-то вроде домашнего задания. Я дам вам небольшое задание и буду ждать от вас результаты ваших трудов. Естественно, это задание не обязательно, но помните, что нельзя научиться программировать просто разбираясь в чужих программах. Программа, которую мы сегодня рассмотрели, является не только достаточно хорошим примером использования интерфейсов, но и примером программы, которой нельзя пользоваться никому, кроме её создателя! Почему? Потому-что в ней отсутствует проверка результатов методов и функций, возврат в стабильное положение, в случае ошибки, и уведомление юзера о случившейся ошибке. Сегодняшнее задание, убрать как можно больше таких недостатков, то есть сделать проверку возвращаемых значений методов и процедур, и, в случае ошибки, по возможности, как-то вернуться к исходному положению. При этом надо сообщить об ошибке юзеру. Далее приведённая таблица может вам помочь...
Функция/Метод
Значение в случае удачи
Значение в случае ошибки
CoInitializeEx
0
негативное значение (бит 31 = 1)
CoGetMalloc
0
негативное значение (бит 31 = 1)
IMalloc::Alloc
указатель на блок памяти
0
CoCreateInstance
0
негативное значение (бит 31 = 1)
Любой метод IShellLink
0
негативное значение (бит 31 = 1)
Любой метод IPersistFile
0
негативное значение (бит 31 = 1)
Далее, следует помнить, что если интерфейс был успешно создан, то он должен быть уничтожен вне зависимости от успешности произведённых с его помощью действий. Теперь дело только за вами. Жду ваши программы в течении двух недель. По прошествии этого срока, самый лучший вариант выполнения этого задания будет опубликован в рассылке (естественно с именем его автора).
На сегодня это всё. Если есть какие вопросы, пишите. Пишите на Dark_Lord@RusFAQ.ru. Предыдущие выпуски можно найти на сайте рассылки