I’m currently developing a wxWidgets application for Windows 10/11 that is supposed to generate a file. This file is security sensitive so minimizing the copies on disk is ideal. As a result, I want to generate this file virtually in memory and copy it to the clipboard for the user to paste it through Microsoft Remote Desktop Connection. I have read Microsoft’s documentation on clipboard formats and attempted to implement this using wxWidget’s wxDataFormat
, wxDataObjectSimple
, and wxDataObjectComposite
.
Microsoft’s documentation leads me to believe that I need to create two data objects with the type CFSTR_FILECONTENTS
and CFSTR_FILEDESCRIPTOR
to hold the virtual file in memory. From viewing the source of wxWidget’s MSW implementation, it appears that wxDataObjectSimple allocates a GlobalAlloc
buffer and stores this buffer in a STGMEDIUM
structure which is expected for these two formats. As a result, I created a VirtualFileContents
and VirtualFileDescriptor
class derived from wxDataObjectSimple which provide the file contents and FILEGROUPDESCRIPTOR
structure data respectively. A VirtualFile
class derives from wxDataObjectComposite
to append these two types together.
When implemented this way, I was able to observe in multiple Win32 clipboard viewers that I had the correct clipboard data as described by Microsoft, with both the “FileContents” and “FileGroupDescriptor” as both “HGlobal” handle types with the expected data and correct format within. When opening a context menu in Explorer, the option to paste is available however a spinning cursor appears for a few frames and then the system bell rings to indicate an error. No error dialog is displayed, and no file is pasted.
To further debug this situation, I opened up a copy of Microsoft Outlook 2016 and copied an attachment from an email. This is also implemented in a similar way with CFSTR_FILECONTENTS
and CFSTR_FILEDESCRIPTOR
but Outlook’s file contains are an “IStream” type and not an “HGlobal” type. This pasted into Explorer just fine, and I compared the layout of the FileGroupDescriptor
structure and saw no memory layout errors. I saw that Outlook includes both a FileGroupDescriptorW
and a FileGroupDescriptor
when copying files, but implementing that myself has not resolved the issue either. Below is a snippet of my current implementation:
Header
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
Source
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);
}
Copy code
if (wxTheClipboard->Open()) {
wxTheClipboard->SetData(new Clipboard::VirtualFile("hello there", "test.txt"));
wxTheClipboard->Close();
}
Is anyone aware of any flaw in my approach, or any tools that might help me debug Windows clipboard/Explorer compatibility more? Is it a more simple issue like me just excluding creation time in the structure?
6
I believe this is impossible to implement through wxWidgets due to a limitation in the MSW port which prevents settings the lindex
value of the FORMATETC
structure which is mandatory for the virtual file to be recognized.
I have created a feature request here to try and discuss how to solve this issue moving forward.