バインディングを作成する人々が、C関数が呼び出すことができるコールバックを提供できるようにしたいというシナリオがたくさんあります。 現在の解決策は、いくつかの不特定の内部APIを使用してメッセージをRustコードに送り返すネイティブC関数を作成することです。 理想的には、Cコードを記述しないことです。
これは、Cコードから呼び出すことができるRustで関数を作成するための最小限のソリューションです。 要点は次のとおりです。1)さらに別の種類の関数宣言があります。2)この関数はRustコードから呼び出すことができません。3)その値は不透明な安全でないポインタと見なすことができます。4)スタックスイッチングマジックでベイクして適応します。 CABIからRustABIへ。
C-to-Rust(クラスト)関数の宣言:
crust fn callback(a: *whatever) {
}
C ABI関数への安全でないポインターの取得:
let callbackptr: *u8 = callback;
この目的のために特別にいくつかのタイプを定義することもできます。
それはほとんど簡単ですが、トランスは醜くなります。 トランスでは、基本的にネイティブmod関数の場合とは逆のことを行う必要があります。
これを実現するには、ランタイムをいくつかの方法で変更する必要があります。
ネイティブコードがC ++例外で巻き戻される保証がないため、Rustスタックに再入力した後に単純に例外をスローすることはできません。 Go言語は、このシナリオではすべてのネイティブフレームをスキップするだけで、途中ですべてがリークするようです。 代わりに、中止します。ユーザーが壊滅的な障害を回避したい場合は、Rustコールバックを使用してメッセージをディスパッチし、すぐに戻る必要があります。
Cスタックの処理方法を変更しないと、CコードからRustスタックを再入力した後、Rust関数がスケジューラーにコンテキストスイッチできるようにすることはできません。 私は2つの解決策を見ます:
1)Rustスタックに再び入った後の降伏は異なり、単にブロックします。 これを実行するタスクは、独自のスケジューラー(#1721)があることを確認する必要があります。
2)スケジューラーのスタックを使用してネイティブコードを実行する代わりに、タスクは各スケジューラーにあるプールからCスタックをチェックアウトします。 タスクがCスタックに再び入るたびに、タスクがすでにCスタックを持っているかどうかを確認して再利用します。そうでない場合は、スケジューラーに新しいスタックを要求します。 これにより、Rustコードは、スケジューラーを拘束することなく、常に正常に生成されます。
私は2番目のオプションを好みます。
#1508も参照してください
ここに飛び込んで申し訳ありませんが、C関数の呼び出しは_blazing_ fastのように、高速である必要があることを強調したいと思います。 Allegro、SDL、OpenglなどのCライブラリを使用してさびでゲームを書きたい場合、これは不可欠です。 そうしないと、C呼び出しが多いレンダリングコードでゲームの速度が低下し、許容できなくなります。 cgoを使用するデフォルトのGo言語コンパイラにはこのような問題があります。
したがって、Rust側の機能で実行できることを制限する可能性があるとしても、高速なソリューションをお勧めします。
また、「クラストfn」の代わりに「ネイティブfn」を使用するのは考えではないでしょうか、それとも別の計画された意味がありますか?
@beoranあなたはこれを後方に持っています。 CがRustを呼び出すことについて話しています。 RustからのC関数の呼び出しは、すでにかなり高速です(多少高速化できます)。
なるほど、分かりました。 さびからCを呼び出す速度を上げるのに役立つ方法はありますか?
@beoranは、私が言ったように、この問題とは直交しています...しかし、おそらくあなたができる最善のことは、パフォーマンスがどのように不十分であるかを示すベンチマークを作成することです。 :)
OK、Allegroのラッピングが十分に進んだら、錆びたRustから呼び出すオーバーヘッドとCから呼び出すオーバーヘッドを比較するためにそれを行います。この問題は今のところそのままにしておきます。ベンチマーク。
Rust-> C呼び出しが現在行っているように、C関数にRustスタックに直接書き込むことで、引数のコピーの1つを回避できますか?
shim構造体からrust関数の引数への引数の最終的なコピーが、インライン化によって削除されることを期待しています。 それがあなたが言及しているものであるかどうかはわかりませんが。
現在、C呼び出しは引数をRustスタックの構造体にコピーしてから、その構造体をCスタックにコピーすると思います。
@pcwalton Rust-> Cは、i386に固有であるため、現在Cスタックに直接書き込みません。 64ビットを機能させるために、パフォーマンスを犠牲にしても、特定の呼び出し規約に固有のコードを記述しなくてもよいようにしたかったのです。 (ただし、このような最適化は今では理にかなっていると思います---特に#1402が指摘しているように、LLVMは実際には呼び出し規約を完全に処理していません_とにかく_)
スタック切り替え関数を読み取った後、arg構造体はスタック間でまったくコピーされず、前のスタックへのポインターが新しいスタックで実行される関数に渡されるだけです。これは完全に理にかなっています。
arg構造体はコピーされませんが、shim関数はそこから値をロードし、それらを新しいスタックに再プッシュします。 引数値を文字通りターゲットスタックに直接書き込むために使用されていた古いコード。 これはi386では理にかなっていますが、x86_64では、スタックなどにどの値が入るかを判断するのがはるかに複雑です。
いくつかのテストの結果、クラスト関数が失敗しないことを保証することは非常に困難になることがわかりました。 現在起こっていることは、トップレベルのタスク(mainなど)が失敗すると、すべてのタスクが失敗するように指示されるため、コールバックがメッセージを送信しようとするとすぐに(またはメッセージの送信から戻ったときに)失敗する可能性があるということですランタイムを異常終了させます。
タスクがrustスタックに再入力されたら、強制終了要求を無視するようにrust_taskを変更できると思います。 イベントループを実装するタスクの場合、ランタイムが失敗していることを示すメッセージを探してモニターポートをのぞき、正常に終了する方法を見つけることができます。
それで、トップレベルのタスクが失敗すると、その子にエラーを伝播しますか? 私たちのエラー伝播モデルを理解していないと思います、私はそれが葉から来たと思いました。 Cスタックで実行されているコードは、教師なしタスクなどで実行できるはずのようです。
基本的には、「main」がカーネルによって監視されているかのように機能するため、mainが失敗すると、すべてが失敗します。
私はこれを完了と呼んでいます。 少しクリーンアップがあり、残りの問題について個別のバグを報告しました。
この復活を許してください、しかしこの問題はy-combinatorの部分と他のいくつかのサイトからリンクされています、そして私は最近それについて初心者に尋ねました、それで私はこの問題とその後の変更に従って、CからRustを呼び出すことに注意しますシンプルです:
#[no_mangle]
pub extern fn hello_rust() -> *const u8 {
"Hello, world!\0".as_ptr()
}
#include "stdio.h"
const char *hello_rust(void);
int main(void) {
printf("%.32s\n", hello_rust());
}
最も参考になるコメント
この復活を許してください、しかしこの問題はy-combinatorの部分と他のいくつかのサイトからリンクされています、そして私は最近それについて初心者に尋ねました、それで私はこの問題とその後の変更に従って、CからRustを呼び出すことに注意しますシンプルです: