Список LPPROC_THREAD_ATTRIBUTE_LIST требует двух разных действий при завершении: удалить внутренние атрибуты, затем освободить выделенный буфер. Удобно связать этот порядок с временем жизни C++-объекта.
Двухфазная инициализация
Первый вызов InitializeProcThreadAttributeList с nullptr запрашивает размер и по документации ожидаемо возвращает ошибку. После выделения буфера нужен второй вызов. Если он не удался, освобождается только память: удалять ещё не инициализированный список нельзя.
#include <windows.h>
#include <new>
#include <system_error>
struct Attributes final {
LPPROC_THREAD_ATTRIBUTE_LIST value = nullptr;
explicit Attributes(DWORD capacity) {
SIZE_T bytes = 0;
InitializeProcThreadAttributeList(nullptr, capacity, 0, &bytes);
if (!bytes)
throw std::system_error(GetLastError(), std::system_category());
void* storage = HeapAlloc(GetProcessHeap(), 0, bytes);
if (!storage) throw std::bad_alloc();
auto candidate = static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(storage);
if (!InitializeProcThreadAttributeList(candidate, capacity, 0, &bytes)) {
DWORD error = GetLastError();
HeapFree(GetProcessHeap(), 0, storage);
throw std::system_error(error, std::system_category());
}
value = candidate;
}
~Attributes() {
if (value) {
DeleteProcThreadAttributeList(value);
HeapFree(GetProcessHeap(), 0, value);
}
}
Attributes(const Attributes&) = delete;
Attributes& operator=(const Attributes&) = delete;
};
Это самостоятельный минимальный пример владения для Windows SDK, без шаблонных WIL-перегрузок. Копирование запрещено, чтобы два объекта не освободили один указатель. Для добавления каждого атрибута вызывайте UpdateProcThreadAttribute и проверяйте результат; память, на которую ссылаются значения, должна оставаться действительной во время использования списка. Для CreateProcess нужны STARTUPINFOEX и флаг EXTENDED_STARTUPINFO_PRESENT.
Схема основана на The Old New Thing от 13 августа 2026 года и проверена по контрактам инициализации и удаления списка. Сборка этого фрагмента в конкретной конфигурации здесь не выполнялась.
