Балансировка списка Known DLLs

Это перевод The Known DLLs Balancing Act. Автор: Реймонд Чен.

Неформально названная «Known DLLs» («Известные DLL») возможность Windows является простым списком DLL (Dynamic Link Library), с

Читать на сайте автора.

Выбираемся из DLL Hell

Это перевод Getting Out of DLL Hell. Автор: Реймонд Чен.

Читать на сайте автора.

Два худших ПК в истории

Это перевод The Two Worst PCs Ever. Автор: Реймонд Чен.

В марте 2007 PC World составил список десяти худших персональных компьютеров в истории. Два первых места бросились мне в глаза — потому что мы вообще-то используем их в работе. Ну, «используем» — это громко сказано. Мне следовало сказать: «я познакомился с этими компьютерами, когда они прожили большую часть своего рабочего цикла в офисах сотрудников Microsoft».

Barbie-ПК от Mattel занял «почётное» второе место в списке PC World. Ведь этот компьютер имел большое преимущество над обычными дешёвыми компьютерами в скучных цветах. Ибо он был обычным дешёвым компьютером в скучном цвете, на который вы могли приклеить розовые стикеры из комплекта — на системник, монитор, колонки… И на нём был предустановлен тематический Барби-софт!

Мы приобрели одну такую машину в качестве тестовой. По крайней мере, несчастный тестер, которого назначили работать с ней, имел чувство юмора. Он с энтузиазмом погрузился в это дело, начав с расклейки всех стикеров — чтобы убедиться, что он установил компьютер «как и было задумано поставщиком». Компьютер был поставлен на видное место, хорошо просматриваемое из коридора. И любой, кто проходил мимо и замечал эту розовую катастрофу, приглашался зайти и опробовать её.

Этот Барби-ПК стал у нас своего рода развлечением. Когда с ним случались проблемы, и разработчику нужно было посетить офис и отладить проблему на нём — почти всегда вы могли услышать «клёво!», когда разработчик осознавал, на какой именно машине ему нужно исследовать проблему. Мне сказали, что этим летом один из студентов-интернов установил на неё копию Windows Server Datacenter Edition — просто чтобы поизвращаться.

Первое же место (или, вернее, последнее) отошло к ПК от Packard Bell. Ах, эти воспоминания… Вот вам просто один пример «гениальности» этой машины: компьютер поставлялся со всеми заполненными слотами расширения. Наверное, расчёт был такой: «Наш компьютер совершенен, вам никогда не придётся его улучшать!». Ага.

Во времена разработки Windows® 95 старший директор купил себе такой компьютер домой и регулярно устанавливал на него свежайшие сборки Windows (как часть «dogfood»-процесса). Как вы догадываетесь, при этом возникало множество проблем, которые нужно было исправлять, для чего компьютер нужно было привозить в офис.

Менеджер разработки Windows 95 был мудрым человеком с дерзко прямолинейным подходом к решению проблем. Смущённый тем, сколько раз директору приходилось привозить на работу свой домашний компьютер для отладки, он снова применил свой прямолинейный подход. Он просто пошёл в магазин и купил точно такой же ПК Packard Bell.

То, что их теперь стало двое — уже само по себе плохо. Но хуже всего, что второй ПК отдали мне, поставив задачу устанавливать на него каждую сборку Windows и устранять все найденные проблемы. Таким образом, когда директор устанавливал новую сборку у себя, то всё было бы гладко.

Если вы были внимательны, то заметили изъян в этом плане. Поскольку в ПК Packard Bell не было свободных слотов расширения, нам некуда было вставить сетевую карту. Мне пришлось прибегнуть к хитрым окружным путям, чтобы подключить его к сети. Тогда я действительно ненавидел этот компьютер.

Читать на сайте автора.

Что в Windows 3.1 делала кнопка "Игнорировать" в диалоге GPF?

Это перевод What did the Ignore button do in Windows 3.1 when an application encountered a general protection fault? Автор: Реймонд Чен.

Когда ваше приложение вызывало General Protection Fault (GPF, общее нарушение защиты, сегодня известное как Access Violation) в Windows 3.0, система показывала такой диалог об ошибке:

Application error
CONTOSO caused a General Protection Fault in
module CONTOSO.EXE at 0002:2403
Close

В Windows 3.1 же вы получали такой диалог при выполнении некоторых условий:

CONTOSO
An error has occurred in your application.
If you choose Ignore, you should save your work in a new file.
If you choose Close, your application will terminate.

Close

Ignore

Хорошо, мы знаем, что делает кнопка «Close» (закрыть). Но что делает кнопка «Ignore» (Игнорировать)? И при каких-таких условиях она появляется?

Говоря грубо, кнопка «Игнорировать» появляется, если:

  • Ошибка является общим нарушением защиты (GPF),
  • Инструкция, вызвавшая ошибку, не принадлежит ядру или менеджеру памяти,
  • Инструкция, вызвавшая ошибку, является одной из следующих (возможно, с одним или несколькими префиксами):
    • Операции с памятью: op r, m; op m, r; или op m.
    • Строковые операции: movs, stos, etc.
    • Загрузки селектора: lds, les, pop ds, pop es.

Если эти условия выполняются, то показывается кнопка «Игнорировать». А если вы нажмёте на эту кнопку, то ядро выполнит следующее:

  • Если инструкция, вызвавшая ошибку — это загрузка селектора, то регистр назначения просто обнуляется;
  • Если инструкция, вызвавшая ошибку — это pop, то указатель стека увеличивается на два байта;
  • IP переходит на следующую машинную команду;
  • Выполнение возобновляется.

Другими словами, ядро выполняло некий ассемблерный эквивалент для ON ERROR RESUME NEXT.

Так, вы можете подумать: «Как это вообще могло работать? Вы же просто пропускаете выполнение случайных команд!». Как бы это ни было странным, но эта идея была настолько сумасбродной, что она работала — или, по крайней мере, работала достаточно часто. Вам могло понадобится нажать «Игнорировать» дюжину раз, но в итоге был хороший шанс, что плохие значения регистров затрутся хорошими (и, вероятно, это не займёт много времени, поскольку у 8086 было крайне мало регистров), и программа продолжит вроде-как-нормально выполняться.

Полное безумие.

Упражнение: почему коду не нужно было знать, как игнорировать инструкции перехода и условного перехода?

Бонусный тривиальный факт: Разработчик, реализовавший эту сумасшедшую возможность, был Дон Корбитт — автор Доктора Ватсона.

Читать на сайте автора.

Почему у каждого диска есть свой собственный текущий каталог?

Это перевод Why does each drive have its own current directory? Автор: Реймонд Чен.

Комментатор Dean Earley спросил: «Почему есть ‘текущий каталог’ и ‘текущий диск’? Почему бы их не объединить?»

Лаконичный ответ: потому что изначально у каждого диска был свой собственный каталог. Сейчас это не так, даже если иногда вам кажется иначе.

Хорошо, давайте развернём это утверждение. Вообще-то, вы уже знаете ответ на этот вопрос, вам только нужно сложить все кусочки вместе.

Заведите вашу машину времени на времена DOS 1.0. Каждый дисковый том был представлен буквой диска. На дисках вообще не было каталогов. Такая структура пришла из CP/M.

Поэтому программы эры DOS 1.0 не понимали и не умели работать с каталогами, они ссылались на файлы просто указанием буквы диска и имени файла. Например: B:PROGRAM.LST. Давайте запустим ассемблер (компиляторы в то время были для богачей) и соберём программу с исходным кодом на дискете A, а результат запишем на дискету B:

A>asm foo       расширение «.asm» у «foo» подразумевается неявно
Assembler version blah blah blah
Source File: FOO.ASM
Listing file [FOO.LST]: NUL листинг нам не нужен
Object file [FOO.OBJ]: B: сохранить объектник на диск B

Поскольку на запрос Object file мы указали только букву диска, то ассемблер возьмёт имя по умолчанию (FOO.OBJ), а итоговое имя файла для записи объектника будет B:FOO.OBJ.

Окей, теперь давайте выпустим DOS 2.0 с поддержкой подкаталогов. Предположим, вы хотите собрать исходный код A:SRCFOO.ASM и положить результат в B:OBJFOO.OBJ. Вот как вы можете сделать это:

A> B:
B> CD OBJ
B> A:
A> CD SRC
A> asm foo
Assembler version blah blah blah
Source File: FOO.ASM
Listing file [FOO.LST]: NUL
Object file [FOO.OBJ]: B:

Ассемблер читает файл A:FOO.ASM и записывает файл B:FOO.OBJ, но поскольку текущий каталог — свой у каждого диска, то результаты идут в A:SRCFOO.ASM и B:OBJFOO.OBJ — как нам и нужно. Если бы текущий каталог был глобальным, то мы не смогли бы указать ассемблеру, чтобы он поместил вывод в подкаталог. Иными словами, тогда любые программы DOS 1.0 (а в момент выхода DOS 2.0 других программ просто не было) были бы ограничены корневым каталогом, что в свою очередь означало бы, что никто не использовал бы подкаталоги, т.к. их программы не могли получить доступ к этим файлам!

С точки зрения DOS 1.0, изменение текущего каталога диска эквивалентно смене дискеты. «Ой, смотри, теперь на этом диске совсем другой набор файлов!»

Да, такая вот краткосрочная память.

Это запоминание текущего каталога для каждого диска сохраняется в системе и сегодня — хотя бы для командных файлов, поскольку Win32 не имеет концепции «текущего каталога диска«. В Win32 у вас есть «просто» текущий каталог. Видимость, что текущий каталог — свой у каждого диска, поддерживается cmd.exe с помощью странных переменных окружения.

Dean продолжает: «Почему бы не слить эти два понятия? Сейчас для смены текущего каталога на конкретный мне нужно устанавливать и каталог и диск».

Ответ на второй вопрос звучит так: «Вообще-то эти понятия объединены в 1995 году. Это только cmd.exe делает вид, что они различны». И если вы хотите сменить и диск и каталог одной командой, то просто добавьте параметр /D к команде CHDIR:

D:> CD /D C:Program FilesWindows NT
C:Program FilesWindows NT> _

Заметьте, что команда CHDIR позволяет вам не указывать кавычки вокруг путей с пробелами, поскольку у этой команды есть всего один аргумент, так что отсутствие кавычек не приводит к неоднозначности трактовки.

Читать на сайте автора.

FPO

Это перевод FPO. Автор: Ларри Остерман.

На прошлой неделе я болтал с одним парнем из команды измерения производительности. Он мне рассказал историю, которая меня очень удивила. Как оказалось, они обнаружили проблему производительности в одном стороннем драйвере. К сожалению, у них возникли проблемы в локализации узкого места, поскольку разработчик драйвера скомпилировал его с FPO (Frame Pointer Ommission) и не предоставил отладочную информацию.

Что тут удивительного? Ну, я удивился что сегодня вообще кто-то использует FPO.

Но что такое FPO?

Чтобы понять ответ, нам нужно вернуться назад во времени.

Процессор Intel 8088 имел чрезвычайно мало регистров, вот они (я игнорировал сегментные регистры):

AX BX CX DX IP
SI DI BP SP FLAGS

Даже в таком ограниченном наборе регистров этим регистрам были заданы специальные роли. Регистры AX, BX, CX и DX были регистрами «общего назначения», SI и DI были «индексными» регистрами, SP был «указателем стека», BP был «указателем фрейма» («указателем базы»), IP был «указателем инструкции», а FLAGS был регистром только для чтения, который содержал информацию о текущем состоянии процессора.

Регистры BX, SI, DI и BP были специальными, потому что они могли использоваться как «индексные» регистры. Индексные регистры чрезвычайно важны для компилятора, поскольку только они могут использоваться в получении доступа к памяти через указатель. Другими словами, если у вас есть структура, расположенная по смещению $1234 в памяти, вы можете установить индексный регистр в значение $1234 и получать доступ к значениям, относительно этого адреса (и регистра). Например:

MOV    BX, [Structure]
MOV AX, [BX]+4

Мы записываем в регистр BX значение памяти, на которое указывает [Structure], а затем записываем в регистр AX слово, находящееся в 4 байте с начала этой структуры.

Здесь нужно отметить, что регистр SP не являлся индексным регистром. Это означало, что для получения доступа к локальным переменным и аргументам на стеке вам нужно использовать другой регистр — и именно так появился регистр BP. Индексный регистр BP был создан специально для получения доступа к значениям в стеке.

Когда вышел 386, разработчики Intel расширили регистры до 32 бит, а также сняли ограничение, что только BX, SI, DI и BP могли использоваться как индексные:

EAX EBX ECX EDX EIP
ESI EDI EBP ESP FLAGS

Это неплохо: неожиданно, вместо жалких трёх регистров, компилятор мог использовать все шесть.

Поскольку индексные регистры используются для доступа к полям структур, для компилятора они на вес золота — чем их больше, тем лучше, так что стоит пойти на многое, лишь бы их было побольше.

Некий очень умный человек сообразил, что раз теперь ESP является индексным регистром, то EBP больше не является единственным специально выделенным регистром для стека. Другими словами, вместо:

MyFunction:
PUSH EBP
MOV EBP, ESP
SUB ESP, <LocalVariableStorage>
MOV EAX, [EBP+8]
:
:
MOV ESP, EBP
POP EBP
RETD

вы могли получить доступ к первому параметру на стеке следующим образом (EBP + 0 — старое значение EBP, EBP + 4 — адрес возврата):

MyFunction:
SUB SP, <LocalVariableStorage>
MOV EAX, [ESP+4+]
:
:
ADD SP, <LocalVariableStorage>
RETD

Это работает на отлично, теперь EBP освобождается и его можно использовать как дополнительный регистр общего назначения! Такая оптимизация в компиляторе называется «Frame Pointer Omission», известная под акронимом FPO.

Но с FPO есть небольшая проблема.

Если вы посмотрите на первый вариант кода (без FPO), то заметите, что первой инструкцией в функции будет PUSH EBP, за которым будет MOV EBP, ESP. Здесь получается интересный и крайне полезный (побочный) эффект. Фактически, при этом получается односвязный список, в котором хранятся указатели на каждый фрейм для всех вызывающих функции. Таким образом, зная значение EBP внутри одной функции, вы можете получить стек вызовов для этой функции. Это невероятно полезно для отладки, потому что это означает, что стеки вызовов всегда точны — даже если у вас на руках нет отладочной информации. К сожалению, если вы начинаете использовать FPO, список фреймов теряется — эта информация просто не отслеживается.

Чтобы решить эту проблему, информация, которая теряется при использовании FPO, заносится в отладочную информацию. Таким образом, если у вас есть отладочная информация («символы»), то вы сможете построить стек вызовов.

FPO был включен для всех модулей Windows, начиная с NT 3.51. Но начиная с Windows Vista FPO был отключен — поскольку он стал не нужен: машины стали значительно быстрее машин 1995 года, так что небольшой выигрыш в производительности от FPO не покрывал его пенальти на отладку и анализ.

Читать на сайте автора.

Почему системный файловый диалог меняет текущий каталог?

Это перевод Why does the common file dialog change the current directory? Автор: Реймонд Чен.

Когда вы перемещаетесь по папкам в системном диалоге открытия (или сохранения) файлов, то диалог вызывает функцию Set­Current­Directory, передавая ей каталог, в который вы перешли (эй, не заставляйте меня возрождать уголок зануды).

Окей, возможно, вашей первой реакцией будет: «Правда? Я вообще не знал, что он так делает!» Что ж, это ещё одна страница в истории проклятия текущего каталога.

Хорошо, но теперь возникает вопрос: «А зачем диалог это делает?»

Вообще-то, вы уже должны знать ответ на этот вопрос. Многие программы рассчитывают, что текущий каталог совпадает с каталогом открываемого документа.

Но, оказывается, существует способ сказать: «Нет, я — это не такая ламерская программа. Я не делаю предположений о текущем каталоге, я могу работать с любым. Так что не меняй текущий каталог». Вы можете сделать это, передав флаг OFN_NO­CHANGE­DIR (а если ваша программа использует интерфейс IFile­Dialog, то опция NO­CHANGE­DIR включена перманентно — ура Прогрессу!).

Прим.пер.: для Delphi флаг OFN_NO­CHANGE­DIR соответствует значению ofNoChangeDir в TCommonDialog.Options (значение fdoNoChangeDir в TCustomFileDialog.Options всегда игнорируется), а интерфейс IFile­Dialog используется Delphi в «диалогах Vista» (например, TFileOpenDialog), а также в стандартных файловых диалогах — автоматически на Vista+ при условии подключения манифеста (только в новых IDE, конечно же). В последнем случае опция ofNoChangeDir игнорируется.

Но теперь, когда вы знаете про второе проклятие текущего каталога, вы можете использовать его против первого проклятия.

Если вы смогли определить программу, удерживающую открытым каталог, и вы подозреваете, что программа пала жертвой проклятия текущего каталога, то вы можете зайти в эту программу и открыть в ней файловый диалог (например, Файл / Открыть… или Файл / Сохранить как…). В этом диалоге вы меняете папку на, скажем, корень диска или на рабочий стол. А затем закрываете (отменяете) диалог.

Поскольку системный файловый диалог изменяет текущий каталог, то вы, фактически, внедрили вызов Set­Current­Directory прямо в целевой процесс, уводя, таким образом, текущий каталог с каталога, который вы хотите удалить. Заметьте, однако, что этот трюк сработает только если это приложение не передаёт флаг OFN_NO­CHANGE­DIR в вызов Get­Save­File­Name.

Для Проводника вы можете вызвать файловый диалог нажатием Win + R и кнопки «Обзор» — и в версиях Windows вплоть до Windows XP Проводник не передаёт флаг OFN_NO­CHANGE­DIR.

Читать на сайте автора.

Ваш отладочный код может быть уязвимостью в безопасности: подгрузка опциональной отладочной DLL без указания полного пути

Это перевод Your debugging code can be a security vulnerability: Loading optional debugging DLLs without a full path. Автор: Реймонд Чен.

Запомните: плохих парней не заботит, если вы добавили код лишь для целей отладки. Если есть код — они его атакуют.

Посмотрите на такой код:

type
TDocLoadingProc = procedure(Stream: TStream);

var
OnDocLoading: TDocLoadingProc;

procedure LoadDebuggingHooks;
var
hmodDebug: HMODULE;
begin
hmodDebug := LoadLibrary('DebugHooks.dll');
if hmodDebug = 0 then
Exit;
OnDocLoading := GetProcAddress(hmodDebug, 'OnDocLoading');
end;

function LoadDocument({...}): HRESULT;
begin
// ...
if Assigned(OnDocLoading)
// Дадим отладочной ловушке изменить документ перед загрузкой
OnDocLoading(DocStream);
// ...
end;

Если вы хотите отлаживать вашу программу, вы копируете DebugHooks.dll в папку приложения. Код выше ищет эту DLL и загружает её, если она есть. Для примера я включил код одной из возможных ловушек из этой отладочной DLL. Идея примера (и это просто пример, так что давайте не будем обсуждать, хороший ли это пример) состоит в том, что непосредственно перед загрузкой документа мы вызываем функцию OnDocLoading. Эта функция может обернуть переданный ей поток в другой поток, так что содержимое потока можно логгировать при его чтении — в попытке локализовать на чём стопорится загрузка документа. Или её можно использовать для инъекции ошибок ввода-вывода с целью тестирования. В общем, используйте своё воображение.

Но этот отладочный код также содержит уязвимость.

Вспомните, что пути для поиска DLL ищут библиотеки в таком порядке:

  1. Каталог приложения;
  2. Каталог system32;
  3. Каталог system;
  4. Каталог Windows;
  5. Текущий каталог;
  6. Пути из PATH.

Когда вы отлаживаете программу, вы копируете DebugHooks.dll в папку приложения, откуда она и загружается (шаг 1 в списке выше). Но если вы не отлаживаете свою программу, то шаг 1 пропускается, и поиск продолжается дальше. DLL не будет найдена на шаге 2, 3 и 4, и в итоге мы достигаем шага 5: текущего каталога.

И теперь вас хакнули.

Как правило, ваше приложение не имеет прямого контроля над текущим каталогом. Пользователь может запустить вашу программу из любого каталога — и это каталог станет вашим текущим каталогом. И когда вызов LoadLibrary будет искать DLL в текущем каталоге, плохие парни могут положить в него их собственную DLL, ваша программа станет жертвой инъекции кода.

Это особенно опасно, если ваше приложение ассоциировано с каким-то типом файла, поскольку пользователь может запустить вашу программу просто дважды-щёлкнув по файлу, с которым вы ассоциированы.

Когда вы дважды-щёлкаете по файлу, Проводник устанавливает текущий каталог для приложения-обработчика этого файла равным каталогу, в котором расположен этот файл. Это необходимо для правильной работы приложений, которые ищут в текущем каталоге вспомогательные файлы. Например, представьте гипотетическое приложение LitWare Writer, ассоциированное с файлами *.LIT. Документ приложения LitWare Writer, представленный файлом ABC.LIT — это просто представитель семейства файлов: ABC.LIT (главный документ), ABC.LTC (индекс документа и содержание), ABC.LDC (словарь грамматики для документа), ABC.LLT (пользовательский шаблон для документа) и так далее. Когда вы открываете документ C:PROPOSALABC.LIT, LitWare Writer ищет остальные части документа в текущей папке, вместо C:PROPOSAL. Чтобы помочь таким приложениям найти все необходимые файлы, Проводник указывает функции CreateProcess, что текущий каталог для приложения LitWare Writer должен быть равен C:PROPOSAL.

Так, вы можете сказать, что программы вроде этого LitWare Writer (которые ищут файлы в текущей папке вместо папки с главным документом) плохо написаны, и я в этом с вами соглашусь. Однако Windows нужно работать даже с плохо написанными программами (упреждающее ехидное замечание: Windows сама плохо написана). В реальном мире есть слишком много плохо написанных программ, некоторые из которых являются лидерами в своих сегментах (см. выше упреждающий остроумный комментарий), и если Windows перестанет их запускать, люди скажут, что это вина Windows, а не этих программ.

Мне даже не надо закрывать глаза, чтобы представить, как нам присылают баг-отчёт с этим поведением:

«Эта программа отлично работает в MS-DOS, но в Windows она не работает. Тупая Windows».

Клиент не будет счастлив услышать следующий ответ: «Вообще-то, эта программа никогда и не работала верно, ей просто везло все эти X лет. Авторы этой программы никогда не рассматривали случай, если открываемый документ расположен вне текущего каталога. Им это сошло с рук, потому что обычно вы открываете файлы в MS-DOS следующим образом: вы переходите в каталог с документом и потом набираете LWW ABC.LIT. Но если вы выполните в MS-DOS LWW C:PROPOSALABC.LIT, то увидите то же (неверное) поведение программы. Иными словами, это поведение системы — не баг, а дизайн».

Ответ на «Это поведение по проекту» обычно звучит как «Я скажу так, что дизайн, который не даёт мне выполнять мою работу — отстойный дизайн» или кратко: «Нет, это не дизайн, это баг» (вы не верите мне, что такое происходит? Просто почитайте Slashdot).

Итак, чтобы все такие программы продолжили бы работу в Windows, система устанавливает текущий каталог для программы в каталог, содержащий открываемый документ. Это вовсе не чрезмерное и не безрассудное решение — ведь благодаря ему начала работать программа. Не то чтобы запускаемой программе было важно конкретное значение текущего каталога — ведь у неё нет контроля над ним!

Но сказанное также означает, что если вы запустили программу двойным щелчком по ассоциированному документу, то программа будет удерживать каталог с документом — потому что этот каталог будет текущим. Если только, конечно, сама программа потом не изменит свой текущий каталог.

Бонус-замечание: изначально я написал эту серию заметок более двух лет назад. Но даже тогда я не считал их чем-то удивительным, новаторским или интересным. Но, похоже, некоторые люди вновь открыли это несколько месяцев назад, и вылезли из кожи вон, чтобы присвоить себе лавры первооткрывателей. Это как новое поколение подростков, которые думают, что они изобрели секс. Чисто для протокола: вот некоторые официальные инструкции (и, чтобы расставить все точки над i: это официальные инструкции по атаке на текущий каталог, а не официальные инструкции по сексу).

Историческое замечание: почему вообще есть текущий каталог? Ответ: это идёт со времён CP/M. В CP/M не было PATH. Всё запускалось из текущего каталога. Сегодняшнее состояние вещей — это цепочка из обратных совместимостей.

Читать на сайте автора.

Одна из причин, почему ShellExecute возвращает SE_ERR_ACCESSDENIED, а ShellExecuteEx возвращает ERROR_ACCESS_DENIED

Это перевод One possible reason why ShellExecute returns SE_ERR_ACCESSDENIED and ShellExecuteEx returns ERROR_ACCESS_DENIED. Автор: Реймонд Чен.

(этот странный заголовок написан для поисковой оптимизации)

Один клиент сообщил, что при вызове ShellExecute эта функция иногда завершалась с ошибкой SE_ERR_ACCESSDENIED (= 5) — в зависимости от того, что они пытались открыть (а если бы они использовали ShellExecuteEx, то они получали бы ошибку ERROR_ACCESS_DENIED (перевод поста)).

После долгой «игры в пинг-понг», проверки файловых ассоциаций и т.п., один из разработчиков из нашей команды использовал свой хрустальный шар и спросил: «А вы случайно не вызываете её из MTA?» (MTA = multi-threaded apartment, многопоточный апартмент).

«Да», — ответил клиент. — «ShellExecute вызывается из специально выделенного MTA-потока. Проблема в этом?».

Ну, вообще-то, да. И об этом явно сказано в документации к ShellExecute:

Поскольку ShellExecute может делегировать выполнение расширениям Оболочки (источникам данных, обработчикам контекстных меню, реализациям действий), которые активируются через COM, то COM должен быть инициализирован до того, как ваш код вызовет ShellExecute. Некоторые расширения Оболочки требуют, чтобы COM был инициализирован в однопоточном апартменте (STA).

Как правило, функции Оболочки требуют STA. Вспомните, что MTA подразумевает отсутствие интерфейса пользователя. Если вы попытаетесь использовать apartment-threaded объект из MTA-потока, вам потребуется маршаллер, а если такого маршаллера нет, то вызов провалится.

Это также объясняет, почему вызов ShellExecute завершается неудачей только для некоторых типов файлов: если обработка типа файла не требует создания объекта COM, то ситуация с несовпадением MTA/STA никогда не произойдёт.

Читать на сайте автора.

На одну короткую минуту славы DirectX стал более популярным чем другое слово, заканчивающееся на X

Это перевод For a brief shining moment, DirectX was more popular than another word that ends in x. Автор: Реймонд Чен.

В месяц сразу после выхода DirectX 3 слово «directx» стало самым популярным для поиска на microsoft.com. Само по себе, это не было столь удивительным.

Удивительным было слово на шестом месте: «sex».

Вы знаете, это вводит меня в ступор до сих пор. Кто вообще ищет «sex» на microsoft.com? И что, собственно говоря, они ожидают найти?

Читать на сайте автора.