清除同一属性后,设置属性时拒绝访问

Access denied on setting property after clearing same property

本文关键字:属性 设置 拒绝访问 清除      更新时间:2023-10-16

我一直在尝试使用Windows的IPropertyStore方法编辑一些音频文件的元数据,并遇到了这个奇怪的问题。通过IPropertyStore::SetValue()将空PROPVARIANT值设置为属性存储的键后,以下尝试设置该键的值失败,返回值为0x80030005,Visual Studio 通知我这是Access Denied.
这是我可以产生这种行为的最小示例:

#include <atlbase.h>
#include <filesystem>
#include <PropIdl.h>
#include <Propsys.h>
#include <propkey.h>
#include <propvarutil.h>
#include <ShObjIdl.h>
#include <Windows.h>

namespace fs = std::experimental::filesystem;
int main() {
HRESULT hr;
if (FAILED(hr = CoInitializeEx(NULL, COINIT_MULTITHREADED))) {
// Handle error...
}
fs::path const test_path(fs::current_path() / "test.mp3");
CComPtr<IPropertyStore> property_store;
if (FAILED(hr = SHGetPropertyStoreFromParsingName(test_path.wstring().c_str(), NULL, GPS_READWRITE, IID_PPV_ARGS(&property_store)))) {
// Handle error...
}
// Set an empty value to the key
{
PROPVARIANT property{};
PropVariantInit(&property);
if (FAILED(hr = property_store->SetValue(PKEY_Title, property))) {
// Handle error...
}
if (FAILED(hr = PropVariantClear(&property))) {
// Handle error...
}
}
// Write a new value to the same key
{
PROPVARIANT property{};
if (FAILED(hr = InitPropVariantFromString(L"test file", &property))) {
// Handle error...
}
if (FAILED(hr = property_store->SetValue(PKEY_Title, property))) {
// Always fails here with hr == 0x80030005 "Access Denied."
}
if (FAILED(hr = PropVariantClear(&property))) {
// Handle error...
}
}
if (FAILED(hr = property_store->Commit())) {
// Handle error...
}
CoUninitialize();
}

这似乎只在先设置空值时发生;任何其他值都会导致程序按预期运行,并且两个更改都成功写入。

据我从文档中可以看出,将空值写入键可以的 - 我可以自己成功写入空值,并且在资源管理器中查看文件属性时会反映更改。此外,我不明白错误怎么可能"访问被拒绝" - 运行程序的用户(我的标准帐户)肯定有权更改密钥(我可以进入资源管理器中的属性并手动更改密钥的值),并且怎么可能只拒绝两个密钥访问中的一个的访问?

那么为什么我不能将空值写入键,然后再覆盖它呢?

对于那些想知道为什么我需要清除一个键然后立即向其写入新值的人 - 我实际上没有。该事件序列恰好发生在一个较大的程序中,其中清除了所有属性,然后重新填充了某些键。

更新:
此后,我对IPropertyStore对象如何处理文件访问进行了更多尝试。
IPropertyStore对象使用文件初始化时(在本例中为SHGetPropertyStoreFromParsingName()),它似乎以独占方式打开文件 - 就像通过调用0作为共享模式的CreateFile()一样。打开后,我通过CreateFile()再次打开文件的各种尝试都没有成功;都失败了,ERROR_SHARING_VIOLATION.我相信这排除了另一个进程(甚至是我的程序的进程)在第一次属性更改后"窃取"文件访问权限(即使,正如我在评论中所讨论的,属性更改直到调用IPropertyStore::Commit()才会写入)。

即使调用了IPropertyStore::Commit()方法(该方法将所有挂起的属性更改写入文件),该文件仍保持打开状态,独占。据我观察,此时重新打开文件仍然是不可能的。这很奇怪,因为文档指出"在它返回之前,Commit 会释放初始化处理程序的文件流或路径"。我发现直到IPropertyStore对象被释放(IUnknown::Release()),关联的文件仍然处于打开状态。

清除密钥后提交并释放IPropertyStore对象,然后重新创建它并写入新值,似乎可以完美运行。密钥已成功清除,然后成功重写。但是,这仍然使原始问题悬而未决:为什么我不能在属性存储的一个打开/写入/提交周期中进行清除和重写?

当您将属性设置为 VT_EMPTY 时,您不会"设置"其值,不会"写入空值",不会"清除"它,而是删除它。请注意,正在运行的属性存储的计数递减 (GetCount),也不能再使用GetAt了。

IPropertyStore::SetValue 方法的官方文档非常明确:

从 不支持属性存储,这可能会导致意外结果。