如何在Rust中向Windows DLL传递回调函数?VB6/C#已实现
Rust调用带回调的Windows DLL解决方案
问题背景
需要在Rust中调用一个要求传入回调函数的Windows DLL(DeviceDrv.dll),该功能已在VB6和C#中实现,但自行编写的Rust代码编译通过却无法正常工作。
参考实现代码
VB6实现
Public Declare Sub Init Lib "DeviceDrv.dll" Alias "INIT" (ByRef iCallbackAddress As Long)
使用示例:
Public Sub InitialiseCallback(ByVal iStation As Integer, ByVal iValue As Single) ' 回调逻辑 End Sub Call Init(AddressOf InitialiseCallback)
C#实现
[DllImport("DeviceDrv.dll", EntryPoint = "INIT", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = false)] public static extern void Init(IntPtr callback);
使用示例:
public delegate void InitialiseCallback(int station, float value); public static void InitCallback(int station, float value) { // 回调逻辑 } // 初始化回调并传入 var cb = new InitialiseCallback(InitCallback); GCHandle gch = GCHandle.Alloc(cb); var callbackPointer = Marshal.GetFunctionPointerForDelegate(cb); Init(callbackPointer);
原Rust代码问题
原代码编译无错但无法工作,核心问题有两点:
- 错误使用静态链接方式(
kind = "static"),但目标是动态链接库 - 调用约定不匹配(使用
extern "C"而非DLL要求的stdcall)
原代码:
#![allow(non_snake_case)] #[link(name = "DeviceDrv", kind = "static")] extern { pub fn INIT(callback: unsafe extern "C" fn(i32, f32)); } #[no_mangle] pub unsafe extern "C" fn cb(station: i32, value: f32) { // 回调逻辑 } crate::dd::INIT(cb);
正确的Rust等效代码
#![allow(non_snake_case)] // 动态链接Windows DLL,默认使用dylib类型,无需额外指定kind #[link(name = "DeviceDrv")] extern "stdcall" { // 声明DLL导出的INIT函数,参数为stdcall调用约定的回调指针 pub fn INIT(callback: unsafe extern "stdcall" fn(i32, f32)); } // 定义符合stdcall调用约定的回调函数,#[no_mangle]确保函数名不被混淆 #[no_mangle] pub unsafe extern "stdcall" fn cb(station: i32, value: f32) { // 这里编写你的回调处理逻辑 println!("回调触发:工位={}, 值={}", station, value); } // 封装初始化调用 pub unsafe fn init_device() { // 传入回调函数指针给DLL INIT(cb); }
关键修正说明
- 链接方式修正:移除
kind = "static",Rust在Windows平台下默认会按动态链接库(dylib)处理.dll文件,无需显式声明静态链接。 - 调用约定匹配:将
extern "C"改为extern "stdcall",与VB6、C#代码使用的调用约定保持一致——Windows系统API和多数VB/C#调用的DLL默认使用stdcall,调用约定不匹配会导致栈异常或参数传递错误。 - 函数指针类型一致:确保传入
INIT的回调函数指针类型,与DLL期望的调用约定、参数类型完全匹配。
如果回调需要长期被DLL调用(程序运行期间多次触发),需确保回调函数不会被编译器优化掉:全局函数默认具备全局生命周期,无需额外处理;若使用闭包作为回调,需用std::mem::forget或持有其所有权以延长生命周期。
内容的提问来源于stack exchange,提问作者user2948475
相关产品推荐
相关产品推荐

