原文

各编译语言相同概念

1,按可重用函数拆分代码. 2,由源码中的函数名生成的串来标识函数.如,g++为void foo()生成_Z3foov的标识.此串总是是可重现的;如,Linux上的Clang和GCC都遵循ItaniumC++ABI约定来装饰函数名.

3,在内存中的特定位置存储该函数的所有参数,然后用调用或等效指令把控制权移动到函数来调用函数. 如,要调用前面的void foo(),编译器会转换C++的foo();语句为汇编的调用(call) _Z3foov.然后,汇编用适当的操作码替换调用,并用_Z3foov标识的第一条指令位置替换_Z3foov.

4,(如果有)在特定位置存储函数返回值,然后使用ret指令或等效指令返回. 5,类和结构可当作原语类型集合(尽管有些类确实有虚表). 6,类方法只是另一个类对象指针为第一个参数的函数.即,这样写时:

class Foo

{

void foo(int bar);

int baz;

};

翻译为:

class Foo

{

int baz;

};

void foo(Foo *this, int bar);

既然每种编译语言都用相同概念编译,为什么它们不能交互呢? 我想举一例来说明要实现的目标:

//文件:`main.cpp`

#include "rustmodule.h"

//或在理想的`C++20`世界中:

//import rustmodule;

int main()

{

foo();

return 0;

}

//-----------

//文件:`rustmodule.h`

#pragma once

//这是在`Rust`中定义的

void foo();

//-----------

//文件:`rustmodule.rs`

pub fn foo() {

println!("Hello from Rust");

}

想可编译这些文件,并得到一个从Rust打印Hello到stdout的可执行文件.

现在看看为什么不能开箱即用.

装饰名,数据布局和标准库

最明显原因是:语法.C++编译器不理解Rust,Rust编译器也不理解C++. 因此,A语言都无法分辨出B语言提供了哪些函数或类.

现在,你也许会说:"但是,如果我使用C++.h文件来导出函数和类到其他.cpp文件,我当然也可以制作一个.h文件,来告诉C++有个Rust的fn foo()函数在那里!

但还有些细节. 互操作性的第一个主要障碍是装饰名.你当然可创建一个带void foo();前向声明的.h文件,但C++编译器会找1个叫_Z3foov的符号,而Rust编译器会装饰fn foo()为_ZN10rustmodule3foo17hdf3dc6f68b54be51E. 开始时是可以编译C++代码的,但是一旦到达链接阶段,链接器就无法找到_Z3foov,因为它不存在.

显然,需要在一侧或另一侧改变行为方式.

第二个主要障碍是数据布局.总之,不同编译器可能会在内存中不同位置,放置字段来声明相同结构字段,以按不同方式处理声明.

第三个也是最后的障碍是标准库.如果要返回std::string的C++函数,Rust无法理解它.相反,要实现某种转换C++串为Rust串的转换器.

同样,除非转换RustVec对象为C++理解的内容,否则,无法在C++中使用它.

看看如何解决第一个装饰名问题.

extern"C"及为什么它很糟糕

简单方法是使用几乎每种语言都有的外部"C"功能:

//文件:`main.cpp`

#include "rustmodule.h"

//或在理想的`C++20`世界中:

//import rustmodule;

int main()

{

foo();

return 0;

}

//-----------

//文件:`rustmodule.h`

#pragma once

extern "C" void foo();

//-----------

//文件:`rustmodule.rs`

#[no_mangle]

pub extern "C" fn foo() {

println!("Hello from Rust");

}

(假设链接了所有正确的标准库),这会编译和运行!但为什么extern"C"很糟糕?好吧,用extern"C",你放弃了:

函数重载

类方法

模板

我想要可直接探测这些功能且人类可读的包装器! 此外,我不想更改现有源码,即必须去掉丑陋的#[no_mangle]pub extern"C"!

用D

D是一个自2001年以来一直存在的语言.虽然它与C++源码不兼容,但它类似C++.我个人喜欢D的直观语法和强大的功能,但对,把Rust和C++粘合在一起中,D脱颖而出有两个原因:extern(C++)和pragma(mangle,"foo").

使用extern(C++),可告诉D对符号使用C++装饰名.因此,编译以下代码:

//文件:`FOO.cpp`

#include

void bar();

void foo()

{

std::cout << "Hello from C++\n";

bar();

}

//-----------

//文件:`main.d`

import std.stdio;

extern(C++) void foo();

extern(C++) void bar()

{

writeln("Hello from D");

}

void main()

{

foo();

}

然而,更好了:现在可用pragma(mangle,"foo")手动覆盖想要的名字!因此,编译以下代码:

//文件:`main.d`

import std.stdio;

pragma(mangle, "_ZN10rustmodule3foo17h18576425cfc60609E") void foo();

pragma(mangle, "bar_d_function") void bar()

{

writeln("Hello from D");

}

void main()

{

foo();

}

//-----------

//文件:`rustmodule.rs`

pub fn foo() {

println!("Hello from Rust");

unsafe {

bar();

}

}

extern {

#[link_name = "bar_d_function"] fn bar();

}

使用pragma(mangle,"foo"),不仅可告诉D,Rust是如何装饰函数名的,还可创建一个Rust可见的函数! 为什么必须告诉Rust来覆盖bar()的装饰.这是因为Rust显然不会对在extern块中的bar()应用装饰名;

测试中,甚至按外部"Rust"标记也没用.

为什么不用Rust的装饰名覆盖而用D.好吧,Rust只允许按extern函数的前向声明覆盖混杂,所以在Rust中,不能按C++函数定义你的函数.

D作为胶水

现在,可用D将基本示例粘合在一起:

//文件:`main.cpp`

#include "rustmodule.h"

//或在理想的`C++20`世界中:

//import rustmodule;

int main()

{

foo();

return 0;

}

//-----------

//文件:`rustmodule.h`

#pragma once

//这是在`Rust`中

void foo();

//-----------

//文件:`rustmodule.rs`

pub fn foo() {

println!("Hello from Rust");

}

//-----------

//文件:`glue.d`

@nogc:

//这是`Rust`函数.

pragma(mangle, "_ZN10rustmodule3foo17h18576425cfc60609E") void foo_from_rust();

//它按别名向`C++`公开.

extern(C++) void foo()

{

foo_from_rust();

}

此例中,当main()从C++调用foo()时,它是在调用一个调用Rust函数的D函数.它有点丑陋,但它可能,让C++和Rust代码都不变就工作的代码.

自动化胶水

不过,没人愿意编写一个巨大的D文件来组C++和Rust件粘合在一起.事实上,甚至没有人愿意手写C++头文件. 因此,我创建了叫polyglot的概念验证工具,它可扫描C++代码并生成包装器以供Rust和D使用. 下一期,探讨语言如何克服互操作性的其他两个主要障碍.

推荐文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: