Каково время жизни данных параметра указателя в определяемых приложением функциях обратного вызова Windows API? Сохраняется ли он после возврата обратного вызова?

В качестве примера рассмотрим EnumWindowStations(), для которого требуется вызывающая сторона передает функцию обратного вызова EnumWindowStationsProc(). Функция обратного вызова будет вызываться один раз для каждой оконной станции в текущем терминальном сеансе. Давайте посмотрим на сигнатуру функции обратного вызова:

BOOL CALLBACK EnumWindowStationProc(
    _In_ LPTSTR lpszWindowStation,
    _In_ LPARAM lParam
);

Первый параметр — это указатель на строковые данные. Был ли этот строковый буфер выделен явно для вызова обратного вызова, и будет ли он освобожден сразу после возврата обратного вызова или, возможно, непосредственно перед возвратом функции перечисления? Или указатель указывает на какую-то постоянную память, так что строковый буфер останется выделенным и пригодным для использования после этого?

Это важный момент, потому что, если он не постоянный, то было бы неправильно, например, хранить необработанный указатель в глобальном контейнере для доступа после завершения обратного вызова и полного процесса перечисления. Вместо этого было бы необходимо скопировать базовые строковые данные в буфер, управляемый приложением, до обратного вызова.

Официальная документация, похоже, не дает ясного представления о времени жизни строковых данных. В описании параметра всего одна строка:

lpszWindowStation [in]

Имя оконной станции.

И нигде на странице документации не говорится о времени жизни строковых данных. Я также не могу припомнить, чтобы когда-либо находил страницу MSDN, которая отвечает на этот вопрос «раз и навсегда», то есть для всех случаев использования идиомы обратного вызова в Windows API.

На данный момент меня больше всего интересует случай EnumWindowStations()/EnumWindowStationsProc(), но было бы лучше, если бы отвечающие обращались к общему случаю, то есть к тому, что следует предполагать для всех функций обратного вызова Windows API.


person bgoldst    schedule 23.08.2016    source источник


Ответы (2)


Как правило, если память выделяется системой, вы можете полагаться на то, что она действительна в течение всего времени обратного вызова и не более того. Это относится к lpszWindowStation в вашем примере. Вам нужно будет получить доступ и скопировать строку внутри вашей функции обратного вызова, и вы не должны ссылаться на предоставленные системой данные за пределами вашего обратного вызова.

Вы можете вывести это с помощью небольшого мысленного эксперимента. Если бы к этой строке можно было получить доступ после возврата обратного вызова, когда бы она стала недействительной? Когда он будет освобожден? Вы должны сообщить системе, что с этим покончено. Поскольку для этого не указан API, единственным выводом является то, что указано выше.

Для lParam все по-другому. Это значение было предоставлено вами, когда вы вызывали EnumWindowStations, и поэтому вы управляете временем жизни всего, на что оно указывает, если вы действительно используете его в качестве указателя.

person David Heffernan    schedule 23.08.2016
comment
Я не заметил вашего ответа, пока не опубликовал свой. Что касается lParam, то это не указатель, так что в любом случае вопрос времени жизни не стоит. Что касается указателя, есть несколько случаев, когда WINAPI выделяет память и ожидает, что обратный вызов освободит ее (или сохранит указатель и освободит его позже). Но это исключения, и они четко задокументированы как таковые. - person P. Kouvarakis; 24.08.2016
comment
@П. Фактически это указатель. Он указывается пользователем. Когда это что-то, это, вероятно, будет указателем. - person David Heffernan; 24.08.2016
comment
Мне нравится твоя идея мысленного эксперимента. Но я подумал, что, возможно, строковые данные уже существуют где-то в памяти, возможно, в структурах данных, которые автоматически загружаются вместе с приложением, и в этом случае функция перечисления могла бы просто возвращать указатели на эту существующую память, и они сохранялись бы после вызов. (Но я полагаю, что такой дизайн автоматической загрузки системных данных был бы расточительным для приложений, которые никогда не используют эти данные, так что, возможно, это не имело бы смысла...) - person bgoldst; 24.08.2016
comment
Настойчиво после звонка до тех пор, пока. Навсегда? Если бы система позволяла вам использовать данные неограниченное время, в конечном итоге возникло бы состояние нехватки памяти. - person David Heffernan; 24.08.2016
comment
Это не вызовет нехватки памяти, потому что я думал, что эта автоматическая загрузка системных данных произойдет только один раз (при запуске приложения) и будет применяться только к конечным количествам и типам системных данных, поэтому никогда не расти. Хотя теперь, когда я задумался об этом более глубоко, оконные станции, существующие в сеансе терминала, могут динамически изменяться (например, см. CreateWindowStation()), так что в любом случае это невозможно. Я предполагаю, что все это должно быть динамичным и преходящим. - person bgoldst; 24.08.2016
comment
Динамика является ключевой в этом примере. Если бы данные были статическими, ваша модель могла бы работать. GetCommandLine — это пример статических данных. Я не могу придумать ни одного в обратном вызове. - person David Heffernan; 24.08.2016

Как правило, указатели, переданные функциям обратного вызова с помощью WINAPI, гарантированно действительны только на время обратного вызова (если иное не указано в документации конкретного обратного вызова). После возврата обратного вызова указатель следует считать недействительным (он может быть освобожден или может указывать на временный буфер, который будет перезаписан при следующей итерации перечисления и т. д.).

Функция обратного вызова отвечает за создание копии любых данных, которые ей могут понадобиться для сохранения после возврата. В случае приведенного выше примера вы должны выделить свой собственный буфер и скопировать строку из lpszWindowStation, если вам нужно использовать ее после возврата обратного вызова. Конечно, вы также должны управлять временем жизни выделенного(ых) буфера(ов).

person P. Kouvarakis    schedule 23.08.2016