如何从外部线程启动Rust Actix-web服务器作为库供C++调用
问题:从C++调用Rust DLL启动Actix-web服务器时触发panic
我尝试通过C++控制台程序启动一个简易Actix-web服务器做概念验证,以下是我的实现代码:
Rust库代码(lib.rs)
use std::{io}; use actix_web::{get, middleware::Logger, App, HttpServer, Responder}; use actix_web_lab::{respond::Html}; #[no_mangle] pub extern "C" fn API_Start_Server() { println!("Rust about to launch server."); actix_web::rt::spawn(async move { // Thread will be consumed until server is closed. let _ = start_server(); }); println!("Server has been launched, returning."); } #[actix_web::main] async fn start_server() -> io::Result<()> { HttpServer::new(move || { App::new() .service(index) .wrap(Logger::default()) }) .bind(("127.0.0.1", 8080))? .workers(2) .run() .await } #[get("/")] async fn index() -> impl Responder { Html(include_str!("../../Client-Side/index.html").to_string()) }
C++调用代码(ServerLauncher.cpp)
#include <iostream> #include <windows.h> // Windows API header // Function signature for initializing and starting the Actix server typedef void (*StartServerFn)(); int main() { std::wstring wsRelativePath = L"..\\..\\..\\..\\..\\..\\..\\target\\release\\actix_server_library.dll"; // Load the Rust shared library HINSTANCE handle = LoadLibrary(wsRelativePath.c_str()); if (!handle) { std::cerr << "Error loading shared library: " << GetLastError() << std::endl; return 1; } // Get a function pointer to the Actix server's entry point StartServerFn startServer = (StartServerFn)GetProcAddress(handle, "API_Start_Server"); if (!startServer) { std::cerr << "Error finding function symbol: " << GetLastError() << std::endl; FreeLibrary(handle); return 1; } try { // Call the Rust function to start the Actix server on a new thread startServer(); } catch (std::exception ex) { std::cerr << ex.what(); } // Continue with other tasks in the main thread // ... // Wait for the Actix server to finish (in a real application, you may use proper synchronization) std::cout << "Press Enter to exit..." << std::endl; std::cin.get(); // Clean up and close the shared library FreeLibrary(handle); return 0; }
错误日志
Rust about to launch server. thread '<unnamed>' panicked at '`spawn_local` called from outside of a `task::LocalSet`', src/Server-Side/Rust/lib.rs:10:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
我推测是服务器线程的调用方式有问题,可能需要存储服务器状态,但作为Rust和Actix新手找不到相关示例。请问如何从外部线程创建服务器,使其在DLL被C++加载期间持续运行?
解决方案
这个panic的核心原因是:Actix-web的#[actix_web::main]宏会自动设置Tokio的LocalSet,但当你从C++调用Rust函数时,这个上下文不存在,直接用actix_web::rt::spawn无法正确启动本地任务。
需要手动创建Tokio运行时和LocalSet,并管理服务器的生命周期,同时要确保运行时在DLL加载期间保持存活。修改后的Rust代码如下:
修改后的lib.rs
use std::{io, sync::Arc}; use tokio::sync::Mutex; use actix_web::{get, middleware::Logger, App, HttpServer, Responder}; use actix_web_lab::{respond::Html}; use tokio::runtime::{Runtime, Builder}; use tokio::task::LocalSet; // 全局存储Tokio运行时和服务器句柄,确保DLL加载期间不被销毁 static mut RUNTIME: Option<Runtime> = None; static mut SERVER_HANDLE: Option<Arc<Mutex<Option<actix_web::dev::Server>>>> = None; #[no_mangle] pub extern "C" fn API_Start_Server() { println!("Rust about to launch server."); // 初始化Tokio运行时(多线程,支持Actix的worker线程) let runtime = Builder::new_multi_thread() .worker_threads(4) .enable_all() .build() .expect("Failed to create Tokio runtime"); // 创建LocalSet来运行Actix的本地任务 let local_set = LocalSet::new(); // 启动服务器并存储句柄 let server_handle = Arc::new(Mutex::new(None)); let handle_clone = server_handle.clone(); local_set.block_on(&runtime, async move { let server = start_server().await.expect("Failed to start server"); *handle_clone.lock().await = Some(server); }); // 将运行时和句柄存入全局静态变量,防止被销毁 unsafe { RUNTIME = Some(runtime); SERVER_HANDLE = Some(server_handle); } println!("Server has been launched, returning."); } // 移除#[actix_web::main]宏,手动实现服务器启动逻辑 async fn start_server() -> io::Result<actix_web::dev::Server> { let server = HttpServer::new(move || { App::new() .service(index) .wrap(Logger::default()) }) .bind(("127.0.0.1", 8080))? .workers(2) .run(); Ok(server) } #[get("/")] async fn index() -> impl Responder { Html(include_str!("../../Client-Side/index.html").to_string()) } // 可选:添加停止服务器的API,供C++调用 #[no_mangle] pub extern "C" fn API_Stop_Server() { println!("Stopping server..."); unsafe { if let Some(runtime) = &RUNTIME { if let Some(server_handle) = &SERVER_HANDLE { runtime.block_on(async { if let Some(mut server) = server_handle.lock().await.take() { server.stop(true).await; } }); } } } }
关键修改点说明
- 手动创建Tokio运行时:因为从C++调用时没有默认的Tokio上下文,需要显式构建多线程运行时并启用所有功能。
- 使用LocalSet:Actix-web的内部任务依赖
LocalSet来运行本地异步任务,需要用local_set.block_on来启动服务器。 - 全局存储运行时和服务器句柄:用静态变量保存运行时和服务器实例,避免函数返回后被销毁,确保服务器持续运行。
- 移除
#[actix_web::main]:该宏会自动处理运行时,但不适合外部调用场景,改为手动返回Server句柄以便管理。
C++端的补充(可选)
如果需要停止服务器,可以在C++代码中添加对API_Stop_Server的调用:
// 添加函数签名 typedef void (*StopServerFn)(); // 在main函数中获取函数指针 StopServerFn stopServer = (StopServerFn)GetProcAddress(handle, "API_Stop_Server"); // 在退出前调用 if (stopServer) { stopServer(); }
内容的提问来源于stack exchange,提问作者pSquared
相关产品推荐
相关产品推荐

