Implemented threadsafe platform channel replies on windows (flutter/engine#36909)

* Implemented threadsafe platform channel replies on windows

* added unit test

* added docstrings

* implemented glfw

* added comments

* made glfw messenger unable to be copied

* stuart feedback 1

* stuart feedback 2: replaced the shared_ptr

* stuart feedback 3

* stuart feedback: remove error log

* Moved FlutterDesktopMessenger to its own file.

* updated licenses

* stuart feedback
This commit is contained in:
gaaclarke
2022-11-09 14:59:00 -08:00
committed by GitHub
parent af036b8857
commit 643e801f2c
13 changed files with 370 additions and 32 deletions

View File

@@ -3099,6 +3099,7 @@ FILE: ../../../flutter/shell/platform/windows/external_texture_d3d.cc
FILE: ../../../flutter/shell/platform/windows/external_texture_d3d.h
FILE: ../../../flutter/shell/platform/windows/external_texture_pixelbuffer.cc
FILE: ../../../flutter/shell/platform/windows/external_texture_pixelbuffer.h
FILE: ../../../flutter/shell/platform/windows/flutter_desktop_messenger.h
FILE: ../../../flutter/shell/platform/windows/flutter_key_map.g.cc
FILE: ../../../flutter/shell/platform/windows/flutter_platform_node_delegate_windows.cc
FILE: ../../../flutter/shell/platform/windows/flutter_platform_node_delegate_windows.h

View File

@@ -26,6 +26,11 @@ namespace flutter {
// ========== binary_messenger_impl.h ==========
namespace {
using FlutterDesktopMessengerScopedLock =
std::unique_ptr<FlutterDesktopMessenger,
decltype(&FlutterDesktopMessengerUnlock)>;
// Passes |message| to |user_data|, which must be a BinaryMessageHandler, along
// with a BinaryReply that will send a response on |message|'s response handle.
//
@@ -36,17 +41,28 @@ void ForwardToHandler(FlutterDesktopMessengerRef messenger,
const FlutterDesktopMessage* message,
void* user_data) {
auto* response_handle = message->response_handle;
BinaryReply reply_handler = [messenger, response_handle](
auto messenger_ptr = std::shared_ptr<FlutterDesktopMessenger>(
FlutterDesktopMessengerAddRef(messenger),
&FlutterDesktopMessengerRelease);
BinaryReply reply_handler = [messenger_ptr, response_handle](
const uint8_t* reply,
size_t reply_size) mutable {
// Note: This lambda can be called on any thread.
auto lock = FlutterDesktopMessengerScopedLock(
FlutterDesktopMessengerLock(messenger_ptr.get()),
&FlutterDesktopMessengerUnlock);
if (!FlutterDesktopMessengerIsAvailable(messenger_ptr.get())) {
// Drop reply if it comes in after the engine is destroyed.
return;
}
if (!response_handle) {
std::cerr << "Error: Response can be set only once. Ignoring "
"duplicate response."
<< std::endl;
return;
}
FlutterDesktopMessengerSendResponse(messenger, response_handle, reply,
reply_size);
FlutterDesktopMessengerSendResponse(messenger_ptr.get(), response_handle,
reply, reply_size);
// The engine frees the response handle once
// FlutterDesktopSendMessageResponse is called.
response_handle = nullptr;

View File

@@ -4,6 +4,8 @@
#include "flutter/shell/platform/common/client_wrapper/testing/stub_flutter_api.h"
#include <cassert>
static flutter::testing::StubFlutterApi* s_stub_implementation;
namespace flutter {
@@ -93,6 +95,31 @@ void FlutterDesktopMessengerSetCallback(FlutterDesktopMessengerRef messenger,
}
}
FlutterDesktopMessengerRef FlutterDesktopMessengerAddRef(
FlutterDesktopMessengerRef messenger) {
assert(false); // not implemented
return nullptr;
}
void FlutterDesktopMessengerRelease(FlutterDesktopMessengerRef messenger) {
assert(false); // not implemented
}
bool FlutterDesktopMessengerIsAvailable(FlutterDesktopMessengerRef messenger) {
assert(false); // not implemented
return false;
}
FlutterDesktopMessengerRef FlutterDesktopMessengerLock(
FlutterDesktopMessengerRef messenger) {
assert(false); // not implemented
return nullptr;
}
void FlutterDesktopMessengerUnlock(FlutterDesktopMessengerRef messenger) {
assert(false); // not implemented
}
FlutterDesktopTextureRegistrarRef FlutterDesktopRegistrarGetTextureRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
return reinterpret_cast<FlutterDesktopTextureRegistrarRef>(1);

View File

@@ -5,6 +5,7 @@
#ifndef FLUTTER_SHELL_PLATFORM_COMMON_PUBLIC_FLUTTER_MESSENGER_H_
#define FLUTTER_SHELL_PLATFORM_COMMON_PUBLIC_FLUTTER_MESSENGER_H_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@@ -87,6 +88,53 @@ FLUTTER_EXPORT void FlutterDesktopMessengerSetCallback(
FlutterDesktopMessageCallback callback,
void* user_data);
// Increments the reference count for the |messenger|.
//
// Operation is thread-safe.
//
// See also: |FlutterDesktopMessengerRelease|
FLUTTER_EXPORT FlutterDesktopMessengerRef
FlutterDesktopMessengerAddRef(FlutterDesktopMessengerRef messenger);
// Decrements the reference count for the |messenger|.
//
// Operation is thread-safe.
//
// See also: |FlutterDesktopMessengerAddRef|
FLUTTER_EXPORT void FlutterDesktopMessengerRelease(
FlutterDesktopMessengerRef messenger);
// Returns `true` if the |FlutterDesktopMessengerRef| still references a running
// engine.
//
// This check should be made inside of a |FlutterDesktopMessengerLock| and
// before any other calls are made to the FlutterDesktopMessengerRef when using
// it from a thread other than the platform thread.
FLUTTER_EXPORT bool FlutterDesktopMessengerIsAvailable(
FlutterDesktopMessengerRef messenger);
// Locks the `FlutterDesktopMessengerRef` ensuring that
// |FlutterDesktopMessengerIsAvailable| does not change while locked.
//
// All calls to the FlutterDesktopMessengerRef from threads other than the
// platform thread should happen inside of a lock.
//
// Operation is thread-safe.
//
// Returns the |messenger| value.
//
// See also: |FlutterDesktopMessengerUnlock|
FLUTTER_EXPORT FlutterDesktopMessengerRef
FlutterDesktopMessengerLock(FlutterDesktopMessengerRef messenger);
// Unlocks the `FlutterDesktopMessengerRef`.
//
// Operation is thread-safe.
//
// See also: |FlutterDesktopMessengerLock|
FLUTTER_EXPORT void FlutterDesktopMessengerUnlock(
FlutterDesktopMessengerRef messenger);
#if defined(__cplusplus)
} // extern "C"
#endif

View File

@@ -2325,6 +2325,7 @@ FlutterEngineResult FlutterPlatformMessageReleaseResponseHandle(
return kSuccess;
}
// Note: This can execute on any thread.
FlutterEngineResult FlutterEngineSendPlatformMessageResponse(
FLUTTER_API_SYMBOL(FlutterEngine) engine,
const FlutterPlatformMessageResponseHandle* handle,

View File

@@ -106,6 +106,10 @@ struct AOTDataDeleter {
};
using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AOTDataDeleter>;
/// Maintains one ref on the FlutterDesktopMessenger's internal reference count.
using FlutterDesktopMessengerReferenceOwner =
std::unique_ptr<FlutterDesktopMessenger,
decltype(&FlutterDesktopMessengerRelease)>;
// Struct for storing state of a Flutter engine instance.
struct FlutterDesktopEngineState {
@@ -116,7 +120,8 @@ struct FlutterDesktopEngineState {
std::unique_ptr<flutter::EventLoop> event_loop;
// The plugin messenger handle given to API clients.
std::unique_ptr<FlutterDesktopMessenger> messenger;
FlutterDesktopMessengerReferenceOwner messenger = {
nullptr, [](FlutterDesktopMessengerRef ref) {}};
// Message dispatch manager for messages from the Flutter engine.
std::unique_ptr<flutter::IncomingMessageDispatcher> message_dispatcher;
@@ -149,10 +154,75 @@ struct FlutterDesktopPluginRegistrar {
// State associated with the messenger used to communicate with the engine.
struct FlutterDesktopMessenger {
FlutterDesktopMessenger() = default;
/// Increments the reference count.
///
/// Thread-safe.
void AddRef() { ref_count_.fetch_add(1); }
/// Decrements the reference count and deletes the object if the count has
/// gone to zero.
///
/// Thread-safe.
void Release() {
int32_t old_count = ref_count_.fetch_sub(1);
if (old_count <= 1) {
delete this;
}
}
/// Getter for the engine field.
FlutterDesktopEngineState* GetEngine() const { return engine_; }
/// Setter for the engine field.
/// Thread-safe.
void SetEngine(FlutterDesktopEngineState* engine) {
std::scoped_lock lock(mutex_);
engine_ = engine;
}
/// Returns the mutex associated with the |FlutterDesktopMessenger|.
///
/// This mutex is used to synchronize reading or writing state inside the
/// |FlutterDesktopMessenger| (ie |engine_|).
std::mutex& GetMutex() { return mutex_; }
FlutterDesktopMessenger(const FlutterDesktopMessenger& value) = delete;
FlutterDesktopMessenger& operator=(const FlutterDesktopMessenger& value) =
delete;
private:
// The engine that backs this messenger.
FlutterDesktopEngineState* engine;
FlutterDesktopEngineState* engine_;
std::atomic<int32_t> ref_count_ = 0;
std::mutex mutex_;
};
FlutterDesktopMessengerRef FlutterDesktopMessengerAddRef(
FlutterDesktopMessengerRef messenger) {
messenger->AddRef();
return messenger;
}
void FlutterDesktopMessengerRelease(FlutterDesktopMessengerRef messenger) {
messenger->Release();
}
bool FlutterDesktopMessengerIsAvailable(FlutterDesktopMessengerRef messenger) {
return messenger->GetEngine() != nullptr;
}
FlutterDesktopMessengerRef FlutterDesktopMessengerLock(
FlutterDesktopMessengerRef messenger) {
messenger->GetMutex().lock();
return messenger;
}
void FlutterDesktopMessengerUnlock(FlutterDesktopMessengerRef messenger) {
messenger->GetMutex().unlock();
}
// Retrieves state bag for the window in question from the GLFWWindow.
static FlutterDesktopWindowControllerState* GetWindowController(
GLFWwindow* window) {
@@ -743,8 +813,10 @@ static void SetUpLocales(FlutterDesktopEngineState* state) {
static void SetUpCommonEngineState(FlutterDesktopEngineState* state,
GLFWwindow* window) {
// Messaging.
state->messenger = std::make_unique<FlutterDesktopMessenger>();
state->messenger->engine = state;
state->messenger = FlutterDesktopMessengerReferenceOwner(
FlutterDesktopMessengerAddRef(new FlutterDesktopMessenger()),
&FlutterDesktopMessengerRelease);
state->messenger->SetEngine(state);
state->message_dispatcher =
std::make_unique<flutter::IncomingMessageDispatcher>(
state->messenger.get());
@@ -846,6 +918,7 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow(
}
void FlutterDesktopDestroyWindow(FlutterDesktopWindowControllerRef controller) {
controller->engine->messenger->SetEngine(nullptr);
FlutterDesktopPluginRegistrarRef registrar =
controller->engine->plugin_registrar.get();
if (registrar->destruction_handler) {
@@ -1045,7 +1118,8 @@ bool FlutterDesktopMessengerSendWithReply(FlutterDesktopMessengerRef messenger,
FlutterPlatformMessageResponseHandle* response_handle = nullptr;
if (reply != nullptr && user_data != nullptr) {
FlutterEngineResult result = FlutterPlatformMessageCreateResponseHandle(
messenger->engine->flutter_engine, reply, user_data, &response_handle);
messenger->GetEngine()->flutter_engine, reply, user_data,
&response_handle);
if (result != kSuccess) {
std::cout << "Failed to create response handle\n";
return false;
@@ -1061,11 +1135,11 @@ bool FlutterDesktopMessengerSendWithReply(FlutterDesktopMessengerRef messenger,
};
FlutterEngineResult message_result = FlutterEngineSendPlatformMessage(
messenger->engine->flutter_engine, &platform_message);
messenger->GetEngine()->flutter_engine, &platform_message);
if (response_handle != nullptr) {
FlutterPlatformMessageReleaseResponseHandle(
messenger->engine->flutter_engine, response_handle);
messenger->GetEngine()->flutter_engine, response_handle);
}
return message_result == kSuccess;
@@ -1084,16 +1158,16 @@ void FlutterDesktopMessengerSendResponse(
const FlutterDesktopMessageResponseHandle* handle,
const uint8_t* data,
size_t data_length) {
FlutterEngineSendPlatformMessageResponse(messenger->engine->flutter_engine,
handle, data, data_length);
FlutterEngineSendPlatformMessageResponse(
messenger->GetEngine()->flutter_engine, handle, data, data_length);
}
void FlutterDesktopMessengerSetCallback(FlutterDesktopMessengerRef messenger,
const char* channel,
FlutterDesktopMessageCallback callback,
void* user_data) {
messenger->engine->message_dispatcher->SetMessageCallback(channel, callback,
user_data);
messenger->GetEngine()->message_dispatcher->SetMessageCallback(
channel, callback, user_data);
}
FlutterDesktopTextureRegistrarRef FlutterDesktopRegistrarGetTextureRegistrar(

View File

@@ -0,0 +1,83 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_DESKTOP_MESSENGER_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_DESKTOP_MESSENGER_H_
#include <atomic>
#include <mutex>
#include "flutter/shell/platform/common/public/flutter_messenger.h"
namespace flutter {
class FlutterWindowsEngine;
/// A messenger object used to invoke platform messages.
///
/// On Windows, the message handler is essentially the |FlutterWindowsEngine|,
/// this allows a handle to the |FlutterWindowsEngine| that will become
/// invalidated if the |FlutterWindowsEngine| is destroyed.
class FlutterDesktopMessenger {
public:
FlutterDesktopMessenger() = default;
/// Convert to FlutterDesktopMessengerRef.
FlutterDesktopMessengerRef ToRef() {
return reinterpret_cast<FlutterDesktopMessengerRef>(this);
}
/// Convert from FlutterDesktopMessengerRef.
static FlutterDesktopMessenger* FromRef(FlutterDesktopMessengerRef ref) {
return reinterpret_cast<FlutterDesktopMessenger*>(ref);
}
/// Getter for the engine field.
flutter::FlutterWindowsEngine* GetEngine() const { return engine; }
/// Setter for the engine field.
/// Thread-safe.
void SetEngine(flutter::FlutterWindowsEngine* arg_engine) {
std::scoped_lock lock(mutex_);
engine = arg_engine;
}
/// Increments the reference count.
///
/// Thread-safe.
FlutterDesktopMessenger* AddRef() {
ref_count_.fetch_add(1);
return this;
}
/// Decrements the reference count and deletes the object if the count has
/// gone to zero.
///
/// Thread-safe.
void Release() {
int32_t old_count = ref_count_.fetch_sub(1);
if (old_count <= 1) {
delete this;
}
}
/// Returns the mutex associated with the |FlutterDesktopMessenger|.
///
/// This mutex is used to synchronize reading or writing state inside the
/// |FlutterDesktopMessenger| (ie |engine|).
std::mutex& GetMutex() { return mutex_; }
FlutterDesktopMessenger(const FlutterDesktopMessenger& value) = delete;
FlutterDesktopMessenger& operator=(const FlutterDesktopMessenger& value) =
delete;
private:
// The engine that owns this state object.
flutter::FlutterWindowsEngine* engine = nullptr;
std::mutex mutex_;
std::atomic<int32_t> ref_count_ = 0;
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOW_STATE_H_

View File

@@ -262,8 +262,9 @@ bool FlutterDesktopMessengerSendWithReply(FlutterDesktopMessengerRef messenger,
const size_t message_size,
const FlutterDesktopBinaryReply reply,
void* user_data) {
return messenger->engine->SendPlatformMessage(channel, message, message_size,
reply, user_data);
return flutter::FlutterDesktopMessenger::FromRef(messenger)
->GetEngine()
->SendPlatformMessage(channel, message, message_size, reply, user_data);
}
bool FlutterDesktopMessengerSend(FlutterDesktopMessengerRef messenger,
@@ -279,15 +280,45 @@ void FlutterDesktopMessengerSendResponse(
const FlutterDesktopMessageResponseHandle* handle,
const uint8_t* data,
size_t data_length) {
messenger->engine->SendPlatformMessageResponse(handle, data, data_length);
flutter::FlutterDesktopMessenger::FromRef(messenger)
->GetEngine()
->SendPlatformMessageResponse(handle, data, data_length);
}
void FlutterDesktopMessengerSetCallback(FlutterDesktopMessengerRef messenger,
const char* channel,
FlutterDesktopMessageCallback callback,
void* user_data) {
messenger->engine->message_dispatcher()->SetMessageCallback(channel, callback,
user_data);
flutter::FlutterDesktopMessenger::FromRef(messenger)
->GetEngine()
->message_dispatcher()
->SetMessageCallback(channel, callback, user_data);
}
FlutterDesktopMessengerRef FlutterDesktopMessengerAddRef(
FlutterDesktopMessengerRef messenger) {
return flutter::FlutterDesktopMessenger::FromRef(messenger)
->AddRef()
->ToRef();
}
void FlutterDesktopMessengerRelease(FlutterDesktopMessengerRef messenger) {
flutter::FlutterDesktopMessenger::FromRef(messenger)->Release();
}
bool FlutterDesktopMessengerIsAvailable(FlutterDesktopMessengerRef messenger) {
return flutter::FlutterDesktopMessenger::FromRef(messenger)->GetEngine() !=
nullptr;
}
FlutterDesktopMessengerRef FlutterDesktopMessengerLock(
FlutterDesktopMessengerRef messenger) {
flutter::FlutterDesktopMessenger::FromRef(messenger)->GetMutex().lock();
return messenger;
}
void FlutterDesktopMessengerUnlock(FlutterDesktopMessengerRef messenger) {
flutter::FlutterDesktopMessenger::FromRef(messenger)->GetMutex().unlock();
}
FlutterDesktopTextureRegistrarRef FlutterDesktopRegistrarGetTextureRegistrar(

View File

@@ -179,14 +179,16 @@ FlutterWindowsEngine::FlutterWindowsEngine(
});
// Set up the legacy structs backing the API handles.
messenger_ = std::make_unique<FlutterDesktopMessenger>();
messenger_->engine = this;
messenger_ =
fml::RefPtr<FlutterDesktopMessenger>(new FlutterDesktopMessenger());
messenger_->SetEngine(this);
plugin_registrar_ = std::make_unique<FlutterDesktopPluginRegistrar>();
plugin_registrar_->engine = this;
messenger_wrapper_ = std::make_unique<BinaryMessengerImpl>(messenger_.get());
messenger_wrapper_ =
std::make_unique<BinaryMessengerImpl>(messenger_->ToRef());
message_dispatcher_ =
std::make_unique<IncomingMessageDispatcher>(messenger_.get());
std::make_unique<IncomingMessageDispatcher>(messenger_->ToRef());
message_dispatcher_->SetMessageCallback(
kAccessibilityChannelName,
[](FlutterDesktopMessengerRef messenger,
@@ -210,6 +212,7 @@ FlutterWindowsEngine::FlutterWindowsEngine(
}
FlutterWindowsEngine::~FlutterWindowsEngine() {
messenger_->SetEngine(nullptr);
Stop();
}

View File

@@ -20,6 +20,7 @@
#include "flutter/shell/platform/common/incoming_message_dispatcher.h"
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/windows/angle_surface_manager.h"
#include "flutter/shell/platform/windows/flutter_desktop_messenger.h"
#include "flutter/shell/platform/windows/flutter_project_bundle.h"
#include "flutter/shell/platform/windows/flutter_windows_texture_registrar.h"
#include "flutter/shell/platform/windows/public/flutter_windows.h"
@@ -124,7 +125,7 @@ class FlutterWindowsEngine {
// Sets switches member to the given switches.
void SetSwitches(const std::vector<std::string>& switches);
FlutterDesktopMessengerRef messenger() { return messenger_.get(); }
FlutterDesktopMessengerRef messenger() { return messenger_->ToRef(); }
IncomingMessageDispatcher* message_dispatcher() {
return message_dispatcher_.get();
@@ -283,7 +284,7 @@ class FlutterWindowsEngine {
std::unique_ptr<TaskRunner> task_runner_;
// The plugin messenger handle given to API clients.
std::unique_ptr<FlutterDesktopMessenger> messenger_;
fml::RefPtr<flutter::FlutterDesktopMessenger> messenger_;
// A wrapper around messenger_ for interacting with client_wrapper-level APIs.
std::unique_ptr<BinaryMessengerImpl> messenger_wrapper_;

View File

@@ -311,6 +311,60 @@ TEST_F(FlutterWindowsEngineTest, PlatformMessageRoundTrip) {
}
}
TEST_F(FlutterWindowsEngineTest, PlatformMessageRespondOnDifferentThread) {
FlutterDesktopEngineProperties properties = {};
properties.assets_path = GetContext().GetAssetsPath().c_str();
properties.icu_data_path = GetContext().GetIcuDataPath().c_str();
properties.dart_entrypoint = "hiPlatformChannels";
FlutterProjectBundle project(properties);
auto engine = std::make_unique<FlutterWindowsEngine>(project);
EngineModifier modifier(engine.get());
modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
auto binary_messenger =
std::make_unique<BinaryMessengerImpl>(engine->messenger());
engine->Run();
bool did_call_callback = false;
bool did_call_reply = false;
bool did_call_dart_reply = false;
std::string channel = "hi";
std::unique_ptr<std::thread> reply_thread;
binary_messenger->SetMessageHandler(
channel,
[&did_call_callback, &did_call_dart_reply, &reply_thread](
const uint8_t* message, size_t message_size, BinaryReply reply) {
if (message_size == 5) {
EXPECT_EQ(message[0], static_cast<uint8_t>('h'));
reply_thread.reset(new std::thread([reply = std::move(reply)]() {
char response[] = {'b', 'y', 'e'};
reply(reinterpret_cast<uint8_t*>(response), 3);
}));
did_call_callback = true;
} else {
EXPECT_EQ(message_size, 3);
EXPECT_EQ(message[0], static_cast<uint8_t>('b'));
did_call_dart_reply = true;
}
});
char payload[] = {'h', 'e', 'l', 'l', 'o'};
binary_messenger->Send(
channel, reinterpret_cast<uint8_t*>(payload), 5,
[&did_call_reply](const uint8_t* reply, size_t reply_size) {
EXPECT_EQ(reply_size, 5);
EXPECT_EQ(reply[0], static_cast<uint8_t>('h'));
did_call_reply = true;
});
// Rely on timeout mechanism in CI.
while (!did_call_callback || !did_call_reply || !did_call_dart_reply) {
engine->task_runner()->ProcessTasks();
}
ASSERT_TRUE(reply_thread);
reply_thread->join();
}
TEST_F(FlutterWindowsEngineTest, SendPlatformMessageWithResponse) {
std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
EngineModifier modifier(engine.get());

View File

@@ -181,6 +181,12 @@ FlutterDesktopEngineGetPluginRegistrar(FlutterDesktopEngineRef engine,
const char* plugin_name);
// Returns the messenger associated with the engine.
//
// This does not provide an owning reference, so should *not* be balanced with a
// call to |FlutterDesktopMessengerRelease|.
//
// Callers should use |FlutterDesktopMessengerAddRef| if the returned pointer
// will potentially outlive 'engine', such as when passing it to another thread.
FLUTTER_EXPORT FlutterDesktopMessengerRef
FlutterDesktopEngineGetMessenger(FlutterDesktopEngineRef engine);

View File

@@ -33,11 +33,4 @@ struct FlutterDesktopPluginRegistrar {
flutter::FlutterWindowsEngine* engine = nullptr;
};
// Wrapper to distinguish the messenger ref from the engine ref given out
// in the C API.
struct FlutterDesktopMessenger {
// The engine that owns this state object.
flutter::FlutterWindowsEngine* engine = nullptr;
};
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOW_STATE_H_