如何在C风格回调中通过IAsyncOperationWithProgress更新UI进度
解决C++/WinRT异步函数中通过C风格回调更新UI进度的问题
问题核心
第三方PutObject函数仅支持C风格回调,字节传输时触发回调更新进度,但异步函数TransferAsync内的进度token(f_taskProgress)是局部变量,且类型为auto无法直接存储为类成员,导致回调无法访问该token更新UI进度。
解决方案思路
将进度token与类实例指针封装为上下文结构体,通过PutObject的用户参数传递给回调,确保回调能获取到进度token。同时用智能指针管理上下文生命周期,避免悬空指针问题。
具体实现代码
winrt::Windows::Foundation::IAsyncOperationWithProgress<Transfer::TransferStatus, Transfer::TransferProgress> Windows::TransferClient::TransferAsync(winrt::Windows::Foundation::Collections::IVector<hstring> filesToSend) { co_await winrt::resume_background(); auto f_taskProgress{ co_await winrt::get_progress_token() }; f_taskProgress.set_result(m_progress.Status); m_progress.FilesSentSuccessfully = 0; // 定义上下文结构体,保存进度token和类实例指针 struct CallbackContext { decltype(f_taskProgress) progressToken; TransferClient* client; }; // 用shared_ptr确保上下文生命周期覆盖异步操作与回调 auto context = std::make_shared<CallbackContext>(); context->progressToken = std::move(f_taskProgress); context->client = this; // 适配C风格的静态回调函数 auto staticCallback = [](LPVOID pvIn, ULONG cbTransmittedIn) -> HRESULT { auto ctxPtr = static_cast<std::shared_ptr<CallbackContext>*>(pvIn); auto& progressToken = (*ctxPtr)->progressToken; auto client = (*ctxPtr)->client; client->m_progress.BytesSent = cbTransmittedIn; progressToken(client->m_progress); return S_OK; }; try { uint32_t listSize = filesToSend.Size(); m_progress.TotalFilesToSend = listSize; for (uint32_t i = 0; i < listSize; i++) { hstring file = filesToSend.GetAt(i); std::wstring str(file.c_str()); m_progress.FileName = file; HANDLE hFile = CreateFileW(str.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) { DWORD gle = GetLastError(); HRESULT hr = HRESULT_FROM_WIN32(gle); THR(hr); } else { LARGE_INTEGER li{}; BOOL b = GetFileSizeEx(hFile, &li); if (!b) { DWORD gle = GetLastError(); HRESULT hr = HRESULT_FROM_WIN32(gle); THR(hr); } else { if (0 != li.HighPart) { HRESULT hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); THR(hr); } else { m_progress.Status = TransferStatus::InProgress; context->progressToken.set_result(m_progress.Status); DWORD _cbSize = li.LowPart; WCHAR _szFilename[MAX_PATH]; StringCchCopy(_szFilename, ARRAYSIZE(_szFilename), file.c_str()); // 传入上下文指针,供回调使用 HRESULT hr = THR(session.PutObject( _szFilename, NULL, hFile, _cbSize, staticCallback, &context)); } } CloseHandle(hFile); } if (FAILED(THR_GetLast())) { break; } else { m_progress.FilesSentSuccessfully += 1; } } context->progressToken(m_progress); m_progress.Status = TransferStatus::Completed; context->progressToken.set_result(m_progress.Status); co_return m_progress.Status; } catch (...) { std::wcout << winrt::to_message().c_str() << std::endl; co_return TransferStatus::Unknown; } }
关键注意事项
- 生命周期管理:必须用
std::shared_ptr管理上下文,确保异步操作和回调执行期间上下文不会被销毁,避免悬空指针。 - 回调兼容性:
staticCallback采用空捕获lambda,可隐式转换为C风格函数指针,符合PutObject的参数要求。 - 变量冲突:代码中调整了局部变量(如
hFile、gle)的声明,避免与类成员变量冲突,实际使用时需结合自身类定义调整。
内容的提问来源于stack exchange,提问作者rsp
相关产品推荐
相关产品推荐

