振り返り:C# P/InvokeとRust FFIは安全ではない
本シリーズ前回の記事(複数開発言語ソフトウェアプロジェクトに潜む落とし穴)では、P/InvokeやRustのFFIといった技術を用いてC API関数とやり取りするソフトウェアプロジェクトにおける、潜在的な落とし穴やバグの原因についてご紹介しました。
例えば、関数シグネチャや型宣言の不一致は、以下のような問題を引き起こす可能性があります:
- クラッシュ:アプリケーションやアプライアンスが動作を停止し、使用不能になる
- データ損失:値が切り捨てられたり誤って解釈されたりし、誤った結果につながる
今回は、C#からP/Invokeを用いてC関数を実行するソフトウェアモジュールにおけるバグの具体例を解説します。同様の基本的なアプローチと潜在的な問題は、RustのFFIにも適用され、C以外の対象言語にも共通します。
前提とサンプルコード
C#で記述された制御ユニットと、C言語で記述された通信インターフェースを備えた組込みデバイスで構成されるシステムを想定します。
使用されているCインターフェースは以下の通りです。
// exports.h
#pragma pack(push, 1)
typedef struct
{
short id;
int flags;
double value;
} HWCommand;
#pragma pack(pop)
extern "C" __declspec(dllexport) void SendCommand(HWCommand cmd);
// exports.cpp
extern "C" __declspec(dllexport) void SendCommand(HWCommand cmd)
{
// just print the values
printf("Received Command - id: %d, flags: %d, value: %f", cmd.id, cmd.flags, cmd.value);
}
これは、id、いくつかのフラグ、およびいくつかの値で構成される構造体定義 HWCommand と、HWCommand インスタンスをデバイスに送信するために使用される関数 SendCommand で構成されています。今回のケースでは、コマンドは表示のためにコンソールに出力されるだけです。SendCommand 関数は __declspec(dllexport) でマークされており、これにより他のライブラリから使用できるようになります。
C インターフェースを利用する C# コードは以下の通りです。
// Program.cs
SendCommand(new HWCommand() { id = 1, flags = 1, value = 3.14 });
[DllImport("UnmanagedLibrary.dll", EntryPoint = "SendCommand")]
static extern void SendCommand(HWCommand cmd);
public struct HWCommand
{
public short id;
public int flags;
public double value;
}
ここでは、C言語で定義されたものと一致するHWCommand構造体の定義が再度確認できます。また、DllImportAttributeを使用してアンマネージドライブラリからエクスポートされた関数をインポートするSendCommandの定義も確認できます。
なお、C#コンパイラは宣言が一致しているか、あるいは指定された名前の関数が実際に存在するか否かを確認することはできません。
上記のコードを実行すると、以下の出力が生成されることが期待されます
Received Command - id: 1, flags: 1, value: 3.140000
それでは、コードを実行し、期待値と実際の実行結果が一致するか確認してみましょう。
Received Command - id: 1, flags: 65536, value: 0.000000
あいにく、これらは期待される値ではありません。データが「デバイス」側に正しく転送されておらず、機能的に保証できません!したがって、こうした問題を迅速に発見することが重要です。しかし、前回のブログで議論したように、個々のコンパイラは2つの言語を独立してしか認識しないため、根本的な問題を検出することは不可能です。
では、複雑なハードウェアテスト環境を構築したり、ユニットテストや統合テストを記述・実行したりすることなく、開発プロセスの早い段階でこうしたバグを発見するにはどうすればよいでしょうか?
Axivionの静的コード解析が解決策となります!
Axivionが実施する静的コード解析は、上記のような問題を見つけるための有用なツールです。
C#およびC/C++コードの両方に対するビルド構成でAxivionプロジェクトを設定し、解析内でC#-CheckPInvokeルールを実行/有効化すると、Axivionダッシュボードで解析結果を確認できます。

分析によりシグネチャの不一致が検出されました。ダッシュボードから直接ソースコードを確認することで、さらに詳細を調査できます。

右側のパネルに表示されている通り、エラーメッセージは問題の詳細情報を提供しています。C/C++で定義された構造体のサイズとアラインメントが、C#コード内の定義と一致していないことを示しており、これがコード実行時に確認された不正な値の原因となっています。
まとめ
結論として、Axivionは異なるプログラミング言語間の境界領域におけるエラーの発見、理解、修正を支援します。これらの問題は、関与する言語を個別に検討するツールでは適切に検出できないため、従来は発見が極めて困難でした。最新リリース7.11では、Axivionは複数の言語にまたがるソフトウェアシステム全体とそのアーキテクチャを、同時かつ一貫して分析することをサポートしています。これには、Axivionアーキテクチャ検証のような広範なチェックに加え、言語間のインターフェースに関する技術的に深い分析が含まれます。当社は、ソフトウェアプロジェクトの個々の言語依存部分に関するAxivionの情報を、さらに多くの言語境界を越えて伝達し、関与する多言語プロジェクトに対して包括的なアーキテクチャおよび言語を意識した品質・安全チェックを提供できるよう取り組んでおります。
詳細はこちら
アーキテクチャ検証および静的コード解析ツールに関する詳細情報は、当社ウェブサイトをご覧ください。Axivion静的コード解析の仕組みについては、インタラクティブなツアーもご利用いただけます。
ご質問やデモのご予約がございましたら、お気軽にお問い合わせください。
最新の製品ニュースやイベント情報については、ニュースレターにご登録ください。