You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从外部线程启动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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 08:06:01