【发布时间】:2018-01-01 10:50:23
【问题描述】:
我一直在尝试使用 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 全部失败。我相信这排除了另一个进程(甚至我的程序的进程)在第一次属性更改后“窃取”文件访问权限(尽管正如我在 cmets 中讨论的那样,直到调用 IPropertyStore::Commit() 才写入属性更改) .
即使调用了IPropertyStore::Commit() 方法,该方法将所有挂起的属性更改写入文件,文件仍以独占方式保持打开状态。据我观察,此时重新打开文件仍然是不可能的。这很奇怪,因为documentation 声明“[b] 在它返回之前,Commit 释放初始化处理程序的文件流或路径”。我发现直到IPropertyStore 对象被释放(IUnknown::Release()),相关文件仍然打开。
在清除密钥后提交和释放IPropertyStore 对象,然后重新创建它并写入新值,似乎工作得很好。成功清除密钥,然后成功重写。然而,这仍然让最初的问题悬而未决:为什么不能我在属性存储的一个打开/写入/提交周期中进行清除和重写?
【问题讨论】:
-
将值设置为 VT_EMPTY 在语义上与删除键相同,因为当属性不存在时 GetValue 返回空 msdn.microsoft.com/en-us/library/windows/desktop/bb761473.aspx
-
@SimonMourier 我知道,这就是我想做的。但是如果密钥不存在,SetValue 应该重新添加该属性。
-
更改属性为另一个进程重新打开文件提供了很好的理由。可以是 Explorer,也可以是 shell 扩展,安装的反恶意软件产品总是一个不错的选择。期望是您必须先转动一下轮子才能稍后再试。
-
@HansPassant 我没有任何第 3 方反恶意软件,只是安装了内置的 Windows Defender。将其关闭并尝试过,但没有运气...资源监视器显示我的程序是唯一一个带有测试文件句柄的程序(但不确定这是否是一个有用的指标)。
-
@HansPassant 还请注意,在调用 IPropertyStore::Commit() 之前,不会将更改写入文件,这永远不会被执行。此外,如here 所述,在执行所述函数之前不会释放文件,因此似乎另一个进程可以在 SetValue() 调用之间“窃取”文件。
标签: c++ windows com windows-10