次の方法で共有


C# で既存のコードを使用する方法

Eric Gunnerson
Microsoft Corporation

July 14, 2002

6 月下旬、ドイツで内輪の結婚式に出席し、イタリアで 1 週間過ごす機会がありました。 パスタとプールの魅力がこのコラムの魅力に勝っていたので、 旅行中はあまり仕事をしませんでした。 しかし、次のコラムで取り上げるべきことについて少し考えました。

C# ユーザーがアプリケーションの一部として既存のコードを使用したがることは、よくあることです。 その既存のコードは、時にはフレームワークがラップしていない Win32® クラスであったり、 時には既存の COM オブジェクトや C++ クラスであったりします。 現行のドキュメントは、既存のコードの使用方法、 またはさまざまな選択肢からの選択方法についてあまり指導していません。 そのため、数回のコラムでこのテーマについて説明することに決めました。 今回のコラムは選択肢についての概要です。 今後のコラムでは、いくつかの例とガイドラインを使用しながら各選択肢をより詳しく取り上げていきます。

コードを使用する 3 つの方法

既存のコードを使用する基本的な方法は 3 つあります。

  1. 組み込みの .NET ランタイム相互運用機能を使用して、既存のコードと直接対話できます。
  2. C++ のマネージ拡張を使用してコードをラップできます。
  3. .NET 言語でコードを書き直すことができます。

これらの選択肢には、それぞれ異なる利点と欠点があります。

また、既存のコードは .NET 環境には不適切なインターフェイスを保持することがよくあります。 そのため、コードにより適切なインターフェイスを付加して、 既存のコードを使い易くする追加作業が必要な場合があります。

話を始める前に、いくつかの用語について説明します。

  • "マネージ コード" または ".NET コード" は、.NET コンパイラが生成したコードで、 .NET ランタイムが実行します。
  • "アンマネージ コード" または "ネイティブ コード" は、 以前の (つまり、.NET 以外の) コンパイラが生成し、実行するために .NET ランタイムを必要としないコードです。

ランタイム相互運用機能

.NET ランタイムは 2 つの異なる種類の相互運用機能をサポートします。 1 つは、Platform Invoke または DLLImport として知られており、 DLL に含まれる "C スタイル" コードを使用するためにデザインされています。 これは主に Win32 関数と対話するために使用しますが、他のライブラリと共に使用することもできます。 C++ クラスでは機能しません。

2 つ目の種類の相互運用機能は、 COM オブジェクトとの相互作用を処理します。 それは .NET 言語から既存の COM オブジェクトを使用すること、 および .NET 言語を使用して新しい COM オブジェクトを記述することの両方をサポートします。

ランタイムの相互運用機能は、 API が単純な場合に最も容易な手段ですが、 物事が複雑になるにつれて作業が困難になります。 たとえば、 COM オブジェクトを使用しているときは、 ランタイムはオートメーション互換の COM オブジェクトを使用して適切に機能します。 このようなオブジェクトのインターフェイスは特定の型のサブセットのみを使用するので、 ランタイムがこのような型を扱っているときは適切に機能できます。

P/Invoke を使用して直接 DLL を呼び出すときも、同じように考えることができます。 関数が単純なインターフェイスを持っていれば、 .NET 言語からその関数を呼び出すことは非常に容易です。

しかし、物事が複雑になるにつれて、ランタイム相互運用機能の使用が困難になります。 ランタイムが特定のシナリオをサポートするかどうかが常に明確になるわけではないので、 実質的なデバッグ機能は存在しません。 つまり、物事が機能するまで見届ける必要があります。 ランタイムがそのシナリオを完全にサポートしない場合、 独自に困難な作業の一部を行う必要があります。

また、パフォーマンスが問題になることもあります。 .NET コードから既存のアンマネージ コードに処理が遷移するたびに、 ある程度のオーバーヘッドが生じます。 使用しているインターフェイスが、情報の各部分を取得するために個別の呼び出しを必要とする場合、 オーバーヘッドが非常に大きくなることがあります。

ガイドライン

  1. ランタイム相互運用機能が容易に機能する場合、それを使用して開始します。
  2. 既存のコードに対して多くの呼び出しを行う場合、 パフォーマンスを監視して、ボトルネックが存在するかどうかを確認します。 ボトルネックが存在する場合、代わりに C++ でそれをラップすることを検討します。
  3. 既存のコードが COM オブジェクトの場合、 COM を使用している理由と長期計画を理解します。 .NET 以前は、C++ と Visual Basic 間の相互運用手法として COM を使用するのが一般的でした。 つまり、C++ で COM オブジェクトを記述し、 Visual Basic でその COM オブジェクトを使用します。 .NET 環境には相互運用機能を使用するのにより適した方法があるので、 COM サポートを使用する必要性が他に存在しない場合は、 代わりに .NET メソッドを使用する方が適切な場合が一般的です。 このシナリオでは、C++で .NET インターフェイスを記述し、Visual Basic または C# で直接利用することになります。
  4. 物事を機能させるのに問題が数多く発生する場合は、 おそらく代わりに C++ を使用する必要があるでしょう。

C++ でのラッピング

2 番目の選択肢は、 C++ のマネージ拡張を使用する .NET ラッパーを記述することです。 以下にいくつか異なるシナリオを示します。

  1. 一部の Win32 呼び出しの上位に .NET ラッパーを記述します。
  2. COM オブジェクトの上位に .NET ラッパーを記述します。
  3. C++ クラスの上位に .NET ラッパーを記述します。
  4. .NET インターフェイスを持つように C++ クラスを変更します。

最初の 3 つのシナリオはすべて、 C++ の強力でレベルの低レベルの制御を使用することに関連しています。 面倒で複雑なシナリオまたは API を受け取り、単純な .NET インターフェイスを提供します。 これらの API は C++ から使用するようにデザインされているので、 API の呼び出しは比較的単純で、現行の内容を簡単にデバッグできます。

そのため .NET の構成要素と、 既存のコード内で使用している構成要素間での変換作業が残ります。 この場合、ランタイムが行う作業をユーザー独自に行う必要があるので、 ランタイム相互運用機能を使用する場合よりも多くの作業を伴います。 これが、単純なシナリオでランタイム相互運用機能を使用することが好まれる理由です。 ただし、複雑なシナリオでは、この方法を使用する方が簡単です。

パフォーマンス上の利点もあります。 煩雑なインターフェイスを簡単なインターフェイスに変換できる場合は、 .NET コードからアンマネージ コードに呼び出しがほとんど行われません。 その結果、オーバーヘッドが減少します。

4 番目のシナリオは、 ラッパーを記述するというよりは、 むしろ C++ クラスが .NET クラスになるように C++ を変更することに関係しています。 .NET 環境とアンマネージ環境の両方からコードを使用する必要がある場合、 アンマネージ環境でコードを管理し、 .NET で使用するためにそのコードの上位にラッパーを記述することが最善です。 そのコードが .NET 環境でのみ使用される場合は、 C++ クラスを変更することで相互運用機能のオーバーヘッドを回避し、 C++ クラスを .NET 環境用にコンパイルできます。

ガイドライン

  1. C++ だという理由だけで、この選択肢を回避しないでください。 いくつかのシナリオでは、ランタイム相互運用機能よりはるかに簡単なので、 それが機能することを保証できるでしょう。
  2. コードを .NET に単に切り替えることができる場所を検討します。

書き直し

最後の選択肢は、コードを .NET 言語に書き直すこと、または移植することです。

このアプローチには、いくつか大きな利点があります。 すべてのコードが 1 つの言語で記述されるので、 複数のモデル、相互運用機能のパフォーマンス問題、または悩みの種である COM を扱う必要がありません。 また、.NET にすることで生産性が向上し、 堅牢な機能強化が得られます。

一方、いくつか欠点もあります。 これは最も作業負荷が高い選択肢になりやすく、 結果的に 2 つのバージョンの同じコードを持つことになります。 1 つは .NET ユーザー用で、もう 1 つはネイティブ コードのユーザー用です。 相互運用機能によるパフォーマンスの低下はありませんが、 .NET コードはネイティブ コードよりも実行速度が遅くなる可能性があります。 これは、ユーザーのアプリケーションでは受け入れられないことかもしれません。

最後に、使用しているコンポーネントを所有していないときは、 これは選択肢にはなりません。 Win32 関数を呼び出したり、Microsoft Word を起動する場合は、 相互運用機能を使用してそれを処理する必要があります。

助言

おそらく、最初はランタイム相互運用機能を使用して、 ユーザーの環境で .NET を使用する方法の調査を開始したいと考えるでしょう。 とりあえず作業を開始し、 どんな選択肢が機能するかを理解していくのが簡単な方法です。

選択肢を理解した後は、 時間をかけて長期方針を検討します。 今後ユーザーのビジネスが .NET に注目していくのであれば、 重要なコードを .NET 環境に移植することをより積極的に検討することになります。 依然として、ネイティブ環境と .NET 環境の両方でコードを使用する必要がある場合は、 その環境で適切に機能する選択肢を採用したいと考えるでしょう。

この概要が役立つことを願っています。 来月はランタイム相互運用機能の詳細について取り上げます。 その際に、関連するサンプル コードを少し用意することをお約束します。

その他の事項

Regular Expression Workbench

ここ数年間、正規表現を扱う仕事をしてきたので、 .NET サポートに大変満足しています。 正規表現の作成と使用を容易にするために、 Regular Expression Workbench と名付けたユーティリティを作成しました。 このワークベンチを使用して、以下のことが行えます。

  • 正規表現を記述し、テストします。
  • C# コードを作成し、正規表現を作成します。
  • C# コードに貼り付けます。
  • RegexOption を実行します。
  • 正規表現の上にマウス カーソルを移動して、 セクションが意味することを示すツール ヒントを表示します。

C# community site (英語) に資料があります。

Win32 Window クラス

.NET Framework がまだ対応していない Win32 の分野の 1 つが、window 関数です。 これらの関数は、他のアプリケーション ウィンドウを検索し、相互作用するのにとても役立ちます。

これらの関数にアクセスできるクラスを記述しました。 コミュニティ Web サイト (英語) にも記載されています。

C# コミュニティ サイト

今月、コミュニティ サイトに Ask a Language Designer が新しく追加されました。 https://www.gotdotnet.com/team/csharp (英語) で見つかります。

Eric Gunnerson は Visual C# チームのプログラム マネージャで、C# デザイン チームのメンバーです。 そして『A Programmer's Introduction to C#』 (英語) の著者でもあります。 彼は、8 インチのフロッピー ディスクが何であるかを知っているぐらい昔からプログラミングを行っています。さらに、かつては簡単にテープを装着できました。 彼は、この経歴を誰か読んだことがあるかどうか疑問に思っています。