从Rust调用Btrieve BTRCALL函数时发生访问\u冲突

从Rust调用Btrieve BTRCALL函数时发生访问\u冲突,rust,ffi,btrieve,Rust,Ffi,Btrieve,我正试图从Rust调用Btrieve(一个非常旧的数据库引擎)。 这有点长,但这是我第一次尝试在FFI从生锈和腐蚀 我想描述一下我所做的一切 Btrieve引擎是在一个DLL w3btrv7.DLL中实现的,它是一个 32位DLL。我使用32位MSVC工具为它创建了一个导入库 (它没有官方版本): lib/Def:w3btrv7.Def/Out:w3btrv7.lib/Machine:x86 然后,我安装了32位Rust toolchainstable-i686-pc-windows-msvc

我正试图从Rust调用Btrieve(一个非常旧的数据库引擎)。 这有点长,但这是我第一次尝试在FFI从生锈和腐蚀 我想描述一下我所做的一切

Btrieve引擎是在一个DLL w3btrv7.DLL中实现的,它是一个 32位DLL。我使用32位MSVC工具为它创建了一个导入库 (它没有官方版本):

lib/Def:w3btrv7.Def/Out:w3btrv7.lib/Machine:x86
然后,我安装了32位Rust toolchain
stable-i686-pc-windows-msvc
并将其设置为默认值。Bindgen在官方Btrieve标题上出现barfs 所以我必须自己做。幸运的是,我们只需要包装一个函数,

我的包装里有这个。h:

short int BTRCALL(
无符号短操作,
void*posBlock,
无效*数据缓冲,
无符号短*数据长度,
void*keyBuffer,
无符号字符密钥长度,
煤焦;
我的链接如下:

println!("cargo:rustc-link-lib=./src/pervasive/w3btrv7");
这似乎工作:程序运行,是一个32位的exe,我可以 请在Process Explorer中查看它已加载的
w3btrv7.dll

当我通过bindgen发送标题时,我得到:

extern "C" {
    pub fn BTRCALL(
        operation: ::std::os::raw::c_ushort,
        posBlock: *mut ::std::os::raw::c_void,
        dataBuffer: *mut ::std::os::raw::c_void,
        dataLength: *mut ::std::os::raw::c_ushort,
        keyBuffer: *mut ::std::os::raw::c_void,
        keyLength: ::std::os::raw::c_uchar,
        ckeynum: ::std::os::raw::c_char,
    ) -> ::std::os::raw::c_short;
}
所有的类型和尺寸似乎都符合要求,而且它们是匹配的 我从一个C#应用程序中获得了一个DllImport,它可以完美地工作:

[DllImport(“w3btrv7.dll”,CharSet=CharSet.Ansi)]
专用静态外部短BTRCALL(
ushort操作,//在C#中,ushort=UInt16。
[Marshallas(UnmanagedType.LPArray,SizeConst=128)]字节[]posBlock,
[Marshallas(UnmanagedType.LPArray)]字节[]数据缓冲,
参考ushort数据长度,
[Marshallas(UnmanagedType.LPArray)]字节[]键缓冲区,
字节键长度,//无符号字节
字符键号);//2字节字符
keyNumber
略有不同,但我在有符号和无符号变体中尝试了字节和短字符,但仍然不起作用

不幸的是,当我运行我的程序时,它在第一次调用后就崩溃了 到BTRCALL。(好的,实际上是当这个调用所在的函数 返回)。我已将所有参数提取到局部变量中并进行了检查 它们的类型和外观都是正确的:

let op: u16 = 0;
let mut pos_block: [u8; 128] = self.pos_block.clone();
let pos_block_ptr: *mut std::ffi::c_void = pos_block.as_mut_ptr() as *mut _;
let mut data_buffer: [u8; 32768] = self.data_buffer.clone();
let data_buffer_ptr: *mut std::ffi::c_void = data_buffer.as_mut_ptr() as *mut _;
let mut data_length: u16 = data_buffer.len() as u16;
let mut key_buffer: [u8; 256] = self.key_buffer.clone();
let key_buffer_ptr: *mut std::ffi::c_void = key_buffer.as_mut_ptr() as *mut _;
let key_length: u8 = 255; //self.key_length;
let key_number: i8 = self.key_number.try_into().unwrap();

let status: i16 = BTRCALL(
    op,
    pos_block_ptr,
    data_buffer_ptr,
    &mut data_length,
    key_buffer_ptr,
    key_length,
    key_number
);
它使程序崩溃

错误:进程未成功退出:`target\debug\blah.exe`(退出代码:0xc0000005,状态\访问\冲突)
据我所知,这可能是由于不正确的地址访问

事实上,当我加入一些跟踪来检查变量时,有一些非常有趣的行为,我的 通过值传递的局部变量似乎正在被覆盖。这里的日志只是转储第一个日志 30个字节的缓冲区,因为其余的都是零:

pos_block=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
位置块ptr=0xad6524
数据缓冲区=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
数据缓冲区ptr=0xad65a8
数据长度=32768
key_buffer=[34,67,58,92,116,101,109,112,92,99,115,115,92,120,100,98,92,67,65,83,69,46,68,66,34,0,0,0,0,0]
按键缓冲区ptr=0xade5b0
键长度=255
键号=0
>>>>>>>>>>>>>>>调用BTRCALL后:
pos_block=[0,0,0,0,0,0,0,76,203,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0]
位置块ptr=0x0
数据缓冲区=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
数据缓冲区ptr=0x42442e45
数据长度=0
键缓冲区=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
键\u缓冲区\u ptr=0x0
键长=173
键号=0
BTRCALL()返回B_无错误
请注意,
pos_block_ptr
已设置为0。相反,成功的执行 来自C#code的完全相同的调用只需将一些数据写入
pos_块的前18个字节
不会改变任何其他变量

它好像变得有点疯狂,开始覆盖内存


此时,我不知道下一步要尝试什么。

将声明从
extern“C”
更改为
extern“stdcall”
有效:

extern "stdcall" {
    pub fn BTRCALL(
        operation: ::std::os::raw::c_ushort,
        posBlock: *mut ::std::os::raw::c_void,
        dataBuffer: *mut ::std::os::raw::c_void,
        dataLength: *mut ::std::os::raw::c_ushort,
        keyBuffer: *mut ::std::os::raw::c_void,
        keyLength: ::std::os::raw::c_uchar,
        ckeynum: ::std::os::raw::c_char,
    ) -> ::std::os::raw::c_short;
}

将声明从
extern“C”
更改为
extern“stdcall”
有效:

extern "stdcall" {
    pub fn BTRCALL(
        operation: ::std::os::raw::c_ushort,
        posBlock: *mut ::std::os::raw::c_void,
        dataBuffer: *mut ::std::os::raw::c_void,
        dataLength: *mut ::std::os::raw::c_ushort,
        keyBuffer: *mut ::std::os::raw::c_void,
        keyLength: ::std::os::raw::c_uchar,
        ckeynum: ::std::os::raw::c_char,
    ) -> ::std::os::raw::c_short;
}

很难回答你的问题,因为它没有包含一个。您忽略了为函数调用中使用的所有类型提供C和Rust定义。您还使用了
self
,但尚未显示该结构。请在您的问题中包含此附加信息。理想情况下,使用将原始代码简化为单个
main
函数和变量声明。谢谢我已经编辑了Rust代码,将参数类型包括到调用中。不可能提供一个可复制的示例,因为其他人不会有所需的DLL。我了解DLL(尽管假设没有人会有DLL似乎有局限性),但这并不意味着你不应该遵循相同的过程,在产生相同错误的同时尽可能减少本地代码,然后发布所有代码。因为你正在反向工程DLL,你确定你有正确的调用约定吗?不,我不确定……我甚至不确定那是什么。刚在land上检查过,将声明更改为外部“stdcall”{…而且很有效!很难回答您的问题,因为它不包含a。您忽略了为函数调用中使用的所有类型提供C和Rust定义。您还使用了
self
,并且没有显示该结构。请您的问题包含此附加信息。理想情况下,使用我们的原始代码只是一个
main
函数和变量声明。谢谢!我已经编辑了Rust代码以包含类型