原文
各編譯語言相同概念
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 <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")
手動覆蓋
想要的名字!因此,編譯以下代碼:
//文件:`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
使用.
下一期
,探討語言如何克服互操作性
的其他兩個主要障礙
.