2312d,d语言来绑定C++和rust

2023-12-09 15:20
文章标签 语言 rust c++ 绑定 2312d

本文主要是介绍2312d,d语言来绑定C++和rust,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文

各编译语言相同概念

1,按可重用函数拆分代码.
2,由源码中的函数名生成的串来标识函数.如,g++void foo()生成_Z3foov的标识.此串总是是可重现的;如,Linux上的ClangGCC都遵循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打印Hellostdout的可执行文件.

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

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

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

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

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

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

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

第三个也是最后的障碍是标准库.如果要返回std::stringC++函数,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的直观语法和强大的功能,但对,把RustC++粘合在一起中,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++代码并生成包装器以供RustD使用.
下一期,探讨语言如何克服互操作性其他两个主要障碍.

这篇关于2312d,d语言来绑定C++和rust的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/474178

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表