arthur2 писал(а):Теперь вопрос: я сейчас подменю указатель на строку с нужным путём. И чем это будет принципиально отличаться от предыдущей ситуации в плане утечки? Старая-то строка не обнулится. А запоминать адрес и возвращать его тоже не получится - диалог может строку переписать. Или если все текстовые поля в структуре объявить строками, то бейсик их корректно будет переписывать?
Вот ты не хочешь пытаться думать. Окей, допустим, никакого перехвата нет. Вызывающая сторона передала одну строку, диалог переписал строку, как ты сказал. Кто удалит старую строку? Всё? Утечка?'
Или оно и без всякого перехвата и подмены устроено так криво, что всё равно даёт утечку (в случае, если диалог перезаписал строку), и тогда за что ты переживаешь? Или же оно работает попросту не так, как ты себе это представляешь, и тогда твой вопрос и твои опасения не имеют смысла.
Чем это отличается от ситуации с перезаписью сомнительной ячейки в памяти процесса? Отличается всем. Давай разбираться. В предыдущем сценарии есть ячейка памяти хранящая указатель на блок памяти, содержащий строку. О всяком ресурсе (любой сущности, которая выделяется и уничтожается) есть владелец. Владелец может быть постоянным. Или же владелец может быть переменным: если ресурс является переходящим, то можно выделить поставщика и потребителя ресурса.
В первом случае кто является владельцем блока памяти со строчкой? Это класс CFileRep. Он же поставщик, он же потребитель. Он выделяет блок, он им пользуется, он его уничтожает. И ты вклиниваешься в этот жизненный цикл самым неожиданным образом.
А вот когда происходит передача ресурса (любого — строки, блока, массива, объекта) между двумя разными функциями, да ещё живущих в двух совершенно разных модулях, написанных разными людьми, обычно ресурс является переходящим и в момент вызова владельцем ресурса становится другая сторона. Но не обязательно.
Поэтому в подобных случаях всегда устанавливаются правила игры, контракт: если вызывающая сторона породила ресурс и передаёт его в пользование другой стороне, то обязанность по уничтожению ресурса либо будет за вызываемой стороной, либо же вызывающая сторона должна дождаться возврата и всё-таки сама уничтожить свой ресурс (или не уничтожить, а использовать как-то дальше).
Правила игры (кто создаёт, а кто уничтожает, кто выделяет, а кто освобождает) обычно указанны в документации либо явно, либо просто понятны исходя из текста.
И если почитать документацию на структуру OPENFILENAME, то всё становится ясным.
Вызывающая сторона передаёт функции доступ к буферу в виде указателя и размера буфера. Вызываемая сторона получает возможность читать и писать в буфер (не выходя за его пределы), но владельцем буфера вызываемая сторона не становится. Хозяином буфера остаётся вызывающая сторона. Вызываемая сторона не удаляет, не увеличивает, не переразмещает буфер. Она только меняет содержимое буфера, но никак не указатель на буфер в структуре.
Все указатели в структуре после вызова остаются неизменными. Меняется только содержимое буферов.
Буферы выделяет вызывающая сторона, и обязанность по уничтожение буферов остаётся за нею же. Поэтому никакой утечки от того, что диалог что-то изменил, быть не может в принципе. А сам подход позволяет буферы выделять не из кучи, а размещать прямо на стеке, используя локальную переменную (массив достаточной длины).
Второй момент состоит в том, что, как я думал, ты собираешься подменять
lpstrInitialDir, а этот параметр функция вообще не должна подменять/перезаписывать.
Соответственно, для замены текста, передаваемого функции, можно пользоваться двумя подходами:
- Не трогать адрес в поле lpstrXxxxxxx, а пойти и записать новые символы по этому адресу (не выходя за границы буфера). То есть попросту заменить содержимое в буфере, любезно предоставленном вызывающей стороной для функции OpenFileName (и перепавшем нам).
- Выделить свой буфер (внутри функции-перехватчика) и в структуре OPENFILE поставить указатель на него (и новый размер), а старый указатель и размер запомнить. Перед возвратом из функции-перехватчика вернуть на место старый указатель и размер.
Логика подсказывает, что первый подход надо применять для всех параметров, которые вызываемая сторона может (и должна) заменить, а второй подход — для всех параметров, которые вызывающая сторона не должна менять.
На практике, если для всех параметров использовать первый подход, или если во втором подходе не запоминать и не возвращать старую пару адрес:размер, то ничего страшного не произойдёт. Но такой поход
попахивает. Откуда мы можем быть на 100 процентов уверенными, что в вызываемой стороне ничего не сломается от того, что мы вдруг решим поменять содержимое буфера, которое по мнению вызывающей стороны ни при каких обстоятельствах вызываемая сторона менять не должна?
В конце-концов, буфер для параметра, который CommonDialog-функция никогда не меняет (а только читает) может лежать в памяти, доступной только для чтения (выделенной с таким атрибутом доступа или же просто в секции PE-файла, доступной только для чтения). И если мы для такого параметра и такого буфера применим первый подход, у нас приложение рухнет с исключением
C0000005.
Поэтому: если контракт между вызывающей и вызываемой стороной предполагает, что вызываемая сторона не меняет буфер, надо использовать второй подход. Для изменяемых буферов — первый (либо второй с копированием содержимого собственного буфера в родной буфер перед возвратом).
Понять, какой буфер явным образом подразумевает доступ «только для чтения» можно легко, взглянув на описание структуры: часть указателей имеют тип
LPSTR, а часть —
LPCSTR. Вот там, где
C (а это не что иное как CONSTANT) — там мы не имеем права менять что-то в буфере, предоставленном вызывающей стороной, а должны выделить свой.
Например это касается lpstrInitialDir и lpstrTitle — логично, что диалоговая функция ни при каких обстоятельствах не будет пытаться поменять буфер, хранящий заголовок для диалогового окна. И буфером под эту строку может оказаться участок секции кода (для которой действует запрет на запись).