diff --git a/sakura_core/_main/CControlProcess.cpp b/sakura_core/_main/CControlProcess.cpp index e99eac807e..e0adc0a474 100644 --- a/sakura_core/_main/CControlProcess.cpp +++ b/sakura_core/_main/CControlProcess.cpp @@ -9,7 +9,7 @@ Copyright (C) 2002, aroka CProcessより分離, YAZAKI Copyright (C) 2006, ryoji Copyright (C) 2007, ryoji - Copyright (C) 2018-2022, Sakura Editor Organization + Copyright (C) 2018-2026, Sakura Editor Organization This source code is designed for sakura editor. Please contact the copyright holder to use this code for other purpose. @@ -130,16 +130,13 @@ bool CControlProcess::InitializeProcess() const auto pszProfileName = GetProfileName(); - // 初期化完了イベントを作成する + // 初期化完了イベントの名前を組み立てる std::wstring strInitEvent = GSTR_EVENT_SAKURA_CP_INITIALIZED; strInitEvent += pszProfileName; - m_hEventCPInitialized = ::CreateEvent( nullptr, TRUE, FALSE, strInitEvent.c_str() ); - if( nullptr == m_hEventCPInitialized ) - { - ErrorBeep(); - TopErrorMessage( nullptr, L"CreateEvent()失敗。\n終了します。" ); - return false; - } + + // 起動元が作成した初期化完了イベントを開く。 + using HandleHolder = cxx::ResourceHolder<&::CloseHandle>; + HandleHolder hEvent{ ::OpenEventW(EVENT_MODIFY_STATE, FALSE, std::data(strInitEvent)) }; /* コントロールプロセスの目印 */ std::wstring strCtrlProcEvent = GSTR_MUTEX_SAKURA_CP; @@ -194,11 +191,7 @@ bool CControlProcess::InitializeProcess() GetDllShareData().m_sHandles.m_hwndTray = hwnd; // 初期化完了イベントをシグナル状態にする - if( !::SetEvent( m_hEventCPInitialized ) ){ - ErrorBeep(); - TopErrorMessage( nullptr, LS(STR_ERR_CTRLMTX4) ); - return false; - } + if (hEvent) ::SetEvent(hEvent); return true; } @@ -234,10 +227,6 @@ CControlProcess::~CControlProcess() { delete m_pcTray; - if( m_hEventCPInitialized ){ - ::ResetEvent( m_hEventCPInitialized ); - } - ::CloseHandle( m_hEventCPInitialized ); if( m_hMutexCP ){ ::ReleaseMutex( m_hMutexCP ); } diff --git a/sakura_core/_main/CControlProcess.h b/sakura_core/_main/CControlProcess.h index 9579314ed0..65feb27172 100644 --- a/sakura_core/_main/CControlProcess.h +++ b/sakura_core/_main/CControlProcess.h @@ -7,7 +7,7 @@ /* Copyright (C) 2002, aroka 新規作成, YAZAKI Copyright (C) 2006, ryoji - Copyright (C) 2018-2022, Sakura Editor Organization + Copyright (C) 2018-2026, Sakura Editor Organization This source code is designed for sakura editor. Please contact the copyright holder to use this code for other purpose. @@ -55,7 +55,7 @@ class CControlProcess final : public CProcess { HANDLE m_hMutex = nullptr; //!< アプリケーション実行検出用ミューテックス HANDLE m_hMutexCP = nullptr; //!< コントロールプロセスミューテックス - HANDLE m_hEventCPInitialized = nullptr; //!< コントロールプロセス初期化完了イベント 2006.04.10 ryoji CControlTray* m_pcTray = nullptr; }; + #endif /* SAKURA_CCONTROLPROCESS_AFB90808_4287_4A11_B7FB_9CD21CF8BFD6_H_ */ diff --git a/sakura_core/_main/CControlTray.cpp b/sakura_core/_main/CControlTray.cpp index 83a8117f0c..22236d2c19 100644 --- a/sakura_core/_main/CControlTray.cpp +++ b/sakura_core/_main/CControlTray.cpp @@ -65,6 +65,18 @@ ///////////////////////////////////////////////////////////////////////// static LRESULT CALLBACK CControlTrayWndProc( HWND, UINT, WPARAM, LPARAM ); +namespace cxx { + +/*! + * @brief トップレベルウインドウを検索する + */ +HWND FindWindowW(std::wstring_view className, const std::optional& optWindowName = std::nullopt) +{ + return ::FindWindowW(std::data(std::wstring(className)), optWindowName.has_value() ? std::data(*optWindowName) : nullptr); +} + +} // namespace cxx + //Stonee, 2001/03/21 //Stonee, 2001/07/01 多重起動された場合は前回のダイアログを前面に出すようにした。 void CControlTray::DoGrep() @@ -221,8 +233,7 @@ HWND CControlTray::Create( HINSTANCE hInstance ) const auto pszProfileName = GetProfileName(); std::wstring strCEditAppName = GSTR_CEDITAPP; strCEditAppName += pszProfileName; - HWND hwndWork = ::FindWindow( strCEditAppName.c_str(), strCEditAppName.c_str() ); - if( nullptr != hwndWork ){ + if (const auto hWndTray = cxx::FindWindowW(strCEditAppName, strCEditAppName); hWndTray){ return nullptr; } @@ -277,7 +288,7 @@ HWND CControlTray::Create( HINSTANCE hInstance ) m_pcPropertyManager = new CPropertyManager(); m_pcPropertyManager->Create( GetTrayHwnd(), &m_hIcons, &m_cMenuDrawer ); - wcscpy(m_szLanguageDll, GetDllShareData().m_Common.m_sWindow.m_szLanguageDll); + m_szLanguageDll = GetDllShareData().m_Common.m_sWindow.m_szLanguageDll; return GetTrayHwnd(); } @@ -320,7 +331,7 @@ bool CControlTray::CreateTrayIcon( [[maybe_unused]] HWND hWnd ) } /* メッセージループ */ -void CControlTray::MessageLoop( void ) +void CControlTray::MessageLoop() const { //複数プロセス版 MSG msg; @@ -338,7 +349,7 @@ void CControlTray::MessageLoop( void ) } /* タスクトレイのアイコンに関する処理 */ -BOOL CControlTray::TrayMessage( HWND hDlg, DWORD dwMessage, UINT uID, HICON hIcon, const WCHAR* pszTip ) +BOOL CControlTray::TrayMessage(HWND hDlg, DWORD dwMessage, UINT uID, HICON hIcon, const WCHAR* pszTip) const { BOOL res; NOTIFYICONDATA tnd; @@ -577,7 +588,7 @@ LRESULT CControlTray::DispatchEvent( } { bool bChangeLang = wcscmp( GetDllShareData().m_Common.m_sWindow.m_szLanguageDll, m_szLanguageDll ) != 0; - wcscpy( m_szLanguageDll, GetDllShareData().m_Common.m_sWindow.m_szLanguageDll ); + m_szLanguageDll = GetDllShareData().m_Common.m_sWindow.m_szLanguageDll; std::vector values; if( bChangeLang ){ CShareData::getInstance()->ConvertLangValues(values, true); @@ -947,7 +958,7 @@ LRESULT CControlTray::DispatchEvent( } /* WM_COMMANDメッセージ処理 */ -void CControlTray::OnCommand( WORD wNotifyCode, [[maybe_unused]] WORD wID , [[maybe_unused]] HWND hwndCtl ) +void CControlTray::OnCommand(WORD wNotifyCode, [[maybe_unused]] WORD wID , [[maybe_unused]] HWND hwndCtl) const { switch( wNotifyCode ){ /* メニューからのメッセージ */ @@ -1114,10 +1125,10 @@ bool CControlTray::OpenNewEditor( ) { /* 共有データ構造体のアドレスを返す */ - DLLSHAREDATA* pShareData = &GetDllShareData(); + const auto pShareData = &GetDllShareData(); /* 編集ウィンドウの上限チェック */ - if( pShareData->m_sNodes.m_nEditArrNum >= MAX_EDITWINDOWS ){ //最大値修正 //@@@ 2003.05.31 MIK + if (MAX_EDITWINDOWS <= pShareData->m_sNodes.m_nEditArrNum) { OkMessage( nullptr, LS(STR_MAXWINDOW), MAX_EDITWINDOWS ); return false; } @@ -1228,11 +1239,15 @@ bool CControlTray::OpenNewEditor( // May 30, 2003 genta カレントディレクトリ指定を可能に //エディタプロセスを起動 DWORD dwCreationFlag = CREATE_DEFAULT_ERROR_MODE; + + if (sync) dwCreationFlag |= CREATE_SUSPENDED; + #ifdef _DEBUG // dwCreationFlag |= DEBUG_PROCESS; //2007.09.22 kobake デバッグ用フラグ #endif WCHAR szCmdLine[1024]; wcscpy_s(szCmdLine, std::size(szCmdLine), cCmdLineBuf.c_str()); - BOOL bCreateResult = CreateProcess( + + if (const auto bCreateResult = ::CreateProcessW( szEXE, // 実行可能モジュールの名前 szCmdLine, // コマンドラインの文字列 nullptr, // セキュリティ記述子 @@ -1244,7 +1259,7 @@ bool CControlTray::OpenNewEditor( &s, // スタートアップ情報 &p // プロセス情報 ); - if( !bCreateResult ){ + !bCreateResult) { // 失敗 WCHAR* pMsg; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | @@ -1267,11 +1282,28 @@ bool CControlTray::OpenNewEditor( return false; } + // タブまとめ時は起動したプロセスが立ち上がるまでしばらくタイトルバーをアクティブに保つため、強制的に同期モードにする + if (pShareData->m_Common.m_sTabBar.m_bDispTabWnd && !pShareData->m_Common.m_sTabBar.m_bDispTabWndMultiWin && !sync){ + sync = true; + } + + // MYWM_FIRST_IDLE が届くまでちょっとだけ余分に待つ // 2008.04.19 ryoji + // Note. 起動先プロセスが初期化処理中に COM 関数(SHGetFileInfo API なども含む)を実行すると、 + // その時点で COM の同期機構が動いて WaitForInputIdle は終了してしまう可能性がある(らしい)。 bool bRet = true; - if( sync ){ - // 起動したプロセスが完全に立ち上がるまでちょっと待つ. - int nResult = WaitForInputIdle( p.hProcess, 10000 ); // 最大10秒間待つ - if( nResult != 0 ){ + if (sync) + { + // エディター初期化完了イベントを作成する + SFilePath initEventName{ std::format(GSTR_EVENT_SAKURA_EP_INITIALIZED, p.dwThreadId) }; + HANDLE hEvent = ::CreateEventW(nullptr, TRUE, FALSE, initEventName); + + // エディターのメインスレッドを再開する + ::ResumeThread(p.hThread); + + // エディター初期化完了を待つ + std::array handles{ hEvent, p.hProcess }; + const auto dwRet = ::WaitForMultipleObjects(DWORD(std::size(handles)), std::data(handles), FALSE, 15000); + if (WAIT_OBJECT_0 != dwRet) { ErrorMessage( hWndParent, LS(STR_TRAY_CREATEPROC2), @@ -1280,40 +1312,6 @@ bool CControlTray::OpenNewEditor( bRet = false; } } - else{ - // タブまとめ時は起動したプロセスが立ち上がるまでしばらくタイトルバーをアクティブに保つ // 2007.02.03 ryoji - if( pShareData->m_Common.m_sTabBar.m_bDispTabWnd && !pShareData->m_Common.m_sTabBar.m_bDispTabWndMultiWin ){ - WaitForInputIdle( p.hProcess, 3000 ); - sync = true; - } - } - - // MYWM_FIRST_IDLE が届くまでちょっとだけ余分に待つ // 2008.04.19 ryoji - // Note. 起動先プロセスが初期化処理中に COM 関数(SHGetFileInfo API なども含む)を実行すると、 - // その時点で COM の同期機構が動いて WaitForInputIdle は終了してしまう可能性がある(らしい)。 - if( sync && bRet ) - { - int i; - for( i = 0; i < 200; i++ ){ - MSG msg; - DWORD dwExitCode; - if( ::PeekMessage( &msg, nullptr, MYWM_FIRST_IDLE, MYWM_FIRST_IDLE, PM_REMOVE ) ){ - if( msg.message == WM_QUIT ){ // 指定範囲外でも WM_QUIT は取り出される - ::PostQuitMessage( int(msg.wParam) ); - break; - } - // 監視対象プロセスからのメッセージなら抜ける - // そうでなければ破棄して次を取り出す - if( msg.wParam == p.dwProcessId ){ - break; - } - } - if( ::GetExitCodeProcess( p.hProcess, &dwExitCode ) && dwExitCode != STILL_ACTIVE ){ - break; // 監視対象プロセスが終了した - } - ::Sleep(10); - } - } CloseHandle( p.hThread ); CloseHandle( p.hProcess ); @@ -1335,13 +1333,9 @@ bool CControlTray::OpenNewEditor2( bool bNewWindow //!< [in] 新規エディタを新しいウインドウで開く ) { - DLLSHAREDATA* pShareData; - - /* 共有データ構造体のアドレスを返す */ - pShareData = &GetDllShareData(); - /* 編集ウィンドウの上限チェック */ - if( pShareData->m_sNodes.m_nEditArrNum >= MAX_EDITWINDOWS ){ //最大値修正 //@@@ 2003.05.31 MIK + if (const auto pShareData = &GetDllShareData(); + MAX_EDITWINDOWS <= pShareData->m_sNodes.m_nEditArrNum) { OkMessage( nullptr, LS(STR_MAXWINDOW), MAX_EDITWINDOWS ); return false; } diff --git a/sakura_core/_main/CControlTray.h b/sakura_core/_main/CControlTray.h index 5b3de53e3b..69152ab63d 100644 --- a/sakura_core/_main/CControlTray.h +++ b/sakura_core/_main/CControlTray.h @@ -73,7 +73,7 @@ class CControlTray HWND Create(HINSTANCE hInstance); /* 作成 */ bool CreateTrayIcon(HWND hWnd); // 20010412 by aroka LRESULT DispatchEvent(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); /* メッセージ処理 */ - void MessageLoop( void ); /* メッセージループ */ + void MessageLoop() const; /* メッセージループ */ void OnDestroy( void ); /* WM_DESTROY 処理 */ // 2006.07.09 ryoji int CreatePopUpMenu_L( void ); /* ポップアップメニュー(トレイ左ボタン) */ int CreatePopUpMenu_R( void ); /* ポップアップメニュー(トレイ右ボタン) */ @@ -111,8 +111,8 @@ class CControlTray static void DoGrepCreateWindow(HINSTANCE hinst, HWND, CDlgGrep& cDlgGrep); protected: void DoGrep(); //Stonee, 2001/03/21 - BOOL TrayMessage(HWND hDlg, DWORD dwMessage, UINT uID, HICON hIcon, const WCHAR* pszTip); /*!< タスクトレイのアイコンに関する処理 */ - void OnCommand(WORD wNotifyCode, WORD wID, HWND hwndCtl); /*!< WM_COMMANDメッセージ処理 */ + BOOL TrayMessage(HWND hDlg, DWORD dwMessage, UINT uID, HICON hIcon, const WCHAR* pszTip) const; /*!< タスクトレイのアイコンに関する処理 */ + void OnCommand(WORD wNotifyCode, WORD wID, HWND hwndCtl) const; /*!< WM_COMMANDメッセージ処理 */ void OnNewEditor(bool bNewWindow); //!< 2003.05.30 genta 新規ウィンドウ作成処理を切り出し static INT_PTR CALLBACK ExitingDlgProc( /*!< 終了ダイアログ用プロシージャ */ // 2006.07.02 ryoji CControlProcess から移動 diff --git a/sakura_core/_main/CNormalProcess.cpp b/sakura_core/_main/CNormalProcess.cpp index a2287972cf..c597c9d22c 100644 --- a/sakura_core/_main/CNormalProcess.cpp +++ b/sakura_core/_main/CNormalProcess.cpp @@ -22,6 +22,9 @@ #include "StdAfx.h" #include "CNormalProcess.h" + +#include "_main/CProcessFactory.h" + #include "CCommandLine.h" #include "CControlTray.h" #include "window/CEditWnd.h" // 2002/2/3 aroka @@ -81,8 +84,17 @@ bool CNormalProcess::InitializeProcess() return false; } + // エディター初期化完了イベントを開く + SFilePath initEventName{ std::format(GSTR_EVENT_SAKURA_EP_INITIALIZED, ::GetCurrentThreadId()) }; + using HandleHolder = cxx::ResourceHolder<&::CloseHandle>; + HandleHolder hEvent{ ::OpenEventW(STANDARD_RIGHTS_REQUIRED | EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, initEventName) }; + + // スコープを抜けるときシグナル状態になるようにする + using InitEventHolder = cxx::ResourceHolder<&::SetEvent>; + InitEventHolder initEvent{ hEvent.get() }; + /* 共有メモリを初期化する */ - if ( !CProcess::InitializeProcess() ){ + if (!CProcessFactory::IsExistControlProcess() && !CProcessFactory::StartControlProcess() || !CProcess::InitializeProcess()) { return false; } @@ -159,7 +171,9 @@ bool CNormalProcess::InitializeProcess() m_pcEditApp = CEditApp::getInstance(); m_pcEditApp->Create(GetProcessInstance(), nGroupId); CEditWnd* pEditWnd = m_pcEditApp->GetEditWindow(); - if( nullptr == pEditWnd->GetHwnd() ){ + + const auto hEditWnd = pEditWnd->GetHwnd(); + if (!hEditWnd) { ::ReleaseMutex( hMutex ); ::CloseHandle( hMutex ); return false; // 2009.06.23 ryoji CEditWnd::Create()失敗のため終了 @@ -195,7 +209,6 @@ bool CNormalProcess::InitializeProcess() // 2010.06.16 Moca Grepでもオプション指定を適用 pEditWnd->SetDocumentTypeWhenCreate( fi.m_nCharCode, false, nType ); pEditWnd->m_cDlgFuncList.Refresh(); // アウトラインを予め表示しておく - HWND hEditWnd = pEditWnd->GetHwnd(); if( !::IsIconic( hEditWnd ) && pEditWnd->m_cDlgFuncList.GetHwnd() ){ RECT rc; ::GetClientRect( hEditWnd, &rc ); @@ -279,8 +292,7 @@ bool CNormalProcess::InitializeProcess() pEditWnd->m_cDlgGrep.m_szFolder[nSize-1] = L'\0'; // Feb. 23, 2003 Moca Owner windowが正しく指定されていなかった - int nRet = pEditWnd->m_cDlgGrep.DoModal( GetProcessInstance(), pEditWnd->GetHwnd(), nullptr); - if( FALSE != nRet ){ + if (pEditWnd->m_cDlgGrep.DoModal(GetProcessInstance(), hEditWnd, LPCWSTR(nullptr))) { pEditWnd->GetActiveView().GetCommander().HandleCommand(F_GREP, true, 0, 0, 0, 0); }else{ // 自分はGrepでない @@ -395,7 +407,6 @@ bool CNormalProcess::InitializeProcess() //WM_SIZEをポスト { // ファイル読み込みしなかった場合にはこの WM_SIZE がアウトライン画面を配置する - HWND hEditWnd = pEditWnd->GetHwnd(); if( !::IsIconic( hEditWnd ) ){ RECT rc; ::GetClientRect( hEditWnd, &rc ); @@ -419,8 +430,9 @@ bool CNormalProcess::InitializeProcess() pEditWnd->GetDocument()->RunAutoMacro( GetDllShareData().m_Common.m_sMacro.m_nMacroOnOpened ); // 起動時マクロオプション - LPCWSTR pszMacro = CCommandLine::getInstance()->GetMacro(); - if( pEditWnd->GetHwnd() && pszMacro && pszMacro[0] != L'\0' ){ + if (const auto pszMacro = CCommandLine::getInstance()->GetMacro(); + pszMacro && pszMacro[0] != L'\0') + { LPCWSTR pszMacroType = CCommandLine::getInstance()->GetMacroType(); if( pszMacroType == nullptr || pszMacroType[0] == L'\0' || _wcsicmp(pszMacroType, L"file") == 0 ){ pszMacroType = nullptr; @@ -435,6 +447,8 @@ bool CNormalProcess::InitializeProcess() // 複数ファイル読み込み OpenFiles( pEditWnd->GetHwnd() ); + initEvent = nullptr; + return pEditWnd->GetHwnd() ? true : false; } @@ -508,26 +522,27 @@ HANDLE CNormalProcess::_GetInitializeMutex() const @date 2015.03.14 novice 新規作成 */ -void CNormalProcess::OpenFiles( HWND hwnd ) +void CNormalProcess::OpenFiles(HWND hwnd) const { EditInfo fi; CCommandLine::getInstance()->GetEditInfo( &fi ); bool bViewMode = CCommandLine::getInstance()->IsViewMode(); - int fileNum = CCommandLine::getInstance()->GetFileNum(); - if( fileNum > 0 ){ - int nDropFileNumMax = GetDllShareData().m_Common.m_sFile.m_nDropFileNumMax - 1; + if (auto fileNum = CCommandLine::getInstance()->GetFileNum(); + 0 < fileNum) + { // ファイルドロップ数の上限に合わせる - if( fileNum > nDropFileNumMax ){ + if (const auto nDropFileNumMax = GetDllShareData().m_Common.m_sFile.m_nDropFileNumMax - 1; + nDropFileNumMax < fileNum) { fileNum = nDropFileNumMax; } - int i; - for( i = 0; i < fileNum; i++ ){ + for (int i = 0; i < fileNum; ++i) { // ファイル名差し替え - wcscpy( fi.m_szPath, CCommandLine::getInstance()->GetFileName(i) ); - bool ret = CControlTray::OpenNewEditor2( GetProcessInstance(), hwnd, &fi, bViewMode ); - if( ret == false ){ + ::wcscpy_s(fi.m_szPath, CCommandLine::getInstance()->GetFileName(i)); + + // 同期モードでファイルを開いていく + if (!CControlTray::OpenNewEditor2(GetProcessInstance(), hwnd, &fi, bViewMode, true)) { break; } } diff --git a/sakura_core/_main/CNormalProcess.h b/sakura_core/_main/CNormalProcess.h index 1ec3d66cda..45c8a3d8a7 100644 --- a/sakura_core/_main/CNormalProcess.h +++ b/sakura_core/_main/CNormalProcess.h @@ -42,13 +42,13 @@ class CNormalProcess final : public CProcess { bool MainLoop() override; void OnExitProcess() override; -protected: //実装補助 HANDLE _GetInitializeMutex() const; // 2002/2/8 aroka - void OpenFiles(HWND hwnd); + void OpenFiles(HWND hEditWnd) const; private: CEditApp* m_pcEditApp = nullptr; //2007.10.23 kobake CMigemo m_cMigemo; }; + #endif /* SAKURA_CNORMALPROCESS_F2808B31_61DC_4BE0_8661_9626478AC7F9_H_ */ diff --git a/sakura_core/_main/CProcessFactory.cpp b/sakura_core/_main/CProcessFactory.cpp index a469574a8f..8afc91e6a9 100644 --- a/sakura_core/_main/CProcessFactory.cpp +++ b/sakura_core/_main/CProcessFactory.cpp @@ -10,7 +10,7 @@ Copyright (C) 2001, masami shoji Copyright (C) 2002, aroka WinMainより分離 Copyright (C) 2006, ryoji - Copyright (C) 2018-2022, Sakura Editor Organization + Copyright (C) 2018-2026, Sakura Editor Organization This source code is designed for sakura editor. Please contact the copyright holder to use this code for other purpose. @@ -68,22 +68,19 @@ CProcess* CProcessFactory::Create( HINSTANCE hInstance, LPCWSTR lpCmdLine ) // しかし、そのような場合でもミューテックスを最初に確保したコントロールプロセスが唯一生き残る。 // if( IsStartingControlProcess() ){ - if( !IsExistControlProcess() ){ + process = new CControlProcess( hInstance, lpCmdLine ); - } + } else{ - if( !IsExistControlProcess() ){ - StartControlProcess(); - } - if( WaitForInitializedControlProcess() ){ // 2006.04.10 ryoji コントロールプロセスの初期化完了待ち + process = new CNormalProcess( hInstance, lpCmdLine ); - } + } return process; } -bool CProcessFactory::ProfileSelect( HINSTANCE hInstance, LPCWSTR lpCmdLine ) +bool CProcessFactory::ProfileSelect(HINSTANCE hInstance, LPCWSTR lpCmdLine) const { // May 30, 2000 genta // 実行ファイル名をもとに漢字コードを固定する. @@ -115,7 +112,7 @@ bool CProcessFactory::ProfileSelect( HINSTANCE hInstance, LPCWSTR lpCmdLine ) @author aroka @date 2002/01/03 */ -bool CProcessFactory::IsValidVersion() +bool CProcessFactory::IsValidVersion() const { // Windowsバージョンは廃止。 // 動作可能バージョン(=windows7以降)でなければ起動できない。 @@ -128,7 +125,7 @@ bool CProcessFactory::IsValidVersion() @author aroka @date 2002/01/03 作成 2002/01/18 変更 */ -bool CProcessFactory::IsStartingControlProcess() +bool CProcessFactory::IsStartingControlProcess() const { return CCommandLine::getInstance()->IsNoWindow(); } @@ -140,7 +137,7 @@ bool CProcessFactory::IsStartingControlProcess() @date 2002/01/03 @date 2006/04/10 ryoji */ -bool CProcessFactory::IsExistControlProcess() +/* static */ bool CProcessFactory::IsExistControlProcess() { const auto pszProfileName = GetProfileName(); std::wstring strMutexSakuraCp = GSTR_MUTEX_SAKURA_CP; @@ -155,7 +152,6 @@ bool CProcessFactory::IsExistControlProcess() return false; // コントロールプロセスは存在していないか、まだ CreateMutex() してない } -// From Here Aug. 28, 2001 genta /*! @brief コントロールプロセスを起動する @@ -166,10 +162,26 @@ bool CProcessFactory::IsExistControlProcess() @date Aug. 28, 2001 @date 2008.05.05 novice GetModuleHandle(NULL)→NULLに変更 */ -bool CProcessFactory::StartControlProcess() +/* static */ bool CProcessFactory::StartControlProcess() { MY_RUNNINGTIMER(cRunningTimer,L"StartControlProcess" ); + using HandleHolder = cxx::ResourceHolder<&::CloseHandle>; + + const auto pszProfileName = GetProfileName(); + + // 初期化完了イベントの名前を組み立てる + SFilePath initEventName{ GSTR_EVENT_SAKURA_CP_INITIALIZED }; + initEventName.append(pszProfileName); + + // 初期化完了イベントを作成する + HandleHolder hEvent{ ::CreateEventW(nullptr, TRUE, FALSE, initEventName) }; + if (!hEvent || ERROR_ALREADY_EXISTS == ::GetLastError()) { + // L"エディタまたはシステムがビジー状態です。\nしばらく待って開きなおしてください。 + TopErrorMessage(nullptr, LS(STR_ERR_DLGPROCFACT5)); + return false; + } + // プロセスの起動 PROCESS_INFORMATION p; STARTUPINFO s; @@ -229,42 +241,16 @@ bool CProcessFactory::StartControlProcess() return false; } - ::CloseHandle( p.hThread ); - ::CloseHandle( p.hProcess ); - - return true; -} -// To Here Aug. 28, 2001 genta + HandleHolder hProcess{ p.hProcess }; + HandleHolder hThread{ p.hThread }; -/*! - @brief コントロールプロセスの初期化完了イベントを待つ。 - - @author ryoji by assitance with karoto - @date 2006/04/10 -*/ -bool CProcessFactory::WaitForInitializedControlProcess() -{ - // 初期化完了イベントを待つ - // - // Note: コントロールプロセス側は多重起動防止用ミューテックスを ::CreateMutex() で - // 作成するよりも先に初期化完了イベントを ::CreateEvent() で作成する。 - // - const auto pszProfileName = GetProfileName(); - std::wstring strInitEvent = GSTR_EVENT_SAKURA_CP_INITIALIZED; - strInitEvent += pszProfileName; - HANDLE hEvent; - hEvent = ::CreateEventW( nullptr, TRUE, FALSE, strInitEvent.c_str() ); - if( nullptr == hEvent ){ - TopErrorMessage( nullptr, L"エディタまたはシステムがビジー状態です。\nしばらく待って開きなおしてください。" ); + // 初期化完了を待つ + std::array handles{ hEvent.get(), hProcess.get()}; + if (const auto dwRet = ::WaitForMultipleObjects(DWORD(std::size(handles)), std::data(handles), FALSE, 15000); WAIT_OBJECT_0 != dwRet) { + // L"エディタまたはシステムがビジー状態です。\nしばらく待って開きなおしてください。 + TopErrorMessage(nullptr, LS(STR_ERR_DLGPROCFACT5)); return false; } - DWORD dwRet; - dwRet = ::WaitForSingleObject( hEvent, 30000 ); - if( WAIT_TIMEOUT == dwRet ){ // コントロールプロセスの初期化が終了しない - ::CloseHandle( hEvent ); - TopErrorMessage( nullptr, L"エディタまたはシステムがビジー状態です。\nしばらく待って開きなおしてください。" ); - return false; - } - ::CloseHandle( hEvent ); + return true; } diff --git a/sakura_core/_main/CProcessFactory.h b/sakura_core/_main/CProcessFactory.h index aabb409955..0db3cc1d79 100644 --- a/sakura_core/_main/CProcessFactory.h +++ b/sakura_core/_main/CProcessFactory.h @@ -7,7 +7,7 @@ /* Copyright (C) 2002, aroka 新規作成 Copyright (C) 2006, ryoji - Copyright (C) 2018-2022, Sakura Editor Organization + Copyright (C) 2018-2026, Sakura Editor Organization This source code is designed for sakura editor. Please contact the copyright holder to use this code for other purpose. @@ -35,14 +35,15 @@ class CProcess; */ class CProcessFactory { public: + static bool IsExistControlProcess(); + static bool StartControlProcess(); + CProcess* Create( HINSTANCE hInstance, LPCWSTR lpCmdLine ); -protected: + private: - bool IsValidVersion(); - bool ProfileSelect(HINSTANCE hInstance, LPCWSTR lpCmdLine); - bool IsStartingControlProcess(); - bool IsExistControlProcess(); - bool StartControlProcess(); - bool WaitForInitializedControlProcess(); // 2006.04.10 ryoji コントロールプロセスの初期化完了イベントを待つ + bool IsValidVersion() const; + bool ProfileSelect(HINSTANCE hInstance, LPCWSTR lpCmdLine) const; + bool IsStartingControlProcess() const; }; + #endif /* SAKURA_CPROCESSFACTORY_5006562F_7795_40FF_AA4C_FFB94842F7C5_H_ */ diff --git a/sakura_core/config/system_constants.h b/sakura_core/config/system_constants.h index ec3292a897..c88a8b3647 100644 --- a/sakura_core/config/system_constants.h +++ b/sakura_core/config/system_constants.h @@ -11,7 +11,7 @@ */ /* Copyright (C) 2008, kobake - Copyright (C) 2018-2022, Sakura Editor Organization + Copyright (C) 2018-2026, Sakura Editor Organization SPDX-License-Identifier: Zlib */ @@ -585,6 +585,9 @@ //! 初期化完了イベント #define GSTR_EVENT_SAKURA_CP_INITIALIZED (L"EventSakuraEditorCPInitialized" _T(CON_SKR_MACHINE_SUFFIX_) _T(_CODE_SUFFIX_) _T(_DEBUG_SUFFIX_) _T(STR_SHAREDATA_VERSION)) +//! 初期化完了イベント +inline constexpr std::wstring_view GSTR_EVENT_SAKURA_EP_INITIALIZED = L"EventSakuraEditorEPInitialized_{:d}"; + // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- // // ウィンドウクラス // // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- // diff --git a/src/main/cpp/cxx/ResourceHolder.hpp b/src/main/cpp/cxx/ResourceHolder.hpp index c66641bad9..a837c0e817 100644 --- a/src/main/cpp/cxx/ResourceHolder.hpp +++ b/src/main/cpp/cxx/ResourceHolder.hpp @@ -74,6 +74,12 @@ struct ResourceHolder { } + ResourceHolder(const Me&) = delete; + Me& operator=(const Me&) = delete; + + ResourceHolder(Me&& other) noexcept = default; + Me& operator=(Me&& rhs) noexcept = default; + // bound 更新(OwnedResourceHolder の updateContext 相当) template void updateBound(Bound&&... bound) noexcept @@ -84,15 +90,19 @@ struct ResourceHolder pointer get() const noexcept { return m_Holder.get(); } pointer release() noexcept { return m_Holder.release(); } + void reset(resource_type p) { m_Holder.reset(p); } Me& operator = (resource_type t) { - m_Holder.reset(t); + reset(t); return *this; } + explicit operator bool() const noexcept { return static_cast(m_Holder); } + /* implicit */ operator resource_type() const noexcept { return get(); } }; } // end of namespace cxx + #endif /* SAKURA_RESOURCEHOLDER_0BDC8DF8_F2AC_486F_8BF7_FD7E5D1B76DE_H_ */ diff --git a/src/test/cpp/tests1/test-cdlgopenfile.cpp b/src/test/cpp/tests1/test-cdlgopenfile.cpp index 58d6275e00..5b3d9c7c11 100644 --- a/src/test/cpp/tests1/test-cdlgopenfile.cpp +++ b/src/test/cpp/tests1/test-cdlgopenfile.cpp @@ -252,10 +252,7 @@ TEST_F(SelectFileTest, SelectFile001) constexpr bool resolvePath = true; // パス解決する場合のテスト std::jthread j([&] { - if (const auto hWndDlgOpenFile = WaitForDialog(L"開く")) { - EmulateSetValue(GetFocusedElement(), path.c_str()); // 絶対パスを入れる - EmulateHitEnter(); - } + EmulateEnterOpenFileName(path); }); EXPECT_THAT(CDlgOpenFile::SelectFile(hWndDlg, hWndFolder, L"*.ini", resolvePath, EFITER_NONE), IsTrue()); @@ -273,10 +270,7 @@ TEST_F(SelectFileTest, SelectFile002) constexpr bool resolvePath = false; // パス解決しない場合のテスト std::jthread j([&] { - if (const auto hWndDlgOpenFile = WaitForDialog(L"開く")) { - EmulateSetValue(GetFocusedElement(), path.c_str()); // 絶対パスを入れる - EmulateHitEnter(); - } + EmulateEnterOpenFileName(path); }); EXPECT_THAT(CDlgOpenFile::SelectFile(hWndDlg, hWndFolder, L"*.ini", resolvePath, EFITER_NONE), IsTrue()); diff --git a/src/test/cpp/tests1/test-window.cpp b/src/test/cpp/tests1/test-window.cpp index b98ca737e8..191f1b6025 100644 --- a/src/test/cpp/tests1/test-window.cpp +++ b/src/test/cpp/tests1/test-window.cpp @@ -1627,13 +1627,13 @@ TEST_F(EditWndTest, ShowPropType003) const auto hWndPage = GetActivePage(hWndDlg); EmulateInvokeButton(hWndPage, L"文字色統一(<)..."); - if (const auto hWndDlgSameColor = WaitForWindow(MAKEINTRESOURCEW(dialog::ModalDialogCloser::DIALOG_CLASS), L"文字色統一")) { + if (const auto hWndDlgSameColor = WaitForDialog(L"文字色統一")) { EmulateInvokeButton(hWndDlgSameColor, L"全チェック(A)"); EmulateInvokeButton(hWndDlgSameColor, L"全解除(N)"); EmulateInvokeButton(hWndDlgSameColor, L"OK"); } EmulateInvokeButton(hWndPage, L"背景色統一(>)..."); - if (const auto hWndDlgSameColor = WaitForWindow(MAKEINTRESOURCEW(dialog::ModalDialogCloser::DIALOG_CLASS), L"背景色統一")) { + if (const auto hWndDlgSameColor = WaitForDialog(L"背景色統一")) { EmulateInvokeButton(hWndDlgSameColor, L"全チェック(A)"); EmulateInvokeButton(hWndDlgSameColor, L"全解除(N)"); EmulateInvokeButton(hWndDlgSameColor, L"OK"); diff --git a/src/test/cpp/tests1/test-winmain.cpp b/src/test/cpp/tests1/test-winmain.cpp index 81abd5f203..35b471adcb 100644 --- a/src/test/cpp/tests1/test-winmain.cpp +++ b/src/test/cpp/tests1/test-winmain.cpp @@ -23,6 +23,7 @@ #include "basis/CMyString.h" #include "mem/CNativeW.h" #include "env/DLLSHAREDATA.h" +#include "env/CSakuraEnvironment.h" #include "util/file.h" #include "config/system_constants.h" #include "_main/CCommandLine.h" @@ -41,6 +42,8 @@ void extract_zip_resource(WORD id, const std::optional& o namespace cxx { +HWND FindWindowW(std::wstring_view className, const std::optional& optWindowName = std::nullopt); + /*! * @brief システムエラーを例外として発生させる * @@ -52,14 +55,6 @@ NORETURN void raise_system_error(const std::string& message) { throw std::system_error(int(::GetLastError()), std::system_category(), message); } -/*! - * @brief トップレベルウインドウを検索する - */ -HWND FindWindowW(std::wstring_view className, const std::optional& optWindowName = std::nullopt) -{ - return ::FindWindowW(std::data(std::wstring(className)), optWindowName.has_value() ? std::data(*optWindowName) : nullptr); -} - /*! * @brief システムディレクトリのパスを取得する * @@ -108,7 +103,7 @@ void writeTextFile( } //! HANDLE型のスマートポインタ -class HandleHolder final : public cxx::ResourceHolder<&::CloseHandle> +class HandleHolder : public cxx::ResourceHolder<&::CloseHandle> { private: using Base = cxx::ResourceHolder<&::CloseHandle>; @@ -120,69 +115,182 @@ class HandleHolder final : public cxx::ResourceHolder<&::CloseHandle> */ using Base::ResourceHolder; - void lock() const noexcept + virtual ~HandleHolder() = default; + + void lock() { Lock(INFINITE); //無限に待つ } - bool try_lock() const noexcept + bool try_lock() { return Lock(0); //ロック取得を試行 } template - bool try_lock_for(const std::chrono::duration& rel_time) const noexcept + bool try_lock_for(const std::chrono::duration& rel_time) { const auto milliseconds = std::chrono::duration_cast(rel_time); return Lock(DWORD(milliseconds.count())); } - bool Lock(DWORD dwTimeout = INFINITE) const noexcept + bool unlock() + { + return Unlock(); + } + + virtual bool Lock(DWORD dwTimeout = INFINITE) { // ロック取得を試行 const auto dwRet = ::WaitForSingleObject(get(), dwTimeout); + if (WAIT_FAILED == dwRet) { + // エラー + return false; + } + return WAIT_OBJECT_0 == dwRet || WAIT_ABANDONED == dwRet; } + + virtual bool Unlock() + { + return true; + } +}; + +/*! + * @brief 起動したプロセスオブジェクト + * + * @note 使い物になるかどうか試作してみた + */ +class ProcessHolder : public cxx::HandleHolder +{ +private: + using Base = cxx::HandleHolder; + using Me = ProcessHolder; + +public: + explicit ProcessHolder( + HANDLE hProcess, + DWORD dwProcessId, + DWORD dwThreadId + ) + : Base(hProcess) + , dwProcessId(dwProcessId) + , dwThreadId(dwThreadId) + { + } + + ProcessHolder(const Me&) = delete; + Me& operator=(const Me&) = delete; + + ProcessHolder(Me&& other) noexcept = default; + Me& operator=(Me&& rhs) noexcept = default; + + DWORD dwProcessId; + DWORD dwThreadId; +}; + +/*! + * @brief 起動したエディタープロセスオブジェクト + * + * @note 使い物になるかどうか試作してみた + */ +class EditorProcessHolder : public cxx::ProcessHolder +{ +private: + using Base = cxx::ProcessHolder; + using Me = EditorProcessHolder; + +public: + explicit EditorProcessHolder( + HANDLE hProcess, + DWORD dwProcessId, + DWORD dwThreadId, + HWND hWnd + ) + : Base(hProcess, dwProcessId, dwThreadId) + , hWnd(hWnd) + { + } + + EditorProcessHolder(const Me&) = delete; + Me& operator=(const Me&) = delete; + + EditorProcessHolder(Me&& other) noexcept = default; + Me& operator=(Me&& rhs) noexcept = default; + + HWND hWnd; }; } // namespace cxx namespace testing { +/*! + * @brief コマンドライン引数を引用符で囲む + * + * @param arg [in] コマンドライン引数 + * @return 引用符で囲まれたコマンドライン引数 + */ +std::wstring QuoteArg(const std::wstring_view arg) +{ + const auto endsWithQuote = arg.ends_with(LR"(")"); + const auto containsQuotes = std::wstring_view::npos != arg.find_first_of(LR"(")"); + if (const auto needsEscape = std::wstring_view::npos != arg.find_first_of(L"\t "); + !endsWithQuote && containsQuotes) + { + return std::format(LR"("{:s}")", std::regex_replace(arg.data(), std::wregex(LR"(")"), LR"("")")); + } + else if (!endsWithQuote && needsEscape) + { + return std::format(LR"("{:s}")", arg); + } + else + { + return std::wstring(arg); + } +} + /*! * @brief サクラエディタのプロセスを起動する * - * @tparam T コマンドライン引数のコンテナ型 * @param si スタートアップ情報 * @param args コマンドライン引数 * @param optWorkingDir カレントディレクトリ(省略した場合は起動元と同じ) * @param optProfileName プロファイル名(省略した場合は指定なし) - * @return 起動したプロセスのハンドルオブジェクト + * @return 起動したプロセスオブジェクト * @note 使い物になるかどうか試作してみた */ -template - requires std::ranges::range && std::convertible_to, std::wstring_view> -cxx::HandleHolder _CreateSakuraProcess( +cxx::ProcessHolder CreateSakuraProcess( STARTUPINFO& si, - const T& args, + std::vector& args, const std::optional& optWorkingDir = std::nullopt, - const std::optional& optProfileName = std::nullopt + const std::optional& optProfileName = std::nullopt, + DWORD dwCreationFlag = CREATE_DEFAULT_ERROR_MODE ) { const auto exePath = GetExeFileName(); - auto strCommandLine = std::format(LR"("{}")", exePath.native()); - strCommandLine = std::accumulate(std::begin(args), std::end(args), strCommandLine, [](const std::wstring& a, std::wstring_view b) { return std::format(LR"({} {})", a, b); }); if (optProfileName.has_value()) { - strCommandLine += std::format(LR"( -PROF="{}")", *optProfileName); + args.emplace(args.begin(), std::format(LR"(-PROF="{}")", *optProfileName)); } - auto lpszCommandLine = std::data(strCommandLine); + args.emplace(args.begin(), std::format(LR"("{:s}")", exePath.native())); - DWORD dwCreationFlag = CREATE_DEFAULT_ERROR_MODE; + auto strCommandLine = std::accumulate(args.begin(), args.end(), std::wstring(), [](const std::wstring& a, std::wstring_view b) { return std::format(LR"({} {})", a, QuoteArg(b)); }); + strCommandLine.erase(strCommandLine.cbegin()); // 先頭のスペースを削除 + + auto lpszCommandLine = std::data(strCommandLine); - LPCWSTR lpszWorkingDir = optWorkingDir.has_value() ? optWorkingDir.value().c_str() : nullptr; + LPCWSTR lpszWorkingDir = nullptr; + if (optWorkingDir.has_value()) { + if (const auto attr = ::GetFileAttributesW(optWorkingDir->c_str()); + INVALID_FILE_ATTRIBUTES != attr && (attr & FILE_ATTRIBUTE_DIRECTORY)) + { + lpszWorkingDir = optWorkingDir->c_str(); + } + } // プロセス情報(出力用構造体なので値は入れない) PROCESS_INFORMATION pi{}; @@ -207,17 +315,17 @@ cxx::HandleHolder _CreateSakuraProcess( // 開いたハンドルは使わないので閉じておく ::CloseHandle(pi.hThread); - return cxx::HandleHolder(pi.hProcess); + return cxx::ProcessHolder{ pi.hProcess, pi.dwProcessId, pi.dwThreadId }; } /*! * @brief コントロールプロセスを起動する * * @param profileName プロファイル名 - * @return コントロールプロセスのプロセスID + * @return 起動したプロセスオブジェクト * @note 使い物になるかどうか試作してみた */ -DWORD CreateControlProcess(std::wstring_view profileName) +cxx::ProcessHolder CreateControlProcess(std::wstring_view profileName) { // 初期化完了イベントの名前を決める SFilePath initEventName{ GSTR_EVENT_SAKURA_CP_INITIALIZED }; @@ -225,7 +333,7 @@ DWORD CreateControlProcess(std::wstring_view profileName) // プロセス起動前に初期化完了イベントを作成する cxx::HandleHolder hEvent = ::CreateEventW(nullptr, TRUE, FALSE, initEventName); - if (!hEvent) { + if (!hEvent || ERROR_ALREADY_EXISTS == ::GetLastError()) { cxx::raise_system_error("create event failed."); } @@ -237,16 +345,73 @@ DWORD CreateControlProcess(std::wstring_view profileName) si.lpTitle = std::data(title); si.wShowWindow = SW_SHOWDEFAULT; + std::vector commandArgs{ LR"(-NOWIN)" }; + // コントロールプロセスを起動する - const auto cp = _CreateSakuraProcess(si, std::array{ LR"(-NOWIN)" }, cxx::GetSystemDirectoryW(), profileName); + auto cp = CreateSakuraProcess(si, commandArgs, cxx::GetSystemDirectoryW(), profileName); + EXPECT_THAT(cp, NotNull()); // 初期化完了を待つ - if (!hEvent.try_lock_for(std::chrono::milliseconds(60000))){ - cxx::raise_system_error("waitEvent is timeout."); - } + std::array handles{ hEvent.get(), cp.get() }; + ::WaitForMultipleObjects(DWORD(std::size(handles)), std::data(handles), FALSE, 15000); - // プロセスIDを取得して返す - return ::GetProcessId(cp); + // プロセスオブジェクトを返す + return cxx::ProcessHolder{ cp.release(), cp.dwProcessId, cp.dwThreadId }; +} + +/*! + * @brief プロセスを起動する + * + * @tparam T コマンドライン引数のコンテナ型 + * @param args コマンドライン引数 + * @param profileName プロファイル名 + * @return 起動したプロセスオブジェクト + * @note 使い物になるかどうか試作してみた + */ +template + requires std::ranges::range && std::convertible_to, std::wstring_view> +cxx::ProcessHolder CreateSakuraProcess( + const T& args, + std::wstring_view profileName, + DWORD dwCreationFlag = CREATE_DEFAULT_ERROR_MODE +) +{ + // コマンドライン引数の編集用vector + std::vector commandArgs{ std::begin(args), std::end(args) }; + + // スタートアップ情報(入力用構造体なので値を入れる) + STARTUPINFO si = { sizeof(STARTUPINFO) }; + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWDEFAULT; + + // エディタープロセスを起動する + auto ep = CreateSakuraProcess(si, commandArgs, std::nullopt, profileName, dwCreationFlag); + EXPECT_THAT(ep, NotNull()); + + // プロセスオブジェクトを返す + return cxx::ProcessHolder{ ep.release(), ep.dwProcessId, ep.dwThreadId }; +} + +/*! + * @brief 編集ウィンドウを列挙するコールバック関数 + * + * @param hWnd 列挙されたウィンドウのハンドル + * @param lParam 列挙の呼び出し元から渡されたパラメータ(HWND* を期待) + * @retval TRUE 列挙続行(編集ウィンドウではなかった。) + * @retval FALSE 列挙停止(編集ウィンドウが見付かった。) + */ +BOOL CALLBACK EnumEditorWindowProc( + _In_ HWND hWnd, + _In_ LPARAM lParam +) +{ + if (!hWnd || !lParam || !IsSakuraMainWindow(hWnd)) return TRUE; // 検索続行 + + auto phWndFound = std::bit_cast(lParam); + + *phWndFound = hWnd; + + return FALSE; } /*! @@ -255,14 +420,15 @@ DWORD CreateControlProcess(std::wstring_view profileName) * @tparam T コマンドライン引数のコンテナ型 * @param args コマンドライン引数 * @param profileName プロファイル名 - * @return 起動したプロセスのハンドルオブジェクト + * @return 起動したプロセスオブジェクト * @note 使い物になるかどうか試作してみた */ template requires std::ranges::range && std::convertible_to, std::wstring_view> -cxx::HandleHolder CreateEditorProcess( +cxx::EditorProcessHolder CreateEditorProcess( const T& args, - std::wstring_view profileName + std::wstring_view profileName, + bool sync = true ) { // コマンドライン引数の編集用vector @@ -278,7 +444,41 @@ cxx::HandleHolder CreateEditorProcess( si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWDEFAULT; - return _CreateSakuraProcess(si, commandArgs, std::nullopt, profileName); + DWORD dwCreationFlag = CREATE_DEFAULT_ERROR_MODE; + if (sync) dwCreationFlag |= CREATE_SUSPENDED; + + // エディタープロセスを起動する + auto ep = CreateSakuraProcess(si, commandArgs, std::nullopt, profileName, dwCreationFlag); + EXPECT_THAT(ep, NotNull()); + + if (!sync) return cxx::EditorProcessHolder{ ep.release(), ep.dwProcessId, ep.dwThreadId, HWND(nullptr) }; + + // エディターのメインスレッドを開く + cxx::HandleHolder hThread{ ::OpenThread(THREAD_SUSPEND_RESUME, FALSE, ep.dwThreadId) }; + + // 初期化完了イベントを作成する + SFilePath initEventName{ std::format(GSTR_EVENT_SAKURA_EP_INITIALIZED, ep.dwThreadId) }; + cxx::HandleHolder hEvent{ ::CreateEventW(nullptr, TRUE, FALSE, initEventName) }; + + // エディターのメインスレッドを再開する + ::ResumeThread(hThread); + + // エディター初期化完了を待つ + std::array handles{ hEvent.get(), ep.get() }; + if (const auto dwRet = ::WaitForMultipleObjects(DWORD(std::size(handles)), std::data(handles), FALSE, 30000); WAIT_OBJECT_0 != dwRet) { + return cxx::EditorProcessHolder{ ep.release(), ep.dwProcessId, ep.dwThreadId, nullptr }; + } + + // メインウインドウを取得する + HWND hWndFound = nullptr; + + // スレッドに含まれるウインドウを列挙する + ::EnumThreadWindows(ep.dwThreadId, EnumEditorWindowProc, LPARAM(&hWndFound)); + + EXPECT_THAT(hWndFound, NotNull()); + + // プロセスオブジェクトを返す + return cxx::EditorProcessHolder{ ep.release(), ep.dwProcessId, ep.dwThreadId, hWndFound }; } //! 外部ウインドウにクローズを要求する @@ -286,35 +486,11 @@ void RequestForeignWindowClose(HWND hWnd) { // ウインドウが閉じられるまで繰り返す while (::IsWindow(hWnd)) { - // ウインドウにクローズを要求する - if (!::SendMessageTimeoutW(hWnd, WM_CLOSE, 0, 0, - SMTO_NOTIMEOUTIFNOTHUNG | SMTO_ERRORONEXIT, - 5000, - nullptr - )) { - // Sendが失敗したらPostしておく - ::PostMessageW(hWnd, WM_CLOSE, 0, 0); - - // 少し待つ - ::Sleep(100); - } - } -} + // プロセス間通信なのでポストする + ::PostMessageW(hWnd, WM_CLOSE, 0, 0); -//! 外部プロセスの終了を待つ -void WaitForForeignProcessExit(const cxx::HandleHolder& process) -{ - // 編集ウインドウが閉じられた後、プロセスが完全に終了するまで待つ - if (!process.try_lock_for(std::chrono::milliseconds(45000))) { - // 終了できないなら強制終了させる - if (const auto exitCode = 1; !::TerminateProcess(process.get(), exitCode)) { - cxx::raise_system_error("waitProcess is timeout and terminate process failed."); - } - - // TerminateProcess は非同期なので操作完了を待つ - if (!process.try_lock_for(std::chrono::milliseconds(5000))) { - cxx::raise_system_error("waitProcess is timeout and force terminate is timeout."); - } + // 少し待つ + ::Sleep(100); } } @@ -346,7 +522,7 @@ void TerminateControlProcess( // プロセス情報の問い合せを行うためのハンドルを開く // タイムアウト時に強制終了へフォールバックできるよう、TERMINATE 権限も付与する - cxx::HandleHolder process = ::OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE, FALSE, dwControlProcessId); + cxx::HandleHolder process{ ::OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE, FALSE, dwControlProcessId) }; if (!process) { // プロセスIDが無効は「既に終了している」なので、除外する if (ERROR_INVALID_PARAMETER == ::GetLastError()) { @@ -356,7 +532,7 @@ void TerminateControlProcess( } // メインウインドウが閉じられた後、プロセスが完全に終了するまで待つ - WaitForForeignProcessExit(process); + process.lock(); } } // namespace testing @@ -497,10 +673,11 @@ struct TWinMainTest : public T, public window::UiaTestSuite { void CControlProcess_StartAndTerminate(std::wstring_view profileName) const { // コントロールプロセスを起動する - const auto dwControlProcessId = testing::CreateControlProcess(profileName); + auto cp = testing::CreateControlProcess(profileName); + EXPECT_THAT(cp, NotNull()); // コントロールプロセスに終了指示を出して終了を待つ - testing::TerminateControlProcess(profileName, dwControlProcessId); + testing::TerminateControlProcess(profileName, cp.dwProcessId); } }; @@ -655,9 +832,6 @@ TEST_P(WinMainTest, runEditorProcess) fs.close(); } - // コントロールプロセスを起動する - const auto dwControlProcessId = testing::CreateControlProcess(profileName); - // 起動時実行マクロの中身を作る constexpr std::array macroCommands = { L"Down();"sv, @@ -786,7 +960,7 @@ TEST_P(WinMainTest, runEditorProcess) EXPECT_EXIT({ StartEditorProcess(command); }, ::testing::ExitedWithCode(0), ".*" ); // コントロールプロセスに終了指示を出して終了を待つ - testing::TerminateControlProcess(profileName, dwControlProcessId); + testing::TerminateControlProcess(profileName); // コントロールプロセスが終了すると、INIファイルが作成される EXPECT_THAT(fexist(iniPath), IsTrue()); @@ -817,6 +991,162 @@ struct WinMainFuncTest : public TWinMainTest<::testing::Test> { } }; +/*! + * @brief WinMainを起動してみるテスト + * プログラムが起動する正常ルートに潜む障害を検出するためのもの。 + * コントロールプロセス起動の失敗をテストする。(ミューテックス競合) + */ +TEST_F(WinMainFuncTest, CreateControlProcess101) +{ + // テスト用プロファイル名 + const auto profileName{ GetProfileName() }; + + // ミューテックスの名前を組み立てる + SFilePath szMutexName{ GSTR_MUTEX_SAKURA_CP }; + szMutexName += profileName; + + // ミューテックスを作成してロックする + cxx::HandleHolder hMutex{ ::CreateMutexW(nullptr, TRUE, szMutexName) }; + EXPECT_THAT(hMutex, NotNull()); + + // コントロールプロセスを起動する + EXPECT_EXIT({ StartEditorProcess(std::format(LR"(-NOWIN -PROF="{:s}")", profileName)); }, ::testing::ExitedWithCode(0), ".*"); // たぶんバグです。エラー終了なのに0を返してる。 +} + +/*! + * @brief WinMainを起動してみるテスト + * プログラムが起動する正常ルートに潜む障害を検出するためのもの。 + * コントロールプロセス起動の失敗をテストする。(異なるバージョン) + */ +TEST_F(WinMainFuncTest, CreateControlProcess102) +{ + // テスト用プロファイル名 + const auto profileName{ GetProfileName() }; + + // 共有データの名前を組み立てる + SFilePath shareDataName{ GSTR_SHAREDATA }; + shareDataName.append(profileName); + + // ファイルマッピングオブジェクトを作る + const auto hFileMap = ::CreateFileMappingW( + INVALID_HANDLE_VALUE, + nullptr, + PAGE_READWRITE | SEC_COMMIT, + 0, + sizeof(DLLSHAREDATA), + shareDataName + ); + + EXPECT_THAT(hFileMap, NotNull()); + + // スマートポインターに入れる + cxx::HandleHolder fileMapHolder{ hFileMap }; + + // ファイルマッピングオブジェクトをマップする + using MappedDataHolder = cxx::ResourceHolder<&::UnmapViewOfFile, DLLSHAREDATA*>; + MappedDataHolder mappedData = (DLLSHAREDATA*)::MapViewOfFile( + hFileMap, + FILE_MAP_ALL_ACCESS, + 0, + 0, + 0 + ); + + EXPECT_THAT(mappedData, NotNull()); + + auto pShareData = static_cast(mappedData); + + EXPECT_THAT(pShareData, NotNull()); + + pShareData->m_nSize = sizeof(DLLSHAREDATA) + 1; + + // コントロールプロセスを起動する + EXPECT_EXIT({ StartEditorProcess(std::format(LR"(-NOWIN -PROF="{:s}")", profileName)); }, ::testing::ExitedWithCode(0), ".*"); // たぶんバグです。エラー終了なのに0を返してる。 +} + +/*! + * @brief コントロールプロセス起動の失敗をテストする。(初期化完了イベント作成済み) + */ +TEST_F(WinMainFuncTest, CreateControlProcess103) +{ + // テスト用プロファイル名 + const auto profileName{ GetProfileName() }; + + // 初期化完了イベントの名前を決める + SFilePath initEventName{ GSTR_EVENT_SAKURA_CP_INITIALIZED }; + initEventName += profileName; + + // プロセス起動前に初期化完了イベントを作成する + cxx::HandleHolder hEvent = ::CreateEventW(nullptr, TRUE, FALSE, initEventName); + + // エディタープロセスを起動する + std::array args{}; + auto ep = testing::CreateEditorProcess(args, profileName, false); + EXPECT_THAT(ep, NotNull()); + + // プロセスが完全に終了するまで待つ + ep.lock(); +} + +/*! + * @brief WinMainを起動してみるテスト + * プログラムが起動する正常ルートに潜む障害を検出するためのもの。 + * 複数ファイルを開くパターンのテスト。 + */ +TEST_F(WinMainFuncTest, CreateEditorProcess001) +{ + // テスト用プロファイル名 + const auto profileName{ GetProfileName() }; + + // コマンドラインを組み立てる + std::array args{ + gm_TestDataPath.native(), // 1つ目のファイル + gm_TestDataPath.native(), // 2つ目のファイルはCNormalProcess::OpenFilesで処理される + gm_TestDataPath.native(), // 3つ目のファイルもCNormalProcess::OpenFilesで処理される + }; + + // エディタープロセスを起動する + auto ep = testing::CreateEditorProcess(args, profileName); + EXPECT_THAT(ep, NotNull()); + EXPECT_THAT(ep.hWnd, NotNull()); + + // 編集ウインドウにクローズを要求する + testing::RequestForeignWindowClose(ep.hWnd); + + // 編集ウインドウが閉じられた後、プロセスが完全に終了するまで待つ + ep.lock(); + + // コントロールプロセスに終了指示を出して終了を待つ + testing::TerminateControlProcess(profileName); +} + +/*! + * @brief WinMainを起動してみるテスト + * プログラムが起動する正常ルートに潜む障害を検出するためのもの。 + * エディター起動の失敗をテストする。 + */ +TEST_F(WinMainFuncTest, CreateEditorProcess101) +{ + // テスト用プロファイル名 + const auto profileName{ GetProfileName() }; + + // ミューテックスの名前を組み立てる + SFilePath szMutexName{ GSTR_MUTEX_SAKURA_CP }; + szMutexName += profileName; + + // ミューテックスを作成してロックする + cxx::HandleHolder hMutex{ ::CreateMutexW(nullptr, TRUE, szMutexName) }; + EXPECT_THAT(hMutex, NotNull()); + + // エディタープロセスを起動する + std::array args{}; + auto ep = testing::CreateEditorProcess(args, profileName, false); + EXPECT_THAT(ep, NotNull()); + + // プロセスが完全に終了するまで待つ + ep.lock(); +} + /*! * @brief WinMainを起動してみるテスト * プログラムが起動する正常ルートに潜む障害を検出するためのもの。 @@ -841,36 +1171,30 @@ TEST_F(WinMainFuncTest, DoGrep001) cxx::writeTextFile(iniPath, iniLines); // コントロールプロセスを起動する - const auto dwControlProcessId = testing::CreateControlProcess(profileName); + auto cp = testing::CreateControlProcess(profileName); + EXPECT_THAT(cp, NotNull()); std::array args{ LR"(-GREPMODE)"s, - LR"(-GKEY="test")"s, - LR"(-GFILE="*.*;#.git;#.svn;#.vs;!*.msi;!*.exe;!*.obj;!*.pdb;!*.ilk;!*.res;!*.pch;!*.iobj;!*.ipdb")"s, - std::format(LR"(-GFOLDER="{}")", iniPath.parent_path().c_str()), + LR"(-GKEY="localhost")"s, + LR"(-GFILE="*.*;#en-US;#DriverData;#UMDF;#udc;#mde;#wd;!*.sys;!*.dll;!*.exe;!*.mui;!*.nls;!*.chm;!*.dat;!*.tmp;!*.wdf")"s, + LR"(-GFOLDER="C:\WINDOWS\System32\Drivers")"s, LR"(-GOPT=SP1)"s }; // エディタープロセスを起動する - const auto ep = testing::CreateEditorProcess(args, profileName); + auto ep = testing::CreateEditorProcess(args, profileName); + EXPECT_THAT(ep, NotNull()); + EXPECT_THAT(ep.hWnd, NotNull()); - // Grepダイアログが表示されるのを待って閉じる - for (const auto startTick = ::GetTickCount64(); ::GetTickCount64() - startTick < 5000;) { - if (const auto hWndFound = ::FindWindowW(GSTR_EDITWINDOWNAME, nullptr); hWndFound) { - break; - } - Sleep(10); // 10msスリープしてリトライ - } - - // 編集ウインドウを閉じる - const auto hWndFound = cxx::FindWindowW(GSTR_EDITWINDOWNAME); - testing::RequestForeignWindowClose(hWndFound); + // 編集ウインドウにクローズを要求する + testing::RequestForeignWindowClose(ep.hWnd); // 編集ウインドウが閉じられた後、プロセスが完全に終了するまで待つ - testing::WaitForForeignProcessExit(ep); + ep.lock(); // コントロールプロセスに終了指示を出して終了を待つ - testing::TerminateControlProcess(profileName, dwControlProcessId); + testing::TerminateControlProcess(profileName, cp.dwProcessId); } /*! @@ -880,26 +1204,22 @@ TEST_F(WinMainFuncTest, DoGrep001) */ TEST_F(WinMainFuncTest, OpenDebugWindow001) { - RunGuiTest([this] { - // テスト用プロファイル名 - const auto profileName{ GetProfileName() }; - - // コントロールプロセスを起動する - const auto dwControlProcessId = testing::CreateControlProcess(profileName); + // テスト用プロファイル名 + const auto profileName{ GetProfileName() }; - // エディタープロセスを起動する - const auto ep = testing::CreateEditorProcess(std::array{ LR"(-DEBUGMODE)" }, profileName); + // エディタープロセスを起動する + auto ep = testing::CreateEditorProcess(std::array{ LR"(-DEBUGMODE)" }, profileName); + EXPECT_THAT(ep, NotNull()); + EXPECT_THAT(ep.hWnd, NotNull()); - // 編集ウインドウが有効になるのを待って閉じる - const auto hWndFound = WaitForWindow(GSTR_EDITWINDOWNAME); - testing::RequestForeignWindowClose(hWndFound); + // 編集ウインドウにクローズを要求する + testing::RequestForeignWindowClose(ep.hWnd); - // 編集ウインドウが閉じられた後、プロセスが完全に終了するまで待つ - testing::WaitForForeignProcessExit(ep); + // 編集ウインドウが閉じられた後、プロセスが完全に終了するまで待つ + ep.lock(); - // コントロールプロセスに終了指示を出して終了を待つ - testing::TerminateControlProcess(profileName, dwControlProcessId); - }); + // コントロールプロセスに終了指示を出して終了を待つ + testing::TerminateControlProcess(profileName); } /*! @@ -909,41 +1229,30 @@ TEST_F(WinMainFuncTest, OpenDebugWindow001) */ TEST_F(WinMainFuncTest, ShowDlgGrep101) { - RunGuiTest([this] { - // テスト用プロファイル名 - const auto profileName{ GetProfileName() }; - - // コントロールプロセスを起動する - const auto dwControlProcessId = testing::CreateControlProcess(profileName); - - // エディタープロセスを起動する - const auto ep = testing::CreateEditorProcess(std::array{ LR"(-GREPDLG)", LR"(-GREPMODE)" }, profileName); + // テスト用プロファイル名 + const auto profileName{ GetProfileName() }; - // Grepダイアログが表示されるのを待って閉じる - const auto hWndDlgGrep = WaitForDialog(L"Grep"); - EmulateInvokeButton(hWndDlgGrep, L"キャンセル(X)"); + // 表示されたGrepダイアログを閉じるためのスレッドを起動する + std::jthread t = StartWindowCloser(L"Grep", [this] (HWND hWndDlg) { + EmulateInvokeButton(hWndDlg, L"キャンセル(X)"); + }); - bool dlgClosed = false; - for (const auto startTick = ::GetTickCount64(); ::GetTickCount64() - startTick < 5000;) { - if (const auto hWndFound = ::FindWindowW(MAKEINTRESOURCEW(dialog::ModalDialogCloser::DIALOG_CLASS), L"Grep"); !hWndFound) { - dlgClosed = true; - break; - } - Sleep(10); // 10msスリープしてリトライ - } + // エディタープロセスを起動する + auto ep = testing::CreateEditorProcess(std::array{ LR"(-GREPDLG)", LR"(-GREPMODE)" }, profileName); + EXPECT_THAT(ep, NotNull()); + EXPECT_THAT(ep.hWnd, NotNull()); - EXPECT_TRUE(dlgClosed) << "Grep dialog should be closed."; + // Grepダイアログが表示されるのを待って閉じる + t.join(); - // 編集ウインドウを閉じる - const auto hWndFound = cxx::FindWindowW(GSTR_EDITWINDOWNAME); - testing::RequestForeignWindowClose(hWndFound); + // 編集ウインドウにクローズを要求する + testing::RequestForeignWindowClose(ep.hWnd); - // 編集ウインドウが閉じられた後、プロセスが完全に終了するまで待つ - testing::WaitForForeignProcessExit(ep); + // 編集ウインドウが閉じられた後、プロセスが完全に終了するまで待つ + ep.lock(); - // コントロールプロセスに終了指示を出して終了を待つ - testing::TerminateControlProcess(profileName, dwControlProcessId); - }); + // コントロールプロセスに終了指示を出して終了を待つ + testing::TerminateControlProcess(profileName); } /*! @@ -953,20 +1262,24 @@ TEST_F(WinMainFuncTest, ShowDlgGrep101) */ TEST_F(WinMainFuncTest, ShowDlgProfileMgr101) { - RunGuiTest([this] { - // テスト用プロファイル名 - const auto profileName{ GetProfileName() }; + // テスト用プロファイル名 + const auto profileName{ GetProfileName() }; - // エディタープロセスを起動する - const auto ep = testing::CreateEditorProcess(std::array{ LR"(-PROFMGR)" }, profileName); + // 表示されたプロファイルマネージャを閉じるためのスレッドを起動する + std::jthread t = StartWindowCloser(L"プロファイルマネージャ", [this] (HWND hWndDlg) { + // プロファイルマネージャを閉じる + EmulateInvokeButton(hWndDlg, L"閉じる(X)"); + }); - // プロファイルマネージャが表示されるのを待って閉じる - const auto hWndDlgProfileMgr = WaitForDialog(L"プロファイルマネージャ"); - EmulateInvokeButton(hWndDlgProfileMgr, L"閉じる(X)"); + // エディタープロセスを起動する + auto ep = testing::CreateEditorProcess(std::array{ LR"(-PROFMGR)" }, profileName, false); + EXPECT_THAT(ep, NotNull()); - // 編集ウインドウが閉じられた後、プロセスが完全に終了するまで待つ - testing::WaitForForeignProcessExit(ep); - }); + // プロファイルマネージャが表示されるのを待って閉じる + t.join(); + + // 編集ウインドウが閉じられた後、プロセスが完全に終了するまで待つ + ep.lock(); } } // namespace winmain diff --git a/src/test/cpp/tests1/window/UiaTestSuite.hpp b/src/test/cpp/tests1/window/UiaTestSuite.hpp index 5ebca40c40..3177a9471c 100644 --- a/src/test/cpp/tests1/window/UiaTestSuite.hpp +++ b/src/test/cpp/tests1/window/UiaTestSuite.hpp @@ -6,6 +6,7 @@ */ #pragma once +#include "config/system_constants.h" #include "cxx/com_pointer.hpp" #include "dlg/ModalDialogCloser.hpp" #include "util/tchar_convert.h" @@ -91,18 +92,18 @@ struct UiaTestSuite EmulateInvoke(pItem); } - void EmulateEnterOpenFileName(const std::filesystem::path& exportPath) const + void EmulateEnterOpenFileName(const std::filesystem::path& path) const { if (const auto hWndDlgOpenFile = WaitForDialog(L"開く")) { - EmulateSetValue(GetFocusedElement(), exportPath.filename().c_str()); + EmulateSetValue(GetFocusedElement(), path.c_str()); EmulateHitEnter(); } } - void EmulateEnterSaveFileName(const std::filesystem::path& exportPath) const + void EmulateEnterSaveFileName(const std::filesystem::path& path) const { if (const auto hWndDlgSaveAs = WaitForDialog(L"名前を付けて保存")) { - EmulateSetValue(GetFocusedElement(), exportPath.filename().c_str()); + EmulateSetValue(GetFocusedElement(), path.c_str()); EmulateHitEnter(); } } @@ -236,9 +237,9 @@ struct UiaTestSuite }); } - HWND WaitForDialog(const std::wstring& title) const + HWND WaitForDialog(const std::wstring& title, ULONGLONG timeoutMillis = 60000) const { - return WaitForWindow(MAKEINTRESOURCEW(dialog::ModalDialogCloser::DIALOG_CLASS), title); + return WaitForWindow(MAKEINTRESOURCEW(dialog::ModalDialogCloser::DIALOG_CLASS), title, timeoutMillis); } IUIAutomationElementPtr WaitForFocus(ULONGLONG startTick, ULONGLONG timeoutMillis) const @@ -259,7 +260,7 @@ struct UiaTestSuite return pFocusedElement; } - HWND WaitForWindow(LPCWSTR targetClass, const std::optional& title = std::nullopt, bool waitCaret = true, ULONGLONG timeoutMillis = defaultTimeoutMillis) const + HWND WaitForWindow(LPCWSTR targetClass, const std::optional& title = std::nullopt, ULONGLONG timeoutMillis = defaultTimeoutMillis, bool waitCaret = true) const { const auto startTick = ::GetTickCount64(); @@ -282,6 +283,11 @@ struct UiaTestSuite return nullptr; } + HWND WaitForEditor(const std::optional& title = std::nullopt, ULONGLONG timeoutMillis = 60000) const + { + return WaitForWindow(GSTR_EDITWINDOWNAME, title, timeoutMillis); + } + template requires (1 < sizeof...(Conditions)) && (std::convertible_to&& ...) IUIAutomationConditionPtr CreateAndCondition(Conditions... conditions) const