Это перевод The format of string resources. Автор: Реймонд Чен. Примечание: в отличие от других постов, этот пост сильно отличается от оригинала. Произведено множество замен от C к Delphi.
В отличие от других типов ресурсов, где идентификатор ресурса совпадает с указаным в *.rc файле, строковые ресурсы упаковываются в "пачки" ("bundles"). В статье Knowledge Base Q196774 это описанно довольно лаконично. Сегодня мы расширим это сжатое описание в работающий код.
...when altering one's mind becomes as easy as programming a computer, what does it mean to be human?..
вторник, 9 декабря 2008 г.
Прозрачная кисть
Это перевод The hollow brush. Автор: Реймонд Чен.
Для чего нужна прозрачная кисть?
Прозрачная кисть - это кисть, которая ничего не делает. Вы можете использовать её в ситуациях, когда от вас требуют кисть, а вы не хотите её использовать.
В качестве примера: вы можете использовать её как классовую кисть. Тогда, когда ваша программа перестаёт реагировать на сообщения, и Windows решает применить "вспышку белого", то она берёт кисть класса и в результате ничего не рисует (по крайней мере так было в Windows 2000. На XP поведение может отличаться).
Ещё одним местом, где вы можете применить прозрачную кисть, является обработка сообщений WM_CTLCOLOR*. Эти сообщения требуют от вас вернуть кисть, которая будет использована для закраски фона. Если вы не хотите стирать фон, то можете использовать прозрачную кисть.
Для чего нужна прозрачная кисть?
Прозрачная кисть - это кисть, которая ничего не делает. Вы можете использовать её в ситуациях, когда от вас требуют кисть, а вы не хотите её использовать.
В качестве примера: вы можете использовать её как классовую кисть. Тогда, когда ваша программа перестаёт реагировать на сообщения, и Windows решает применить "вспышку белого", то она берёт кисть класса и в результате ничего не рисует (по крайней мере так было в Windows 2000. На XP поведение может отличаться).
Ещё одним местом, где вы можете применить прозрачную кисть, является обработка сообщений WM_CTLCOLOR*. Эти сообщения требуют от вас вернуть кисть, которая будет использована для закраски фона. Если вы не хотите стирать фон, то можете использовать прозрачную кисть.
Вспышка белого
Это перевод The white flash. Автор: Реймонд Чен.
Если у вас есть программа, которая долго не обрабатывает сообщения, но при этом по какой-либо причине нуждается в прорисовке (например, кто-то перетащил окно, закрывающее программу), Windows потеряет терпение с вами и закрасит ваше окно белым.
Или, по-крайней мере, так утверждают некоторые люди. В действительности Windows закрашивает ваше окно фоновой кистью класса окна. Поскольку чаще всего туда вписывают COLOR_WINDOW, а COLOR_WINDOW - это белый цвет на большинстве цветовых схем, то в результат получаются белые области.
Почему вообще нужно закрашивать окно? Почему бы не оставить его как есть?
Ну, изначально так и предполагалось делать, но в результате получается, что предыдущее содержание экрана будет показываться на подвисшем окне. Предположим, что вы сейчас смотрите на окно Проводника, потом вы разворачиваете программу, которая не реагирует на сообщения. Внутри окна этой программы вы видите... изображение окна Проводника. И тогда люди будут пытаться щёлкать по тому, что, как они думают, является окном Проводника, но в действительности это зависшая программа.
Поведение Windows XP для зависшей программы немного другое. Теперь система захватывает пиксели зависшего окна и просто рисует эти пиксели снова, если окно не может само нарисовать себя. Заметьте, что если система не может захватить все пиксели (например, окно было частично закрыто), то тогда те части, что не были захвачены, всё равно будут рисоваться классовой кистью...
Которая обычно белая.
Если у вас есть программа, которая долго не обрабатывает сообщения, но при этом по какой-либо причине нуждается в прорисовке (например, кто-то перетащил окно, закрывающее программу), Windows потеряет терпение с вами и закрасит ваше окно белым.
Или, по-крайней мере, так утверждают некоторые люди. В действительности Windows закрашивает ваше окно фоновой кистью класса окна. Поскольку чаще всего туда вписывают COLOR_WINDOW, а COLOR_WINDOW - это белый цвет на большинстве цветовых схем, то в результат получаются белые области.
Почему вообще нужно закрашивать окно? Почему бы не оставить его как есть?
Ну, изначально так и предполагалось делать, но в результате получается, что предыдущее содержание экрана будет показываться на подвисшем окне. Предположим, что вы сейчас смотрите на окно Проводника, потом вы разворачиваете программу, которая не реагирует на сообщения. Внутри окна этой программы вы видите... изображение окна Проводника. И тогда люди будут пытаться щёлкать по тому, что, как они думают, является окном Проводника, но в действительности это зависшая программа.
Поведение Windows XP для зависшей программы немного другое. Теперь система захватывает пиксели зависшего окна и просто рисует эти пиксели снова, если окно не может само нарисовать себя. Заметьте, что если система не может захватить все пиксели (например, окно было частично закрыто), то тогда те части, что не были захвачены, всё равно будут рисоваться классовой кистью...
Которая обычно белая.
Что случилось с DirectX 4?
Это перевод What happened to DirectX 4? Автор: Реймонд Чен.
Если вы в курсе истории DirectX, то вы знаете, что никогда не выходило DirectX 4. Был DirectX 3, а сразу за ним - DirectX 5. Что тут за история?
После выпуска DirectX 3 одновременно стартовала разработка сразу двух последователей: на короткий срок разработки - DirectX 4 и продукт с длинным сроком разработки - DirectX 5.
Но судя по реакции общества разработчиков игр, они не сильно ждали небольшие фишки DirectX 4; зато они были действительно заинтересованы в продвинутом DirectX 5. Так что было принято решение отменить DirectX 4 и добавить все его возможности в DirectX 5.
Так почему бы не переименовать DirectX 5 в DirectX 4?
Потому что уже были написаны сотни и тысячи документов, которые ссылались на два проекта как на DirectX 4 и DirectX 5. Документы, в которых были слова вроде "Возможность XYZ будет доступна только в DirectX 5". Изменение имени в середине цикла разработки создало бы кучу путанницы. А в итоге вы получили бы заголовки типа "Microsoft сняло DirectX 5 с производства - скажите прощай возможности XYZ" и обсуждения, напоминающие Who's on First:
- У меня есть e-mail от вас, где говориться, что возможность ABC не будет готова до DirectX 5. Когда вы планируете выпустить DirectX 5?
- Мы ещё даже не начали планировать DirectX 5; мы полностью сфокусировались на работе над DirectX 4, который, как мы надеемся, будет выпущен к концу весны.
- Но мне нужна возможность ABC и вы сказали, что она не будет готова до DirectX 5.
- А, ну это письмо было написано две недели назад. Потом DirectX 5 был переименован в DirectX 4, а DirectX 4 отменили.
- Так, значит, если я получаю письмо от вас, в котором говориться о "DirectX 5", то мне надо считать, что на самом деле оно говорит о DirectX 4, а если там сказано "DirectX 4", то я должен читать это как "проект, который был отменён"?
- Точно. Только проверьте дату отправки: если она позже последней недели, тогда слова "DirectX 4" в действительности значат новый DirectX 4.
- А что если там будет написано DirectX 5?
- Ну значит кто-то облажался и перепутал.
- Окей, спасибо. Всё ясно как в тумане.
Если вы в курсе истории DirectX, то вы знаете, что никогда не выходило DirectX 4. Был DirectX 3, а сразу за ним - DirectX 5. Что тут за история?
После выпуска DirectX 3 одновременно стартовала разработка сразу двух последователей: на короткий срок разработки - DirectX 4 и продукт с длинным сроком разработки - DirectX 5.
Но судя по реакции общества разработчиков игр, они не сильно ждали небольшие фишки DirectX 4; зато они были действительно заинтересованы в продвинутом DirectX 5. Так что было принято решение отменить DirectX 4 и добавить все его возможности в DirectX 5.
Так почему бы не переименовать DirectX 5 в DirectX 4?
Потому что уже были написаны сотни и тысячи документов, которые ссылались на два проекта как на DirectX 4 и DirectX 5. Документы, в которых были слова вроде "Возможность XYZ будет доступна только в DirectX 5". Изменение имени в середине цикла разработки создало бы кучу путанницы. А в итоге вы получили бы заголовки типа "Microsoft сняло DirectX 5 с производства - скажите прощай возможности XYZ" и обсуждения, напоминающие Who's on First:
- У меня есть e-mail от вас, где говориться, что возможность ABC не будет готова до DirectX 5. Когда вы планируете выпустить DirectX 5?
- Мы ещё даже не начали планировать DirectX 5; мы полностью сфокусировались на работе над DirectX 4, который, как мы надеемся, будет выпущен к концу весны.
- Но мне нужна возможность ABC и вы сказали, что она не будет готова до DirectX 5.
- А, ну это письмо было написано две недели назад. Потом DirectX 5 был переименован в DirectX 4, а DirectX 4 отменили.
- Так, значит, если я получаю письмо от вас, в котором говориться о "DirectX 5", то мне надо считать, что на самом деле оно говорит о DirectX 4, а если там сказано "DirectX 4", то я должен читать это как "проект, который был отменён"?
- Точно. Только проверьте дату отправки: если она позже последней недели, тогда слова "DirectX 4" в действительности значат новый DirectX 4.
- А что если там будет написано DirectX 5?
- Ну значит кто-то облажался и перепутал.
- Окей, спасибо. Всё ясно как в тумане.
Исправление дыр безопасности в чужих программах
Это перевод Fixing security holes in other programs. Автор: Реймонд Чен.
Любой отчёт об ошибке, который включает в себя ошибку переполнения буфера, быстро поднимается в приоритете. Несколько последних таких отчётов, которые мне пришлось разбирать, на самом деле были отчётами об ошибках в чужих программах, которые были обнаружены Windows.
Например, было несколько программ, которые отвечали на уведомление LVN_GETDISPINFO переполнением буфера LVITEM.pszText, записывая туда больше, чем LVITEM.cchTextMax символов.
Другая программа, отвечая на IContextMenu.GetCommandString, переполняла буфер pszName, записывая туда больше, чем cchMax символов.
К счастью, в обоих случаях переполнение было всего на один символ, так что мы смогли исправить это создавая буфер на один символ больше, чем это действительно необходимо, и указывая его размер меньше действительного. Таким образом, если программа переполняла буфер, то она ничего не портила.
Другая программа переполняла свой собственный стековый буфер, если вы щёлкали правой кнопкой по файлу, чьё имя содержит больше чем MAX_PATH символов в пути (эти файлы легальны, но их непросто создать и манипулировать ими). Для этого случая мы смогли сделать не много.
Так что, ребята, не забываем: следим за размерами буферов и не переполняем их. Безопасность - это работа каждого. Мы с вами в этом все вместе.
Любой отчёт об ошибке, который включает в себя ошибку переполнения буфера, быстро поднимается в приоритете. Несколько последних таких отчётов, которые мне пришлось разбирать, на самом деле были отчётами об ошибках в чужих программах, которые были обнаружены Windows.
Например, было несколько программ, которые отвечали на уведомление LVN_GETDISPINFO переполнением буфера LVITEM.pszText, записывая туда больше, чем LVITEM.cchTextMax символов.
Другая программа, отвечая на IContextMenu.GetCommandString, переполняла буфер pszName, записывая туда больше, чем cchMax символов.
К счастью, в обоих случаях переполнение было всего на один символ, так что мы смогли исправить это создавая буфер на один символ больше, чем это действительно необходимо, и указывая его размер меньше действительного. Таким образом, если программа переполняла буфер, то она ничего не портила.
Другая программа переполняла свой собственный стековый буфер, если вы щёлкали правой кнопкой по файлу, чьё имя содержит больше чем MAX_PATH символов в пути (эти файлы легальны, но их непросто создать и манипулировать ими). Для этого случая мы смогли сделать не много.
Так что, ребята, не забываем: следим за размерами буферов и не переполняем их. Безопасность - это работа каждого. Мы с вами в этом все вместе.
ia64 - неверное объявление данных near и far
Это перевод ia64 - misdeclaring near and far data. Автор: Реймонд Чен.
Как я сказал вчера, ia64 - это очень требовательная архитектура. Сегодня я буду обсуждать ещё один способ наврать компилятору так, что ему потом придётся вас за это стукнуть.
Как я сказал вчера, ia64 - это очень требовательная архитектура. Сегодня я буду обсуждать ещё один способ наврать компилятору так, что ему потом придётся вас за это стукнуть.
Неинициализированный мусор на ia64 может быть смертелен
Это перевод Uninitialized garbage on ia64 can be deadly. Автор: Реймонд Чен.
В прошлый раз мы говорили о некоторых плохих вещах, которые могут произойти, если вы вызываете функцию с неверной сигнатурой. Архитектура ia64 привносит ещё одну возможность столкнуться с плохими последствиями в, казалось бы, безобидной ситуации.
В прошлый раз мы говорили о некоторых плохих вещах, которые могут произойти, если вы вызываете функцию с неверной сигнатурой. Архитектура ia64 привносит ещё одну возможность столкнуться с плохими последствиями в, казалось бы, безобидной ситуации.
Как программа может выжить после повреждения стека?
Это перевод How can a program survive a corrupted stack? Автор: Реймонд Чен.
Продолжение вчерашнего разговора:
Продолжение вчерашнего разговора:
Что может пойти не так, если я напутаю с моделями вызова?
Это перевод What can go wrong when you mismatch the calling convention? Автор: Реймонд Чен.
Верите вы мне или нет, но соглашение вызова - это одна из тех вещей, которые программы часто делают неправильно. Компилятор кричит на вас, когда вы путаете соглашения вызова, но ленивые программисты просто вставляют преобразование типов, чтобы "компилятор наконец заткнулся".
Верите вы мне или нет, но соглашение вызова - это одна из тех вещей, которые программы часто делают неправильно. Компилятор кричит на вас, когда вы путаете соглашения вызова, но ленивые программисты просто вставляют преобразование типов, чтобы "компилятор наконец заткнулся".
История соглашений вызова, часть 5: amd64
Это перевод The history of calling conventions, part 5: amd64. Автор: Реймонд Чен.
Четвёртая часть.
Последней архитектурой, которую мы рассмотрим будет архитектура AMD64 (также известная как x86-64).
AMD64 берёт традиционную архитектуру x86 и увеличивает регистры до 64-х бит, именуя их rax, rbx и т.д. И также добавляет восемь дополнительных регистров, называя их просто как R8-R15.
- Первые четыре параметры в функцию передаются в rcx, rdx, r8 и r9. Все другие параметры передаются в стеке. Более того, для параметров в регистре резервируется место в стеке, на случай если вызываемая функция захочет сбросить регистры в стек; это также важно для функций с переменным числом аргументов.
- Параметры меньше 64-бит не дополняются нулями; верхние биты содержат мусор, поэтому не забывайте обнулять их явно, если вы собираетесь их использовать. Параметры размером больше 64-х бит передаются по ссылке.
- Возвращаемое значение помещается в rax. Если возвращаемое значение больше 64-х бит, то в функцию будет передан неявный секретный параметр, который содержит адрес, по которому нужно записать результат.
- Все регистры обязаны сохраняться во всемя вызова, кроме регистров rax, rcx, rdx, r8, r9, r10 и r11, которые свободны.
- Вызываемый не чистит стек. Это работа вызывающего.
- Стек должен быть всё время выровненным на границу 16-ти байт. Поскольку инструкция "call" записывает в стек 8-ми байтовый адрес возврата, то это значит, что каждая не листовая функция должна подправлять стек на значение вида 16n + 8 для восстановления выравнивания на 16 байт.
Вот пример:
Четвёртая часть.
Последней архитектурой, которую мы рассмотрим будет архитектура AMD64 (также известная как x86-64).
AMD64 берёт традиционную архитектуру x86 и увеличивает регистры до 64-х бит, именуя их rax, rbx и т.д. И также добавляет восемь дополнительных регистров, называя их просто как R8-R15.
- Первые четыре параметры в функцию передаются в rcx, rdx, r8 и r9. Все другие параметры передаются в стеке. Более того, для параметров в регистре резервируется место в стеке, на случай если вызываемая функция захочет сбросить регистры в стек; это также важно для функций с переменным числом аргументов.
- Параметры меньше 64-бит не дополняются нулями; верхние биты содержат мусор, поэтому не забывайте обнулять их явно, если вы собираетесь их использовать. Параметры размером больше 64-х бит передаются по ссылке.
- Возвращаемое значение помещается в rax. Если возвращаемое значение больше 64-х бит, то в функцию будет передан неявный секретный параметр, который содержит адрес, по которому нужно записать результат.
- Все регистры обязаны сохраняться во всемя вызова, кроме регистров rax, rcx, rdx, r8, r9, r10 и r11, которые свободны.
- Вызываемый не чистит стек. Это работа вызывающего.
- Стек должен быть всё время выровненным на границу 16-ти байт. Поскольку инструкция "call" записывает в стек 8-ми байтовый адрес возврата, то это значит, что каждая не листовая функция должна подправлять стек на значение вида 16n + 8 для восстановления выравнивания на 16 байт.
Вот пример:
void SomeFunction(int a, int b, int c, int d, int e);После входа в CallThatFunction стек выглядит примерно так:
void CallThatFunction()
{
SomeFunction(1, 2, 3, 4, 5);
SomeFunction(6, 7, 8, 9, 10);
}
xxxxxxx0 .. данные на стеке ..Из-за наличия в нём адреса возврата стек оказывается не выровненным. Функция CallThatFunction настраивает свой фрейм примерно так:
xxxxxxx8 адрес возврата <- RSP
sub rsp, 0x28Заметим, что размер локального стекового фрейма равен 16n + 8, так что в результате у нас получается выравненный стек:
xxxxxxx0 .. данные на стеке ..Теперь мы готовим первый вызов:
xxxxxxx8 адрес возврата
xxxxxxx0 (arg5)
xxxxxxx8 (место для arg4)
xxxxxxx0 (место для arg3)
xxxxxxx8 (место для arg2)
xxxxxxx0 (место для arg1) <- RSP
mov dword ptr [rsp+0x20], 5 ; параметр 5Когда функция SomeFunction возвращает управление, стек ещё не очищен и поэтому выглядит так же, как и выше. Тогда для второго вызова нам просто нужно занести новые значения в уже подготовленное место:
mov r9d, 4 ; параметр 4
mov r8d, 3 ; параметр 3
mov edx, 2 ; параметр 2
mov ecx, 1 ; параметр 1
call SomeFunction ; Вперёд, Спиди-Гонщик!
mov dword ptr [rsp+0x20], 10 ; параметр 5Теперь CallThatFunction завершена и она должна очистить стек и вернуть управление:
mov r9d, 9 ; параметр 4
mov r8d, 8 ; параметр 3
mov edx, 7 ; параметр 2
mov ecx, 6 ; параметр 1
call SomeFunction ; Вперёд, Спиди-Гонщик!
add rsp, 0x28Заметьте, что вы практичеси не встречаете инструкций "push" в коде amd64, поскольку парадигмой вызывающего является резервирование пространства и повторное использование его.
ret
История соглашений вызова, часть 4: ia64
Это перевод The history of calling conventions, part 4: ia64. Автор: Реймонд Чен.
Третья часть.
Архитектура ia-64 (Itanium) и архитектура AMD64 (AMD64) относительно новые, так что маловероятно, чтобы многие из вас имели дело с соглашениями вызова на этих платформах, но я включу их обзор в эту серию, потому что, кто знает, может однажды у вас появится такая машина.
Третья часть.
Архитектура ia-64 (Itanium) и архитектура AMD64 (AMD64) относительно новые, так что маловероятно, чтобы многие из вас имели дело с соглашениями вызова на этих платформах, но я включу их обзор в эту серию, потому что, кто знает, может однажды у вас появится такая машина.
Почему у меня не получается вызвать GetProcAddress для функции, которую я экспортировал dllexport-ом?
Это перевод Why can't I GetProcAddress a function I dllexport'ed? Автор: Реймонд Чен.
Атрибут dllexport просит линкёр сгенерировать запись в таблице экспорта для указанной функции. Название функции в этой записи декорируется. Это необходимо для поддержки эксорта (dllexporting) перегружаемых (overload) функций. Но это также означает, что имя, которое вы указываете в GetProcAddress, тоже должно быть декорировано.
Как мы узнали ранее, схема декорирования зависит от архитектуры и модели вызова. Так что, например, если функция импортируется из DLL на PPC, вы должны будете написать GetProcAddress(hinst, '..SomeFunction'), но если она импортируется из DLL на 80386 с моделью вызова stdcall, то вам нужно вызывать GetProcAddress(hinst, '_SomeFunction@8'), но если соглашение вызова будет fastcall, то вам придётся использовать GetProcAddress(hinst, '@SomeFunction@8').
Но это ещё не всё. Декорация имён C++ может меняться в зависимости от компилятора. Экспортируемая функция C++ может требовать GetProcAddress(hinst, '?SomeFunction@@YGXHH@Z'), если она была скомпилирована компилятором Microsoft C++, но какую-то другое декорированное имя, если она была скомпилирована Borland C++.
Так что если вы планируете, что вашу функцию будут импортировать через GetProcAddress, и вы хотите сделать свой код портируемым или разрешить использование вашей DLL из языка, отличного от C/C++ или использовать компилятор, отличный от Microsoft Visual Studio, то тогда вы должны экспортировать функцию по её недекорированному имени (да, для этого вам понадобится DEF-файл).
Когда DLL генерируется, компоновщик C/C++ создаёт соответствующий LIB-файл, который переводит декорированные имена в недекорируемые. Так, к примеру, LIB-файл может содержать запись, которая говорит: "если кто-то спросит функцию _GetTickCount@0, то перенаправьте его на kernel32!GetTickCount".
Упражнение: если dllexport привязывает вас к архитектуре, компилятору и языку (экспортом имён в декорированном виде), то почему же тогда MSVCRT.DLL использует его?
Атрибут dllexport просит линкёр сгенерировать запись в таблице экспорта для указанной функции. Название функции в этой записи декорируется. Это необходимо для поддержки эксорта (dllexporting) перегружаемых (overload) функций. Но это также означает, что имя, которое вы указываете в GetProcAddress, тоже должно быть декорировано.
Как мы узнали ранее, схема декорирования зависит от архитектуры и модели вызова. Так что, например, если функция импортируется из DLL на PPC, вы должны будете написать GetProcAddress(hinst, '..SomeFunction'), но если она импортируется из DLL на 80386 с моделью вызова stdcall, то вам нужно вызывать GetProcAddress(hinst, '_SomeFunction@8'), но если соглашение вызова будет fastcall, то вам придётся использовать GetProcAddress(hinst, '@SomeFunction@8').
Но это ещё не всё. Декорация имён C++ может меняться в зависимости от компилятора. Экспортируемая функция C++ может требовать GetProcAddress(hinst, '?SomeFunction@@YGXHH@Z'), если она была скомпилирована компилятором Microsoft C++, но какую-то другое декорированное имя, если она была скомпилирована Borland C++.
Так что если вы планируете, что вашу функцию будут импортировать через GetProcAddress, и вы хотите сделать свой код портируемым или разрешить использование вашей DLL из языка, отличного от C/C++ или использовать компилятор, отличный от Microsoft Visual Studio, то тогда вы должны экспортировать функцию по её недекорированному имени (да, для этого вам понадобится DEF-файл).
Когда DLL генерируется, компоновщик C/C++ создаёт соответствующий LIB-файл, который переводит декорированные имена в недекорируемые. Так, к примеру, LIB-файл может содержать запись, которая говорит: "если кто-то спросит функцию _GetTickCount@0, то перенаправьте его на kernel32!GetTickCount".
Упражнение: если dllexport привязывает вас к архитектуре, компилятору и языку (экспортом имён в декорированном виде), то почему же тогда MSVCRT.DLL использует его?
Почему методы класса должны быть помечены словом "static", чтобы их можно было использовать в качестве функции обратного вызова?
Это перевод Why do member functions need to be "static" to be used as a callback? Автор: Реймонд Чен.
Как мы узнали вчера, обычные методы класса принимают секретный параметр "this", что, конечно же, делает их несовместимыми с сигнатурами функций обратного вызова в Win32.
Как мы узнали вчера, обычные методы класса принимают секретный параметр "this", что, конечно же, делает их несовместимыми с сигнатурами функций обратного вызова в Win32.
История соглашений вызова, часть 3
Это перевод The history of calling conventions, part 3. Автор: Реймонд Чен.
Вторая часть.
Окей, поехали: 32-х битные модели вызова x86.
(Кстати, на случай, если вы не поняли: я говорю только о тех моделях вызова, которые вы можете встретить в Windows-программировании или в компиляторах от Microsoft. Я не буду рассматривать соглашения вызова других операционных систем или модели вызова, специфичные для конкретного языка или производителя компилятора).
Запомните: если какая-либо модель вызова используется для члена класса C++, тогда будет существовать скрытый параметр "this", который будет неявно передан первым аргументом в функцию (*).
Все модели
Все 32-х разрядные соглашения вызова x86 сохраняют регистры EDI, ESI, EBP и EBX и используют пару EDX:EAX для возврата результата (**).
C (cdecl)
В 32-х битном мире работают те же правила, что и в 16-ти битном. Параметры передаются справа налево (поэтому первый параметр будет ближе к вершине стека), а чистит стек вызывающий. Имена функций декорируются ведущим знаком подчёркивания.
stdcall
Это стандартное соглашение, используемое во всём Win32, за исключением функций с переменным числом параметров (которые используют cdecl) и очень небольшого числа функций, использующих fastcall. Параметры передаются справа налево, а стек чистит вызываемый. Имена функций декорируются ведущим знаком подчёркивания и в конце ставится знак @ и размер принимаемых функцией параметров в байтах.
fastcall
Первые два параметра передаются в ECX и EDX, а остаток передаётся через стек так же, как и при stdcall. И снова стек чистит вызываемый. Имена функций декорируются ведущим знаком @, в конце также ставится @ и после него указывается размер принимаемых функцией параметров в байтах (включая параметры в регистрах).
thiscall
Первый параметр (который "this") передаётся в ECX, а все остальные параметры передаются как в stdcall - через стек. И здесь стек чистит вызываемый. Имена функций декорируются весьма сложным образом компилятором C++, включая и типы параметров (среди всех прочих вещей). Это необходимо, потому что C++ допускает перегрузку функций, поэтому необходимо использовать сложные правила декорирования для того, чтобы два варианта перегруженных функций имели бы различные имена.
На MSDN есть несколько неплохих диаграм, демонстрирующих эти соглашения вызова.
(***)
Помните: модель вызова - это контракт между вызываемой и вызывающей стороной. Если вы один из тех психов, которые ещё пишут на ассемблере, то для вас это означает, что ваша callback-функция обязана сохранять регистры, как того требует соглашение вызова, потому что вызывающая сторона (операционная система) рассчитывает на это. Если, к примеру, вы во время вызова испортите регистр EBX, то не удивляйтесь, когда начнут происходить всякие плохие вещи. Подробнее об этом - в следующий раз.
Читать далее.
Примечания переводчика:
(*) Для Delphi справедливо аналогичное утверждение: у любого метода класса есть неявный параметр Self, который является первым параметром функции этого метода. Поэтому у метода, объявленного с двумя параметрами, на самом деле будет три параметра, причём первым будет Self, а вторым и третьим - первый и второй параметры в объявлении метода.
(**) Вообще, пара EDX:EAX используется скорее как исключение из правила. Т.е. все возвращаемые значения возвращаются в EAX, а если они там не помещаются, то в функцию передаётся указатель, по которому и записывается результат. Однако, (только) для 8-ми байтовых записей есть спец. правило: они возвращаются в паре EDX:EAX. В Delphi даже есть баг: компилятор генерирует неверный код для функции, возвращающей 8-ми байтовую запись.
(***) Моделью вызова по-умолчанию в Delphi является модель register. В ней для передачи параметров используются регистры EAX, EDX, ECX и стек. Обычно первые три параметра идут в регистры, а остальные - в стек как в stdcall, но это необязательно. В языке есть свои правила для распределения параметров между регистрами и стеком - эти правила довольно сложны (так, например, действительные числа передаются через стек сопроцессора). Параметры передаются слева-направо и стек чистит вызываемый.
Вторая часть.
Окей, поехали: 32-х битные модели вызова x86.
(Кстати, на случай, если вы не поняли: я говорю только о тех моделях вызова, которые вы можете встретить в Windows-программировании или в компиляторах от Microsoft. Я не буду рассматривать соглашения вызова других операционных систем или модели вызова, специфичные для конкретного языка или производителя компилятора).
Запомните: если какая-либо модель вызова используется для члена класса C++, тогда будет существовать скрытый параметр "this", который будет неявно передан первым аргументом в функцию (*).
Все модели
Все 32-х разрядные соглашения вызова x86 сохраняют регистры EDI, ESI, EBP и EBX и используют пару EDX:EAX для возврата результата (**).
C (cdecl)
В 32-х битном мире работают те же правила, что и в 16-ти битном. Параметры передаются справа налево (поэтому первый параметр будет ближе к вершине стека), а чистит стек вызывающий. Имена функций декорируются ведущим знаком подчёркивания.
stdcall
Это стандартное соглашение, используемое во всём Win32, за исключением функций с переменным числом параметров (которые используют cdecl) и очень небольшого числа функций, использующих fastcall. Параметры передаются справа налево, а стек чистит вызываемый. Имена функций декорируются ведущим знаком подчёркивания и в конце ставится знак @ и размер принимаемых функцией параметров в байтах.
fastcall
Первые два параметра передаются в ECX и EDX, а остаток передаётся через стек так же, как и при stdcall. И снова стек чистит вызываемый. Имена функций декорируются ведущим знаком @, в конце также ставится @ и после него указывается размер принимаемых функцией параметров в байтах (включая параметры в регистрах).
thiscall
Первый параметр (который "this") передаётся в ECX, а все остальные параметры передаются как в stdcall - через стек. И здесь стек чистит вызываемый. Имена функций декорируются весьма сложным образом компилятором C++, включая и типы параметров (среди всех прочих вещей). Это необходимо, потому что C++ допускает перегрузку функций, поэтому необходимо использовать сложные правила декорирования для того, чтобы два варианта перегруженных функций имели бы различные имена.
На MSDN есть несколько неплохих диаграм, демонстрирующих эти соглашения вызова.
(***)
Помните: модель вызова - это контракт между вызываемой и вызывающей стороной. Если вы один из тех психов, которые ещё пишут на ассемблере, то для вас это означает, что ваша callback-функция обязана сохранять регистры, как того требует соглашение вызова, потому что вызывающая сторона (операционная система) рассчитывает на это. Если, к примеру, вы во время вызова испортите регистр EBX, то не удивляйтесь, когда начнут происходить всякие плохие вещи. Подробнее об этом - в следующий раз.
Читать далее.
Примечания переводчика:
(*) Для Delphi справедливо аналогичное утверждение: у любого метода класса есть неявный параметр Self, который является первым параметром функции этого метода. Поэтому у метода, объявленного с двумя параметрами, на самом деле будет три параметра, причём первым будет Self, а вторым и третьим - первый и второй параметры в объявлении метода.
(**) Вообще, пара EDX:EAX используется скорее как исключение из правила. Т.е. все возвращаемые значения возвращаются в EAX, а если они там не помещаются, то в функцию передаётся указатель, по которому и записывается результат. Однако, (только) для 8-ми байтовых записей есть спец. правило: они возвращаются в паре EDX:EAX. В Delphi даже есть баг: компилятор генерирует неверный код для функции, возвращающей 8-ми байтовую запись.
(***) Моделью вызова по-умолчанию в Delphi является модель register. В ней для передачи параметров используются регистры EAX, EDX, ECX и стек. Обычно первые три параметра идут в регистры, а остальные - в стек как в stdcall, но это необязательно. В языке есть свои правила для распределения параметров между регистрами и стеком - эти правила довольно сложны (так, например, действительные числа передаются через стек сопроцессора). Параметры передаются слева-направо и стек чистит вызываемый.
История соглашений вызова, часть 2
Это перевод The history of calling conventions, part 2. Автор: Реймонд Чен.
Первая часть.
Предварительно: информация ниже будет использована в дальнейшей дискуссии. Ну, здесь приведены не самые подробные детали, но вы можете увидеть объяснение... эммм... это трудно описать. Просто читайте далее.
Любопытно, что только платформы 8086 и x86 имеют несколько соглашений вызова. На всех других платформах есть только одна единственная модель вызова!
Первая часть.
Предварительно: информация ниже будет использована в дальнейшей дискуссии. Ну, здесь приведены не самые подробные детали, но вы можете увидеть объяснение... эммм... это трудно описать. Просто читайте далее.
Любопытно, что только платформы 8086 и x86 имеют несколько соглашений вызова. На всех других платформах есть только одна единственная модель вызова!
Почему диалог копирования так плохо показывает оставшееся время?
Это перевод Why does the copy dialog give such horrible estimates?. Автор: Реймонд Чен.
Потому что диалог копирования просто угадывает. Он не умеет предсказывать будущее, но его заставляют это делать. В самом начале копирования, когда на руках совсем немного данных, его предсказания могут быть совершенно ужасны.
Вот аналогия: представьте, что ваш друг говорит вам: "давай я считать до ста, а ты будешь говорить, когда я досчитаю до конца". Он начинает считать: "один, два, три...". Вы замечаете, что он говорит примерно одно число в секунду, поэтому вы даёте оценку в 100 секунд. Ой-ёй, а теперь он замедляется. "Четыре... ... ... пять... ... ...". Теперь вам нужно изменить свою оценку, скажем, на 200 секунд. Теперь он ускоряется: "шесть-семь-восемь-девять". И вам снова нужно менять свою оценку.
Представьте, что кто-то слушал только ваши оценки, но не человека, который считает. Ваши оценки варьировались от 100 секунд к 200, а затем к 50 секундам: "Эй, что у тебя за проблемы? Ты что, не можешь дать нормальную оценку?".
Копирование файлов - ровно то же самое. Оболочка знает только сколько файлов и байт нужно скопировать, но ничего не знает о том, как быстро будет работать винчестер, сеть или интернет, поэтому ей приходится просто угадывать. Если скорость копирования меняется, то меняется и конечная оценка.
Потому что диалог копирования просто угадывает. Он не умеет предсказывать будущее, но его заставляют это делать. В самом начале копирования, когда на руках совсем немного данных, его предсказания могут быть совершенно ужасны.
Вот аналогия: представьте, что ваш друг говорит вам: "давай я считать до ста, а ты будешь говорить, когда я досчитаю до конца". Он начинает считать: "один, два, три...". Вы замечаете, что он говорит примерно одно число в секунду, поэтому вы даёте оценку в 100 секунд. Ой-ёй, а теперь он замедляется. "Четыре... ... ... пять... ... ...". Теперь вам нужно изменить свою оценку, скажем, на 200 секунд. Теперь он ускоряется: "шесть-семь-восемь-девять". И вам снова нужно менять свою оценку.
Представьте, что кто-то слушал только ваши оценки, но не человека, который считает. Ваши оценки варьировались от 100 секунд к 200, а затем к 50 секундам: "Эй, что у тебя за проблемы? Ты что, не можешь дать нормальную оценку?".
Копирование файлов - ровно то же самое. Оболочка знает только сколько файлов и байт нужно скопировать, но ничего не знает о том, как быстро будет работать винчестер, сеть или интернет, поэтому ей приходится просто угадывать. Если скорость копирования меняется, то меняется и конечная оценка.
История соглашений вызова, часть 1
Это перевод The history of calling conventions, part 1. Автор: Реймонд Чен.
Хорошо, когда вокруг так много соглашений вызовов: есть из чего выбирать!
Хорошо, когда вокруг так много соглашений вызовов: есть из чего выбирать!
Не доверяйте адресу возврата
Это перевод Don't trust the return address. Автор: Реймонд Чен.
Иногда люди спрашивают: "так, я знаю как получить адрес возврата [в C++ можно использовать _ReturnAddress(); Прим. пер.: в Delphi прямого аналога нет, но можно использовать Caller из JCL]; как мне определить, какой DLL принадлежит этот адрес?"
Иногда люди спрашивают: "так, я знаю как получить адрес возврата [в C++ можно использовать _ReturnAddress(); Прим. пер.: в Delphi прямого аналога нет, но можно использовать Caller из JCL]; как мне определить, какой DLL принадлежит этот адрес?"
Вы не можете исправить проблему совместимости с помощью диалога.
Это перевод You can’t fix application compatibility problems with dialog boxes. Автор: Крис Джексон.
Вот интересный рассказ о том, что, я надеюсь, усвоит каждый разработчик: люди не читают то, что вы хотите им сказать.
Так уж получилось, что люди не читают инструкции, пока у них есть выбор. Иногда это доходит до того, что люди вообще перестают читать мануалы, поэтому другие люди перестают их писать. Но сегодня большинство людей даже не читают диалоговые окна. Может быть, потому что их так много, а может быть потому что от них обычно мало пользы. Но, с практической точки зрения, вот что говорит типичный диалог о проблеме совместимости в приложении:

Прим. пер.: это диалог с заголовком "Похоже тебе скучно, вот, прочти это", кучей текста "Бла-бла-бла" и кнопками "Работай!" и "Не работай!".
Ближе к теме: я женился несколько месяцев назад и, как часть этого процесса, обнаружил себя разговаривающим с торговкой цветами. В разговоре всплыла моя работа, поэтому она спросила меня, не мог бы я помочь решить её проблему. Какую проблему?
Adobe Photoshop показывал диалог UAC (UAC Prompt), и она не знала, как избавиться от него.
Я спросил, какую версию она использует. Она сказала, что последнюю (в то время). Я помнил, что эта версия Photoshop не требует элевации, поэтому я попросил посмотреть на этот диалог.
И вот, что я увидел (прим. пер.: чисто случайно я увидел в точности такой диалог на чужом ноуте, поэтому я заменил оригинальный скрин на русифицированный):

Итак, вот диалог, который выводит из себя кучу людей. “Мы можем помочь с проблемой совместимости приложений, если мы просто скажем пользователю, что происходит. Ну т.е. мы же помогаем, это же хорошо, да?”.
Давайте посчитаем вещи, которые пользователь не прочитает:
Заголовок диалогового окна. Это вовсе не диалог UAC. Но она слышала так много об этом противном UAC, который постоянно надоедает своими диалогами, что она решила, что это что-то, спрашивающее её о чём-то, - это тоже UAC.
Название приложения. Она уловила название компании, но не разницу между Photoshop и Acrobat Reader. Я видел немало таких примеров – самый значительный из них: люди считают Office частью Windows.
Рекомендацию сходить за решением проблемы. У нас есть большая крупная кнопка, которая предложит вам помощь. Она не видела её, хотя она привела бы её на web-сайт для скачивания (бесплатного!) обновления, которое исправило бы проблемы и избавило бы её от диалога.
Чек-бокс для скрытия сообщения. Вспомните, проблема была не в том, что приложение работало неправильно - она просто ненавидела это постоянно всплывающее окно. У нас есть чек-бокс, прямо в диалоге, чтобы скрыть его - но она никогда его не читала.
Фактически, единственной кнопкой, которую она увидела, была та, на которой было написано “Запуск программы” (“Run program”) - та самая кнопка “Работай!”.
Получается, что вы не можете исправлять проблемы с совместимостью словами и диалогами. Потому что люди не читают слова. Этот опыт многому научил меня, показал, что мы могли бы сделать для совместимости приложений. И увеличение числа диалоговых окон - это не подходящий способ.
Вот интересный рассказ о том, что, я надеюсь, усвоит каждый разработчик: люди не читают то, что вы хотите им сказать.
Так уж получилось, что люди не читают инструкции, пока у них есть выбор. Иногда это доходит до того, что люди вообще перестают читать мануалы, поэтому другие люди перестают их писать. Но сегодня большинство людей даже не читают диалоговые окна. Может быть, потому что их так много, а может быть потому что от них обычно мало пользы. Но, с практической точки зрения, вот что говорит типичный диалог о проблеме совместимости в приложении:

Прим. пер.: это диалог с заголовком "Похоже тебе скучно, вот, прочти это", кучей текста "Бла-бла-бла" и кнопками "Работай!" и "Не работай!".
Ближе к теме: я женился несколько месяцев назад и, как часть этого процесса, обнаружил себя разговаривающим с торговкой цветами. В разговоре всплыла моя работа, поэтому она спросила меня, не мог бы я помочь решить её проблему. Какую проблему?
Adobe Photoshop показывал диалог UAC (UAC Prompt), и она не знала, как избавиться от него.
Я спросил, какую версию она использует. Она сказала, что последнюю (в то время). Я помнил, что эта версия Photoshop не требует элевации, поэтому я попросил посмотреть на этот диалог.
И вот, что я увидел (прим. пер.: чисто случайно я увидел в точности такой диалог на чужом ноуте, поэтому я заменил оригинальный скрин на русифицированный):

Итак, вот диалог, который выводит из себя кучу людей. “Мы можем помочь с проблемой совместимости приложений, если мы просто скажем пользователю, что происходит. Ну т.е. мы же помогаем, это же хорошо, да?”.
Давайте посчитаем вещи, которые пользователь не прочитает:
Заголовок диалогового окна. Это вовсе не диалог UAC. Но она слышала так много об этом противном UAC, который постоянно надоедает своими диалогами, что она решила, что это что-то, спрашивающее её о чём-то, - это тоже UAC.
Название приложения. Она уловила название компании, но не разницу между Photoshop и Acrobat Reader. Я видел немало таких примеров – самый значительный из них: люди считают Office частью Windows.
Рекомендацию сходить за решением проблемы. У нас есть большая крупная кнопка, которая предложит вам помощь. Она не видела её, хотя она привела бы её на web-сайт для скачивания (бесплатного!) обновления, которое исправило бы проблемы и избавило бы её от диалога.
Чек-бокс для скрытия сообщения. Вспомните, проблема была не в том, что приложение работало неправильно - она просто ненавидела это постоянно всплывающее окно. У нас есть чек-бокс, прямо в диалоге, чтобы скрыть его - но она никогда его не читала.
Фактически, единственной кнопкой, которую она увидела, была та, на которой было написано “Запуск программы” (“Run program”) - та самая кнопка “Работай!”.
Получается, что вы не можете исправлять проблемы с совместимостью словами и диалогами. Потому что люди не читают слова. Этот опыт многому научил меня, показал, что мы могли бы сделать для совместимости приложений. И увеличение числа диалоговых окон - это не подходящий способ.
понедельник, 8 декабря 2008 г.
В чём разница между CreateMenu и CreatePopupMenu?
Это перевод What's the difference between CreateMenu and CreatePopupMenu? Автор: Реймонд Чен.
Функция CreateMenu создаёт горизонтальную полосу меню, пригодную для прикрепления к окнам верхнего уровня. Это тот тип меню, где написано "Файл", "Правка" и т.д.
Функция CreatePopupMenu создаёт вертикальное всплывающее меню, подходящее для использования в качестве подменю другого меню (в горизонтальном меню или всплывающем меню) или для использования в качестве контекстного меню.
Функция CreateMenu создаёт горизонтальную полосу меню, пригодную для прикрепления к окнам верхнего уровня. Это тот тип меню, где написано "Файл", "Правка" и т.д.
Функция CreatePopupMenu создаёт вертикальное всплывающее меню, подходящее для использования в качестве подменю другого меню (в горизонтальном меню или всплывающем меню) или для использования в качестве контекстного меню.
А что это за пустые кнопки на панели задач, которые исчезают, когда я щёлкаю по ним?
Это перевод What's with those blank taskbar buttons that go away when I click on them? Автор: Реймонд Чен.
Иногда вы можете увидеть пустую кнопку на панели задач, которая исчезает, когда вы щёлкаете по ней. Что это ещё за чёрт?
Есть несколько основных правил, согласно которым окна появляются на панели задач. Вкратце:
- Если установлен расширенный стиль WS_EX_APPWINDOW, тогда окно показывается (когда оно видимо).
- Если окно является окном верхнего уровня без владельца (owner в терминах WinAPI - прим. пер.), тогда оно показывается (когда оно видимо).
- В противном случае окно не показывается.
(хотя интерфейс ITaskbarList немножко всё это запутывает).
Когда "пригодное для панели задач" окно становится видимым, панель задач создаёт кнопку для него. Когда "пригодное для панели задач" становится скрытым - панель задач скрывает кнопку.
Пустые кнопки появляются, когда окно переходит от "пригодное для панели задач" к "не пригодное для панели задач" в то время, когда оно видимо. Посмотрите:
- Окно - "пригодное для панели задач".
- Окно становится видимым -> создаётся кнопка на панели задач.
- Окно становится "не пригодное для панели задач".
- Окно становится невидимым -> поскольку окно не является "пригодное для панели задач", то панель задач игнорирует окно.
Результат: на панели задач появляется ничейная кнопка, без прикреплённого к ней окна.
Именно поэтому документация советует: "если вы хотите динамически изменить стиль окна на такой, который не поддерживает кнопку на панели задач, то сначала вы должны скрыть окно (вызовом ShowWindow с SW_HIDE), изменить стиль окна, а затем показать окно".
Бонус-вопрос: почему панель задач не проверяет все окна подряд?
Ответ: потому что это было бы расточительно. Фильтрация окон, которые являются "таскбаро-непригодными", происходит внутри USER32, которая уведомляет панель задач (или любого, кто установил хук WH_SHELL) с помощью уведомлений HSHELL_* только об окнах, которые изменили своё состояние и являются "пригодными для панели задач". Таким образом, код панели задач может быть выгружен в файл подкачки, если для него нет никакой работы.
Примечание переводчика: я думаю, что в области уведомлений панели задач остаются значки после вылета приложения примерно по той же причине. Слишком накладно постоянно проверять актуальность значков. А когда мы щёлкаете по ним мышью - это как раз хороший момент для проверки.
Иногда вы можете увидеть пустую кнопку на панели задач, которая исчезает, когда вы щёлкаете по ней. Что это ещё за чёрт?
Есть несколько основных правил, согласно которым окна появляются на панели задач. Вкратце:
- Если установлен расширенный стиль WS_EX_APPWINDOW, тогда окно показывается (когда оно видимо).
- Если окно является окном верхнего уровня без владельца (owner в терминах WinAPI - прим. пер.), тогда оно показывается (когда оно видимо).
- В противном случае окно не показывается.
(хотя интерфейс ITaskbarList немножко всё это запутывает).
Когда "пригодное для панели задач" окно становится видимым, панель задач создаёт кнопку для него. Когда "пригодное для панели задач" становится скрытым - панель задач скрывает кнопку.
Пустые кнопки появляются, когда окно переходит от "пригодное для панели задач" к "не пригодное для панели задач" в то время, когда оно видимо. Посмотрите:
- Окно - "пригодное для панели задач".
- Окно становится видимым -> создаётся кнопка на панели задач.
- Окно становится "не пригодное для панели задач".
- Окно становится невидимым -> поскольку окно не является "пригодное для панели задач", то панель задач игнорирует окно.
Результат: на панели задач появляется ничейная кнопка, без прикреплённого к ней окна.
Именно поэтому документация советует: "если вы хотите динамически изменить стиль окна на такой, который не поддерживает кнопку на панели задач, то сначала вы должны скрыть окно (вызовом ShowWindow с SW_HIDE), изменить стиль окна, а затем показать окно".
Бонус-вопрос: почему панель задач не проверяет все окна подряд?
Ответ: потому что это было бы расточительно. Фильтрация окон, которые являются "таскбаро-непригодными", происходит внутри USER32, которая уведомляет панель задач (или любого, кто установил хук WH_SHELL) с помощью уведомлений HSHELL_* только об окнах, которые изменили своё состояние и являются "пригодными для панели задач". Таким образом, код панели задач может быть выгружен в файл подкачки, если для него нет никакой работы.
Примечание переводчика: я думаю, что в области уведомлений панели задач остаются значки после вылета приложения примерно по той же причине. Слишком накладно постоянно проверять актуальность значков. А когда мы щёлкаете по ним мышью - это как раз хороший момент для проверки.
понедельник, 1 декабря 2008 г.
Вы можете читать контракт и с другой стороны
Это перевод You can read a contract from the other side. Автор: Реймонд Чен. Примечание: в отличие от других постов, этот пост сильно отличается от оригинала. Произведено множество замен от C к Delphi.
Любой интерфейс - это контракт, но помните, что контракт применим к двум сторонам. Чаще всего, когда вы читаете соглашения какого-то интерфейса, вы смотрите на него с точки зрения клиентской стороны (вызывающего), но иногда бывает полезно взглянуть на него и со стороны сервера.
Например, давайте посмотрим на интерфейс для приложений Панели управления.
Чаще всего, когда вы читаете эту документацию, на вас надета кепка "Сейчас я пишу приложение для Панели управления". Поэтому, к примеру, когда в документации написано:
Но если вместо этого вы носите кепку "Я пишу замену Панели управления", то это означает: "дьявол, мне лучше бы вызвать GetProcAddress для получения адреса функции CPlApplet, чтобы я мог передавать сообщения".
Аналогично, в секции "Обработка сообщений Панели управления" перечисленны сообщения, которые отправляются от управляющего приложения в приложение панели управления. Если вы носите свою кепку "Сейчас я пишу приложение для Панели управления", то это значит "лучше бы мне быть готовым принять и обработать эти сообщения". Но если вы носите "Я пишу замену Панели управления", то это будет значить: "мне лучше бы отправлять эти сообщения в указанном здесь порядке".
И, наконец, когда документация говорит "управляющее приложение освобождает приложение Панели управления вызовом функции FreeLibrary", ваша кепка "Сейчас я пишу приложение для Панели управления" говорит: "мне лучше бы подготовиться к выгрузке", в то время как ваша "Я пишу замену Панели управления" говорит: "а вот здесь я выгружаю DLL".
Так давайте же попробуем. Начнём с пустого приложения и изменим его FormCreate:
Всё, что мы делаем - это следуем спецификации, только читаем её со стороны сервера. Поэтому, мы загружаем библиотеку, ищем её точку входа, и вызываем эту точку входа с CPL_INIT, а затем - с CPL_GETCOUNT. Если в этом файле есть приложение Панели управления, то тогда мы получаем информацию о первом апплете, "дважды щёлкаем" на нём (это здесь происходят все интересные вещи), а затем останавливаем его. После всего этого, мы выполняем очистку, согласно правилам (а именно: отправляем сообщение CPL_EXIT).
Вот и всё. Ну, за исключением тех двух блоков кода. Что насчёт них?
Эти два блока кода - поддержка приложений Панели управления, которые имеют свой манифест. Это новая возможность в Windows XP и она документирована в MSDN здесь.
Если вы перейдёте к секции "Using ComCtl32 Version 6 in Control Panel or a DLL That Is Run by RunDll32.exe" ("Использование ComCtl32 версии 6 в Панели управления или в DLL, запускаемой RunDll32.exe"), то вы увидите, что приложение предоставляет Панели управления свой манифест подключением его в виде ресурса с номером 123. Так вот что делает этот код: он загружает и активирует манифест, затем приглашает приложение Панели управления выполнить свою работу (с активным манифестом), потом выполняет очистку. Если манифеста нет, то CreateActCtx возвратит INVALID_HANDLE_VALUE. Мы не считаем это ошибкой, потому что многие программы не имеют манифеста.
Упражнение: какие последствия для безопасности несёт передача nil первым параметром в SearchPath?
Любой интерфейс - это контракт, но помните, что контракт применим к двум сторонам. Чаще всего, когда вы читаете соглашения какого-то интерфейса, вы смотрите на него с точки зрения клиентской стороны (вызывающего), но иногда бывает полезно взглянуть на него и со стороны сервера.
Например, давайте посмотрим на интерфейс для приложений Панели управления.
Чаще всего, когда вы читаете эту документацию, на вас надета кепка "Сейчас я пишу приложение для Панели управления". Поэтому, к примеру, когда в документации написано:
Когда Microsoft Windows в первый раз загружает элемент панели управления, она получает адрес функции CPlApplet и в дальнейшем использует его для вызова функции и передаче ей сообщений.Когда на вас кепка "Сейчас я пишу приложение для Панели управления", это будет означать: "дьявол, мне лучше бы написать функцию CPlApplet и экспортировать её, чтобы я мог получать сообщения".
Но если вместо этого вы носите кепку "Я пишу замену Панели управления", то это означает: "дьявол, мне лучше бы вызвать GetProcAddress для получения адреса функции CPlApplet, чтобы я мог передавать сообщения".
Аналогично, в секции "Обработка сообщений Панели управления" перечисленны сообщения, которые отправляются от управляющего приложения в приложение панели управления. Если вы носите свою кепку "Сейчас я пишу приложение для Панели управления", то это значит "лучше бы мне быть готовым принять и обработать эти сообщения". Но если вы носите "Я пишу замену Панели управления", то это будет значить: "мне лучше бы отправлять эти сообщения в указанном здесь порядке".
И, наконец, когда документация говорит "управляющее приложение освобождает приложение Панели управления вызовом функции FreeLibrary", ваша кепка "Сейчас я пишу приложение для Панели управления" говорит: "мне лучше бы подготовиться к выгрузке", в то время как ваша "Я пишу замену Панели управления" говорит: "а вот здесь я выгружаю DLL".
Так давайте же попробуем. Начнём с пустого приложения и изменим его FormCreate:
procedure TForm1.FormCreate(Sender: TObject);Вместо обычного показа окна и входа в цикл сообщений, мы начинаем вести себя, как Панель управления. Сегодняшняя наша жертва: access.cpl (приложение "Специальные возможности"). После нахождения программы на диске, мы просим метод RunControlPanel выполнить всю тяжёлую работу:
var
S: String;
Last: PChar;
C: Integer;
begin
SetLength(S, MAX_PATH);
C := SearchPath(nil, 'access.cpl', nil, Length(S), PChar(S), Last);
if (C > 0) and (C < MAX_PATH) then
begin
Application.ShowMainForm := False;
try
RunControlPanel(S);
finally
Application.Terminate;
end;
end;
end;
usesПока проигнорируйте два блока в начале и в конце, отделённые пустыми строками от центрального кода; мы обсудим их позже.
JwaWinBase, JwaWinCPL;
...
procedure TForm1.RunControlPanel(const AFileName: String);
var
Act: TACTCTX;
Ctx: THandle;
Cookie: ULONG_PTR;
CPL: HMODULE;
CPlApplet: TCPLApplet;
AppletsCount: Integer;
CPLI: TCPLINFO;
begin
// Может быть, приложение Панели управления имеет свой манифест
FillChar(Act, SizeOf(Act), 0);
Act.cbSize := SizeOf(Act);
Act.dwFlags := 0;
Act.lpSource := PChar(AFileName);
Act.lpResourceName := MAKEINTRESOURCE(123);
Ctx := CreateActCtx(Act);
if (Ctx = INVALID_HANDLE_VALUE) or ActivateActCtx(Ctx, Cookie) then
begin
CPL := SafeLoadLibrary(PChar(AFileName));
if CPL <> 0 then
begin
CPlApplet := GetProcAddress(CPL, 'CPlApplet');
if Assigned(CPlApplet) then
begin
if CPlApplet(Handle, CPL_INIT, 0, 0) <> 0 then
begin
AppletsCount := CPlApplet(Handle, CPL_GETCOUNT, 0, 0);
// Мы собираемся запустить приложение номер ноль
// (В реальной программе мы бы сначала вывели их список пользователю,
// чтобы он выбрал какое-то)
if AppletsCount > 0 then
begin
FillChar(CPLI, SizeOf(CPLI), 0);
CPlApplet(Handle, CPL_INQUIRE, 0, Integer(@CPLI));
CPlApplet(Handle, CPL_DBLCLK, 0, CPLI.lData);
CPlApplet(Handle, CPL_STOP, 0, CPLI.lData);
end;
end;
CPlApplet(Handle, CPL_EXIT, 0, 0);
end;
FreeLibrary(CPL);
end;
if Ctx <> INVALID_HANDLE_VALUE then
begin
DeactivateActCtx(0, Cookie);
ReleaseActCtx(Ctx);
end;
end;
end;
Всё, что мы делаем - это следуем спецификации, только читаем её со стороны сервера. Поэтому, мы загружаем библиотеку, ищем её точку входа, и вызываем эту точку входа с CPL_INIT, а затем - с CPL_GETCOUNT. Если в этом файле есть приложение Панели управления, то тогда мы получаем информацию о первом апплете, "дважды щёлкаем" на нём (это здесь происходят все интересные вещи), а затем останавливаем его. После всего этого, мы выполняем очистку, согласно правилам (а именно: отправляем сообщение CPL_EXIT).
Вот и всё. Ну, за исключением тех двух блоков кода. Что насчёт них?
Эти два блока кода - поддержка приложений Панели управления, которые имеют свой манифест. Это новая возможность в Windows XP и она документирована в MSDN здесь.
Если вы перейдёте к секции "Using ComCtl32 Version 6 in Control Panel or a DLL That Is Run by RunDll32.exe" ("Использование ComCtl32 версии 6 в Панели управления или в DLL, запускаемой RunDll32.exe"), то вы увидите, что приложение предоставляет Панели управления свой манифест подключением его в виде ресурса с номером 123. Так вот что делает этот код: он загружает и активирует манифест, затем приглашает приложение Панели управления выполнить свою работу (с активным манифестом), потом выполняет очистку. Если манифеста нет, то CreateActCtx возвратит INVALID_HANDLE_VALUE. Мы не считаем это ошибкой, потому что многие программы не имеют манифеста.
Упражнение: какие последствия для безопасности несёт передача nil первым параметром в SearchPath?
Когда программы начинают закапываться в недокументированные структуры...
Это перевод When programs grovel into undocumented structures... Автор: Реймонд Чен.
Вот навскидку три примера программ, которые полагались на недокументированные структуры.
Вот навскидку три примера программ, которые полагались на недокументированные структуры.
Почему бы просто не заблокировать приложения, которые используют недокументированное поведение?
Это перевод Why not just block the apps that rely on undocumented behavior? Автор: Реймон Чен.
Потому что каждое приложение, которое будет заблокировано, станет ещё одной причиной, почему люди не станут обновлять Windows до следующей версии.
Потому что каждое приложение, которое будет заблокировано, станет ещё одной причиной, почему люди не станут обновлять Windows до следующей версии.
Иногда приложение просто просит вас вылететь
Это перевод Sometimes, an app just wants to crash. Автор: Реймонд Чен.
Мне кажется, что это было с Internet Explorer 5.0, когда мы нашли стороннее расширение браузера, которое имело в себе серьёзный баг, детали которого сейчас не так важны. Дело в том, что эта ошибка была настолько плоха, что расширение крэшило IE практически каждый раз. Это было не хорошо. Чтобы защитить пользователей от такой ужасной судьбы, мы отметили это расширение как "плохое", так что IE не запускал его при старте.
Мне кажется, что это было с Internet Explorer 5.0, когда мы нашли стороннее расширение браузера, которое имело в себе серьёзный баг, детали которого сейчас не так важны. Дело в том, что эта ошибка была настолько плоха, что расширение крэшило IE практически каждый раз. Это было не хорошо. Чтобы защитить пользователей от такой ужасной судьбы, мы отметили это расширение как "плохое", так что IE не запускал его при старте.
Как мне определить, что я владею критической секцией, если я не могу смотреть во внутренние поля?
Это перевод How do I determine whether I own a critical section if I am not supposed to look at internal fields? Автор: Реймонд Чен. Примечание: в отличие от других постов, этот пост сильно отличается от оригинала. Произведено множество замен от C к Delphi.
Seth спросил (перевод оригинального поста без комментариев), как может он выполнить корректную очистку ресурсов при возбуждении исключения, если он не знает, надо ли ему освобождать критическую секцию.
Seth спросил (перевод оригинального поста без комментариев), как может он выполнить корректную очистку ресурсов при возбуждении исключения, если он не знает, надо ли ему освобождать критическую секцию.
Как выбросить на свалку свою гарантию
Это перевод How to void your warranty. Автор: Реймонд Чен.
MSDN только что опубликовала статью Break Free of Code Deadlocks in Critical Sections Under Windows, в которой подробно описываются внутренние поля системной записи. Любой, кто воспользовался этой информацией, просто отменил свою гарантию. Пожалуйста, наклейте стикер "Весьма вероятно, что эта программа перестанет работать после того, как вы установите очередной сервис пак или обновите свою систему" на своё лицензионное соглашение.
И после этого вы удивляетесь, почему обратная совместимость приложений так сложна.
MSDN только что опубликовала статью Break Free of Code Deadlocks in Critical Sections Under Windows, в которой подробно описываются внутренние поля системной записи. Любой, кто воспользовался этой информацией, просто отменил свою гарантию. Пожалуйста, наклейте стикер "Весьма вероятно, что эта программа перестанет работать после того, как вы установите очередной сервис пак или обновите свою систему" на своё лицензионное соглашение.
И после этого вы удивляетесь, почему обратная совместимость приложений так сложна.
Есть ли ограничение на максимальную вложенность окон?
Это перевод What is the window nesting limit? Автор: Реймонд Чен.
В давние времена, Windows не заботилась об установке специального ограничения на вложенность окон, потому что, ну, если вы хотели вложить друг в друга 200 окон - это было целиком ваше решение. Многие оконные операции рекурсивны по своей сути, но поскольку вся работа происходила на стеке вашего приложения, то держать стек достаточно большим для работы (чтобы не было ошибок переполнения буфера) было только вашей ответственностью.
Но Windows NT переместила оконный менедежер со стека приложения (сначала в отдельный процесс, а затем и в режим ядра). Поэтому теперь ОС нужно было защищаться от атак переполнения стека от людей, которые создавали слишком много вложенных окон.
В ранних версиях Windows NT максимальное число вложенных окон было установлено в 100. В Windows XP это ограничение снизили до 50, потому что увеличенные требования к стеку в некоторых внутренних функциях приводили к переполнению при значении около 75. Усиление ограничения до 50-ти было сделано для запаса.
Оговорка: я не был лично причастен к этому вопросу. Я только сообщаю, что я смог выяснить из чтения checkin-логов.
В давние времена, Windows не заботилась об установке специального ограничения на вложенность окон, потому что, ну, если вы хотели вложить друг в друга 200 окон - это было целиком ваше решение. Многие оконные операции рекурсивны по своей сути, но поскольку вся работа происходила на стеке вашего приложения, то держать стек достаточно большим для работы (чтобы не было ошибок переполнения буфера) было только вашей ответственностью.
Но Windows NT переместила оконный менедежер со стека приложения (сначала в отдельный процесс, а затем и в режим ядра). Поэтому теперь ОС нужно было защищаться от атак переполнения стека от людей, которые создавали слишком много вложенных окон.
В ранних версиях Windows NT максимальное число вложенных окон было установлено в 100. В Windows XP это ограничение снизили до 50, потому что увеличенные требования к стеку в некоторых внутренних функциях приводили к переполнению при значении около 75. Усиление ограничения до 50-ти было сделано для запаса.
Оговорка: я не был лично причастен к этому вопросу. Я только сообщаю, что я смог выяснить из чтения checkin-логов.
Почему размеры записей проверяются строго?
Это перевод Why are structure sizes checked strictly? Автор: Реймонд Чен.
Вы могли заметить, что в Windows строгая проверка размеров записей является обычным делом.
Вы могли заметить, что в Windows строгая проверка размеров записей является обычным делом.
Как бы мне передать большой объём данных процессу при его запуске?
Это перевод How do I pass a lot of data to a process when it starts up? Автор: Реймонд Чен. Примечание: в отличие от других постов, этот пост сильно отличается от оригинала. Произведено множество замен от C к Delphi.
Как мы обсуждали вчера, если вам нужно передать больше, чем 32767 символов в дочерний процесс, вам придётся использовать что-то отличное от командной строки.
Как мы обсуждали вчера, если вам нужно передать больше, чем 32767 символов в дочерний процесс, вам придётся использовать что-то отличное от командной строки.
Какой максимальный размер у командной строки?
Это перевод What is the command line length limit? Автор: Реймонд Чен.
Зависит от того, о чём вы спрашиваете.
Максимальная длина строки для функции CreateProcess - это 32767 символов. Это ограничение идёт из записи UNICODE_STRING (TUnicodeString в JwaWinTypes.pas).
CreateProcess - это функция ядра для создания процессов, поэтому, если вы общаетесь напрямую с Win32, то это будет единственным ограничением, о котором вам нужно волноваться. Но если вы работаете с CreateProcess ещё через кого-то, то по пути доступа могут стоять и другие ограничения.
Если вы используете коммандный процессор CMD.EXE, тогда вы сталкиваетесь с пределом в 8192 символов - это ограничение командной строки, вносимое самим CMD.EXE.
Если вы используете функции ShellExecute/Ex, тогда вы будете ограничены INTERNET_MAX_URL_LENGTH (около 2048) символами на командную строку, вводимыми функциями ShellExecute/Ex (а если вы работаете в Windows 95, то тогда предел и вовсе MAX_PATH символов).
Пока я обсуждаю этот вопрос, я хотел бы упомянуть и другое ограничение: максимальный размер ваших переменных окружения - это 32767 символов. Размер переменных окружения включает в себя все имена переменных плюс их значения.
Окей, но что если вам нужно передать процессу больше, чем 32767 символов данных? Тогда вам придётся поискать другие пути, отличные от командной строки.
Читать далее.
Зависит от того, о чём вы спрашиваете.
Максимальная длина строки для функции CreateProcess - это 32767 символов. Это ограничение идёт из записи UNICODE_STRING (TUnicodeString в JwaWinTypes.pas).
CreateProcess - это функция ядра для создания процессов, поэтому, если вы общаетесь напрямую с Win32, то это будет единственным ограничением, о котором вам нужно волноваться. Но если вы работаете с CreateProcess ещё через кого-то, то по пути доступа могут стоять и другие ограничения.
Если вы используете коммандный процессор CMD.EXE, тогда вы сталкиваетесь с пределом в 8192 символов - это ограничение командной строки, вносимое самим CMD.EXE.
Если вы используете функции ShellExecute/Ex, тогда вы будете ограничены INTERNET_MAX_URL_LENGTH (около 2048) символами на командную строку, вводимыми функциями ShellExecute/Ex (а если вы работаете в Windows 95, то тогда предел и вовсе MAX_PATH символов).
Пока я обсуждаю этот вопрос, я хотел бы упомянуть и другое ограничение: максимальный размер ваших переменных окружения - это 32767 символов. Размер переменных окружения включает в себя все имена переменных плюс их значения.
Окей, но что если вам нужно передать процессу больше, чем 32767 символов данных? Тогда вам придётся поискать другие пути, отличные от командной строки.
Читать далее.
Почему вам никогда не следует приостанавливать поток
Это перевод Why you should never suspend a thread. Автор: Реймон Чен.
Приостановка (suspend) потока почти также плоха, как его уничтожение (terminate).
Вместо того, чтобы ответить на вопрос, я собираюсь задать вам несколько вопросов и посмотреть, какие ответы вы сможете на них дать.
Приостановка (suspend) потока почти также плоха, как его уничтожение (terminate).
Вместо того, чтобы ответить на вопрос, я собираюсь задать вам несколько вопросов и посмотреть, какие ответы вы сможете на них дать.
Если FlushInstructionCache ничего не делает, почему я должен её вызывать?
Это перевод If FlushInstructionCache doesn't do anything, why do you have to call it? Автор: Реймонд Чен.
Если вы посмотрите на реализацию FlushInstructionCache в Windows 95, то вы увидите, что она содержит только инструкцию возврата (иными словами, FlushInstructionCache - пустая функция). Она, по-сути, ничего не делает. Ну так почему же нам нужно вызывать функцию, которая ничего не делает?
Если вы посмотрите на реализацию FlushInstructionCache в Windows 95, то вы увидите, что она содержит только инструкцию возврата (иными словами, FlushInstructionCache - пустая функция). Она, по-сути, ничего не делает. Ну так почему же нам нужно вызывать функцию, которая ничего не делает?
Почему я должен возвращать это глупое значение из WM_DEVICECHANGE?
Это перевод Why do I have to return this goofy value for WM_DEVICECHANGE?
Чтобы запретить изъятие устройства из запроса, вы должны вернуть специальное значение BROADCAST_QUERY_DENY, любопытно, что значение этой константы равно $424D5144. Какая история скрывается за этим числом?
Чтобы запретить изъятие устройства из запроса, вы должны вернуть специальное значение BROADCAST_QUERY_DENY, любопытно, что значение этой константы равно $424D5144. Какая история скрывается за этим числом?
Кому принадлежат различные биты в масках прав доступа?
Это перевод Which access rights bits belong to whom? Автор: Реймонд Чен.
Каждый ACE в дескрипторе безопасности (security descriptor) содержит 32-х битную маску доступа. Что означают отдельные биты в этой маске?
Маска доступа представляет собой 32-х битное значение. Верхние (старшие) 16 бит определяются операционной системой, а нижние (младшие) 16 бит - защищаемым объектом.
Например, в качестве маски доступа рассмотрим значение $00060002. Это значение разбивается на определяемые системой правила WRITE_DAC ($00040000) и READ_CONTROL ($00020000) и одно правило, определяемое объектом: $0002.
Смысл правила доступа $0002, определяемого объектом, зависит от типа объекта. Конкретно это значение ($0002) может иметь любой из следующих смыслов:
или может иметь совершенно иной смысл, если тип объекта не принадлежит этому списку.
Если вы попросите функцию ConvertSecurityDescriptorToStringSecurityDescriptor перевести дескриптор безопасности в строковое представление, она попытается угадать тип объекта. Но поскольку здесь очень мало информации, то чаще всего она ошибается. К примеру, наша маска доступа из примера будет переведена в SDDL как "DCRCWD". Права RC = READ_CONTROL и WD = WRITE_DAC являются стандартными правами для любых объектов, но для $0002 SDDL предположил, что это DC = ADS_RIGHTS_DS_DELETE_CHILD.
Заметьте, что существует ещё несколько системных кодов, содержащих "GENERIC" в своём имени - к примеру, GENERIC_READ или GENERIC_WRITE. Каждый из типов объектов по-разному определяют права доступа "на чтение", "на запись" и, возможно, "на выполнение" (например, у ключей реестра есть KEY_QUERY_VALUE и KEY_SET_VALUE). Но все эти типы должны задать соответствие: какие из их прав являются правами чтения, записи и выполнения, так что вы можете запросить только маску доступа типа GENERIC и получить соответствующий вашему выбору маски набор прав - в зависимости от типа объекта.
Каждый ACE в дескрипторе безопасности (security descriptor) содержит 32-х битную маску доступа. Что означают отдельные биты в этой маске?
Маска доступа представляет собой 32-х битное значение. Верхние (старшие) 16 бит определяются операционной системой, а нижние (младшие) 16 бит - защищаемым объектом.
Например, в качестве маски доступа рассмотрим значение $00060002. Это значение разбивается на определяемые системой правила WRITE_DAC ($00040000) и READ_CONTROL ($00020000) и одно правило, определяемое объектом: $0002.
Смысл правила доступа $0002, определяемого объектом, зависит от типа объекта. Конкретно это значение ($0002) может иметь любой из следующих смыслов:
| Смысл | Если объект - это... |
|---|---|
| FILE_WRITE_DATA | файл |
| FILE_ADD_FILE | каталог |
| PROCESS_CREATE_THREAD | процесс |
| THREAD_SUSPEND_RESUME | поток |
| JOB_OBJECT_SET_ATTRIBUTES | задание |
| EVENT_MODIFY_STATE | событие |
| SEMAPHORE_MODIFY_STATE | семафор |
| TIMER_MODIFY_STATE | таймер |
| IO_COMPLETION_MODIFY_STATE | порт завершения ввода-вывода |
| KEY_SET_VALUE | ключ реестра |
| TOKEN_DUPLICATE | токен |
| WINSTA_READATTRIBUTES | оконная станция |
| DESKTOP_CREATEWINDOW | десктоп |
или может иметь совершенно иной смысл, если тип объекта не принадлежит этому списку.
Если вы попросите функцию ConvertSecurityDescriptorToStringSecurityDescriptor перевести дескриптор безопасности в строковое представление, она попытается угадать тип объекта. Но поскольку здесь очень мало информации, то чаще всего она ошибается. К примеру, наша маска доступа из примера будет переведена в SDDL как "DCRCWD". Права RC = READ_CONTROL и WD = WRITE_DAC являются стандартными правами для любых объектов, но для $0002 SDDL предположил, что это DC = ADS_RIGHTS_DS_DELETE_CHILD.
Заметьте, что существует ещё несколько системных кодов, содержащих "GENERIC" в своём имени - к примеру, GENERIC_READ или GENERIC_WRITE. Каждый из типов объектов по-разному определяют права доступа "на чтение", "на запись" и, возможно, "на выполнение" (например, у ключей реестра есть KEY_QUERY_VALUE и KEY_SET_VALUE). Но все эти типы должны задать соответствие: какие из их прав являются правами чтения, записи и выполнения, так что вы можете запросить только маску доступа типа GENERIC и получить соответствующий вашему выбору маски набор прав - в зависимости от типа объекта.
Кому принадлежат разные типы сообщений?
Это перевод Which message numbers belong to whom? Автор: Реймонд Чен.
Допустимые номера оконных сообщений разбиваются на четыре категории.
Допустимые номера оконных сообщений разбиваются на четыре категории.
Что это за странные значения, возвращаемые GWLP_WNDPROC?
Это перевод What are these strange values returned from GWLP_WNDPROC? Автор: Реймонд Чен.
GetWindowLongPtr(hwnd, GWLP_WNDPROC) (или GetWindowLong(hwnd, GWL_WNDPROC), если вы ещё не сделали свой код совместимым с 64-х разрядной ОС) должна возвращать текущую оконную процедуру. Почему же я иногда получаю при этом какие-то совершенно левые значения?
Потому что иногда "вы не можете справиться с истиной".
Если текущая оконная процедура несовместима с вызывающей стороной, тогда нельзя вернуть вам настоящий указатель на функцию, потому что вы не сможете его вызвать. Вместо этого возвращается "волшебное число". Единственный смысл этого значения - быть узнанным функцией CallWindowProc, чтобы она смогла перевести парамеры сообщения в формат, который ожидает настоящая оконная процедура, и корректно её вызвать.
Например, предположим, что вы работаете на Windows XP и что окно является окном UNICODE, но компонент, скомпилированный как ANSI, вызывает GetWindowLong(hwnd, GWL_WNDPROC) (в действительности это будет вызов GetWindowLongA, т.к. компонент скомпилирован как ANSI - прим. пер.). При этом нельзя вернуть прямой указатель на оконную процедуру, потому что она ожидает параметры сообщений в формате UNICODE, а ваш компонент оперирует с сообщениями в формате ANSI. Поэтому вместо этого возвращается волшебное значение. Когда вы передаёте это волшебное значение в CallWindowProc, она распознаёт это значение как: "ох, мне нужно перевести параметры для сообщения из ANSI в UNICODE и передать UNICODE-сообщение в вон ту оконную процедуру".
В качестве другого примера, предположим, что вы работаете в Windows 95 и окно было создано 32-х битным приложением, но 16-ти битный компонент вызывает GetWindowLong(hwnd, GWLP_WNDPROC). И снова, вы не можете вернуть указатель на 32-х битную оконную процедуру, потому что сообщения нужно конвертировать между 16-ю и 32-мя битами (и кроме того, 16-ти битная программа просто не может перейти по 32-х битному плоскому адресу). Поэтому снова, вместо настоящего указателя, возвращается полшебное значение, по которому CallWindowProc понимает, что: "ох, мне нужно перевести сообщение из 16-ти битного в 32-х битное и передать его вон той оконной процедуре".
(Эти превращения известны как "thunks".)
Поэтому помните: единственная вещь, которую можно сделать со значением, полученным от GetWindowLongPtr(hwnd, GWLP_WNDPROC), это: (1) передать это значение в CallWindowProc, или (2) передать его обратно через SetWindowLongPtr(hwnd, GWLP_WNDPROC).
GetWindowLongPtr(hwnd, GWLP_WNDPROC) (или GetWindowLong(hwnd, GWL_WNDPROC), если вы ещё не сделали свой код совместимым с 64-х разрядной ОС) должна возвращать текущую оконную процедуру. Почему же я иногда получаю при этом какие-то совершенно левые значения?
Потому что иногда "вы не можете справиться с истиной".
Если текущая оконная процедура несовместима с вызывающей стороной, тогда нельзя вернуть вам настоящий указатель на функцию, потому что вы не сможете его вызвать. Вместо этого возвращается "волшебное число". Единственный смысл этого значения - быть узнанным функцией CallWindowProc, чтобы она смогла перевести парамеры сообщения в формат, который ожидает настоящая оконная процедура, и корректно её вызвать.
Например, предположим, что вы работаете на Windows XP и что окно является окном UNICODE, но компонент, скомпилированный как ANSI, вызывает GetWindowLong(hwnd, GWL_WNDPROC) (в действительности это будет вызов GetWindowLongA, т.к. компонент скомпилирован как ANSI - прим. пер.). При этом нельзя вернуть прямой указатель на оконную процедуру, потому что она ожидает параметры сообщений в формате UNICODE, а ваш компонент оперирует с сообщениями в формате ANSI. Поэтому вместо этого возвращается волшебное значение. Когда вы передаёте это волшебное значение в CallWindowProc, она распознаёт это значение как: "ох, мне нужно перевести параметры для сообщения из ANSI в UNICODE и передать UNICODE-сообщение в вон ту оконную процедуру".
В качестве другого примера, предположим, что вы работаете в Windows 95 и окно было создано 32-х битным приложением, но 16-ти битный компонент вызывает GetWindowLong(hwnd, GWLP_WNDPROC). И снова, вы не можете вернуть указатель на 32-х битную оконную процедуру, потому что сообщения нужно конвертировать между 16-ю и 32-мя битами (и кроме того, 16-ти битная программа просто не может перейти по 32-х битному плоскому адресу). Поэтому снова, вместо настоящего указателя, возвращается полшебное значение, по которому CallWindowProc понимает, что: "ох, мне нужно перевести сообщение из 16-ти битного в 32-х битное и передать его вон той оконной процедуре".
(Эти превращения известны как "thunks".)
Поэтому помните: единственная вещь, которую можно сделать со значением, полученным от GetWindowLongPtr(hwnd, GWLP_WNDPROC), это: (1) передать это значение в CallWindowProc, или (2) передать его обратно через SetWindowLongPtr(hwnd, GWLP_WNDPROC).
Что означают буквы W и L в WPARAM и LPARAM?
Это перевод What do the letters W and L stand for in WPARAM and LPARAM? Автор: Реймонд Чен.
Давным-давно, Windows была 16-ти разрядной. Каждое сообщение могло нести с собой две части данных, называемые WPARAM и LPARAM.
Давным-давно, Windows была 16-ти разрядной. Каждое сообщение могло нести с собой две части данных, называемые WPARAM и LPARAM.
Почему "Быстрое переключение пользователей" не доступно в доменах?
Это перевод Why isn't Fast User Switching enabled on domains? Автор: Реймонд Чен.
Windows XP добавила новую возможность под названием "Быстрое переключение пользователей" ("Fast User Switching"), которое позволяет вам переключаться между пользователями, без необходимости выхода (log off). Но эта возможность отключается, как только ваш компьютер входит в домен. Почему?
Есть несколько причин, ни одна из которых не является препятствием, но все вместе они создают кучу работы для системных администраторов, которую они не желали бы получить (см. предыдущее сообщение о расходах на переподготовку).
- Как вы покажете всех пользователей в домене на экране приветствия (Welcome screen)? Уж наверно вы не захотите показывать список в 10'000 имён (скроллимся-скроллимся-скроллимся...).
- Как вы проверите, имеет ли пользователь пароль? Экран приветствия в Windows XP просто пытается впустить вас с пустым паролем. Если это срабатывает, то - пуфф! и вы вошли. Если же нет, то экран приветствия показывает диалог ввода пароля. Это работает, но при этом создаёт попытку входа с неверным паролем. Во-первых она заносится в системный лог. А во-вторых, многие системные администраторы устанавливают политику блокировки, когда ваш аккаунт блокируется, если вы ввели свой пароль неверно более N раз. Проба входа с пустым паролем приводила бы к отключению аккаунтов по всей компании.
Те из вас, кто уже видел Longhorn, могли заметить, что теперь быстрое переключение пользователей доступно и для доменов. Потребовалось разработать целую новую инфраструктуру, чтобы включить эту возможность и при этом не разрушить повседневную жизнь системных администраторов.
Windows XP добавила новую возможность под названием "Быстрое переключение пользователей" ("Fast User Switching"), которое позволяет вам переключаться между пользователями, без необходимости выхода (log off). Но эта возможность отключается, как только ваш компьютер входит в домен. Почему?
Есть несколько причин, ни одна из которых не является препятствием, но все вместе они создают кучу работы для системных администраторов, которую они не желали бы получить (см. предыдущее сообщение о расходах на переподготовку).
- Как вы покажете всех пользователей в домене на экране приветствия (Welcome screen)? Уж наверно вы не захотите показывать список в 10'000 имён (скроллимся-скроллимся-скроллимся...).
- Как вы проверите, имеет ли пользователь пароль? Экран приветствия в Windows XP просто пытается впустить вас с пустым паролем. Если это срабатывает, то - пуфф! и вы вошли. Если же нет, то экран приветствия показывает диалог ввода пароля. Это работает, но при этом создаёт попытку входа с неверным паролем. Во-первых она заносится в системный лог. А во-вторых, многие системные администраторы устанавливают политику блокировки, когда ваш аккаунт блокируется, если вы ввели свой пароль неверно более N раз. Проба входа с пустым паролем приводила бы к отключению аккаунтов по всей компании.
Те из вас, кто уже видел Longhorn, могли заметить, что теперь быстрое переключение пользователей доступно и для доменов. Потребовалось разработать целую новую инфраструктуру, чтобы включить эту возможность и при этом не разрушить повседневную жизнь системных администраторов.
Просто следуйте правилам и никто не пострадает
Это перевод Just follow the rules and nobody gets hurt. Автор: Реймонд Чен.
Может быть вы были ленивы и никогда не вызывали VirtualProtect(PAGE_EXECUTE), когда вы создавали код на лету. Вам это сходило с рук, потому что у системы защиты страниц процессора i386 нет режима "читать, но не выполнять", поэтому всё, что вы могли читать, вы могли и выполнять.
Может быть вы были ленивы и никогда не вызывали VirtualProtect(PAGE_EXECUTE), когда вы создавали код на лету. Вам это сходило с рук, потому что у системы защиты страниц процессора i386 нет режима "читать, но не выполнять", поэтому всё, что вы могли читать, вы могли и выполнять.
Длинная и печальная история ключа Shell Folders
Это перевод The long and sad story of the Shell Folders key. Автор: Реймонд Чен.
Когда вы создаёте архитектуру операционной системы, обратная совместимость - это одна из тех вещей, которые вам при этом нужно просто принять. Но когда новые программы начинают зависеть от хаков, созданных для старых программ, вам захочется рвать на себе волосы.
Когда вы создаёте архитектуру операционной системы, обратная совместимость - это одна из тех вещей, которые вам при этом нужно просто принять. Но когда новые программы начинают зависеть от хаков, созданных для старых программ, вам захочется рвать на себе волосы.
Подписаться на:
Сообщения (Atom)