Siv3D / siv8

Siv3D v0.8 の開発リポジトリ | Development repository for Siv3D v0.8
9 stars 3 forks source link

JetBrains Rider でデバッグ実行が出来ない #39

Open sashi0034 opened 2 months ago

sashi0034 commented 2 months ago

v0.6 世代の Siv3D では JetBrains Rider を使用して開発およびエディタからのデバッグ実行が可能でした。しかし、現時点での v0.8 では Failed to load a engine shader というエラーダイアログが表示され、初期化に失敗します。

詳しく調査したところ、初期化時の SetWorkingDirectory で Visual Studio 実行時かどうかを判定する条件分岐が、Rider実行時には適用されていないことが原因であることが分かりました。そのため、デバッグ時の実行ディレクトリが siv8/WindowsDesktop/App/ではなく siv8/WindowsDesktop/Intermediate/Siv3D-Test/Debug/ になってしまいます。

Reputeless commented 2 months ago

Siv3D 側で回避するためには、プログラムが JetBrains Rider によって実行されているかを判定できる必要があります。

次のコードはプログラムの環境変数を出力するコードです。

#include <iostream>
#include <cstdlib>

int main()
{
    extern char** environ;

    for (int i = 0; environ[i] != nullptr; ++i)
    {
        std::cout << environ[i] << '\n';
    }
}

Visual Studio によって実行した場合、システム環境変数とは別に VisualStudioVersion などの環境変数が表示され、Visual Studio から起動されたことがわかります。

同様に JetBrains Rider で実行したときにだけ設定される環境変数があれば、System::IsRunningInJetBrainsRider() を実装でき、問題を回避できます。

なお、v0.6 世代でも v0.6.15 からは v8 と同じ仕様になっています。engine フォルダの内容を Resource.rc で実行ファイルに埋め込んでいるので、カレントディレクトリの影響を受けず、エンジン初期化には成功します。

sashi0034 commented 2 months ago

ありがとうございます。頂いたコードをもとに JetBrains Rider で実行してみましたが、Visual Studio とは異なり、デバッグ実行時に実行環境を表す特有の環境変数が存在しないことが分かりました。

sashi0034 commented 2 months ago

参考までに、ユーザー側で可能な回避策がありました。あらかじめ Run Configuration の Environment variables に VisualStudioVersion のダミーの値を入れておくという方法です。

image

Reputeless commented 2 months ago

.vcxproj ファイル内に環境変数 SIV3D_APP_LAUNCHED_FROM_IDE=1 を追加し、VisualStudioVersion ではなく SIV3D_APP_LAUNCHED_FROM_IDE の有無で判定するよう仕様を変更してみました。

JetBrains Rider にも上記設定がインポートされるかはわかりませんが、今後は SIV3D_APP_LAUNCHED_FROM_IDE=1 で対応する予定です。

sashi0034 commented 2 months ago

ご対応ありがとうございます。 しかしながら、Rider では LocalDebuggerCommandArgumentsLocalDebuggerWorkingDirectory などは反映が可能であるのにも関わらず、LocalDebuggerEnvironment は扱えないようでデバッグ実行時に SIV3D_APP_LAUNCHED_FROM_IDE=1 が設定されませんでした。

Reputeless commented 2 months ago

MSVC においても、最初 LocalDebuggerEnvironment は反映されなかったのですが、 MSVC を起動したときに Siv3D-Test.vcxproj と同じフォルダに自動的に生成されるローカルファイル Siv3D-Test.vcxproj.user を一旦削除して再起動したところ反映されました。設定が .user によってオーバーライドされていたようです。 もしかしたら Rider でも Siv3D-Test.vcxproj.user が悪さをしているかもしれません。

image

sashi0034 commented 2 months ago

確かに、ローカル環境のキャッシュの問題であるという可能性は盲点でした

しかし、改めてリポジトリを別フォルダにクローンし直してブランチ v8_develop_0706 に切り替えてから起動・実行してみたのですが、やはり依然として環境変数が設定されないようでした

どうにも、Rider は VisualStudio で設定された LocalDebuggerEnvironment を取り込むことが出来なさそうです

Reputeless commented 2 months ago

確認ありがとうございます。そうでしたか。 何か別の良い対処法が見つかった場合は対応できるのでお知らせください。

sashi0034 commented 2 months ago

Win32 API で IsDebuggerPresent という関数があることが分かりました。これを使用すれば、VisualStudio でも Rider でもデバッグ実行時かどうかを実際に検出できました

https://learn.microsoft.com/ja-jp/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent

yaito3014 commented 2 months ago

標準機能で言うならば std::is_debugger_present が C++26 に採択されるみたいですね 提案書 にはどういったものが現在利用可能か書いてあるので参考までにどうぞ

Reputeless commented 2 months ago

IsDebuggerPresent() は、MSVC で「デバッグなしで開始」から実行すると false になるため、完全な判定には使えなかったと記憶しています。Rider の場合はどうでしょうか。 でも無策よりは Rider での実行に役立つので、IDE 利用判定条件に追加したいと思います。

sashi0034 commented 2 months ago

確かに、Rider でも「(デバッグ無しの) Run」をすると IsDebuggerPresent() が false になってしまいました...難しいですね ひとまず対応ありがとうございます

Reputeless commented 2 months ago

親プロセスの名前を調べることで、Rider からの起動を判定できそうでした。 ただ、この処理にはかなりの時間(10 ms 程度)がかかります。 エンジン初期化が遅くなってしまうため、Siv3D 本体への採用は不可能です。

# include <Siv3D.hpp> // Siv3D v0.8.0
# include <Siv3D/Windows/Windows.hpp>
# include <tlhelp32.h>

struct ProcessInfo
{
    DWORD pid = 0;
    String name;
};

Optional<ProcessInfo> GetParentProcess(DWORD processID)
{
    HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (hSnapshot == INVALID_HANDLE_VALUE)
    {
        return none;
    }

    PROCESSENTRY32W pe32
    {
        .dwSize = sizeof(PROCESSENTRY32W)
    };

    ProcessInfo parentInfo;

    if (::Process32FirstW(hSnapshot, &pe32))
    {
        do
        {
            if (pe32.th32ProcessID == processID)
            {
                parentInfo.pid = pe32.th32ParentProcessID;
                break;
            }

        } while (::Process32NextW(hSnapshot, &pe32));

        // 親プロセスの名前を取得
        if (parentInfo.pid != 0)
        {
            ::Process32FirstW(hSnapshot, &pe32);

            do
            {
                if (pe32.th32ProcessID == parentInfo.pid)
                {
                    parentInfo.name = Unicode::FromWstring(pe32.szExeFile);
                    break;
                }

            } while (::Process32NextW(hSnapshot, &pe32));
        }
    }

    ::CloseHandle(hSnapshot);
    return parentInfo;
}

void Main()
{
    Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

    MicrosecClock clock;

    if (const Optional<ProcessInfo> parentInfo = GetParentProcess(::GetCurrentProcessId()))
    {
        clock.console();
        Console << parentInfo->pid;
        Console << parentInfo->name;
    }
    else
    {
        Console << U"Parent process not found";
    }

    while (System::Update())
    {

    }
}
sashi0034 commented 1 month ago

実行中のファイルを含むフォルダの中に実行ファイルに対応する .pdb ファイルが存在するか否かを調べることにより、中間ディレクトリで実行中か否かを判断すると効果的なのではないかと考えました。この方法についてご意見をお聞かせいただけますでしょうか

Reputeless commented 1 month ago

漏れていたケースをおおよそカバーできる、低コストで良い方法だと思います。 PDB を生成しないビルドオプションもあるので 100% にはなりませんが、採用してみたいと思います。 この先実装したらお知らせします。Rider で検証していただけると幸いです。

sashi0034 commented 1 month ago

ありがとうございます!検証の件承知いたしました