C言語で発生するLNK2028エラーの原因と対策について解説
c言語で開発する際、LNK2028エラーが発生することがあります。
これは、ネイティブ関数の呼び出し規則と純粋コンパイル環境で期待される規則が一致しないためです。
特に/clr:pure
オプションを使用する場合、規則の不整合によりエラーが出るため、関数やライブラリの設定を確認するとよいです。
エラー原因の詳細検証
呼び出し規則の不整合
ネイティブ関数と純粋コンパイルの違い
C言語において、通常のネイティブ関数は暗黙的に __cdecl 呼び出し規則が適用されます。
一方、/clr:pure オプションを使用して純粋なマネージドコードとしてコンパイルすると、呼び出し規則が __clrcall に変更されます。
これらは内部的な処理方法やスタックの引数処理方式に違いがあるため、混在して使用するとリンク時エラー(LNK2028)が発生する可能性があります。
例えば、以下のサンプルコードはネイティブ関数として __cdecl が適用された関数を示しています。
#include <stdio.h>
__declspec(dllexport) int func() { // ネイティブ関数:暗黙の __cdecl
// 関数は42を返す
return 42;
}
int main() {
// エクスポートされたネイティブ関数の呼び出し
int result = func();
printf("結果: %d\n", result);
return 0;
}
結果: 42
__cdecl と __clrcall の比較
__cdecl は主にC/C++の標準的な呼び出し規則で、関数呼び出し後に呼び出し元がスタックをクリアします。
一方、__clrcall はマネージドコード向けに設計され、共通言語ランタイム(CLR)のガイドラインに沿って動作します。
これらの規則の不整合が原因で、ネイティブ関数を純粋なマネージドコードから呼び出すとリンクエラーが生じることがあります。
以下は、__cdecl と __clrcall の違いを示すためのサンプルコードです。
コード内の関数宣言には特に呼び出し規則は明示していません。
ビルド時のオプションにより、規則が自動的に変わる点に注意してください。
#include <stdio.h>
#ifdef PURE_MODE
// 純粋なマネージドコード用の宣言(暗黙の __clrcall として解釈される)
int func();
#else
// ネイティブコードの場合、暗黙の __cdecl が適用される
__declspec(dllexport) int func() {
return 42;
}
#endif
int main() {
int value = func();
printf("関数呼び出しの結果: %d\n", value);
return 0;
}
関数呼び出しの結果: 42
コンパイルオプションの影響
/clr:pure の注意点
/ clr:pure オプションは、コンパイル対象を完全な中間言語(IL)に変換し、マネージド環境で実行できるようにします。
ただし、このモードでは呼び出し規則が __clrcall となるため、暗黙的に __cdecl が前提となっているネイティブコンポーネントとの整合性に問題が生じます。
このため、ネイティブ関数を/ clr:pure で呼び出すと LNK2028 エラーが発生する可能性が高まります。
問題を回避するためには、以下のように関数宣言やリンカ設定を見直す必要があります。
以下は/ clr:pure で発生する可能性のある状況を模擬したサンプルコードです。
#include <stdio.h>
// /clr:pure 環境下での呼び出しを想定した関数宣言(暗黙の __clrcall となる)
int func();
int main() {
int res = func(); // ネイティブ側で __cdecl として実装された func() との整合性が問題になる
printf("呼び出し結果: %d\n", res);
return 0;
}
Visual Studio バージョンによる相違点
Visual Studio のバージョンによっては、/clr:pure オプションのサポート状況が異なります。
たとえば、Visual Studio 2015 では/ clr:pure は非推奨となっており、Visual Studio 2017 以降ではサポート対象外となっています。
このため、使用する Visual Studio のバージョンに合わせて、プロジェクトのビルド設定および呼び出し規則の整合性を確認する必要があります。
以下の表は、Visual Studio の主要なバージョンと/ clr:pure オプションのサポート状況をまとめたものです。
Visual Studio Version | /clr:pure のサポート状況 |
---|---|
2015 | 非推奨 |
2017 | サポートされていない |
最新 | 使用が推奨されない |
対策方法の検討
リンカ設定の確認
エクスポート設定の見直し
ネイティブコンポーネントとして関数を提供する場合、関数宣言に __declspec(dllexport) を付与し、利用側では __declspec(dllimport) を明示することが望ましいです。
これにより、リンカが正しくエクスポートされた関数を認識し、呼び出し規則の不整合を回避できます。
以下は、DLLを生成する際のエクスポート設定を示すサンプルコードです。
#include <stdio.h>
#ifdef BUILD_DLL
// DLLを生成する際の関数定義:エクスポート時には __declspec(dllexport) を使用
__declspec(dllexport) int func() {
return 55;
}
#else
// DLLを利用する際の関数宣言:__declspec(dllimport) を付与
__declspec(dllimport) int func();
#endif
int main() {
int value = func();
printf("エクスポート設定確認結果: %d\n", value);
return 0;
}
エクスポート設定確認結果: 55
関数宣言の調整
関数宣言において、呼び出し規則を明示的に指定することで、ネイティブコードとマネージドコード間の不整合を防ぐことができます。
たとえば、__cdecl を明示的に指定して関数を定義することで、/clr:pure 環境下でも呼び出し規則の混在によるエラーを回避できる場合があります。
以下は、関数宣言に __cdecl を明示したサンプルコードです。
#include <stdio.h>
// __cdecl を明示的に指定して関数を定義
__declspec(dllexport) int __cdecl func() {
return 100;
}
int main() {
int result = func();
printf("関数宣言の調整結果: %d\n", result);
return 0;
}
関数宣言の調整結果: 100
プロジェクト構成の調整
コンパイル環境の整合性確認
プロジェクト全体で使用するコンパイルオプションやリンカ設定が一貫性を保つことが重要です。
たとえば、プロジェクト内で一部のコンポーネントが/ clr:pure で、他がネイティブコードとしてコンパイルされている場合、呼び出し規則の不整合が生じやすくなります。
そのため、プロジェクトの各コンポーネントが同一のコンパイル環境およびビルド設定で構築されていることを確認してください。
- コンパイルオプション(例:/clr vs /clr:pure)の統一
- 依存ライブラリのバージョンや設定の整合性確認
ビルドオプションの最適化
Visual Studio などの統合開発環境において、ビルドオプションはリンクエラーを回避するための重要なポイントです。
特に、プロジェクトのプロパティから「C/C++」や「リンカ」の設定を確認し、使用する呼び出し規則や出力形式が適切に設定されているかを見直すことが必要です。
また、Visual Studio のバージョンごとに推奨される設定が異なる場合があるため、公式ドキュメントも参照しながら最適化を行ってください。
- /clr:pure 以外のオプション(例:/clr)を検討
- プロジェクトごとにリンカとコンパイラの設定の整合性を確保
以上の点を確認・調整することで、LNK2028 エラーの原因となる呼び出し規則の不整合を解消し、安定したビルド環境を実現できます。
まとめ
この記事では、LNK2028エラーの原因として、ネイティブ関数とマネージドコード間での呼び出し規則(__cdeclと__clrcall)の不整合、/clr:pureオプション特有の問題、Visual Studioのバージョン差異が挙げられる点を示しました。
また、エクスポート設定や関数宣言の明示、プロジェクト全体のコンパイル環境およびビルドオプションの整合性確認など、具体的な対策方法も解説しています。