diff --git a/flutter/windows/runner/main.cpp b/flutter/windows/runner/main.cpp
index efa26314e..3921e03dd 100644
--- a/flutter/windows/runner/main.cpp
+++ b/flutter/windows/runner/main.cpp
@@ -7,7 +7,8 @@
 #include "utils.h"
 // #include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
 
-typedef bool (*FUNC_RUSTDESK_CORE_MAIN)(void);
+typedef char** (*FUNC_RUSTDESK_CORE_MAIN)(int*);
+typedef void (*FUNC_RUSTDESK_FREE_ARGS)( char**, int);
 
 // auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
 int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
@@ -26,11 +27,23 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
     std::cout << "Failed to get rustdesk_core_main" << std::endl;
     return EXIT_FAILURE;
   }
-  if (!rustdesk_core_main())
+  FUNC_RUSTDESK_FREE_ARGS free_c_args =
+      (FUNC_RUSTDESK_FREE_ARGS)GetProcAddress(hInstance, "free_c_args");
+  if (!free_c_args)
+  {
+    std::cout << "Failed to get free_c_args" << std::endl;
+    return EXIT_FAILURE;
+  }
+
+  int args_len = 0;
+  char** c_args = rustdesk_core_main(&args_len);
+  if (!c_args)
   {
     std::cout << "Rustdesk core returns false, exiting without launching Flutter app" << std::endl;
     return EXIT_SUCCESS;
   }
+  std::vector<std::string> rust_args(c_args, c_args + args_len);
+  free_c_args(c_args, args_len);
 
   // Attach to console when present (e.g., 'flutter run') or create a
   // new console when running with a debugger.
@@ -48,6 +61,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
   std::vector<std::string> command_line_arguments =
       GetCommandLineArguments();
 
+  command_line_arguments.insert(command_line_arguments.end(), rust_args.begin(), rust_args.end());
   project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
 
   FlutterWindow window(project);
diff --git a/src/flutter.rs b/src/flutter.rs
index bf758e31c..b65ce4412 100644
--- a/src/flutter.rs
+++ b/src/flutter.rs
@@ -1,5 +1,7 @@
 use std::{
     collections::HashMap,
+    ffi::CString,
+    os::raw::{c_char, c_int},
     sync::{Arc, RwLock},
 };
 
@@ -24,6 +26,78 @@ lazy_static::lazy_static! {
     pub static ref GLOBAL_EVENT_STREAM: RwLock<HashMap<String, StreamSink<String>>> = Default::default(); // rust to dart event channel
 }
 
+/// FFI for rustdesk core's main entry.
+/// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit.
+#[cfg(not(windows))]
+#[no_mangle]
+pub extern "C" fn rustdesk_core_main() -> bool {
+    #[cfg(not(any(target_os = "android", target_os = "ios")))]
+    return crate::core_main::core_main().is_some();
+    #[cfg(any(target_os = "android", target_os = "ios"))]
+    false
+}
+
+#[cfg(windows)]
+#[no_mangle]
+pub extern "C" fn rustdesk_core_main(args_len: *mut c_int) -> *mut *mut c_char {
+    unsafe { std::ptr::write(args_len, 0) };
+    #[cfg(not(any(target_os = "android", target_os = "ios")))]
+    {
+        if let Some(args) = crate::core_main::core_main() {
+            return rust_args_to_c_args(args, args_len);
+        }
+        return std::ptr::null_mut() as _;
+    }
+    #[cfg(any(target_os = "android", target_os = "ios"))]
+    return std::ptr::null_mut() as _;
+}
+
+// https://gist.github.com/iskakaushik/1c5b8aa75c77479c33c4320913eebef6
+fn rust_args_to_c_args(args: Vec<String>, outlen: *mut c_int) -> *mut *mut c_char {
+    let mut v = vec![];
+
+    // Let's fill a vector with null-terminated strings
+    for s in args {
+        v.push(CString::new(s).unwrap());
+    }
+
+    // Turning each null-terminated string into a pointer.
+    // `into_raw` takes ownershop, gives us the pointer and does NOT drop the data.
+    let mut out = v.into_iter().map(|s| s.into_raw()).collect::<Vec<_>>();
+
+    // Make sure we're not wasting space.
+    out.shrink_to_fit();
+    assert!(out.len() == out.capacity());
+
+    // Get the pointer to our vector.
+    let len = out.len();
+    let ptr = out.as_mut_ptr();
+    std::mem::forget(out);
+
+    // Let's write back the length the caller can expect
+    unsafe { std::ptr::write(outlen, len as c_int) };
+
+    // Finally return the data
+    ptr
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn free_c_args(ptr: *mut *mut c_char, len: c_int) {
+    let len = len as usize;
+
+    // Get back our vector.
+    // Previously we shrank to fit, so capacity == length.
+    let v = Vec::from_raw_parts(ptr, len, len);
+
+    // Now drop one string at a time.
+    for elem in v {
+        let s = CString::from_raw(elem);
+        std::mem::drop(s);
+    }
+
+    // Afterwards the vector will be dropped and thus freed.
+}
+
 #[derive(Default, Clone)]
 pub struct FlutterHandler {
     pub event_stream: Arc<RwLock<Option<StreamSink<EventToUI>>>>,
diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs
index b33c9490f..f788c679d 100644
--- a/src/flutter_ffi.rs
+++ b/src/flutter_ffi.rs
@@ -43,16 +43,6 @@ fn initialize(app_dir: &str) {
     }
 }
 
-/// FFI for rustdesk core's main entry.
-/// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit.
-#[no_mangle]
-pub extern "C" fn rustdesk_core_main() -> bool {
-    #[cfg(not(any(target_os = "android", target_os = "ios")))]
-    return crate::core_main::core_main().is_some();
-    #[cfg(any(target_os = "android", target_os = "ios"))]
-    false
-}
-
 pub enum EventToUI {
     Event(String),
     Rgba(ZeroCopyBuffer<Vec<u8>>),