04 апреля 2009

Unexpected side effect

У каждого windows-процесса есть такая "глобальная переменная" - текущая директория. Её можно прочитать функцией GetCurrentDirectory() и изменить функцией SetCurrentDirectory().

Вот чего я не ожидал, что её меняют кто попало. Например, она может изменится после вызовов GetSaveFileName(), GetOpenFileName(). В их документации такое поведение описано и дана возможность его отключить. Но другое дело, что она может измениться внутри... функции StartDoc(). Это такая функция, которая "The StartDoc function starts a print job.", вобщем начинает печать.

И вот почему такое может происходить: драйвер принтера Microsoft Office Document Image Writer - поставляется с Microsoft Office и "печатает" в TIFF-файлы. При получении новой задачи он спрашивает куда же нужно сохранить создаваемый файл. Делает он это, я полагаю, с помощью GetSaveFileName(). Та, в свою очередь, меняет текущую директорию. Я бы сказал что врядли рядовой программист будет ожидать такие побочные эффекты от функции StartDoc().

Мораль (кроме той, что нужно драйвер подправить, добавив флажок OFN_NOCHANGEDIR) - старайтесь не менять глобального окружения кроме случаев, где это будет очевидно.

1 комментарий:

Мазов Гоша aka Carc комментирует...

С драйвером конечно случай тяжелый, а в своем коде частенько выкручиваюсь в стиле auto_ptr
Когда есть "мрачный" код, который имеет много точек выхода и неохота морочить себе голову, где что нужно освобождать или восстанавливать контекст до вызов.
К примеру для директории, это будет нечто вроде такого
class CAutoCurrentDirectory {
CString m_strPrevDirectory;
public:
CAutoCurrentDirectory(const CString strNewDirectory)
{
GetCurrentDirectory(MAX_PATH, m_strPrevDirectory.GetBufferSetLength(MAX_PATH+1);//сохранили предыдущую директорию
m_strPrevDirectory.ReleaseBuffer();
SetCurrentDirectory(strNewDirectory);
}
~CAutoCurrentDirectory()
{
SetCurrentDirectory(strNewDirectory);//восстановили предыдущую директорию
}
};

Ну это конечно так... На коленке писано - по уму нужно и наследование запретить, и new закрыть чтобы с кучей не баловались. Но обычно такое пишется каждый раз заново, поэтому некоего обобщенного "шаблона" и не создавалось. А вот где реально выручает такой подход, так это в работе с HANDLE + CloseHandle, и всякими GDI-дескрипторами