Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit c3c7e33

Browse files
authored
Support custom entrypoints in public Windows API (#35285)
This adds a dart_entrypoint field to FlutterDesktopEngineProperties in the public C Windows API, which mirrors that in the embedder API. When a null or empty entrypoint is specified, a default entrypoint of 'main' is assumed. Otherwise, the app is launched at the top-level function specified, which must be annotated with @pragma('vm:entry-point') in the Dart source. This change is backward-compatible for existing users of the Windows C API and the C++ client wrapper API. To avoid breaking backward compatibility, this patch preserves the entry_point parameter to FlutterDesktopEngineRun in the public Windows C API as well as in the FlutterEngine::Run method in the C++ client wrapper API. The entrypoint can be specified in either the engine properties struct or via the parameter, but if conflicting non-empty values are specified, the engine launch will intentionally fail with an error message. This change has no effect on existing Flutter Windows desktop apps and no migration is required, because our app templates never specify a custom entrypoint, nor was the option to specify one via the old method particularly feasible, because the FlutterViewController class constructor immediately invokes FlutterViewControllerCreate which immediately launches the engine passed to it with a null entrypoint argument, so long as the engine is not already running. However, running the engine without a view controller previously resulted in errors due to failure to create a rendering surface. This is a followup patch to #35273 which added support for running Dart fixture tests with a live Windows embedder engine. Fixes: flutter/flutter#93537 Related: flutter/flutter#87299
1 parent 6192818 commit c3c7e33

18 files changed

+205
-32
lines changed

shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ std::unique_ptr<FlutterWindowsEngine> GetTestEngine() {
8686
MockEmbedderApiForKeyboard(modifier,
8787
std::make_shared<MockKeyResponseController>());
8888

89-
engine->RunWithEntrypoint(nullptr);
89+
engine->Run();
9090
return engine;
9191
}
9292

shell/platform/windows/client_wrapper/flutter_engine.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ FlutterEngine::FlutterEngine(const DartProject& project) {
1616
c_engine_properties.assets_path = project.assets_path().c_str();
1717
c_engine_properties.icu_data_path = project.icu_data_path().c_str();
1818
c_engine_properties.aot_library_path = project.aot_library_path().c_str();
19+
c_engine_properties.dart_entrypoint = project.dart_entrypoint().c_str();
1920

2021
const std::vector<std::string>& entrypoint_args =
2122
project.dart_entrypoint_arguments();
@@ -40,6 +41,10 @@ FlutterEngine::~FlutterEngine() {
4041
ShutDown();
4142
}
4243

44+
bool FlutterEngine::Run() {
45+
return Run(nullptr);
46+
}
47+
4348
bool FlutterEngine::Run(const char* entry_point) {
4449
if (!engine_) {
4550
std::cerr << "Cannot run an engine that failed creation." << std::endl;

shell/platform/windows/client_wrapper/flutter_engine_unittests.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@ TEST(FlutterEngineTest, CreateDestroy) {
8686
EXPECT_EQ(test_api->destroy_called(), true);
8787
}
8888

89+
TEST(FlutterEngineTest, CreateDestroyWithCustomEntrypoint) {
90+
testing::ScopedStubFlutterWindowsApi scoped_api_stub(
91+
std::make_unique<TestFlutterWindowsApi>());
92+
auto test_api = static_cast<TestFlutterWindowsApi*>(scoped_api_stub.stub());
93+
{
94+
DartProject project(L"fake/project/path");
95+
project.set_dart_entrypoint("customEntrypoint");
96+
FlutterEngine engine(project);
97+
engine.Run();
98+
EXPECT_EQ(test_api->create_called(), true);
99+
EXPECT_EQ(test_api->run_called(), true);
100+
EXPECT_EQ(test_api->destroy_called(), false);
101+
}
102+
// Destroying should implicitly shut down if it hasn't been done manually.
103+
EXPECT_EQ(test_api->destroy_called(), true);
104+
}
105+
89106
TEST(FlutterEngineTest, ExplicitShutDown) {
90107
testing::ScopedStubFlutterWindowsApi scoped_api_stub(
91108
std::make_unique<TestFlutterWindowsApi>());

shell/platform/windows/client_wrapper/include/flutter/dart_project.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ class DartProject {
4545

4646
~DartProject() = default;
4747

48+
// Sets the Dart entrypoint to the specified value.
49+
//
50+
// If not set, the default entrypoint (main) is used. Custom Dart entrypoints
51+
// must be decorated with `@pragma('vm:entry-point')`.
52+
void set_dart_entrypoint(const std::string& entrypoint) {
53+
if (entrypoint.empty()) {
54+
return;
55+
}
56+
dart_entrypoint_ = entrypoint;
57+
}
58+
59+
// Returns the Dart entrypoint.
60+
const std::string& dart_entrypoint() const { return dart_entrypoint_; }
61+
4862
// Sets the command line arguments that should be passed to the Dart
4963
// entrypoint.
5064
void set_dart_entrypoint_arguments(std::vector<std::string> arguments) {
@@ -77,6 +91,8 @@ class DartProject {
7791
// The path to the AOT library. This will always return a path, but non-AOT
7892
// builds will not be expected to actually have a library at that path.
7993
std::wstring aot_library_path_;
94+
// The Dart entrypoint to launch.
95+
std::string dart_entrypoint_;
8096
// The list of arguments to pass through to the Dart entrypoint.
8197
std::vector<std::string> dart_entrypoint_arguments_;
8298
};

shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ class FlutterEngine : public PluginRegistry {
3535
FlutterEngine(FlutterEngine const&) = delete;
3636
FlutterEngine& operator=(FlutterEngine const&) = delete;
3737

38+
// Starts running the engine at the entrypoint function specified in the
39+
// DartProject used to configure the engine, or main() by default.
40+
bool Run();
41+
3842
// Starts running the engine, with an optional entry point.
3943
//
4044
// If provided, entry_point must be the name of a top-level function from the
4145
// same Dart library that contains the app's main() function, and must be
4246
// decorated with `@pragma(vm:entry-point)` to ensure the method is not
4347
// tree-shaken by the Dart compiler. If not provided, defaults to main().
44-
bool Run(const char* entry_point = nullptr);
48+
bool Run(const char* entry_point);
4549

4650
// Terminates the running engine.
4751
void ShutDown();

shell/platform/windows/fixtures/main.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,10 @@
33
// found in the LICENSE file.
44

55
void main() {
6-
print('Hello windows engine test!');
6+
print('Hello windows engine test main!');
7+
}
8+
9+
@pragma('vm:entry-point')
10+
void customEntrypoint() {
11+
print('Hello windows engine test customEntrypoint!');
712
}

shell/platform/windows/flutter_project_bundle.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ FlutterProjectBundle::FlutterProjectBundle(
2020
aot_library_path_ = std::filesystem::path(properties.aot_library_path);
2121
}
2222

23+
if (properties.dart_entrypoint && properties.dart_entrypoint[0] != '\0') {
24+
dart_entrypoint_ = properties.dart_entrypoint;
25+
}
26+
2327
for (int i = 0; i < properties.dart_entrypoint_argc; i++) {
2428
dart_entrypoint_arguments_.push_back(
2529
std::string(properties.dart_entrypoint_argv[i]));

shell/platform/windows/flutter_project_bundle.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class FlutterProjectBundle {
5050
// Logs and returns nullptr on failure.
5151
UniqueAotDataPtr LoadAotData(const FlutterEngineProcTable& engine_procs);
5252

53+
// Returns the Dart entrypoint.
54+
const std::string& dart_entrypoint() const { return dart_entrypoint_; }
55+
5356
// Returns the command line arguments to be passed through to the Dart
5457
// entrypoint.
5558
const std::vector<std::string>& dart_entrypoint_arguments() const {
@@ -63,6 +66,9 @@ class FlutterProjectBundle {
6366
// Path to the AOT library file, if any.
6467
std::filesystem::path aot_library_path_;
6568

69+
// The Dart entrypoint to launch.
70+
std::string dart_entrypoint_;
71+
6672
// Dart entrypoint arguments.
6773
std::vector<std::string> dart_entrypoint_arguments_;
6874

shell/platform/windows/flutter_windows.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate(
7878
std::unique_ptr<flutter::FlutterWindowsEngine>(EngineFromHandle(engine)));
7979
state->view->CreateRenderSurface();
8080
if (!state->view->GetEngine()->running()) {
81-
if (!state->view->GetEngine()->RunWithEntrypoint(nullptr)) {
81+
if (!state->view->GetEngine()->Run()) {
8282
return nullptr;
8383
}
8484
}
@@ -144,7 +144,7 @@ bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine_ref) {
144144

145145
bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine,
146146
const char* entry_point) {
147-
return EngineFromHandle(engine)->RunWithEntrypoint(entry_point);
147+
return EngineFromHandle(engine)->Run(entry_point);
148148
}
149149

150150
uint64_t FlutterDesktopEngineProcessMessages(FlutterDesktopEngineRef engine) {

shell/platform/windows/flutter_windows_engine.cc

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,11 @@ void FlutterWindowsEngine::SetSwitches(
200200
project_->SetSwitches(switches);
201201
}
202202

203-
bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) {
203+
bool FlutterWindowsEngine::Run() {
204+
return Run("");
205+
}
206+
207+
bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
204208
if (!project_->HasValidPaths()) {
205209
std::cerr << "Missing or unresolvable paths to assets." << std::endl;
206210
return false;
@@ -259,6 +263,26 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) {
259263
args.icu_data_path = icu_path_string.c_str();
260264
args.command_line_argc = static_cast<int>(argv.size());
261265
args.command_line_argv = argv.empty() ? nullptr : argv.data();
266+
267+
// Fail if conflicting non-default entrypoints are specified in the method
268+
// argument and the project.
269+
//
270+
// TODO(cbracken): https://github.com/flutter/flutter/issues/109285
271+
// The entrypoint method parameter should eventually be removed from this
272+
// method and only the entrypoint specified in project_ should be used.
273+
if (!project_->dart_entrypoint().empty() && !entrypoint.empty() &&
274+
project_->dart_entrypoint() != entrypoint) {
275+
std::cerr << "Conflicting entrypoints were specified in "
276+
"FlutterDesktopEngineProperties.dart_entrypoint and "
277+
"FlutterDesktopEngineRun(engine, entry_point). "
278+
<< std::endl;
279+
return false;
280+
}
281+
if (!entrypoint.empty()) {
282+
args.custom_dart_entrypoint = entrypoint.data();
283+
} else if (!project_->dart_entrypoint().empty()) {
284+
args.custom_dart_entrypoint = project_->dart_entrypoint().c_str();
285+
}
262286
args.dart_entrypoint_argc = static_cast<int>(entrypoint_argv.size());
263287
args.dart_entrypoint_argv =
264288
entrypoint_argv.empty() ? nullptr : entrypoint_argv.data();
@@ -301,9 +325,6 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) {
301325
if (aot_data_) {
302326
args.aot_data = aot_data_.get();
303327
}
304-
if (entrypoint) {
305-
args.custom_dart_entrypoint = entrypoint;
306-
}
307328

308329
FlutterRendererConfig renderer_config = surface_manager_
309330
? GetOpenGLRendererConfig()

shell/platform/windows/flutter_windows_engine.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include <map>
1010
#include <memory>
1111
#include <optional>
12+
#include <string>
13+
#include <string_view>
1214
#include <vector>
1315

1416
#include "flutter/shell/platform/common/accessibility_bridge.h"
@@ -72,11 +74,22 @@ class FlutterWindowsEngine {
7274
FlutterWindowsEngine(FlutterWindowsEngine const&) = delete;
7375
FlutterWindowsEngine& operator=(FlutterWindowsEngine const&) = delete;
7476

75-
// Starts running the engine with the given entrypoint. If null, defaults to
76-
// main().
77+
// Starts running the entrypoint function specifed in the project bundle. If
78+
// unspecified, defaults to main().
7779
//
7880
// Returns false if the engine couldn't be started.
79-
bool RunWithEntrypoint(const char* entrypoint);
81+
bool Run();
82+
83+
// Starts running the engine with the given entrypoint. If the empty string
84+
// is specified, defaults to the entrypoint function specified in the project
85+
// bundle, or main() if both are unspecified.
86+
//
87+
// Returns false if the engine couldn't be started or if conflicting,
88+
// non-default values are passed here and in the project bundle..
89+
//
90+
// DEPRECATED: Prefer setting the entrypoint in the FlutterProjectBundle
91+
// passed to the constructor and calling the no-parameter overload.
92+
bool Run(std::string_view entrypoint);
8093

8194
// Returns true if the engine is currently running.
8295
bool running() { return engine_ != nullptr; }

shell/platform/windows/flutter_windows_engine_unittests.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ TEST(FlutterWindowsEngine, RunDoesExpectedInitialization) {
129129
// Set the AngleSurfaceManager to !nullptr to test ANGLE rendering.
130130
modifier.SetSurfaceManager(reinterpret_cast<AngleSurfaceManager*>(1));
131131

132-
engine->RunWithEntrypoint(nullptr);
132+
engine->Run();
133133

134134
EXPECT_TRUE(run_called);
135135
EXPECT_TRUE(update_locales_called);
@@ -206,7 +206,7 @@ TEST(FlutterWindowsEngine, RunWithoutANGLEUsesSoftware) {
206206
// Set the AngleSurfaceManager to nullptr to test software fallback path.
207207
modifier.SetSurfaceManager(nullptr);
208208

209-
engine->RunWithEntrypoint(nullptr);
209+
engine->Run();
210210

211211
EXPECT_TRUE(run_called);
212212

@@ -351,7 +351,7 @@ TEST(FlutterWindowsEngine, AddPluginRegistrarDestructionCallback) {
351351
MockEmbedderApiForKeyboard(modifier,
352352
std::make_shared<MockKeyResponseController>());
353353

354-
engine->RunWithEntrypoint(nullptr);
354+
engine->Run();
355355

356356
// Verify that destruction handlers don't overwrite each other.
357357
int result1 = 0;

shell/platform/windows/flutter_windows_unittests.cc

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ TEST(WindowsNoFixtureTest, GetTextureRegistrar) {
3030
TEST_F(WindowsTest, LaunchMain) {
3131
auto& context = GetContext();
3232
WindowsConfigBuilder builder(context);
33-
ViewControllerPtr controller{builder.LaunchEngine()};
33+
ViewControllerPtr controller{builder.Run()};
3434
ASSERT_NE(controller, nullptr);
3535

3636
// Run for 1 second, then shut down.
@@ -41,5 +41,56 @@ TEST_F(WindowsTest, LaunchMain) {
4141
std::this_thread::sleep_for(std::chrono::seconds(1));
4242
}
4343

44+
TEST_F(WindowsTest, LaunchCustomEntrypoint) {
45+
auto& context = GetContext();
46+
WindowsConfigBuilder builder(context);
47+
builder.SetDartEntrypoint("customEntrypoint");
48+
ViewControllerPtr controller{builder.Run()};
49+
ASSERT_NE(controller, nullptr);
50+
51+
// Run for 1 second, then shut down.
52+
//
53+
// TODO(cbracken): Support registring a native function we can use to
54+
// determine that execution has made it to a specific point in the Dart
55+
// code. https://github.com/flutter/flutter/issues/109242
56+
std::this_thread::sleep_for(std::chrono::seconds(1));
57+
}
58+
59+
// Verify that engine launches with the custom entrypoint specified in the
60+
// FlutterDesktopEngineRun parameter when no entrypoint is specified in
61+
// FlutterDesktopEngineProperties.dart_entrypoint.
62+
//
63+
// TODO(cbracken): https://github.com/flutter/flutter/issues/109285
64+
TEST_F(WindowsTest, LaunchCustomEntrypointInEngineRunInvocation) {
65+
auto& context = GetContext();
66+
WindowsConfigBuilder builder(context);
67+
EnginePtr engine{builder.InitializeEngine()};
68+
ASSERT_NE(engine, nullptr);
69+
70+
ASSERT_TRUE(FlutterDesktopEngineRun(engine.get(), "customEntrypoint"));
71+
72+
// Run for 1 second, then shut down.
73+
//
74+
// TODO(cbracken): Support registring a native function we can use to
75+
// determine that execution has made it to a specific point in the Dart
76+
// code. https://github.com/flutter/flutter/issues/109242
77+
std::this_thread::sleep_for(std::chrono::seconds(1));
78+
}
79+
80+
// Verify that engine fails to launch when a conflicting entrypoint in
81+
// FlutterDesktopEngineProperties.dart_entrypoint and the
82+
// FlutterDesktopEngineRun parameter.
83+
//
84+
// TODO(cbracken): https://github.com/flutter/flutter/issues/109285
85+
TEST_F(WindowsTest, LaunchConflictingCustomEntrypoints) {
86+
auto& context = GetContext();
87+
WindowsConfigBuilder builder(context);
88+
builder.SetDartEntrypoint("customEntrypoint");
89+
EnginePtr engine{builder.InitializeEngine()};
90+
ASSERT_NE(engine, nullptr);
91+
92+
ASSERT_FALSE(FlutterDesktopEngineRun(engine.get(), "conflictingEntrypoint"));
93+
}
94+
4495
} // namespace testing
4596
} // namespace flutter

shell/platform/windows/flutter_windows_view_unittests.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ std::unique_ptr<FlutterWindowsEngine> GetTestEngine() {
9090

9191
MockEmbedderApiForKeyboard(modifier, key_response_controller);
9292

93-
engine->RunWithEntrypoint(nullptr);
93+
engine->Run();
9494
return engine;
9595
}
9696

shell/platform/windows/keyboard_unittests.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ class KeyboardTester {
524524

525525
MockEmbedderApiForKeyboard(modifier, key_response_controller);
526526

527-
engine->RunWithEntrypoint(nullptr);
527+
engine->Run();
528528
return engine;
529529
}
530530

shell/platform/windows/public/flutter_windows.h

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ typedef struct {
4747
// it will be ignored in that case.
4848
const wchar_t* aot_library_path;
4949

50+
// The name of the top-level Dart entrypoint function. If null or the empty
51+
// string, 'main' is assumed. If a custom entrypoint is used, this parameter
52+
// must specifiy the name of a top-level function in the same Dart library as
53+
// the app's main() function. Custom entrypoint functions must be decorated
54+
// with `@pragma('vm:entry-point')` to ensure the method is not tree-shaken
55+
// by the Dart compiler.
56+
const char* dart_entrypoint;
57+
5058
// Number of elements in the array passed in as dart_entrypoint_argv.
5159
int dart_entrypoint_argc;
5260

@@ -129,13 +137,19 @@ FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopEngineCreate(
129137
// |engine| is no longer valid after this call.
130138
FLUTTER_EXPORT bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine);
131139

132-
// Starts running the given engine instance and optional entry point in the Dart
133-
// project. If the entry point is null, defaults to main().
140+
// Starts running the given engine instance.
141+
//
142+
// The entry_point parameter is deprecated but preserved for
143+
// backward-compatibility. If desired, a custom Dart entrypoint function can be
144+
// set in the dart_entrypoint field of the FlutterDesktopEngineProperties
145+
// struct passed to FlutterDesktopEngineCreate.
134146
//
135-
// If provided, entry_point must be the name of a top-level function from the
147+
// If sprecified, entry_point must be the name of a top-level function from the
136148
// same Dart library that contains the app's main() function, and must be
137149
// decorated with `@pragma(vm:entry-point)` to ensure the method is not
138-
// tree-shaken by the Dart compiler.
150+
// tree-shaken by the Dart compiler. If conflicting non-null values are passed
151+
// to this function and via the FlutterDesktopEngineProperties struct, the run
152+
// will fail.
139153
//
140154
// Returns false if running the engine failed.
141155
FLUTTER_EXPORT bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine,

0 commit comments

Comments
 (0)