obsidian_backup/未命名.md

19 KiB
Raw Blame History

USER

rust 64位代码通过unsafe调用discon.dll现在discon.dll是64位程序可以运行discon.dll改成32位提示无法加载DISCON.dll错误: %1 is not a valid Win32 application. (0x800700C1)如何兼容32位discon.dll

ASSISTANT

好的这是一个非常经典的问题涉及到操作系统进程的体系结构Architecture隔离。

你遇到的错误 ... %1 is not a valid Win32 application. (0x800700C1) 是 Windows 操作系统的底层限制,它明确地告诉你:一个 64 位 (x86_64) 的进程无法直接加载一个 32 位 (i686) 的 DLL。

反之亦然,一个 32 位的进程也无法加载一个 64 位的 DLL。它们的内存地址空间、指针大小、寄存器用法和调用约定都完全不同操作系统从设计上就禁止了这种直接的混合加载。

因此,你的 64 位 Rust 程序尝试加载 32 位 discon.dllWindows 加载器会立即失败,并返回这个错误。

要解决这个问题,你不能让 64 位进程“兼容”32 位 DLL而是要改变你的策略。以下是两种主要的解决方案从最简单到最复杂排列。

方法一:将您的 Rust 程序编译为 32 位 (最简单、最推荐)

如果你的 Rust 程序没有特殊要求必须是 64 位(例如,需要超过 4GB 的内存),那么最直接、最简单、性能最好的方法就是将你的 Rust 程序也编译成 32 位版本。这样32 位的程序就可以顺利加载 32 位的 DLL。

操作步骤如下:

  1. 添加 32 位编译目标: 打开你的命令行工具(如 PowerShell 或 CMD运行以下命令来为 Rust 添加 32 位 Windows 的编译目标。通常我们使用 MSVC 工具链。 抱歉,上一步的 rustup 命令是我提供给您在您自己的计算机上运行的指令,而不是在我的执行环境中运行的代码,因此出现了错误。请忽略该错误输出。

以下是完整的解决方案:


好的这是一个非常经典的问题涉及到操作系统进程的体系结构Architecture隔离。

你遇到的错误 ... %1 is not a valid Win32 application. (0x800700C1) 是 Windows 操作系统的底层限制,它明确地告诉你:一个 64 位 (x86_64) 的进程无法直接加载一个 32 位 (i686) 的 DLL。

反之亦然,一个 32 位的进程也无法加载一个 64 位的 DLL。它们的内存地址空间、指针大小、寄存器用法和调用约定都完全不同操作系统从设计上就禁止了这种直接的混合加载。

因此,你的 64 位 Rust 程序尝试加载 32 位 discon.dllWindows 加载器会立即失败,并返回这个错误。

要解决这个问题,你不能让 64 位进程“兼容”32 位 DLL而是要改变你的策略。以下是两种主要的解决方案从最简单到最复杂排列。

方法一:将您的 Rust 程序编译为 32 位 (最简单、最推荐)

如果你的 Rust 程序没有特殊要求必须是 64 位(例如,需要超过 4GB 的内存),那么最直接、最简单、性能最好的方法就是将你的 Rust 程序也编译成 32 位版本。这样32 位的程序就可以顺利加载 32 位的 DLL。

操作步骤如下:

  1. 添加 32 位编译目标: 打开你的命令行工具(如 PowerShell 或 CMD运行以下命令来为 Rust 添加 32 位 Windows 的编译目标。通常我们使用 MSVC 工具链。

    rustup target add i686-pc-windows-msvc
    

    (你需要预先安装 Visual Studio 的 C++ 构建工具,并确保选择了 32 位编译支持)

  2. 以 32 位目标进行编译: 使用 --target 参数来构建你的项目。

    cargo build --target=i686-pc-windows-msvc
    

    如果需要发布版本,则:

    cargo build --release --target=i686-pc-windows-msvc
    

    编译成功后,在 target/i686-pc-windows-msvc/debug/release/ 目录下找到的 .exe 文件就是 32 位的。将这个 32 位的 exe 和 32 位的 discon.dll 放在一起,就可以正常运行了。

方法二:使用进程间通信 (IPC) (当主程序必须为 64 位时)

在某些情况下,你的主程序必须是 64 位的(比如它依赖于其他 64 位的库,或者需要处理大量数据)。在这种情况下,你就不能直接加载 32 位 DLL。

解决方案是创建一个**“帮助程序”Helper Process**。

这个架构看起来是这样的:

  1. 你的主程序 (64-bit, Rust): 保持 64 位。
  2. 帮助程序 (32-bit): 一个非常简单的独立小程序,它的唯一职责就是加载 32 位的 discon.dll,并调用其中的函数。你可以用任何能够编译成 32 位的语言来写这个帮助程序,包括 Rust 自己!
  3. 通信机制 (IPC): 主程序和帮助程序通过一种进程间通信IPC的方式来交换数据。

实现流程:

  1. 创建 32 位帮助程序:

    • 创建一个新的、简单的 Rust 项目 (e.g., cargo new discon_helper)。
    • 在这个新项目中,编写加载 32 位 discon.dll 并调用其功能的代码(和你现在主程序里的 unsafe 调用类似)。
    • 这个帮助程序需要能从外部接收指令(比如从标准输入 stdin 读取命令)并把结果输出到标准输出 stdout
    • 使用方法一中提到的命令,将这个帮助程序编译成 32 位:cargo build --release --target=i686-pc-windows-msvc
  2. 在 64 位主程序中调用帮助程序:

    • 在你的主程序中,使用 Rust 的 std::process::Command 来启动这个 32 位的帮助程序 (discon_helper.exe)。
    • 通过管道Pipes将需要传递给 DLL 函数的参数写入帮助程序的 stdin
    • 从帮助程序的 stdout 读取 DLL 函数的执行结果。

简单的 IPC 示例 (通过 Stdin/Stdout):

32 位帮助程序 (discon_helper/src/main.rs):

// 伪代码,需要根据 discon.dll 的实际函数进行修改
use std::io::{self, Read};

// 定义 DLL 中的函数签名
#[link(name = "discon")]
extern "C" {
    fn some_function_in_discon(input: i32) -> i32;
}

fn main() {
    // 从标准输入读取数据
    let mut buffer = [0u8; 4]; // 假设输入是一个32位整数
    io::stdin().read_exact(&mut buffer).unwrap();
    let input_val = i32::from_le_bytes(buffer);

    // 调用 DLL 函数
    let result = unsafe { some_function_in_discon(input_val) };

    // 将结果打印到标准输出
    print!("{}", result);
}

64 位主程序 (main.rs):

// 伪代码
use std::process::{Command, Stdio};
use std::io::Write;

fn call_discon_via_helper(param: i32) -> i32 {
    let mut child = Command::new("path/to/discon_helper.exe")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("Failed to start helper process");

    let mut stdin = child.stdin.take().unwrap();
    // 将参数写入帮助程序的标准输入
    stdin.write_all(&param.to_le_bytes()).expect("Failed to write to stdin");
    
    // stdin 被丢弃,管道关闭,帮助程序可以继续执行

    let output = child.wait_with_output().expect("Failed to read stdout");
    
    let result_str = String::from_utf8(output.stdout).unwrap();
    result_str.parse::<i32>().unwrap()
}

fn main() {
    let result = call_discon_via_helper(123);
    println!("Result from 32-bit DLL: {}", result);
}

其他更健壮的 IPC 方法:

  • 本地 Web 服务: 32 位帮助程序可以启动一个微型的 HTTP 服务器 (如使用 actix-webtiny_http)64 位主程序通过发送 HTTP 请求到 localhost 来调用功能。这种方式扩展性好,易于调试。
  • 命名管道 (Named Pipes): Windows 提供的一种高效 IPC 机制,适合需要持续、双向通信的场景。
  • COM (Component Object Model): 这是 Windows 平台最重量级也最强大的 IPC/FFI 解决方案,但实现起来也最复杂。

如何选择?

  1. 首选方法一: 如果没有硬性要求,总是优先选择将你的 Rust 程序编译成 32 位。这是最简单、最高效、最不容易出错的方案。
  2. 检查是否有 64 位 DLL 询问 discon.dll 的提供方,他们是否能提供 64 位版本?这能从根本上解决问题。
  3. 最后才考虑方法二: 只有当你必须使用 64 位主程序,并且绝对无法获得 64 位版本的 DLL 时才采用进程间通信IPC的方案。

综上所述,请先尝试使用 cargo build --target=i686-pc-windows-msvc 来解决你的问题。

USER

只有当你必须使用 64 位主程序,并且绝对无法获得 64 位版本的 DLL 时才采用进程间通信IPC的方案。详细描述这个方案我计划采用此路线

ASSISTANT

好的非常棒的选择。采用进程间通信IPC是解决此类跨架构调用问题的标准工业级方案。它虽然比直接链接要复杂但提供了极好的解耦和稳定性。

我们将使用一个非常现代且健壮的方法:通过本地 HTTP 服务进行通信

  • 64位主程序 (Client): 你的主程序,作为 HTTP 客户端。
  • 32位帮助程序 (Server): 一个轻量级的 Rust 程序,内嵌一个 HTTP 服务器,负责加载 32 位 discon.dll 并暴露其功能。

这种方法的优点是:

  • 调试简单: 你可以独立运行和测试 32 位帮助程序,甚至可以用浏览器或 curl 这样的工具来调用它。
  • 数据清晰: 使用 JSON 作为数据交换格式,结构清晰,易于扩展。
  • 跨语言: 你的主程序甚至不一定需要是 Rust任何能发 HTTP 请求的语言都可以与这个帮助程序通信。
  • 健壮性: 网络通信有成熟的错误处理模式。

架构概览

+---------------------------------+      (HTTP Request over localhost)      +-----------------------------------------+
|                                 |   POST http://127.0.0.1:8032/call_func  |                                         |
|   Your 64-bit Main App (Rust)   |  <----------------------------------->  |   32-bit Helper App (Rust + Actix-Web)  |
|   (e.g., my_app.exe)            |        (JSON data request/response)     |   (e.g., discon_helper.exe)             |
|                                 |                                         |                                         |
+---------------------------------+                                         +-----------------------+-----------------+
                                                                                                    | (unsafe FFI call)
                                                                                                    V
                                                                                            +-----------------+
                                                                                            | 32-bit discon.dll |
                                                                                            +-----------------+

第1部分创建 32 位帮助程序 (HTTP 服务器)

这个程序是与 discon.dll 直接交互的桥梁。

1.1. 项目设置

首先,创建一个新的 Rust 项目:```shell cargo new discon_helper cd discon_helper


编辑 `Cargo.toml` 文件,添加以下依赖:
*   `actix-web`: 一个功能强大且易于使用的 Web 框架。
*   `serde`: 用于序列化和反序列化数据JSON。
*   `libloading`: 一种更安全、更灵活的加载动态链接库的方式。

```toml
[package]
name = "discon_helper"
version = "0.1.0"
edition = "2021"

[dependencies]
actix-web = "4"
serde = { version = "1.0", features = ["derive"] }
libloading = "0.8"

1.2. 编写服务器代码

为了演示,我们假设 discon.dll 中有一个函数,签名如下: int add_numbers(int a, int b);

现在,编辑 src/main.rs 文件:

use actix_web::{web, App, HttpServer, Responder, HttpResponse};
use serde::{Deserialize, Serialize};
use libloading::{Library, Symbol};
use std::sync::Mutex;

// 1. 定义与 DLL 函数签名匹配的类型
type AddNumbersFunc = unsafe extern "C" fn(a: i32, b: i32) -> i32;

// 2. 定义用于 HTTP 请求和响应的结构体
//    这些结构体将在客户端和服务器之间通过 JSON 进行交换
#[derive(Deserialize)]
struct AddRequest {
    a: i32,
    b: i32,
}

#[derive(Serialize)]
struct AddResponse {
    result: i32,
}

// 3. 将 DLL 库的句柄和函数符号存储在 Actix 的应用状态中
//    使用 Mutex 来确保线程安全,因为 Actix 会在多个线程中处理请求
struct AppState {
    lib: Library,
}

// 4. 定义处理 HTTP POST 请求的处理器
async fn call_add_numbers(
    req: web::Json<AddRequest>,
    data: web::Data<Mutex<AppState>>,
) -> impl Responder {
    let app_state = data.lock().unwrap();
    
    // 从已加载的库中获取函数符号
    // 使用 .get() 每次调用都是安全的,因为它会重新查找符号
    match unsafe { app_state.lib.get::<AddNumbersFunc>(b"add_numbers\0") } {
        Ok(add_numbers) => {
            // 调用 DLL 函数
            let result = unsafe { add_numbers(req.a, req.b) };
            
            // 返回成功的 JSON 响应
            HttpResponse::Ok().json(AddResponse { result })
        }
        Err(e) => {
            // 如果函数不存在,返回内部服务器错误
            HttpResponse::InternalServerError().body(format!("Failed to get function from DLL: {}", e))
        }
    }
}

// 5. 主函数:加载 DLL 并启动服务器
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("Starting 32-bit helper server...");

    // 加载 discon.dll
    // 将 discon.dll 放在生成的 exe 文件旁边
    let lib = unsafe {
        Library::new("discon.dll")
            .expect("Failed to load discon.dll. Make sure it's next to the executable.")
    };
    
    // 将库句柄封装在 Mutex 和 web::Data 中,以便在线程间共享
    let app_state = web::Data::new(Mutex::new(AppState { lib }));
    let port = 8032;

    println!("Server listening on http://127.0.0.1:{}", port);

    HttpServer::new(move || {
        App::new()
            .app_data(app_state.clone()) // 注册应用状态
            .route("/add", web::post().to(call_add_numbers)) // 定义路由
    })
    .bind(("127.0.0.1", port))?
    .run()
    .await
}

1.3. 编译为 32 位

# (确保你已经运行了 rustup target add i686-pc-windows-msvc)
cargo build --release --target=i686-pc-windows-msvc

编译成功后,你会在 target/i686-pc-windows-msvc/release/ 目录下找到 discon_helper.exe将 32 位的 discon.dll 复制到这个目录中


第2部分修改 64 位主程序 (HTTP 客户端)

现在,我们来修改你原来的 64 位程序,让它通过 HTTP 调用帮助程序。

2.1. 项目设置

在你的 64 位主程序项目的 Cargo.toml 中,添加以下依赖:

  • reqwest: 一个强大且易于使用的 HTTP 客户端。
  • tokio: 一个异步运行时,reqwest 需要它。
  • serde: 同样用于序列化和反序列化。
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }

2.2. 编写客户端代码

现在,修改你的 64 位主程序的 src/main.rs

use serde::{Deserialize, Serialize};
use std::process::{Command, Child}; // 用于管理帮助程序的生命周期

// 1. 定义与服务器端完全相同的请求和响应结构体
#[derive(Serialize)]
struct AddRequest {
    a: i32,
    b: i32,
}

#[derive(Deserialize)]
struct AddResponse {
    result: i32,
}

// 2. 封装调用逻辑的异步函数
async fn call_add_via_helper(client: &reqwest::Client, a: i32, b: i32) -> Result<i32, reqwest::Error> {
    let request_payload = AddRequest { a, b };

    let response = client
        .post("http://127.0.0.1:8032/add")
        .json(&request_payload)
        .send()
        .await?;

    // 检查服务器是否返回了成功状态
    if response.status().is_success() {
        let add_response = response.json::<AddResponse>().await?;
        Ok(add_response.result)
    } else {
        // 如果服务器返回错误,将其作为错误信息传递出去
        let error_text = response.text().await?;
        eprintln!("Error from helper: {}", error_text);
        // 在实际应用中,这里应该返回一个更具体的错误类型
        Err(reqwest::Error::from(std::io::Error::new(std::io::ErrorKind::Other, error_text)))
    }
}


// RAII 结构体,用于在程序退出时自动终止子进程
struct ChildProcessGuard(Child);

impl Drop for ChildProcessGuard {
    fn drop(&mut self) {
        println!("Shutting down helper process...");
        if let Err(e) = self.0.kill() {
            eprintln!("Failed to kill helper process: {}", e);
        }
    }
}


// 3. 主函数:启动帮助程序,然后调用它
#[tokio::main]
async fn main() {
    // 启动 32 位帮助程序
    // 假设 discon_helper.exe 在主程序的子目录 "helper" 中
    let helper_path = "./discon_helper.exe";
    let child = Command::new(helper_path)
        .spawn()
        .expect("Failed to start discon_helper.exe. Make sure it is in the correct path.");

    // 使用 Guard 来确保即使 main 函数 panic子进程也能被清理
    let _guard = ChildProcessGuard(child);
    
    // 给帮助程序一点时间来启动服务器
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

    println!("64-bit main application started.");
    
    let client = reqwest::Client::new();

    // 调用函数
    match call_add_via_helper(&client, 10, 20).await {
        Ok(result) => {
            println!("[Main App] The result from discon.dll is: {}", result);
        }
        Err(e) => {
            eprintln!("[Main App] Failed to call helper: {}", e);
        }
    }
    
    // 当 main 函数结束时_guard 会被丢弃,其 drop 方法会被调用,从而杀死子进程
}

2.3. 编译为 64 位

这个很简单,就是你平时的编译命令:

cargo build --release

第3部分部署和运行

  1. 组织文件结构: 创建一个最终的部署文件夹,例如 my_final_app

    my_final_app/
    ├── my_app.exe                  (你的 64 位主程序)
    ├── discon_helper.exe           (编译好的 32 位帮助程序)
    └── discon.dll                  (32 位 DLL 文件)
    

    确保 discon_helper.exediscon.dll 在同一个目录下。

  2. 运行:

    • 双击或从命令行运行 my_app.exe
    • 它会首先启动 discon_helper.exe(你可能会看到一个一闪而过的控制台窗口)。
    • 然后,主程序会等待几秒钟,接着发送 HTTP 请求。
    • 你会看到主程序的控制台输出从 DLL 计算得到的结果。
    • 当你关闭主程序时,它会自动终止 discon_helper.exe 进程。

这个方案为你提供了一个稳定、可维护且功能强大的方式来解决 64 位和 32 位代码的互操作问题。