大数跨境

FreePascal/Lazarus 和 Rust 集成

FreePascal/Lazarus 和 Rust 集成 索引目录
2026-01-07
0
导读:关注「索引目录」公众号,获取更多干货。你好世界!

关注「索引目录」公众号,获取更多干货。

你好世界!

让我们从一个“Hello World”集成开始:一个简单的 Rust 函数,用于将两个数字相加,以及一个调用该函数的 Pascal 程序。

锈蚀面

在 Rust 端,我们创建一个供 Pascal 代码使用的库,我们称之为rustlaz

cargo new rustlaz --lib
cd rustlaz && zed .
我写的是zed .,但你可以写的是hx .code .或者任何你正在使用的编辑器。

Cargo.toml 文件中,确保我们编译了一个库:

[lib]
crate-type = ["cdylib"]

在src/lib.rs中,我们可以创建一个虚拟函数:

#[unsafe(no_mangle)]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
    a + b
}

#[unsafe(no_mangle)]将确保该函数已准备好被外部语言调用,当然,该函数本身必须能够pub extern "C"利用 C 风格的 FFI(外部函数集成)。

现在,根据我们的系统(Linux、Windows 或 Mac),构建过程会生成.so 文件.dll文件或.dylib文件。

cargo build --release

在 Windows 系统中,我们需要将编译后的库文件从/target/release/复制到 Lazarus 项目的根目录。而在 Linux 系统中,我们需要将其复制到链接器可以访问的位置,通常是/usr/lib/

sudo cp target/release/librustlaz.so /usr/lib/librustlaz.so

拉撒路·赛德

我们启动 Lazarus IDE 并选择一个New.. Project/Simple Program。我们可以将其保存在项目根目录下,命名为LazRust.lpi。在配套的 Github 仓库中,该文件夹名为lazrust/

此时,在编写程序之前,我们需要创建一个New unit并将其保存为RustLib.pas。该单元将负责集成 C 风格的库接口,并将其映射到 Pascal 函数。

这是本单元的实际内容:

unit RustLib;

{$mode ObjFPC}{$H+}

interface

uses
  Classes, SysUtils;

function add_numbers(a: LongInt; b: LongInt): LongInt; cdecl; external 'librustlaz';

implementation

end.

这样我们就声明了库中函数 ` add_numbers`external的接口,以便在 Lazarus 中自由使用它。鉴于在 Pascal 中调用外部函数如此简单,难怪它是一种广泛应用且备受喜爱的语言。

现在我们可以回到主程序并使用该函数:

program LazRust;

uses RustLib;

begin
  Writeln('Rust says 2 + 3 = ', add_numbers(2, 3));
end.

编译完成后(SHIFT+F9),我们就可以在命令行运行程序并查看结果:

./lazrust

Rust says 2 + 3 = 5

我们甚至可以创建一个使用该库的标准 GUI 程序。在LazRust2/文件夹中,您可以找到一个利用onEditingDone两次编辑事件来更新标签的示例。欢迎查看代码。

一个稍微复杂一些的例子

Point如果我们创建一个结构体并在 Rust 和 Free Pascal 之间传递它,可能会使接口变得稍微复杂一些。

在 Rust 中,rustlaz/src/lib.rs

use std::os::raw::c_int;

#[repr(C)]
pub struct Point {
    pub x: c_int,
    pub y: c_int,
}

#[unsafe(no_mangle)]
pub extern "C" fn move_point(p: Point, dx: c_int, dy: c_int) -> Point {
    Point {
        x: p.x + dx,
        y: p.y + dy,
    }
}

现在我们有了一个结构体和一个处理该结构体的函数。
我们可以编译它(发布模式),并将库复制到 Lazarus 可见的任何地方(参见上面的注释)。

现在,我们再次以拉撒路的身份,创造了一个New.. Project/Simple Program

RustLib 单元必须类似于以下内容:

unit RustLib;

{$mode ObjFPC}{$H+}

interface

uses
  Classes, SysUtils;

type
  TPoint = record
    x: LongInt;
    y: LongInt;
  end;

function move_point(p: TPoint; dx: LongInt; dy: LongInt): TPoint; cdecl; external 'librustlaz';

implementation

end.

这样,我们就“重构”了 Rust 结构体接口,将其映射到名为 `<record>` 的 Pascal 记录TPoint,并将 FFI 函数链接到 `<iterface>`,就像链接到TPointRust 结构体一样。
通过这种方式,将数据结构和函数一一对应,我们实现了两种语言之间的 100% 兼容性。

现在来看我们的lazrust.lpr

program lazrust;

uses RustLib;

var
  P: TPoint;
  P2: TPoint;

begin
  P.x := 10;
  P.y := 20;

  P2 := move_point(P, 3, -2);

  Writeln('Original: (', P.x, ', ', P.y, ')');
  Writeln('Moved:    (', P2.x, ', ', P2.y, ')');
end.

上面的代码很简单;实际上,在 Pascal 中使用起来非常自然。

编译完成后(SHIFT+F9),我们可以在命令行启动程序,并看到我们确实能够将 Pascal 记录作为结构体传递给 Rust,并得到映射到记录上的 Rust 结构体。

./lazrust

Original: (10, 20)
Moved:    (13, 18)

您可以在lazrust3/中查看代码。

处理字符串和内存

我们的第三个例子更加复杂,因为它涉及从 Rust 和 Pascal 两方面管理字符串和内存,以避免内存泄漏和不安全行为。

我们从 Rust 开始,创建一个结构体和一个操作该结构体的函数(接受该结构体作为参数,并返回修改后的结构体):

use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};

#[repr(C)]
pub struct Person {
    name: *mut c_char,
    office: *mut c_char,
    phone: *mut c_char,
    age: c_int,
}

/// Utility to convert &str to *mut c_char.
fn make_cstring(s: &str) -> *mut c_char {
    CString::new(s).unwrap().into_raw()
}

/// Utility to convert *mut c_char to Rust String.
unsafe fn to_rust_string(ptr: *mut c_char) -> String {
    if ptr.is_null() {
        String::new()
    } else {
        unsafe { CStr::from_ptr(ptr).to_string_lossy().to_string() }
    }
}

/// Free CStrings.
#[unsafe(no_mangle)]
pub extern "C" fn free_cstring(s: *mut c_char) {
    if s.is_null() {
        return;
    }
    unsafe {
        drop(CString::from_raw(s));
    }
}


#[unsafe(no_mangle)]
pub extern "C" fn verify_person(p: Person) -> Person {
    unsafe {
        let name = to_rust_string(p.name);
        let office = to_rust_string(p.office);
        let phone = to_rust_string(p.phone);

        // Add "(verified)" to the name
        let new_name = format!("{} {}", name, "(verified)");

        Person {
            name: make_cstring(&new_name),
            office: make_cstring(&office),
            phone: make_cstring(&phone),
            age: p.age,
        }
    }
}

除了Person结构体和verify_person()函数之外,上面的代码中还包含两个实用工具(一个用于将 CString 转换*mut c_char为 Rust 字符串,另一个用于从 CString 对象创建 CString &str)。
此外,我们还可以看到一个用于释放 CString 的函数:我们将从 Pascal 端调用它,以便 Rust 能够正确释放已分配的 CString,从而避免内存泄漏。

了解了 Rust 部分之后,我们来看看 Pascal 部分。我们像往常一样创建一个项目,并将 Rust 库映射到常用的 Pascal 单元,但这次略有不同:

unit rustlib;

{$mode ObjFPC}{$H+}

interface

uses
  Classes, SysUtils;

type
  PPerson = ^TPerson;

  TPerson = record
    Name: pchar;
    office: pchar;
    phone: pchar;
    age: longint;
  end;

function verify_person(p: TPerson): TPerson; cdecl; external 'librustlaz';
function free_cstring(s: pchar): longint; cdecl; external 'librustlaz';

function NewCString(const S: string): pchar;

implementation

function NewCString(const S: string): pchar;
var
  Len: SizeInt;
begin
  Len := Length(S) + 1; // include null terminator
  Result := StrAlloc(Len);
  StrPLCopy(Result, S, Len);
end;

end.

如您所见,除了将Person结构体映射到TPerson记录,以及公开 `and`verify_person()和 ` free_cstring()functions` 函数之外,本单元还包含一个实用工具:`or` 函数NewCString()
该实用工具会分配字符串所需的内存并将其复制到调用方:我们将使用它来初始化结构体中的 CString,以便将其传递给 Rust。

让我们来看看程序的实际代码:

program lazrust;

uses rustlib, SysUtils;

var
  P, P2: TPerson;

begin

  P.name := NewCString('John Doe');
  P.office := NewCString('IT');
  P.phone := NewCString('555-0101');
  P.age := 28;

  P2 := verify_person(P);

  Writeln('Rust returned:');
  Writeln('  name:   ', P2.name);
  Writeln('  office: ', P2.office);
  Writeln('  phone:  ', P2.phone);
  Writeln('  age:    ', P2.age);

  // Free Rust-allocated strings
  free_cstring(P2.name);
  free_cstring(P2.office);
  free_cstring(P2.phone);

  // Free Pascal-allocated strings
  StrDispose(P.name);
  StrDispose(P.office);
  StrDispose(P.phone);

end.

如您所见,我们在 Pascal 记录中分配字符串,以便传递给 Rust(我们使用上面创建的实用程序来完成此操作)。完成后,我们使用 Rust 的 expose 函数free_cstring()来释放 Rust 端已分配的内存;但是,我们也需要在 Pascal 端释放内存:这同样可能导致内存泄漏。

诚然,我们有很多清理工作要做,这是因为我们既不能使用完全由 Rust 管理的字符串,也不能使用完全由 Pascal 管理的对应字符串。由于我们正在研究这两种语言的互操作性,因此我们必须自行清理已使用的资源,而不是依赖于托管类型。

除了卫生方面的一些小麻烦(这使得代码稍微冗长一些)之外,两种语言之间的互操作性似乎真的很好。

以上代码位于lazrust4/文件夹中。

结论

总而言之,Rust 和 Free Pascal 之间的接口非常流畅。面对改造整个遗留代码库的难题,令人欣慰的是,只需用现代且安全的 Rust 代码重写和扩展一些关键库,就能完成大量工作。这里的可能性是无限的。尤其值得一提的是,Lazarus 提供了一种简单且久经考验的创建 GUI 的方法:与其追求完全使用 Rust 编写代码库的不切实际的幻想,不如将一些关键功能委托给其他语言,这有时才是最明智的选择。


关注「索引目录」公众号,获取更多干货。


【声明】内容源于网络
0
0
索引目录
索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
内容 444
粉丝 0
索引目录 索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
总阅读544
粉丝0
内容444