用于管理 LPPROC_THREAD_ATTRIBUTE_LIST 的小型辅助类
摘要
这篇博客文章介绍了一个使用 Windows Implementation Library (WIL) 的 C++ 辅助类,通过 RAII 管理 LPPROC_THREAD_ATTRIBUTE_LIST 分配,并提供抛出异常和不抛出异常两种变体。
<p>The <code>LPPROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />LIST</code> is a bit annoying to manage. You have to allocate memory for it yourself, but you don’t know how much; you have to ask <code>InitializeProcThreadAttributeList</code>. And then when you’re done, you have to call <code>DeleteProcThreadAttributeList</code> before freeing the memory.</p>
<p>We suffered through this when <a title="Programmatically controlling which handles are inherited by new processes in Win32" href="https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873"> we controlled which handles are inherited by a new process</a>. <a title="Another way to create a process with attributes, maybe worse maybe better" href="https://devblogs.microsoft.com/oldnewthing/20130426-00/?p=4543"> I wrote a helper function</a> to try to make it easier, by taking the attributes as a separate parameter beyond the parameters to <code>CreateProcess</code> but I’m not sure if it was entirely successful.</p>
<p>Here’s another try, this time building on the Windows Implementation Library.</p>
<pre>namespace details
{
inline void FreeProcThreadAttributeList(
_Pre_valid_ _Frees_ptr_ LPPROC_THREAD_ATTRIBUTE_LIST list)
{
::DeleteProcThreadAttributeList(list);
::HeapFree(::GetProcessHeap(), 0, list);
}
};
using unique_proc_thread_attribute_list = wil::unique_any<LPPROC_THREAD_ATTRIBUTE_LIST,
decltype(&details::FreeProcThreadAttributeList), details::FreeProcThreadAttributeList>;
HRESULT make_proc_thread_attribute_list_nothrow(
DWORD attributeCount, _Out_ LPPROC_THREAD_ATTRIBUTE_LIST* result)
{
*result = nullptr;
SIZE_T size = 0;
InitializeProcThreadAttributeList(nullptr, attributeCount, 0, &size);
auto p = wil::unique_process_heap_ptr<std::remove_pointer_t<LPPROC_THREAD_ATTRIBUTE_LIST>>(
static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(::HeapAlloc(::GetProcessHeap(), 0, size)));
RETURN_IF_NULL_ALLOC(p);
RETURN_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(p.get(), attributeCount, 0, &size));
*result = p.release();
return S_OK;
}
unique_proc_thread_attribute_list make_proc_thread_attribute_list(DWORD attributeCount)
{
unique_proc_thread_attribute_list result;
THROW_IF_FAILED(make_proc_thread_attribute_list_nothrow(attributeCount, result.put()));
return result;
}
</pre>
<p>We start by declaring a helper function that cleans up an <code>LPPROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />LIST</code> by deleting the contents, and then freeing the buffer. We use that to define a <code>unique_<wbr />proc_<wbr />thread_<wbr />attribute_<wbr />list</code> which holds a heap-allocated pointer that has been initialized as a <code>LPPROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />LIST</code>.</p>
<p>The first helper function is the nonthrowing version: it asks for the required size of a <code>LPPROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />LIST</code> for the specified number of attributes, then allocates that much memory on the heap, storing it in a <code>unique_<wbr />process_<wbr />heap_<wbr />ptr</code> so that it will be freed if we fail to initialize it. Declaring that <code>unique_<wbr />process_<wbr />heap_<wbr />ptr</code> is a bit of a pain because we want it to be a “unique pointer to whatever it is that <code>LPPROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />LIST</code> points to.” It’s also annoying that we have to repeat ourselves in both the template type parameter as well as in the cast of the heap-allocated pointer, because CTAD doesn’t work here.</p>
<p>After we allocate the memory, we try to initialize it. If that fails (and I can’t imagine why), we propagate the error, and the RAII type frees the (uninitialized) heap memory.</p>
<p>If initialization succeeds, we return the pointer to the caller, who now takes responsibility for freeing it.</p>
<p>Note that the temporary holding place has to be a <code>unique_<wbr />process_<wbr />heap_<wbr />ptr</code> and not a <code>unique_<wbr />proc_<wbr />thread_<wbr />attribute_<wbr />list</code>: If the initialization fails, we must not call <code>DeleteProcThreadAttributeList</code>, so we have to hold the heap pointer in something that won’t try to call <code>DeleteProcThreadAttributeList</code>.</p>
<p>We can easily use the nonthrowing version to build a throwing version.</p>
<p>My next idea was to let you pass the attributes you want to pre-fill into the attribute list.</p>
<pre>struct proc_thread_attribute {
template<typename T = void>
proc_thread_attribute(DWORD_PTR attribute, T* value, SIZE_T size = sizeof(T)) :
attribute(attribute), value(value), size(size) {
}
DWORD_PTR attribute;
PVOID value;
SIZE_T size;
};
template<typename C>
HRESULT update_proc_thread_attribute_list_nothrow(
LPPROC_THREAD_ATTRIBUTE_LIST list, C&& attributes)
{
for (auto&& attribute : attributes) {
RETURN_IF_WIN32_BOOL_FALSE(
UpdateProcThreadAttribute(list, 0, attribute.attribute,
attribute.value, attribute.size, nullptr, nullptr));
}
return S_OK;
}
template<typename C>
void update_proc_thread_attribute_list(
LPPROC_THREAD_ATTRIBUTE_LIST list, C&& attributes)
{
THROW_IF_FAILED(update_proc_thread_attribute_list_nothrow(
list, std::forward<C>(attributes)));
}
</pre>
<p>The container parameter can be anything iterable whose value type has <code>attribute</code>, <code>value</code>, and <code>size</code> members. It’s probably a collection of <code>proc_<wbr />thread_<wbr />attribute</code>s, but it doesn’t have to be. (Maybe it’s a collection of things derived from <code>proc_<wbr />thread_<wbr />attribute</code>.)</p>
<p>We can add this to our <code>make_<wbr />proc_<wbr />thread_<wbr />attribute_<wbr />list</code> function so that callers can pass in a list of attributes they want, and we’ll make a list that holds them all. And as an extra bonus, you can request room for additional attributes beyond those in the collection you passed in. For example, you might have some attributes that you always use, and then some others you decide on dynamically.</p>
<pre>// No changes to this function
HRESULT make_proc_thread_attribute_list_nothrow(
DWORD attributeCount, _Out_ LPPROC_THREAD_ATTRIBUTE_LIST* result)
{
*result = nullptr;
SIZE_T size = 0;
InitializeProcThreadAttributeList(nullptr, attributeCount, 0, &size);
auto p = wil::unique_process_heap_ptr<std::remove_pointer_t<LPPROC_THREAD_ATTRIBUTE_LIST>>(
static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(::HeapAlloc(::GetProcessHeap(), 0, size)));
RETURN_IF_NULL_ALLOC(p);
RETURN_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(p.get(), attributeCount, 0, &size));
*result = p.release();
return S_OK;
}
// New overload that takes a list of attributes to preload,
// with room for any additional attributes you want to add later.
template<typename C>
HRESULT make_proc_thread_attribute_list_nothrow(
C&& attributes, DWORD extraAttributeCount,
_Out_ LPPROC_THREAD_ATTRIBUTE_LIST* result)
{
*result = nullptr;
unique_proc_thread_attribute_list list;
RETURN_IF_FAILED(make_proc_thread_attribute_list_nothrow(
static_cast<DWORD>(attributes.size()) + extraAttributeCount,
list.put()));
RETURN_IF_FAILED(update_proc_thread_attribute_list_nothrow(
list.get(), std::forward<C>(attributes)));
*result = list.release();
return S_OK;
}
// New overload that takes a list of attributes to preload,
// with no room for more.
template<typename C>
std::enable_if_t<!std::is_integral_v<C>, HRESULT>
make_proc_thread_attribute_list_nothrow(
C&& attributes,
_Out_ LPPROC_THREAD_ATTRIBUTE_LIST* result)
{
return make_proc_thread_attribute_list_nothrow(
std::forward<C>(attributes), 0, result);
}
</pre>
<p>Note that without the <code>std::enable_if_t</code> on the third overload, we would have an ambiguity if somebody called <code>make_<wbr />proc_<wbr />thread_<wbr />attribute_<wbr />list_<wbr />nothrow(1, p)</code> because the parameter <code>1</code> would satisfy both the <code>DWORD</code> parameter from the first overload as well as matching the third overload with <code>C = int</code>. To force the third one to be rejected, we use SFINAE to make the return type a substitution failure if the parameter is integral.</p>
<p>We can then build a throwing version out of the nonthrowing version.</p>
<pre>unique_proc_thread_attribute_list
make_proc_thread_attribute_list(DWORD attributeCount)
{
unique_proc_thread_attribute_list result;
THROW_IF_FAILED(make_proc_thread_attribute_list_nothrow(
attributeCount, result.put()));
return result;
}
template<typename C = std::initializer_list<proc_thread_attribute>>
std::enable_if_t<!std::is_integral_v<C>, unique_proc_thread_attribute_list>
make_proc_thread_attribute_list(
C&& attributes, DWORD extraAttributeCount = 0)
{
unique_proc_thread_attribute_list result;
THROW_IF_FAILED(make_proc_thread_attribute_list_nothrow(
std::forward<C>(attributes), extraAttributeCount,
result.put()));
return result;
}
</pre>
<p>We use a defaulted parameter to collapse the “collection initializer” and “collection initializer with additional space” overloads into one. We still need to use SFINAE to avoid an ambiguity that tries to treat a sole integer parameter as a collection.</p>
<p>You can use this to build process/thread attribute lists at one go.</p>
<pre>HANDLE handles[2] = { handle1, handle2 };
DWORD protection = PROTECTION_LEVEL_SAME;
auto list = make_proc_thread_attribute_list({
{ PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles, sizeof(handles) },
{ PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL, &protection, sizeof(protection) },
});
</pre>
<p>Or you can build it up with some premade attributes, and others that you add conditionally:</p>
<pre>HANDLE handles[2] = { handle1, handle2 };
DWORD protection = PROTECTION_LEVEL_SAME;
auto list = make_proc_thread_attribute_list({
{ PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles, sizeof(handles) },
{ PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL, &protection, sizeof(protection) },
}, 1); // "1" leaves room for one more attribute
if (job != nullptr) {
UpdateProcThreadAttribute(list.get(),
PROC_THREAD_ATTRIBUTE_JOB_LIST,
&job, sizeof(job), nullptr, nullptr);
}
</pre>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260813-00/?p=112611">A little helper class for managing <CODE>LPPROC_<WBR>THREAD_<WBR>ATTRIBUTE_<WBR>LIST</CODE>s</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
查看缓存全文
缓存时间: 2026/08/14 15:21
# 管理 LPPROC_THREAD_ATTRIBUTE_LIST 的小辅助类 - The Old New Thing
来源:https://devblogs.microsoft.com/oldnewthing/20260813-00?p=112611
管理 `LPPROC\_THREAD\_ATTRIBUTE\_LIST` 有点麻烦。你得自己为它分配内存,但又不知道需要多少;你得向 `InitializeProcThreadAttributeList` 询问。然后等你用完,还得在释放内存之前调用 `DeleteProcThreadAttributeList`。在[控制新进程继承哪些句柄](https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873)时,我们就吃过这个苦头。
我之前写过一篇[辅助函数](https://devblogs.microsoft.com/oldnewthing/20130426-00/?p=4543),试图通过将属性作为 `CreateProcess` 参数之外的独立参数传入来简化操作,但我不确定它是否完全成功。这次再试一次,这次基于 Windows 实现库(WIL)来构建。
```
namespace details {
inline void FreeProcThreadAttributeList(
_Pre_valid_ _Frees_ptr_ LPPROC_THREAD_ATTRIBUTE_LIST list)
{
::DeleteProcThreadAttributeList(list);
::HeapFree(::GetProcessHeap(), 0, list);
}
};
using unique_proc_thread_attribute_list = wil::unique_any;
HRESULT make_proc_thread_attribute_list_nothrow(
DWORD attributeCount,
_Out_ LPPROC_THREAD_ATTRIBUTE_LIST* result)
{
*result = nullptr;
SIZE_T size = 0;
InitializeProcThreadAttributeList(nullptr, attributeCount, 0, &size);
auto p = wil::unique_process_heap_ptr>(
static_cast(::HeapAlloc(::GetProcessHeap(), 0, size)));
RETURN_IF_NULL_ALLOC(p);
RETURN_IF_WIN32_BOOL_FALSE(
InitializeProcThreadAttributeList(p.get(), attributeCount, 0, &size));
*result = p.release();
return S_OK;
}
unique_proc_thread_attribute_list make_proc_thread_attribute_list(DWORD attributeCount)
{
unique_proc_thread_attribute_list result;
THROW_IF_FAILED(make_proc_thread_attribute_list_nothrow(
attributeCount,
result.put()));
return result;
}
```
我们首先声明一个辅助函数,通过删除内容然后释放缓冲区来清理 `LPPROC\_THREAD\_ATTRIBUTE\_LIST`。我们用这一点来定义一个 `unique\_proc\_thread\_attribute\_list`,它持有一个已经初始化为 `LPPROC\_THREAD\_ATTRIBUTE\_LIST` 的堆分配指针。
第一个辅助函数是非抛出版本:它针对指定的属性数量请求 `LPPROC_THREAD_ATTRIBUTE_LIST` 所需的大小,然后在堆上分配相应内存,并将其存储在一个 `unique_process_heap_ptr` 中,这样如果初始化失败,内存就会被释放。
声明这个 `unique_process_heap_ptr` 有点麻烦,因为我们希望它是一个“指向 `LPPROC_THREAD_ATTRIBUTE_LIST` 所指向的任何东西的唯一指针”。另外,我们不得不在模板类型参数和堆分配指针的强制转换中重复自己,这也让人恼火,因为 CTAD 在这里不适用。
分配内存后,我们尝试初始化它。如果失败(我想象不出为什么会失败),就传播错误,RAII 类型会释放(未初始化的)堆内存。如果初始化成功,我们将指针返回给调用者,由调用者负责释放它。
注意,临时存储处必须是 `unique_process_heap_ptr` 而不是 `unique_proc_thread_attribute_list`:如果初始化失败,我们绝对不能调用 `DeleteProcThreadAttributeList`,所以必须把堆指针放在一个不会尝试调用 `DeleteProcThreadAttributeList` 的东西里。我们可以很容易地用非抛出版本构建抛出版本。我的下一个想法是让你把想要预先填充到属性列表中的属性传进来。
```
struct proc_thread_attribute {
template <typename T>
proc_thread_attribute(DWORD_PTR attribute, T* value, SIZE_T size = sizeof(T))
: attribute(attribute), value(value), size(size) { }
DWORD_PTR attribute;
PVOID value;
SIZE_T size;
};
template <typename C>
HRESULT update_proc_thread_attribute_list_nothrow(
LPPROC_THREAD_ATTRIBUTE_LIST list,
C&& attributes)
{
for (auto&& attribute : attributes) {
RETURN_IF_WIN32_BOOL_FALSE(
UpdateProcThreadAttribute(list, 0,
attribute.attribute, attribute.value, attribute.size,
nullptr, nullptr));
}
return S_OK;
}
template <typename C>
void update_proc_thread_attribute_list(
LPPROC_THREAD_ATTRIBUTE_LIST list,
C&& attributes)
{
THROW_IF_FAILED(update_proc_thread_attribute_list_nothrow(
list,
std::forward<C>(attributes)));
}
```
容器参数可以是任何可迭代的东西,其值类型具有 `attribute`、`value` 和 `size` 成员。它很可能是一个 `proc_thread_attribute` 的集合,但也不一定。(也许它是派生自 `proc_thread_attribute` 的东西的集合。)我们可以把它添加到我们的 `make_proc_thread_attribute_list` 函数中,这样调用者可以传入他们想要的属性列表,我们就创建一个包含所有这些属性的列表。额外的好处是,你还可以为超出传入集合的额外属性预留空间。例如,你可能有一些总是使用的属性,还有一些则是动态决定的。
```
// 此函数没有变化
HRESULT make_proc_thread_attribute_list_nothrow(
DWORD attributeCount,
_Out_ LPPROC_THREAD_ATTRIBUTE_LIST* result)
{
*result = nullptr;
SIZE_T size = 0;
InitializeProcThreadAttributeList(nullptr, attributeCount, 0, &size);
auto p = wil::unique_process_heap_ptr>(
static_cast(::HeapAlloc(::GetProcessHeap(), 0, size)));
RETURN_IF_NULL_ALLOC(p);
RETURN_IF_WIN32_BOOL_FALSE(
InitializeProcThreadAttributeList(p.get(), attributeCount, 0, &size));
*result = p.release();
return S_OK;
}
// 新重载:接受一个要预加载的属性列表,
// 并为以后要添加的额外属性预留空间。
template <typename C>
HRESULT make_proc_thread_attribute_list_nothrow(
C&& attributes,
DWORD extraAttributeCount,
_Out_ LPPROC_THREAD_ATTRIBUTE_LIST* result)
{
*result = nullptr;
unique_proc_thread_attribute_list list;
RETURN_IF_FAILED(make_proc_thread_attribute_list_nothrow(
static_cast<DWORD>(attributes.size()) + extraAttributeCount,
list.put()));
RETURN_IF_FAILED(update_proc_thread_attribute_list_nothrow(
list.get(),
std::forward<C>(attributes)));
*result = list.release();
return S_OK;
}
// 新重载:接受一个要预加载的属性列表,
// 不预留额外空间。
template <typename C>
std::enable_if_t<!std::is_integral_v<C>, HRESULT>
make_proc_thread_attribute_list_nothrow(
C&& attributes,
_Out_ LPPROC_THREAD_ATTRIBUTE_LIST* result)
{
return make_proc_thread_attribute_list_nothrow(
std::forward<C>(attributes), 0, result);
}
```
请注意,如果没有第三个重载上的 `std::enable_if_t`,当有人调用 `make_proc_thread_attribute_list_nothrow(1, p)` 时会产生歧义,因为参数 `1` 既满足第一个重载的 `DWORD` 参数,也匹配第三个重载的 `C = int`。为了强制拒绝第三个重载,我们使用 SFINAE,如果参数是整数类型,就让返回类型发生替换失败。然后我们可以用非抛出版本构建抛出版本。
```
unique_proc_thread_attribute_list make_proc_thread_attribute_list(
DWORD attributeCount)
{
unique_proc_thread_attribute_list result;
THROW_IF_FAILED(make_proc_thread_attribute_list_nothrow(
attributeCount,
result.put()));
return result;
}
template <typename C>
std::enable_if_t<!std::is_integral_v<C>, unique_proc_thread_attribute_list>
make_proc_thread_attribute_list(
C&& attributes,
DWORD extraAttributeCount = 0)
{
unique_proc_thread_attribute_list result;
THROW_IF_FAILED(make_proc_thread_attribute_list_nothrow(
std::forward<C>(attributes),
extraAttributeCount,
result.put()));
return result;
}
```
我们使用一个默认参数将“集合初始化器”和“带额外空间的集合初始化器”两个重载合并为一个。我们仍然需要使用 SFINAE 来避免将单个整数参数视为集合的歧义。你可以用这个一次性构建进程/线程属性列表。
```
HANDLE handles[2] = { handle1, handle2 };
DWORD protection = PROTECTION_LEVEL_SAME;
auto list = make_proc_thread_attribute_list({
{ PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles, sizeof(handles) },
{ PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL, &protection, sizeof(protection) },
});
```
或者你也可以用一些预先准备好的属性来构建,再加上一些条件性添加的其他属性:
```
HANDLE handles[2] = { handle1, handle2 };
DWORD protection = PROTECTION_LEVEL_SAME;
auto list = make_proc_thread_attribute_list({
{ PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles, sizeof(handles) },
{ PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL, &protection, sizeof(protection) },
}, 1); // "1" 为再增加一个属性留出空间
if (job != nullptr) {
UpdateProcThreadAttribute(list.get(),
PROC_THREAD_ATTRIBUTE_JOB_LIST,
&job, sizeof(job), nullptr, nullptr);
}
```
### 分类
### 主题
## 作者 Raymond Chen
Raymond 参与 Windows 的发展已有 30 多年。2003 年,他创办了一个名为 The Old New Thing 的网站,其受欢迎程度远远超出他最疯狂的想象——这一进展至今仍让他感到不寒而栗。
相似文章
在C++/WinRT中创建Windows Runtime委托的敏捷版本,第8部分
本文讨论了在C++/WinRT中创建敏捷委托时修复异常安全性问题,解决了未指定lambda捕获构造顺序导致的引用泄漏。
在C++/WinRT中创建敏捷版的Windows Runtime委托,第6部分
系列文章第6部分:在C++/WinRT中创建敏捷的Windows Runtime委托,修复std::unique_ptr自定义删除器构造函数可能引发的异常问题。
在C++/WinRT中创建Windows运行时代理的敏捷版本,第3部分
这篇博客文章讨论了在C++/WinRT中处理实现INoMarshal接口的Windows运行时代理,提供了一个敏捷的代理包装器,通过检查调用上下文来避免封送错误。
在 C++/WinRT 中创建 Windows Runtime 委托的敏捷版本,第 9 部分
Raymond Chen 继续他的系列文章,讨论在 C++/WinRT 中创建敏捷版 Windows Runtime 委托,并比较 C++/WinRT、C++/CX 和 WRL 如何处理不可封送委托和敏捷引用创建。
llama.cpp 自适应 MTP PR#27210
一个用于自适应 MTP 的拉取请求 (PR#27210) 已提交到 llama.cpp,该实现是用于LLM推理的C/C++代码,设置简单且性能高效。