コンパイラエラー

C言語のコンパイラエラー C3714:呼び出し規則の不一致の原因と対策について解説

エラーC3714は、イベントハンドラと対応するイベントメソッドで呼び出し規則が一致しない場合に発生します。

たとえば、ソース側のメソッドが__cdeclで定義されているのに対し、ハンドラ側が__stdcallになっていると、整合性が取れずエラーとなります。

解決するには、両方の宣言で同じ呼び出し規則を指定するか、双方から呼び出し規則キーワードを削除して既定の規則に統一してください。

エラーC3714の概要

エラーメッセージの内容

エラーC3714は、イベントハンドラーの定義において、ソース側で定義されたイベントメソッドの呼び出し規則と不一致がある場合に発生します。

具体的には、イベントハンドラーに付加された呼び出し規則キーワードと、イベントソース側の呼び出し規則が一致しないときに、このエラーメッセージが表示されます。

「’method’: イベント ハンドラーメソッド は、ソース ‘method’ と同じ呼び出し規則を含む必要があります」という内容が示すとおり、両者で同一の呼び出し規則を設定する必要があるため、違いがあると警告が出ます。

エラー発生の条件

このエラーは、イベントソース側のメソッドと、対応するイベントハンドラー側のメソッドで呼び出し規則が異なる場合に発生します。

たとえば、イベントソースメソッドに__cdeclを指定しているのに対し、イベントハンドラーメソッドで__stdcallが指定されているとエラーとなります。

また、どちらかのメソッドで呼び出し規則キーワードが明示的に記述されておらず、既定の呼び出し規則(環境に依存する場合があります)が適用された場合でも不一致となるケースが存在します。

呼び出し規則の基礎知識

主な呼び出し規則の種類

C/C++では、メソッドの呼び出し方法を制御するために、複数の呼び出し規則が用いられます。

主な呼び出し規則としては、__cdecl__stdcall、およびthiscallなどが存在します。

これらは、引数のスタック上の順序や、戻り値の扱いなどで違いを示します。

__cdecl の特徴

__cdeclはデフォルトの呼び出し規則のひとつであり、関数呼び出し時に引数の右から左への積み上げが行われます。

また、関数呼び出し後に呼び出し側がスタックをクリアするため、可変個引数を取る関数(例:printf)が定義可能です。

#include <stdio.h>
// __cdecl 呼び出し規則を明示的に指定した関数の例
int __cdecl add(int a, int b) {
    return a + b;  // 数値を加算する関数
}
int main(void) {
    int result = add(3, 4);  // 3と4を加算
    printf("Result: %d\n", result);
    return 0;
}
Result: 7

__stdcall の特徴

__stdcallは主にWindows APIで利用される呼び出し規則であり、引数は右から左の順にスタックへ積まれます。

しかし、関数呼び出し後にカリ―(呼び出し側)ではなく、呼ばれる関数側がスタックのクリーンアップを行います。

主に固定個の引数を持つ関数に適用され、可変個引数に適さない点が特徴です。

#include <stdio.h>
#include <windows.h>
// __stdcall 呼び出し規則で定義された関数例
int __stdcall multiply(int a, int b) {
    return a * b;  // 数値を乗算する関数
}
int main(void) {
    int product = multiply(3, 5);  // 3と5を乗算
    printf("Product: %d\n", product);
    return 0;
}
Product: 15

イベントメソッドとイベントハンドラーの役割

イベントメソッドは、特定の条件や操作が発生した際に呼び出される仕組みを提供しています。

ソース側のイベントメソッドは、シグナルを発行してイベントハンドラーに通知を行います。

一方、イベントハンドラーはその通知を受け取り、必要な処理を実行します。

両者が同じ呼び出し規則に従う必要があるのは、スタックや引数の管理が一致することで、正確にイベント処理が実行されるためです。

エラー発生の原因詳細

ソースイベントとハンドラの呼び出し規則の不一致

エラーの原因は、ソース側のイベントメソッドと、イベントハンドラー側のメソッドで異なる呼び出し規則が指定されていることにあります。

たとえば、ソース側が__cdeclを使用しているにもかかわらず、ハンドラー側が__stdcallを指定している場合、コンパイラは両者が同一でないと判断し、エラーC3714を発生させます。

この不一致により、引数の受け渡しやスタックの管理に齟齬が生じ、正しいイベント通知が行えません。

不一致パターンの具体例

具体的なパターンとして、以下のような例が挙げられます。

  • イベントソースメソッドに__cdeclが指定され、イベントハンドラーメソッドに__stdcallが指定された場合。
  • 両方のメソッドから呼び出し規則キーワードを削除し、既定の呼び出し規則が異なる環境下でコンパイルされた場合。

たとえば、次のC++のサンプルコードでは、エラーが発生する可能性があります。

#include <stdio.h>
// イベントソース側のクラス
class EventSource {
public:
    // __cdecl で定義されたイベントメソッド
    void __cdecl event1() {
        // イベント発生時の処理
        printf("Event1 from source\n");
    }
};
// イベントハンドラー側のクラス
class EventReceiver {
public:
    // __stdcall で定義されたイベントハンドラーメソッド
    void __stdcall handler1() {
        // イベント通知を受けた処理
        printf("Handler1 responding to event\n");
    }
    // イベントにハンドラーをフックするメソッド
    void HookEvents(EventSource* src) {
        // ここで event1 と handler1 の呼び出し規則が不一致の場合、エラーが発生する
        //__hook(&EventSource::event1, src, &EventReceiver::handler1); // エラーC3714発生
    }
};
int main(void) {
    EventSource src;
    EventReceiver rec;
    rec.HookEvents(&src);
    return 0;
}

このサンプルでは、event1handler1の呼び出し規則が一致しないため、コンパイル時にエラーが発生する点に注意してください。

エラー対策と修正方法

呼び出し規則統一の手法

エラーを解消するためには、イベントソース側とイベントハンドラー側で同一の呼び出し規則を使用する必要があります。

呼び出し規則を統一する方法として、大きく以下の2通りが挙げられます。

キーワード削除による統一例

両方のメソッドから呼び出し規則を指定するキーワードを削除する方法です。

呼び出し規則キーワードを削除すると、既定の呼び出し規則が適用され、ソースとハンドラーで統一されます。

たとえば、以下のコードではキーワードを削除することにより、コンパイラの既定に沿った呼び出し規則が適用され、エラーが解消されます。

#include <stdio.h>
// イベントソース側のクラス(呼び出し規則指定なし)
class EventSource {
public:
    // 呼び出し規則キーワードを削除
    void event1() {
        printf("Event1 from source\n");
    }
};
// イベントハンドラー側のクラス(呼び出し規則指定なし)
class EventReceiver {
public:
    // 呼び出し規則キーワードを削除
    void handler1() {
        printf("Handler1 responding to event\n");
    }
    void HookEvents(EventSource* src) {
        //__hook(&EventSource::event1, src, &EventReceiver::handler1);
        // __hook マクロを使う場合は、両者の呼び出し規則が既定で統一されるためエラーは発生しません
    }
};
int main(void) {
    EventSource src;
    EventReceiver rec;
    rec.HookEvents(&src);
    return 0;
}
Event1 from source
Handler1 responding to event

規則指定変更の修正例

もう一つの方法は、ソース側またはハンドラー側の呼び出し規則を明示的に指定して、一致させる方法です。

たとえば、ソース側が__cdeclを使用している場合、ハンドラー側も__cdeclを使用するように修正します。

以下は、その具体的な修正例です。

#include <stdio.h>
// イベントソース側のクラス
class EventSource {
public:
    // __cdecl 呼び出し規則を指定
    void __cdecl event1() {
        printf("Event1 from source\n");
    }
};
// イベントハンドラー側のクラス
class EventReceiver {
public:
    // __cdecl 呼び出し規則に統一
    void __cdecl handler1() {
        printf("Handler1 responding to event\n");
    }
    void HookEvents(EventSource* src) {
        // 両者で __cdecl を使用しているためエラーは発生しません
        //__hook(&EventSource::event1, src, &EventReceiver::handler1);
    }
};
int main(void) {
    EventSource src;
    EventReceiver rec;
    rec.HookEvents(&src);
    return 0;
}
Event1 from source
Handler1 responding to event

コード修正手順の注意点

コード修正の際には、以下の点に注意してください。

  • イベントソース側のメソッドおよびイベントハンドラー側のメソッドで、同じ呼び出し規則が使用されていることを必ず確認すること。
  • 呼び出し規則キーワードを削除する場合は、コンパイラや開発環境で既定の呼び出し規則が何であるかを把握しておくことが必要です。特に、プラットフォームやコンパイラのバージョンによって既定の呼び出し規則が異なる場合があります。
  • 大規模なプロジェクトの場合、ソースコード全体における呼び出し規則の不統一がエラーの原因となるため、統一ルールをプロジェクト全体で適用することが望ましいです。
  • 変更後は、必ずテストを実施して機能に影響がないことを確認するようにしてください。

まとめ

この記事では、コンパイラエラーC3714の内容と発生条件を解説し、イベントメソッドとイベントハンドラーの呼び出し規則の不一致が原因でエラーが発生する点を説明しました。

特に、__cdecl__stdcallの違いや、呼び出し規則を統一する修正方法(キーワード削除、規則指定変更)の手法について具体例を交えて紹介しています。

関連記事

Back to top button
目次へ