A3サイズの升目帖(旧)

ゲーム制作やプログラミングに関する雑記

【D言語】-SUBSYSTEM:WINDOWS と writeln の確執

D言語のロゴマーク

マップエディタの実装が遅々として進まないな。周辺ツール最大の難所なのでまあ予想はしていたが…

キリのいい進捗がないからブログも放置しがちになってしまっていけない。

というわけで、お茶を濁すわけじゃないけど、技術的なハマりポイントについてちょっと書こうと思う。


D言語GUI アプリケーションを作る際

D言語Windows 向けの GUI アプリケーションを作成し、出力された exe を直接実行すると、目的のウインドウとは別にコンソールウインドウが表示されてしまう。

実際の画像はこんな感じ。裏にコンソールウインドウがいるのがおわかりいただけるだろう。

背後にコンソールが表示されてしまったゲームウインドウ

これを解決するには、リンカフラグとして dub.json に以下の設定を追加すればよい。

"lflags": [
    "-SUBSYSTEM:WINDOWS",
    "-ENTRY:mainCRTStartup"
]

これでコンソールウインドウは表示されなくなる。


しかしとんでもない代償が

そう、確かにコンソールウインドウは消えるのだが、なんと代わりに writeln などの出力が使えなくなる という特大の副作用があるのだ。

上述のリンカフラグを設定した状態で writeln を呼ぶと、以下のようなエラーが出てアプリケーションが落ちる。

Program exited with code -1073740791

おいおい… これじゃあログ出力できねぇよ…(絶望)


理屈としては単純で、writeln の出力先であるコンソールウインドウを抹殺してしまったから。当然と言えば当然の結果なのだ。


解決方法

出力先がないから落ちるのなら、出力先を作ってやればよい。

ソースコードは以下のとおり。

// 標準出力を file.log というファイルに差し替えている。
stdout.reopen("file.log", "a");

repoen でファイル名を指定すれば、出力内容はすべてそのファイルに書き出される。(ファイルは exe と同階層に出力されるはず)

ちなみに、出力内容を捨てて構わないなら、ファイル名の代わりに "NUL" と指定すればよさそうだ。


うーん、ちょっと微妙

ええ、わかります。

このやり方だと確かにエラーは出ない。しかし、VSCode のターミナルなどにリアルタイム出力するという機能は失われたままなのだ。

これは例えば、デバッグ実行中にログ出力をリアルタイムに確認したいといった場合に大きな障害になる。


解決方法 その2

まず断っておくのは、ターミナルにログを出力しつつ、exe を直接実行した際のコンソールを消す、といった 両立はできない ようだ。(筆者調べ)

ただ、状況に応じて使い分けは可能だ。

dub.json の設定で対応していく。

設定例1

例えば、デバッグモードのみターミナルでログを確認したい、ということであれば "buildTypes" の設定が使える。

リリースモードにだけ "lflags" を適用する、という寸法だ。

"configurations": [
    {
        "name": "application",
        "targetType": "executable",

        "buildTypes": {
            "release": {
                "lflags": [
                    "-SUBSYSTEM:WINDOWS",
                    "-ENTRY:mainCRTStartup"
                ]
            }
        }
    }
]


設定例2

デバッグモードでもリリースモードでもターミナルでログを確認したい、でも完成版ではコンソールを消したい、ということであれば、"configurations" を分離してしまうという手もある。

完成版用の設定として、例えば "application-shipment" のようなものを新しく追加し、"lflags" の設定はその中に記載すればよい。

"configurations": [
    {
        "name": "application",
        "targetType": "executable"
    },
    {
        "name": "application-shipment",
        "targetType": "executable",

        "lflags": [
            "-SUBSYSTEM:WINDOWS",
            "-ENTRY:mainCRTStartup"
        ],

        "versions": [
            "Shipment"
        ]
    }
]

"versions""Shipment" という完成版専用のフラグを設定している。

D言語ソースコードのほうでフラグを見て分岐してやれば、ログ出力先も設定で差し替えられるためだ。

// 完成版では file.log に出力するよ!
version (Shipment)
{
    stdout.reopen("file.log", "a");
}


なお、完成版の設定を明示的に選択してビルドするには -c コマンドを使用する。

dub build -c=application-shipment


以上

個人的に writeln が使えなくなったときは滅茶苦茶あせったけど、解決方法が見つかってよかった。

これでログ出力がはかどるわい。(マップエディタの実装もはかどれ)


参考文献