28 12月 2024
私たちのフレームワーク CodePorting.Translator Cs2Cpp は、.NET プラットフォーム向けに開発されたライブラリを C++ でリリースすることを可能にします。本記事では、これら二つの言語のメモリモデルをどのように調整し、非管理環境で翻訳されたコードを正しく動作させることができたかについて説明します。
私たちが使用する スマートポインタ と、それらの実装を独自に開発する必要があった理由について学びます。さらに、オブジェクトのライフタイム管理の観点から C# コードをポーティングする準備プロセスや、直面した課題のいくつか、および作業で使用する特定の診断方法についても説明します。
C# コードは 管理された環境 で ガベージコレクション を使用して実行されます。翻訳の目的では、主に C# プログラマーが、C++ 開発者とは異なり、もはや使用されていないヒープメモリをシステムに返す必要がないことを意味します。これは、GC(ガベージコレクタ)と呼ばれる CLR 環境のコンポーネントによって実行されます。GC はプログラムでまだ使用されているオブジェクトを定期的にチェックし、もはやアクティブな参照がないオブジェクトをクリーンアップします。
アクティブな参照とは以下のようなものです:
ガベージコレクタのアルゴリズムは通常、スタックおよび静的フィールドに位置する参照からオブジェクトグラフをトラバースし、前の段階で到達しなかったものをすべて削除することとして説明されます。GC 操作中に参照グラフが無効になるのを防ぐために、ストップ・ザ・ワールド メカニズムが実装されています。この説明は簡略化されているかもしれませんが、私たちの目的には十分です。
スマートポインタとは異なり、ガベージコレクションのアプローチはクロスリファレンスやサイクリックリファレンスの問題がありません。二つのオブジェクトが互いに参照している場合、あるいは複数の中間オブジェクトを介している場合でも、GC はアイソレーションアイランドと呼ばれるグループ全体がもはやアクティブな参照を持たないときにそれらを削除することを妨げません。したがって、C# プログラマーは任意のタイミングで任意の組み合わせでオブジェクトをリンクすることに何の偏見も持っていません。
C# のデータ型は 参照型 と 値型 に分かれます。値型のインスタンスは常にスタック、静的メモリ、または構造体やオブジェクトのフィールドに直接配置されます。それに対して、参照型のインスタンスは常にヒープに作成され、スタック、静的メモリ、およびフィールドにはその参照(アドレス)のみが保存されます。値型には構造体、基本的な算術値、参照が含まれます。参照型にはクラスおよび、歴史的に、デリゲートが含まれます。このルールの唯一の例外は、ボクシング の場合で、構造体や他の値型が特定のコンテキストで使用されるためにヒープにコピーされるときです。
オブジェクトの破棄のタイミングは定義されておらず、言語は最後のアクティブな参照が削除される前にそれが発生しないことだけを保証します。変換されたコードでは、最後のアクティブな参照が削除された直後にオブジェクトが破棄された場合でも、プログラムの動作は妨げられません。
もう一つの重要な点は、C# のジェネリック型およびメソッドのサポートに関連しています。C# は一度ジェネリックを記述し、その後、参照型および値型パラメータの両方で使用することを可能にします。後に示すように、この点は重要であることがわかります。
C# の型を C++ の型にどのようにマッピングするかについて少し説明します。CodePorting.Translator Cs2Cpp フレームワークはアプリケーションではなくライブラリの移植を目的としているため、元の .NET プロジェクトの API をできるだけ正確に再現することが重要な要件です。したがって、C# のクラス、インターフェイス、および構造体を対応する基本型を継承する C++ クラスに変換します。
例えば、次のコードを考えてみましょう:
interface I1 {}
interface I2 {}
interface I3 : I2 {}
class A {}
class B : A, I1 {}
class C : B, I2 {}
class D : C, I3 {}
class Generic<T> { public T value; }
struct S {}
これを次のように変換します:
class I1 : public virtual System::Object {};
class I2 : public virtual System::Object {};
class I3 : public virtual I2 {};
class A : public virtual System::Object {};
class B : public A, public virtual I1 {};
class C : public B, public virtual I2 {};
class D : public C, public virtual I3 {};
template <typename T> class Generic { public: T value; };
class S : public System::Object {};
System::Object
クラスは、変換されたプロジェクトの外部にある翻訳サポートライブラリで宣言されているシステムクラスです。クラスやインターフェイスは ダイアモンド問題 を避けるために仮想的に継承されます。変換された C++ コードの構造体は System::Object
を継承しますが、C# では System.ValueType
を介して継承しますが、最適化のためにこの余分な継承は削除されます。ジェネリック型およびメソッドは、それぞれテンプレートクラスおよびメソッドに変換されます。
変換された型のインスタンスには、C# で提供されていたのと同じ保証が適用されなければなりません。クラスのインスタンスはヒープ上に作成され、その寿命はアクティブな参照の寿命によって決定されなければなりません。構造体のインスタンスはボクシングの場合を除いてスタック上に作成されなければなりません。デリゲートは特別な比較的単純なケースであり、この記事の範囲外です。
C# のメモリ管理モデルが C++ へのコード変換プロセスにどのように影響するかを検討しました。次の記事では、ヒープ割り当てされたオブジェクトの寿命を管理する方法の決定方法とこのアプローチがどのように実装されたかについて説明します。