You’re calling WaitForSingleObject on wrong things… I assume you wanted to call it on the events you created?
Anyway, here’s my take on it. C# code:
private delegate void OnFilePickedDelegate(IntPtr storageFile, IntPtr data);
[DllImport("__Internal")]
private static extern int ShowFileSavePicker([MarshalAs(UnmanagedType.LPWStr)]string suggestedFileName, OnFilePickedDelegate onFilePicked, IntPtr data);
[DllImport("__Internal")]
private static extern int SaveDataToFileAndReleaseIt(IntPtr storageFile, byte[] data, int dataLength);
[MonoPInvokeCallback(typeof(OnFilePickedDelegate))]
private static void OnFilePicked(IntPtr storageFile, IntPtr data)
{
var byteArray = (byte[])GCHandle.FromIntPtr(data).Target;
var hr = SaveDataToFileAndReleaseIt(storageFile, byteArray, byteArray.Length);
if (hr != 0)
Marshal.ThrowExceptionForHR(hr);
}
private static void SaveBytes(string suggestedFileName, byte[] bytes)
{
var gcHandle = GCHandle.Alloc(bytes);
var hr = ShowFileSavePicker(suggestedFileName, OnFilePicked, GCHandle.ToIntPtr(gcHandle));
if (hr != 0)
Marshal.ThrowExceptionForHR(hr);
}
Complete C++ code (note, it’s missing error checking in case async operations fail):
#include <cstdint>
#include <vector>
#include <inspectable.h>
#include <robuffer.h>
#include <windows.storage.pickers.h>
#include <windows.storage.streams.h>
#include <wrl.h>
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::Storage;
using namespace ABI::Windows::Storage::Pickers;
using namespace ABI::Windows::Storage::Streams;
using Windows::Storage::Streams::IBufferByteAccess;
#define AssertAndReturnIfFailed(hr) do { if (FAILED(hr)) { __debugbreak(); return hr; } } while (false);
class StringVector : public RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, IVector<HSTRING>, IVectorView<HSTRING>, IIterable<HSTRING>, FtmBase>
{
public:
StringVector()
{
}
virtual HRESULT __stdcall GetAt(uint32_t index, HSTRING* item) override
{
if (index >= m_Items.size())
return E_BOUNDS;
return m_Items[index].CopyTo(item);
}
virtual HRESULT __stdcall get_Size(uint32_t* size) override
{
*size = static_cast<uint32_t>(m_Items.size());
return S_OK;
}
virtual HRESULT __stdcall GetView(IVectorView<HSTRING>** view) override
{
AddRef();
*view = this;
return S_OK;
}
virtual HRESULT __stdcall IndexOf(HSTRING value, uint32_t* index, boolean* found) override
{
for (uint32_t i = 0; i < m_Items.size(); i++)
{
int32_t comparisonResult;
auto hr = WindowsCompareStringOrdinal(m_Items[i].Get(), value, &comparisonResult);
if (SUCCEEDED(hr) && comparisonResult == 0)
{
*found = true;
*index = i;
return S_OK;
}
}
*found = false;
return S_OK;
}
virtual HRESULT __stdcall SetAt(uint32_t index, HSTRING item) override
{
if (index >= m_Items.size())
return E_BOUNDS;
return m_Items[index].Set(item);
}
virtual HRESULT __stdcall InsertAt(uint32_t index, HSTRING item) override
{
if (index > m_Items.size())
return E_BOUNDS;
HString itemWrapper;
auto hr = itemWrapper.Set(item);
AssertAndReturnIfFailed(hr);
m_Items.insert(m_Items.begin() + index, std::move(itemWrapper));
return S_OK;
}
virtual HRESULT __stdcall RemoveAt(uint32_t index) override
{
if (index >= m_Items.size())
return E_BOUNDS;
m_Items.erase(m_Items.begin() + index);
return S_OK;
}
virtual HRESULT __stdcall Append(HSTRING item) override
{
HString itemWrapper;
auto hr = itemWrapper.Set(item);
AssertAndReturnIfFailed(hr);
m_Items.push_back(std::move(itemWrapper));
return S_OK;
}
virtual HRESULT __stdcall RemoveAtEnd() override
{
m_Items.pop_back();
return S_OK;
}
virtual HRESULT __stdcall Clear() override
{
m_Items.clear();
return S_OK;
}
virtual HRESULT __stdcall First(IIterator<HSTRING>** first) override
{
*first = Make<Iterator>(this).Detach();
return S_OK;
}
private:
std::vector<HString> m_Items;
class Iterator : public RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, IIterator<HSTRING>, FtmBase>
{
public:
Iterator(StringVector* parent) :
m_Parent(parent)
{
}
virtual HRESULT __stdcall get_Current(HSTRING* current) override
{
if (m_Position >= m_Parent->m_Items.size())
return E_BOUNDS;
return m_Parent->m_Items[m_Position].CopyTo(current);
}
virtual HRESULT __stdcall get_HasCurrent(boolean* hasCurrent) override
{
*hasCurrent = m_Parent->m_Items.size() > m_Position;
return S_OK;
}
virtual HRESULT __stdcall MoveNext(boolean* hasCurrent) override
{
if (m_Position < m_Parent->m_Items.size())
{
m_Position++;
return get_HasCurrent(hasCurrent);
}
*hasCurrent = false;
return S_OK;
}
private:
ComPtr<StringVector> m_Parent;
uint32_t m_Position;
};
};
class WindowsBuffer : public RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, IBuffer, IBufferByteAccess>
{
public:
WindowsBuffer(uint8_t* data, uint32_t length) :
m_Buffer(data, data + length),
m_Length(length)
{
}
virtual HRESULT __stdcall get_Capacity(UINT32* value) override
{
*value = static_cast<UINT32>(m_Buffer.size());
return S_OK;
}
virtual HRESULT __stdcall get_Length(UINT32* value) override
{
*value = m_Length;
return S_OK;
}
virtual HRESULT __stdcall put_Length(UINT32 value) override
{
if (value > m_Buffer.size())
return E_BOUNDS;
m_Length = value;
return S_OK;
}
virtual HRESULT __stdcall Buffer(byte** value) override
{
*value = &m_Buffer[0];
return S_OK;
}
private:
std::vector<uint8_t> m_Buffer;
uint32_t m_Length;
};
typedef void (__stdcall* OnFilePicked)(IStorageFile* storageFile, void* data);
HRESULT GetOperationErrorCode(IAsyncInfo* asyncInfo, AsyncStatus status)
{
HRESULT errorCode;
auto hr = asyncInfo->get_ErrorCode(&errorCode);
AssertAndReturnIfFailed(hr);
return errorCode;
}
template <typename T>
HRESULT GetOperationErrorCode(T* operation, AsyncStatus status)
{
if (status == AsyncStatus::Completed)
return S_OK;
// Figure out the error
ComPtr<IAsyncInfo> asyncInfo;
auto hr = operation->QueryInterface(IID_PPV_ARGS(&asyncInfo));
AssertAndReturnIfFailed(hr);
return GetOperationErrorCode(asyncInfo.Get(), status);
}
extern "C" HRESULT __stdcall ShowFileSavePicker(const wchar_t* suggestedFileName, OnFilePicked onFilePicked, void* data)
{
ComPtr<IInspectable> basePicker;
auto hr = RoActivateInstance(HStringReference(RuntimeClass_Windows_Storage_Pickers_FileSavePicker).Get(), &basePicker);
AssertAndReturnIfFailed(hr);
ComPtr<IFileSavePicker> picker;
hr = basePicker.As(&picker);
AssertAndReturnIfFailed(hr);
hr = picker->put_SuggestedStartLocation(PickerLocationId::PickerLocationId_DocumentsLibrary);
AssertAndReturnIfFailed(hr);
hr = picker->put_SuggestedFileName(HStringReference(suggestedFileName).Get());
AssertAndReturnIfFailed(hr);
ComPtr<IMap<HSTRING, IVector<HSTRING>*>> fileTypeChoices;
hr = picker->get_FileTypeChoices(&fileTypeChoices);
AssertAndReturnIfFailed(hr);
auto txtChoice = Make<StringVector>();
hr = txtChoice->Append(HStringReference(L".txt").Get());
AssertAndReturnIfFailed(hr);
boolean replaced;
hr = fileTypeChoices->Insert(HStringReference(L"Text file").Get(), txtChoice.Get(), &replaced);
AssertAndReturnIfFailed(hr);
ComPtr<IAsyncOperation<StorageFile*>> op;
hr = picker->PickSaveFileAsync(&op);
AssertAndReturnIfFailed(hr);
hr = op->put_Completed(Callback<IAsyncOperationCompletedHandler<StorageFile*>>([onFilePicked, data](IAsyncOperation<StorageFile*>* operation, AsyncStatus status) -> HRESULT
{
auto hr = GetOperationErrorCode(operation, status);
if (FAILED(hr))
{
// Do something with the error code
// ...
// ...
return S_OK;
}
ComPtr<IStorageFile> storageFile;
hr = operation->GetResults(&storageFile);
AssertAndReturnIfFailed(hr);
// Hooray, file got picked. Do something with storageFile.
if (storageFile != nullptr)
onFilePicked(storageFile.Detach(), data);
return S_OK;
}).Get());
AssertAndReturnIfFailed(hr);
return S_OK;
}
extern "C" __declspec(dllexport) HRESULT __stdcall SaveDataToFileAndReleaseIt(IStorageFile* storageFileRaw, uint8_t* data, uint32_t dataLength)
{
ComPtr<IStorageFile> storageFile;
storageFile.Attach(storageFileRaw);
ComPtr<IAsyncOperation<IRandomAccessStream*>> openOperation;
auto hr = storageFile->OpenAsync(FileAccessMode::FileAccessMode_ReadWrite, &openOperation);
AssertAndReturnIfFailed(hr);
auto buffer = Make<WindowsBuffer>(data, dataLength);
hr = openOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<IRandomAccessStream*>>([buffer](IAsyncOperation<IRandomAccessStream*>* operation, AsyncStatus status) -> HRESULT
{
auto hr = GetOperationErrorCode(operation, status);
if (FAILED(hr))
{
// Do something with the error code
// ...
// ...
return S_OK;
}
ComPtr<IRandomAccessStream> stream;
hr = operation->GetResults(&stream);
AssertAndReturnIfFailed(hr);
ComPtr<IOutputStream> outputStream;
hr = stream.As(&outputStream);
AssertAndReturnIfFailed(hr);
ComPtr<IAsyncOperationWithProgress<UINT32, UINT32>> writeOperation;
hr = outputStream->WriteAsync(buffer.Get(), &writeOperation);
AssertAndReturnIfFailed(hr);
hr = writeOperation->put_Completed(Callback<IAsyncOperationWithProgressCompletedHandler<UINT32, UINT32>>([](IAsyncOperationWithProgress<UINT32, UINT32>* operation, AsyncStatus status) -> HRESULT
{
auto hr = GetOperationErrorCode(operation, status);
if (FAILED(hr))
{
// Do something with the error code
// ...
// ...
return S_OK;
}
// Success! At this point the file is written.
return S_OK;
}).Get());
AssertAndReturnIfFailed(hr);
return S_OK;
}).Get());
AssertAndReturnIfFailed(hr);
return S_OK;
}