各编译语言相同概念
1,按可重用
函数拆分
代码.
2,由源码
中的函数名
生成的串来标识函数
.如,g++
为void foo()
生成_Z3foov
的标识.此串总是是可重现的;如,Linux
上的Clang
和GCC
都遵循ItaniumC++ABI
约定来装饰
函数名.
3,在内存
中的特定位置
存储该函数
的所有参数,然后用调用
或等效指令
把控制权移动
到函数来调用函数.
如,要调用前面的void foo()
,编译器会转换C++
的foo();
语句为汇编
的调用(call) _Z3foov
.然后,汇编
用适当的操作码
替换调用
,并用_Z3foov
标识的第一条指令
位置替换_Z3foov
.
4,(如果有)在特定位置
存储函数返回值
,然后使用ret
指令或等效指令返回
.
5,类和结构
可当作原语类型
集合(尽管有些类确实有虚表
).
6,类方法
只是另一个类对象
指针为第一个参数
的函数.即,这样写时:
cpp
class Foo
{
void foo(int bar);
int baz;
};
翻译为:
cpp
class Foo
{
int baz;
};
void foo(Foo *this, int bar);
既然每种编译
语言都用相同
概念编译,为什么它们不能交互呢?
我想举一例
来说明要实现的目标
:
cpp
//文件:`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"
功能:
cpp
//文件:`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++
装饰名.因此,编译
以下代码:
cpp
//文件:`FOO.cpp`
#include <iostream>
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")
手动覆盖
想要的名字!因此,编译以下代码:
cpp
//文件:`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将基本示例
粘合在一起:
cpp
//文件:`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
使用.
下一期
,探讨语言如何克服互操作性
的其他两个主要障碍
.