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

Asposeの製品は、人気のあるフォーマットのプロトコルやファイルを操作できるもので、顧客に高く評価されています。これらの製品のほとんどは元々.NET向けに開発されました。同時に、ファイルフォーマットのビジネスアプリケーションは異なる環境で実行されています。この記事では、Asposeの製品をC++ 向けにリリースするために、C#からのコード変換のためのフレームワークを構築することで、.NETバージョンの機能をこれらの製品に保持する方法について説明します。

私たちは必要な基盤を自ら開発し、言語間のコード変換と.NETライブラリ関数のエミュレーションを実現しました。これにより、通常は学術的に考慮される問題を解決しました。これにより、毎月の.NET製品をC++ 言語向けにリリースし、各リリースのコードを対応するC#コードバージョンから取得できるようになりました。さらに、元のC#コードをカバーするテストも一緒に翻訳し、C++で特別に作成されたテストと同等の機能を監視できるようにしました。

事前の経緯

C#からC++ へのコードトランスレーターの成功は、CodePortingチームが自動化されたC#からJavaへのコード変換を設定する際に得た成功体験に基づいています。作成されたフレームワークは、C#クラスをJavaクラスに変換し、適切に.NETライブラリ呼び出しを置き換えていました。

フレームワークには異なるアプローチが考慮されました。ゼロから純粋なJavaバージョンを開発するには、あまりにも多くのリソースが必要でした。1つのオプションは、Javaコードから.NET環境への呼び出しをマーシャリングすることでしたが、これは将来サポートできるプログラミングプラットフォームのセットを制限することになります。当時、.NETはWindowsのみに存在していました。呼び出しのマーシャリングは、広く使用されるデータ型を持つ稀な呼び出しには便利ですが、多くのオブジェクトとカスタムデータ型を扱う際には圧倒的になります。

代わりに、既存のコードを新しいプラットフォームに完全に変換する方法を考えました。これは、コードの移行を毎月行い、同様の機能を備えたリリースの同期されたフローを生成する必要があるため、トピカルな問題でした。

この問題は2つの部分に分かれています:

  • トランスレータ — C#の構文をJavaの構文に変換するアプリケーションで、ターゲット言語のライブラリから適切な置換を行います。
  • ライブラリ — Javaに適切にマッピングできなかった.NETライブラリの一部をエミュレートするコンポーネントです。この作業を簡素化するために、利用可能なサードパーティのコンポーネントを使用できます。

以下の議論により、この計画が技術的に実現可能であることが確認されました:

  1. C# と Java の言語は、少なくとも型の構造やメモリ管理モデルに関して類似したイデオロギーを持っています。
  2. 私たちはライブラリの翻訳のみを行い、GUIを別のプラットフォームに移す必要はありませんでした。
  3. 翻訳されたライブラリは主にビジネスロジックと低レベルのファイル操作を含んでおり、最も複雑な依存関係は System.NetSystem.Drawing でした。
  4. ライブラリは最初から、.NET Framework、.NET Standard、さらにはXamarinを含む幅広い.NETバージョンで動作するように開発されていました。したがって、マイナーなプラットフォームの違いは無視できました。

C#からJavaへの変換についての詳細は省略しますが、これには専用の記事が必要です。要約すると、C#製品をJavaに変換することは、コードトランスレーターのおかげで会社の定期的な実践となっていました。このトランスレーターは、単純なルールベースのテキスト変換ツールから、ソースコードのAST表現と連携する複雑なコードジェネレーターに成長しました。

C#からJavaへのトランスレーターの成功は、Java市場への参入を支援し、同じシナリオでC++向けのリリースを開始するきっかけとなりました。

要件

C++ バージョンの製品をリリースできるようにするために、C#コードをC++に変換し、コンパイル、テストし、顧客に送信できるフレームワークを作成する必要がありました。コードはいくつかの百万行に及ぶライブラリのセットで構成されていました。コードトランスレーターのライブラリコンポーネントは、次の点をカバーする必要がありました:

  1. 翻訳されたコードのために.NET環境をエミュレートする。
  2. C++に適した形式に翻訳されたコード:型の構造、メモリ管理など。
  3. .NETのパラダイムに慣れていない開発者がコードを簡単に使用できるように、翻訳されたC#のコードスタイルをC++スタイルに変更する。

多くの読者は、なぜ既存のソリューション(Monoプロジェクトなど)を使用しなかったのかと尋ねるでしょう。以下の理由があります:

  1. これは第2および第3の要件を満たしませんでした。
  2. MonoはC#で実装されており、そのランタイムに依存しています。
  3. サードパーティのコードを私たちのニーズに適応させる(API、型システム、メモリ管理モデル、最適化など)は、私たちのソリューションを作成するのと同じくらいの時間がかかるでしょう。
  4. 私たちの製品は完全な.NET実装を必要としません。ただし、完全な実装があれば、どのメソッドやクラスが必要であり、どれが不要であるかを区別するのは難しいでしょう。私たちは使わない機能を修正するために多くの時間を費やすことになります。

理論的には、既存のソリューションをC++に変換するためにトランスレーターを使用できるはずです。ただし、システムライブラリなしで翻訳されたコードをデバッグすることは不可能です。さらに、最適化の問題は、翻訳された製品のコードよりもさらに重要になります。なぜなら、システムライブラリの呼び出しはボトルネックになりやすいからです。

コードトランスレーターの要件に戻りましょう。.NETの型をSTLの型にマッピングできないため、カスタムライブラリ型を代替として使用することにしました。このライブラリは、Javaと同様の.NETライクなAPIを介してサードパーティライブラリの機能を利用できるようにするアダプターのセットとして開発されました。

既存のAPIを使用してライブラリを翻訳している間、翻訳されたコードの重要な要件は、顧客のアプリケーション内で実行できることでした。したがって、翻訳されたコードにガベージコレクションを使用することはできませんでした。なぜなら、それはアプリケーション全体をカバーしてしまうからです。代わりに、C++開発者にとってメモリ管理モデルが明確である必要がありました。そのため、スマートポインタを使用することが選択されました。メモリモデルの変更に成功した方法については、別の記事で説明します。

CodePortingは強力なテストカバレッジの文化を持っており、C#コード用に書かれたテストをC++製品に適用できる能力は、トラブルシューティングを大幅に簡素化します。コードトランスレーターはテストも翻訳できる必要がありました。

最初に、翻訳されたJavaコードの手動修正は、開発と製品リリースを加速させることができました。しかし、長期的には、各バージョンをリリースするために必要な費用が著しく増加しました。なぜなら、翻訳エラーごとに修正する必要があったからです。毎回ゼロから変換する代わりに、2つの連続するC#コードのリビジョンに対してトランスレーターの出力の差分として計算されたパッチをJavaコードに適用することで、これを管理できる可能性がありました。それにもかかわらず、C++フレームワークの修正を結果のコードの修正よりも優先することが決定され、翻訳エラーは一度だけ修正されました。

関連記事