C言語およびC++におけるコンパイラエラー C3395 の原因と対策を解説
この記事ではコンパイラ エラー C3395について説明します。
C言語およびC++の開発環境で、__clrcall呼び出し規約を利用する関数に__declspec(dllexport)属性を適用するとエラーが発生します。
具体例を交えながら原因と回避策について触れ、開発時の参考情報を提供します。
エラー発生の原因
属性指定の不整合
属性指定の不整合とは、関数に対して付与する属性が互換性のない組み合わせとなっている状態を指します。
たとえば、Microsoftの拡張機能である__declspec(dllexport)
は、関数をDLLからエクスポートするために使用されます。
一方、__clrcall
は共通言語ランタイム(CLR)環境で使用される呼び出し規約を示します。
これら二つの指定子は、仕様上混在させることができず、その結果としてコンパイラエラー C3395 が発生します。
このエラーの背景には、以下のような考え方があります。
CLR環境においては関数呼び出しの方式が異なるため、エクスポート機能と呼び出し規約の整合性が担保できなくなってしまうのです。
__declspec(dllexport) と __clrcall の組み合わせの問題
具体的には、以下のコード例において、__declspec(dllexport)
と__clrcall
を同時に宣言した関数を定義すると、C3395エラーが発生します。
この場合、コンパイラはエラー文として
「’function’ : __declspec(dllexport) は、__clrcall 呼び出し規約を伴う関数に適用することはできません」というメッセージを出力します。
この現象は、Microsoftのドキュメントにも記載されており、どちらか一方の指定に統一する必要があるとされています。
エラーメッセージ解析
C3395 エラーの具体的内容
エラーメッセージ C3395 は、特定の関数定義に対して不適切な属性指定が行われたときに発生します。
具体的には、関数のエクスポート指示属性__declspec(dllexport)
と、共通言語ランタイム用の呼び出し規約__clrcall
が同時に指定された場合にこのエラーが出力されます。
エラーメッセージの内容は、関数宣言部分において指定された属性が互いに矛盾していることを示しており、次のように理解できます。
- 関数をDLLエクスポートするための設定がなされている
- しかし、同時にCLR特有の呼び出し規約が適用されている
これにより、実行時にどのような呼び出し方法を採用すべきか、またエクスポート時にどのような対応が必要かが曖昧になり、整合性が取れなくなっています。
エラー発生時の対策
コード修正方法
属性の除去または呼び出し規約の変更
このエラーを解決する方法として、まずは属性を調整する方法があります。
具体的には、関数のエクスポートが不要な場合は、__declspec(dllexport)
を削除する方法を検討していただくとよいでしょう。
また、CLR環境が不要であれば、__clrcall
を外すことで、標準の呼び出し規約へ変更することも有効です。
どちらの場合も、プロジェクトの目的に合わせた正しい属性指定が必要となります。
正しい関数定義の選択
プロジェクトの要件に沿って、正しい関数定義を選択してください。
- DLLから関数をエクスポートする必要がある場合は、一般的な呼び出し規約(たとえば、
__cdecl
や__stdcall
)を使用します。 - CLR環境内で動作する必要がある場合は、
__clrcall
に合わせた定義を維持し、エクスポートする必要がない場合に限定します。
適切な定義を行うことで、コンパイラの警告やエラーを回避し、コードの可読性と保守性も向上します。
コード例の提示
C++ における修正前と修正後の例
修正前のコード例は、エラーが発生する組み合わせがそのまま記述されています。
以下は、エラー発生時のサンプルコードです。
#include <iostream>
// 以下の関数は、__declspec(dllexport) と __clrcall が同時に指定されているためエラー C3395 が発生します。
// compile with: /clr /c
__declspec(dllexport) void __clrcall TestFunction() {
// テスト用関数
}
int main() {
// 関数呼び出し例(実行されることはなく、コンパイルエラーが発生します)
TestFunction();
return 0;
}
// コンパイル時に以下のエラーメッセージが出力される例
// 'TestFunction' : __declspec(dllexport) は、__clrcall 呼び出し規約を伴う関数に適用することはできません
修正後のコード例は、どちらかの属性指定を除去することでエラーを解消したものです。
以下は、エクスポート属性を削除した例です。
#include <iostream>
// __declspec(dllexport) を削除して、__clrcall のみを適用
void __clrcall TestFunction() {
std::cout << "TestFunction executed." << std::endl;
}
int main() {
// 正しく関数が呼び出され、出力が得られます
TestFunction();
return 0;
}
TestFunction executed.
C言語での注意点
C言語においては、通常__clrcall
は使用しません。
そのため、Cプロジェクトで同様の属性指定を行っている場合には、コンパイラ環境や拡張機能の設定を確認してください。
また、C言語では標準の呼び出し規約を用いる場合が多いため、Microsoft固有の属性は必要最小限に留めることが望ましいです。
以下は、C言語でDLLエクスポートを行う際のサンプルコードです。
#include <stdio.h>
// C言語の場合、__clrcallは使用せず、通常の関数定義を行います。
// DLLエクスポートが必要な場合は、__declspec(dllexport) を使用しますが、呼び出し規約との衝突は発生しません。
__declspec(dllexport) void TestFunction(void) {
printf("TestFunction executed.\n");
}
int main(void) {
TestFunction();
return 0;
}
TestFunction executed.
修正後の確認項目
再コンパイルによる動作チェック
修正後のコードがエラーなくコンパイルされるかどうかを確認してください。
修正したソースコードを再コンパイルし、エラーや警告が出ないことを確認することが重要です。
また、サンプルコードの実行結果が期待通りであることも動作チェックの一環です。
開発環境への影響評価
修正内容が他の部分に影響を及ぼしていないかを検証してください。
DLLエクスポート属性や呼び出し規約の変更は、リンク時エラーや実行時の動作にも影響を与える可能性があります。
プロジェクト全体をビルドし、テストを実施することで、これらの変更がシステム全体に与える影響を評価してください。
まとめ
本記事では、__declspec(dllexport) と __clrcall を同時に使用することで発生するエラー C3395 の原因と、エラーメッセージの詳細な内容について解説しました。
具体的な修正方法として、属性の除去や呼び出し規約の変更、正しい関数定義の選択を紹介し、C++およびC言語のサンプルコードを示しました。
また、修正後の再コンパイルによる動作チェックと環境への影響評価の重要性についても触れ、実践的な対策が理解できる内容となっています。