我目前正在为 Windows 10/11 开发一个 wxWidgets 应用程序,该应用程序应该生成一个文件。该文件是安全敏感文件,因此尽量减少磁盘上的副本是理想的选择。因此,我想在内存中虚拟生成这个文件,并将其复制到剪贴板,以便用户通过 Microsoft 远程桌面连接粘贴它。我已阅读 Microsoft 关于剪贴板格式的文档,并尝试使用 wxWidget 的
wxDataFormat
、wxDataObjectSimple
和 wxDataObjectComposite
来实现此功能。
微软的文档让我相信我需要创建两个类型为
CFSTR_FILECONTENTS
和 CFSTR_FILEDESCRIPTOR
的数据对象来将虚拟文件保存在内存中。从 wxWidget 的 MSW 实现的源代码来看,wxDataObjectSimple 分配了一个 GlobalAlloc
缓冲区,并将该缓冲区存储在 STGMEDIUM
结构中,这对于这两种格式来说是预期的。因此,我创建了一个从 wxDataObjectSimple 派生的 VirtualFileContents
和 VirtualFileDescriptor
类,它们分别提供文件内容和 FILEGROUPDESCRIPTOR
结构数据。 VirtualFile
类派生自 wxDataObjectComposite
,将这两种类型附加在一起。
以这种方式实现时,我能够在多个 Win32 剪贴板查看器中观察到我拥有 Microsoft 所描述的正确剪贴板数据,其中“FileContents”和“FileGroupDescriptor”都是“HGlobal”句柄类型,具有预期的数据和内的正确格式。在资源管理器中打开上下文菜单时,可以使用粘贴选项,但会出现旋转的光标几帧,然后系统铃声响起以指示错误。不显示错误对话框,并且不粘贴任何文件。
为了进一步调试这种情况,我打开了 Microsoft Outlook 2016 的副本并从电子邮件中复制了附件。这也以类似的方式与
CFSTR_FILECONTENTS
和 CFSTR_FILEDESCRIPTOR
实现,但 Outlook 的文件包含“IStream”类型而不是“HGlobal”类型。这很好地粘贴到资源管理器中,我比较了 FileGroupDescriptor
结构的布局,没有发现内存布局错误。我看到 Outlook 在复制文件时同时包含 FileGroupDescriptorW
和 FileGroupDescriptor
,但我自己实现也没有解决问题。以下是我当前实现的片段:
标题
namespace Clipboard {
class VirtualFileContents : public wxDataObjectSimple {
private:
std::string m_str;
public:
VirtualFileContents(const std::string &str);
std::size_t GetDataSize() const;
bool GetDataHere(void *buf) const;
};
class VirtualFileDescriptorW : public wxDataObjectSimple {
private:
std::wstring m_fileName;
VirtualFileContents &m_contents;
public:
VirtualFileDescriptorW(const std::wstring &file_name, VirtualFileContents &contents);
std::size_t GetDataSize() const;
bool GetDataHere(void *buf) const;
};
class VirtualFileDescriptorA : public wxDataObjectSimple {
private:
std::string m_fileName;
VirtualFileContents &m_contents;
public:
VirtualFileDescriptorA(const std::string &file_name, VirtualFileContents &contents);
std::size_t GetDataSize() const;
bool GetDataHere(void *buf) const;
};
class VirtualFile : public wxDataObjectComposite {
private:
VirtualFileContents *m_contents;
VirtualFileDescriptorW *m_wDescriptor;
VirtualFileDescriptorA *m_aDescriptor;
public:
VirtualFile(const std::string &str, const std::filesystem::path &file_name);
};
}; // namespace Clipboard
来源
Clipboard::VirtualFileContents::VirtualFileContents(const std::string &str)
: wxDataObjectSimple{CFSTR_FILECONTENTS}, m_str(str) {
}
std::size_t Clipboard::VirtualFileContents::GetDataSize() const {
return this->m_str.size();
}
bool Clipboard::VirtualFileContents::GetDataHere(void *buf) const {
std::memcpy(buf, this->m_str.data(), this->m_str.size());
return true;
}
Clipboard::VirtualFileDescriptorW::VirtualFileDescriptorW(const std::wstring &file_name,
VirtualFileContents &contents)
: wxDataObjectSimple{CFSTR_FILEDESCRIPTORW}, m_fileName(file_name), m_contents(contents) {
}
std::size_t Clipboard::VirtualFileDescriptorW::GetDataSize() const {
return sizeof(FILEGROUPDESCRIPTORW);
}
bool Clipboard::VirtualFileDescriptorW::GetDataHere(void *buf) const {
if (this->m_fileName.size() >= MAX_PATH) {
std::runtime_error("File name too long.");
}
FILEGROUPDESCRIPTORW files = {
.cItems = 1,
.fgd = {{.dwFlags = 0,
.clsid = {},
.sizel = {},
.pointl = {},
.dwFileAttributes = 0x0,
.ftCreationTime = {},
.ftLastAccessTime = {},
.ftLastWriteTime = {},
.nFileSizeHigh = 0, //static_cast<DWORD>(this->m_contents.GetDataSize() >> 32),
.nFileSizeLow = 0, //static_cast<DWORD>(this->m_contents.GetDataSize() & 0xFFFFFFFF),
.cFileName = L""}}};
std::memcpy(files.fgd[0].cFileName, this->m_fileName.data(),
this->m_fileName.size() * sizeof(decltype(this->m_fileName)::value_type));
std::memcpy(buf, &files, sizeof(files));
return true;
}
Clipboard::VirtualFileDescriptorA::VirtualFileDescriptorA(const std::string &file_name,
VirtualFileContents &contents)
: wxDataObjectSimple{CFSTR_FILEDESCRIPTORA}, m_fileName(file_name), m_contents(contents) {
}
std::size_t Clipboard::VirtualFileDescriptorA::GetDataSize() const {
return sizeof(FILEGROUPDESCRIPTORA);
}
bool Clipboard::VirtualFileDescriptorA::GetDataHere(void *buf) const {
if (this->m_fileName.size() >= MAX_PATH) {
std::runtime_error("File name too long.");
}
FILEGROUPDESCRIPTORA files = {
.cItems = 1,
.fgd = {{.dwFlags = 0,
.clsid = {},
.sizel = {},
.pointl = {},
.dwFileAttributes = 0x0,
.ftCreationTime = {},
.ftLastAccessTime = {},
.ftLastWriteTime = {},
.nFileSizeHigh = 0,
.nFileSizeLow = 0,
.cFileName = ""}}};
std::memcpy(files.fgd[0].cFileName, this->m_fileName.data(),
this->m_fileName.size() * sizeof(decltype(this->m_fileName)::value_type));
std::memcpy(buf, &files, sizeof(files));
return true;
}
Clipboard::VirtualFile::VirtualFile(const std::string &str, const std::filesystem::path &file_name)
: m_contents(new VirtualFileContents{str}),
m_wDescriptor(new VirtualFileDescriptorW{file_name.wstring(), *this->m_contents}),
m_aDescriptor(new VirtualFileDescriptorA{file_name.string(), *this->m_contents}) {
this->Add(this->m_wDescriptor, true);
this->Add(this->m_aDescriptor);
this->Add(this->m_contents);
}
复制代码
if (wxTheClipboard->Open()) {
wxTheClipboard->SetData(new Clipboard::VirtualFile("hello there", "test.txt"));
wxTheClipboard->Close();
}
有人知道我的方法有任何缺陷,或者有任何工具可以帮助我更多地调试 Windows 剪贴板/资源管理器兼容性吗?这是像我这样一个更简单的问题,只是排除结构中的创建时间吗?
我相信这是不可能通过 wxWidgets 实现的,因为 MSW 端口的限制阻止了设置
lindex
结构的 FORMATETC
值,这是识别虚拟文件所必需的。
我已在此处创建了功能请求,以尝试并讨论如何解决此问题。