C#からC++へ:プロジェクト変換を自動化した方法 – パート2

開発

C#からC++ のコードトランスレータの設計と開発は、CodePortingによって単独で行われました。これには多くの調査、複数のアプローチの適用、およびメモリモデルなどの異なるテストが必要でした。最終的に、2つのソリューションが選択されました。そのうちの1つは、現在Aspose製品のC++リリースで使用されています。

技術

次に、コードトランスレータで使用している技術について説明します。トランスレータはC#で書かれたコンソールアプリケーションであり、これによりtranslate-compile-testなどの一般的なシーケンスを実行するスクリプトに埋め込むことが容易です。また、同じ操作をボタンをクリックすることで実行できるGUIコンポーネントもあります。

構文解析は、古いトランスレータの世代ではNRefactoryライブラリによって、新しいトランスレータではRoslynによって実行されています。

トランスレータは、情報を収集し出力C++ コードを生成するためにいくつかのAST treeのウォークスルーを使用しています。C++ コードにはAST表現が作成されていないため、代わりに純粋なテキスト形式で出力コードを処理しています。

トランスレータを微調整するために追加の情報が必要な場合があります。この情報はオプションと属性を介して渡されます。オプションはプロジェクト全体に適用されます。通常、クラスのエクスポートマクロ名を指定するためや、コードの解析時に使用されるC#の条件付きシンボルを指定するために使用されます。属性は型やエンティティに適用され、それらに特定の情報を提供します。たとえば、どのクラスメンバーが翻訳されたコードでconstまたはmutable修飾子を必要とするか、どのエンティティを翻訳から除外するかを示します。

C#のクラスと構造はC++ のクラスに変換されます。メンバーとソースコードは最も近いアナログに変換されます。ジェネリックな型とメソッドはC++ のテンプレートにマップされます。C#の参照はスマートポインタ(共有または弱い)に変換されます。参照クラスはライブラリで定義されています。コードトランスレータの他の内部詳細については、別の記事で説明します。

だから、C#からC++へのプロジェクトの翻訳は、.NETライブラリの代わりに私たちのライブラリに依存しています:

C# to C++

Cmakeを使用して、コードトランスレータライブラリと翻訳されたプロジェクトをビルドしています。現在、VS 2017および2019(Windows)、GCC、およびClang(Linux)コンパイラをサポートしています。

既に述べたように、私たちの.NET実装のほとんどは、以下のサードパーティライブラリの薄いアダプターです:

  • Skia:グラフィックスサポート。
  • Botan:暗号化機能。
  • ICU:文字列、コードページ、およびカルチャーサポート。
  • Libxml2:XML操作。
  • PCRE2:正規表現サポート。
  • zlib:圧縮機能。
  • Boost:さまざまな目的のためのライブラリ。
  • その他のいくつかのライブラリ。

トランスレータとライブラリの両方には多くのテストがあります。ライブラリのテストはGoogleTestフレームワークを使用しています。トランスレータのテストは主にNUnit/xUnitで書かれており、次のことを確認しています:

  • トランスレータの出力が特定の入力データに対して正確であること。
  • 翻訳されたプログラムの出力が正確であること。
  • 入力プロジェクトのNUnit/xUnitテストがGoogleTestに翻訳されてパスすること。
  • 翻訳されたプロジェクトのAPIがC++で正常に動作すること。
  • トランスレータのオプションと属性が期待どおりに動作すること。

バージョン管理システムとしてGitLabを使用しており、CIにはJenkinsを利用しています。翻訳された製品はNuGetパッケージとダウンロード可能なアーカイブとして提供されています。

問題点

このプロジェクトを進行する中で、さまざまな問題に直面しました。その中には予想されたものもあれば、途中で明らかになったものもあります:

  1. .NETとC++の型システムの違い
    C++にはObject型の代替がなく、ほとんどのライブラリクラスにRTTIがありません。これにより、.NETの型をSTLの型にマッピングすることは不可能です。
  2. 翻訳アルゴリズムは複雑です。
    翻訳されたコードで多くの微妙なニュアンスを明らかにする必要があります。たとえば、C#ではメソッドの引数の計算順序が定義されていますが、C++ではUB(未定義動作)があります。
  3. トラブルシューティングは難しいです。
    翻訳されたコードのデバッグには特定のスキルが必要です。上記で説明したようなニュアンスは、プログラムの動作に重大な影響を与え、説明しづらいエラーを生み出すことがあります。一方で、それらは簡単に隠れたバグに変わり、長い間残る可能性があります。
  4. メモリ管理システムは異なります。
    C++にはガベージコレクションがないため、翻訳されたコードが元のコードと同様に動作するためにはより多くのリソースが必要です。
  5. C#開発者には規律が必要です。
    C#開発者はコードの翻訳プロセスによって引き起こされる制限に慣れる必要があります。その制限の理由は次のとおりです:
    • 言語バージョンは、トランスレータ構文解析器でサポートされる必要があります。
    • トランスレータでサポートされていないコード構造は禁止されています(例:yield)。
    • コードスタイルは、翻訳されたコードの構造に制限されています(例:各参照フィールドは弱参照または共有参照である必要がありますが、任意のC#コードでは必ずしもそうではありません)。
    • C++ 言語は独自の制限を課しています(例:C#では静的変数はすべての前景スレッドが終了する前に削除されませんが、C++ではそうではありません)。
  6. 多くの作業量が必要です。
    当社の製品で使用されている.NETライブラリのサブセットは十分に大きく、すべてのクラスとメソッドを実装するのに多くの時間がかかります。
  7. 開発者に対する特別な要件
    複雑なプラットフォームの内部に深く入り込む必要があり、2つ以上のプログラミング言語で作業することは利用可能な候補者の数を制限します。一方で、コンパイラ理論やその他のエキゾチックな分野に興味を持つ開発者はプロジェクトに簡単に適応できます。
  8. システムの脆弱性
    トランスレータのテストには数千ものテストと数百万行ものコードがありますが、時々、1つのプロジェクトのコンパイル修正のために行った変更が別のプロジェクトで壊れる問題に直面します。たとえば、プロジェクト内の稀な構文構造や特定のコードスタイルでこれが起こる可能性があります。
  9. 高い参入障壁
    コードトランスレータプロジェクトのほとんどのタスクは深い分析を必要とします。多くのサブシステムとシナリオがあるため、新しいタスクごとにプロジェクトの新しい側面に慣れる必要があります。
  10. 知的財産保護の問題
    C#コードを効果的に難読化するための準備が整っている一方で、C++ではクラスヘッダーに多くの情報が保存されています。さらに、一部の定義は後戻りなしに公開ヘッダーから削除できません。ジェネリッククラスとメソッドをテンプレートにマッピングすることは別の脆弱性を作成します。アルゴリズムが明らかになるためです。

それでも、コードトランスレータプロジェクトは技術的な観点から非常に興味深く、学術的な複雑さは私たちに常に新しいことを学ばせてくれます。

結論

コードトランスレータプロジェクトに取り組んでいる間に、コード翻訳の興味深い学術的課題を解決するシステムを実装することに成功しました。Asposeライブラリの月次リリースを、それらが動作するはずの言語で行いました。

コードトランスレータに関するさらなる記事を公開する予定です。次の記事では、具体的なC#の構造がC++の構造にどのようにマッピングされるかを詳しく説明します。別の記事ではメモリ管理モデルについて説明します。

読者からの質問にできる限りお答えしようと思います。コードトランスレータ開発の他の側面に興味を持つ読者がいれば、それについてさらに記事を書くことを検討します。

関連記事