0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【DxLib】DXライブラリでも使用できるAssertマクロ

Last updated at Posted at 2024-10-19

はじめに

DxLibでゲームを作っている際に,assertを使いたいと思うことがありました.
しかし,DxLibでassertを使うとDxLibの終了処理DxLib_Endを呼ぶ前にプログラムが終了してしまうという問題があるため,そのまま使うことはできません.(assertってなにという人はこちら)

「龍神録2ゲームプログラミングの館」ではこれの対策として,アサートを自作されています.
内容としてはERRというマクロを定義し,マクロからエラー発生条件の表示とDxLib_Endを呼ぶ機能を持った関数を呼びだすというものです.関数の中ではwhile(!ProcessMessage())を読んでプログラムが終了しないようにされており,printfDxでエラー文を表示しています.

大変参考になる実装であり,私も長らくこれを使わせていただいていました.しかし,使用していく中で以下のような要望が自分の中で出てきました.

  • DxLib_Initを呼ぶ前でも使用できるようにしたい
  • assertのようにReleaseビルド時には処理されてほしくない
  • 折角なら__LINE____FILE__ではなく,source_locationを使って呼び出し位置を表示したい

これらを踏まえて作成した,DxLib専用の自作Assertを紹介します.

ソースコード

ソースコードは以下の通りです.C++20で作成していることに留意してください.

dxlib_assert.h
#pragma once

#include <source_location>
#include <stdexcept>
#include <string>

#include <DxLib.h>

// 呼び出さないこと.
namespace assert_internal {

void ErrorAssert(const std::string& conditional_expression, const std::string& error_mes,
                 const std::string& file, const std::string& func, const int line);

}  // namespace assert_internal

#ifdef _DEBUG

#define INTERNAL_ERROR_MESSAGE(expression, error_mes)                       \
const std::source_location location = std::source_location::current();      \
::assert_internal::ErrorAssert(expression, error_mes, location.file_name(), \
    location.function_name(), location.line());

//! @brief エラーが発生したときにエラーメッセージを表示する.
//! DxLib の動作を止め,独自のエラーメッセージを表示する.
//! DxLib が初期化されていない場合は,例外を投げる.
//! @param expr TRUE であることが期待される条件
//! @param error_mes エラーメッセージ
#define ASSERT(expr, error_mes)                                     \
do {                                                                \
    if (!(expr)) {                                                  \
        if (DxLib_IsInit() != TRUE) {                               \
            throw std::runtime_error("DxLib is not initialized.");  \
        } else {                                                    \
            const std::string expr_str = #expr;                     \
            const std::string message = error_mes;                  \
            INTERNAL_ERROR_MESSAGE(expr_str, message);              \
        }                                                           \
    }                                                               \
} while (0)

#define ASSERT_NOT_NULL_PTR(ptr) ASSERT((ptr) != nullptr, "nullptr passed.")
#define ASSERT_MUST_NOT_REACH_HERE() ASSERT(false, "This part is never reached.")

#else

#define ASSERT(expr, error_mes) ((void)0)
#define ASSERT_NOT_NULL_PTR(ptr) ((void)0)
#define ASSERT_MUST_NOT_REACH_HERE() ((void)0)

#endif  // _DEBUG
dxlib_assert.cpp
#include "dxlib_assert.h"

#include <format>

#include <DxLib.h>

void ::assert_internal::ErrorAssert(
    const std::string& conditional_expression,
    const std::string& error_mes, const std::string& file,
    const std::string& func, const int line) {
    // DxLib がまだ初期化されていない場合は,終了する.
    if (DxLib::DxLib_IsInit() != TRUE) {
        return;
    }

    // エラーメッセージを表示する.
    DxLib::clsDx();
    DxLib::printfDx("Error! Please press the X button on the window to exit! \n\n");
    DxLib::printfDx("%s", std::format("Error Condition : {}\n\n", conditional_expression).c_str());
    DxLib::printfDx("%s", std::format("Error Cause : {}\n\n", error_mes).c_str());
    DxLib::printfDx("%s", std::format("File Name : {}\n\n", file).c_str());
    DxLib::printfDx("%s", std::format("Function Name : {}\n\n", func).c_str());
    DxLib::printfDx("%s", std::format("Line Number : {}\n\n", line).c_str());

    // 何もせずに待つ.
    while (!DxLib::ProcessMessage()) {
        DxLib::ClearDrawScreen();
        DxLib::ScreenFlip();
    }

    DxLib::DxLib_End();

    // プログラムを終了する.
    exit(99);
}

マクロについて

ASSERT(expr, error_mes)

基本的なアサートの処理です.exprのほうにtrueとなっていてほしい条件式,error_mesのほうに表示されるエラーメッセージを渡してください.なお,マクロは全てReleaseビルドの際は((void)0)に置き換えられ無視されます.
また,DxLib_Initを呼ぶ前に使用すると,例外を出すようになっています.

ASSERT_NOT_NULL_PTR(ptr)

ptrnullptrと比較し,ヌルポインタではないことを確認します.個人的にnullチェックはたくさん書くので,専用のマクロを用意してみました.

ASSERT_MUST_NOT_REACH_HERE()

switchのdefaultなど「ここまで処理が来るはずない」というところで使用します.ASSERT(false, "This part is never reached.")に置換されるので,必ずアサートは失敗します.

使用例

sample.cpp
#include <DxLib.h>

#include "dxlib_assert.h"

void Success() {
    ASSERT(1 == 1, "これは呼ばれません");
}

void Failure() {
    ASSERT(1 == 2, "条件式がfalseなので,アサートが呼ばれます");
}

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
    ChangeWindowMode(TRUE);
    SetGraphMode(800, 600, 32);
    SetMainWindowText("dxlib_assert");

    if (DxLib_Init() == -1) {
        return -1;
    }

    SetDrawScreen(DX_SCREEN_BACK);

    while (!ProcessMessage()) {
        ClearDrawScreen();

        Success();
        Failure();

        ScreenFlip();
    }

    DxLib_End();

    return 0;
}

プログラムを実行すると以下のようにアサートが呼ばれます.

スクリーンショット 2024-10-19 233921.png

参考にしたリンク

assertについてしらない方は,以下のページが参考になると思います.


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?