Skip to content

Commit 682faae

Browse files
Fix Windows crash: marshal InvokeMethod calls to platform thread via TaskDispatcher
1 parent 14e6961 commit 682faae

4 files changed

Lines changed: 120 additions & 30 deletions

File tree

windows/camera.cpp

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,12 @@ std::string CameraStateStr(CameraState s) {
196196

197197
Camera::Camera(int camera_id, flutter::TextureRegistrar* texture_registrar,
198198
flutter::MethodChannel<flutter::EncodableValue>* channel,
199-
CameraConfig config)
199+
CameraConfig config,
200+
PlatformTaskPoster platform_task_poster)
200201
: camera_id_(camera_id),
201202
texture_registrar_(texture_registrar),
202203
channel_(channel),
204+
platform_task_poster_(std::move(platform_task_poster)),
203205
config_(std::move(config)) {}
204206

205207
Camera::~Camera() {
@@ -1188,6 +1190,8 @@ void Camera::PostImageStreamFrame(const uint8_t* data, int width, int height) {
11881190

11891191
void Camera::ImageStreamLoop() {
11901192
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
1193+
auto* channel = channel_;
1194+
const int camera_id = camera_id_;
11911195

11921196
while (image_stream_running_.load()) {
11931197
ImageStreamSlot local;
@@ -1201,18 +1205,21 @@ void Camera::ImageStreamLoop() {
12011205
image_stream_slot_.dirty = false;
12021206
}
12031207

1204-
channel_->InvokeMethod(
1205-
"imageStreamFrame",
1206-
std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
1207-
{flutter::EncodableValue("cameraId"),
1208-
flutter::EncodableValue(camera_id_)},
1209-
{flutter::EncodableValue("width"),
1210-
flutter::EncodableValue(local.width)},
1211-
{flutter::EncodableValue("height"),
1212-
flutter::EncodableValue(local.height)},
1213-
{flutter::EncodableValue("bytes"),
1214-
flutter::EncodableValue(local.data)},
1215-
}));
1208+
platform_task_poster_(
1209+
[channel, camera_id, local = std::move(local)]() mutable {
1210+
channel->InvokeMethod(
1211+
"imageStreamFrame",
1212+
std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
1213+
{flutter::EncodableValue("cameraId"),
1214+
flutter::EncodableValue(camera_id)},
1215+
{flutter::EncodableValue("width"),
1216+
flutter::EncodableValue(local.width)},
1217+
{flutter::EncodableValue("height"),
1218+
flutter::EncodableValue(local.height)},
1219+
{flutter::EncodableValue("bytes"),
1220+
flutter::EncodableValue(local.data)},
1221+
}));
1222+
});
12161223
}
12171224

12181225
CoUninitialize();
@@ -1241,14 +1248,18 @@ void Camera::ResumePreview() {
12411248
// ============================================================================
12421249

12431250
void Camera::SendError(const std::string& description) {
1244-
channel_->InvokeMethod(
1245-
"cameraError",
1246-
std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
1247-
{flutter::EncodableValue("cameraId"),
1248-
flutter::EncodableValue(camera_id_)},
1249-
{flutter::EncodableValue("description"),
1250-
flutter::EncodableValue(description)},
1251-
}));
1251+
auto* channel = channel_;
1252+
int camera_id = camera_id_;
1253+
platform_task_poster_([channel, camera_id, description]() {
1254+
channel->InvokeMethod(
1255+
"cameraError",
1256+
std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
1257+
{flutter::EncodableValue("cameraId"),
1258+
flutter::EncodableValue(camera_id)},
1259+
{flutter::EncodableValue("description"),
1260+
flutter::EncodableValue(description)},
1261+
}));
1262+
});
12521263
}
12531264

12541265
// ============================================================================
@@ -1373,12 +1384,18 @@ void Camera::DisposeInternal() {
13731384
texture_.reset();
13741385
}
13751386

1376-
channel_->InvokeMethod(
1377-
"cameraClosing",
1378-
std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
1379-
{flutter::EncodableValue("cameraId"),
1380-
flutter::EncodableValue(camera_id_)},
1381-
}));
1387+
{
1388+
auto* channel = channel_;
1389+
int camera_id = camera_id_;
1390+
platform_task_poster_([channel, camera_id]() {
1391+
channel->InvokeMethod(
1392+
"cameraClosing",
1393+
std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
1394+
{flutter::EncodableValue("cameraId"),
1395+
flutter::EncodableValue(camera_id)},
1396+
}));
1397+
});
1398+
}
13821399

13831400
{
13841401
std::lock_guard<std::mutex> lk(state_mutex_);

windows/camera.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,12 @@ struct CameraConfig {
4444

4545
class Camera : public std::enable_shared_from_this<Camera> {
4646
public:
47+
using PlatformTaskPoster = std::function<void(std::function<void()>)>;
48+
4749
Camera(int camera_id, flutter::TextureRegistrar* texture_registrar,
4850
flutter::MethodChannel<flutter::EncodableValue>* channel,
49-
CameraConfig config);
51+
CameraConfig config,
52+
PlatformTaskPoster platform_task_poster);
5053
~Camera();
5154

5255
int64_t RegisterTexture();
@@ -109,6 +112,7 @@ class Camera : public std::enable_shared_from_this<Camera> {
109112

110113
flutter::TextureRegistrar* texture_registrar_;
111114
flutter::MethodChannel<flutter::EncodableValue>* channel_;
115+
PlatformTaskPoster platform_task_poster_;
112116
std::unique_ptr<CameraTexture> texture_;
113117

114118
// ── Capture engine + D3D11 ─────────────────────────────────────────────

windows/camera_desktop_plugin.cpp

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,50 @@
1818

1919
CameraDesktopPlugin* CameraDesktopPlugin::instance_ = nullptr;
2020

21+
// ---------------------------------------------------------------------------
22+
// TaskDispatcher
23+
// ---------------------------------------------------------------------------
24+
25+
static const UINT kWmTask = WM_APP + 100;
26+
static const wchar_t kTaskWndClass[] = L"CameraDesktopTaskDispatcher";
27+
28+
TaskDispatcher::TaskDispatcher() {
29+
WNDCLASSEX wc = {};
30+
wc.cbSize = sizeof(wc);
31+
wc.lpfnWndProc = &TaskDispatcher::WndProc;
32+
wc.hInstance = GetModuleHandle(nullptr);
33+
wc.lpszClassName = kTaskWndClass;
34+
RegisterClassEx(&wc); // Ignore failure — already-registered is fine.
35+
36+
hwnd_ = CreateWindowEx(0, kTaskWndClass, nullptr, 0,
37+
0, 0, 0, 0, HWND_MESSAGE, nullptr,
38+
GetModuleHandle(nullptr), nullptr);
39+
}
40+
41+
TaskDispatcher::~TaskDispatcher() {
42+
if (hwnd_) {
43+
DestroyWindow(hwnd_);
44+
hwnd_ = nullptr;
45+
}
46+
}
47+
48+
void TaskDispatcher::Post(std::function<void()> task) {
49+
if (!hwnd_) return;
50+
PostMessage(hwnd_, kWmTask, 0,
51+
reinterpret_cast<LPARAM>(new std::function<void()>(std::move(task))));
52+
}
53+
54+
LRESULT CALLBACK TaskDispatcher::WndProc(HWND hwnd, UINT msg,
55+
WPARAM wparam, LPARAM lparam) {
56+
if (msg == kWmTask) {
57+
auto* task = reinterpret_cast<std::function<void()>*>(lparam);
58+
(*task)();
59+
delete task;
60+
return 0;
61+
}
62+
return DefWindowProc(hwnd, msg, wparam, lparam);
63+
}
64+
2165
int64_t camera_desktop_ffi_register_stream_handle(Camera* camera);
2266
void camera_desktop_ffi_release_stream_handle(int64_t stream_handle);
2367
void camera_desktop_ffi_release_handles_for_camera(Camera* camera);
@@ -64,7 +108,9 @@ void CameraDesktopPlugin::RegisterWithRegistrar(
64108
CameraDesktopPlugin::CameraDesktopPlugin(
65109
flutter::PluginRegistrarWindows* registrar,
66110
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> channel)
67-
: registrar_(registrar), channel_(std::move(channel)) {}
111+
: registrar_(registrar),
112+
channel_(std::move(channel)),
113+
task_dispatcher_(std::make_unique<TaskDispatcher>()) {}
68114

69115
CameraDesktopPlugin::~CameraDesktopPlugin() {
70116
shutting_down_ = true;
@@ -273,11 +319,16 @@ void CameraDesktopPlugin::HandleCreate(
273319

274320
int camera_id = next_camera_id_++;
275321
DebugLog("HandleCreate: assigning camera_id=" + std::to_string(camera_id));
322+
TaskDispatcher* dispatcher = task_dispatcher_.get();
323+
Camera::PlatformTaskPoster poster = [dispatcher](std::function<void()> task) {
324+
dispatcher->Post(std::move(task));
325+
};
276326
auto camera = std::make_shared<Camera>(
277327
camera_id,
278328
registrar_->texture_registrar(),
279329
channel_.get(),
280-
config);
330+
config,
331+
std::move(poster));
281332

282333
int64_t texture_id = camera->RegisterTexture();
283334
if (texture_id < 0) {

windows/camera_desktop_plugin.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,30 @@
22

33
#include <flutter/method_channel.h>
44
#include <flutter/plugin_registrar_windows.h>
5+
#include <windows.h>
56

7+
#include <functional>
68
#include <map>
79
#include <memory>
810
#include <mutex>
911

1012
#include "camera.h"
1113

14+
// Marshals arbitrary work to the Win32 message-loop thread (the Flutter
15+
// platform thread) via a hidden message-only HWND. Must be constructed on
16+
// the platform thread; Post() is thread-safe.
17+
class TaskDispatcher {
18+
public:
19+
TaskDispatcher();
20+
~TaskDispatcher();
21+
void Post(std::function<void()> task);
22+
23+
private:
24+
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
25+
WPARAM wparam, LPARAM lparam);
26+
HWND hwnd_ = nullptr;
27+
};
28+
1229
class CameraDesktopPlugin : public flutter::Plugin {
1330
public:
1431
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar);
@@ -75,6 +92,7 @@ class CameraDesktopPlugin : public flutter::Plugin {
7592

7693
flutter::PluginRegistrarWindows* registrar_;
7794
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> channel_;
95+
std::unique_ptr<TaskDispatcher> task_dispatcher_;
7896
mutable std::mutex cameras_mutex_;
7997
std::map<int, std::shared_ptr<Camera>> cameras_;
8098
int next_camera_id_ = 1;

0 commit comments

Comments
 (0)