vcl/inc/win/saldata.hxx | 3 + vcl/inc/win/salframe.h | 3 + vcl/win/app/salinst.cxx | 8 +++ vcl/win/window/salframe.cxx | 101 +++++++++++++++++++++++++++++++++++++++----- 4 files changed, 104 insertions(+), 11 deletions(-)
New commits: commit fa5b29052dc5768048d7098a4829aa2a14ec35c7 Author: Noel Grandin <noelgran...@gmail.com> AuthorDate: Sun Jul 13 11:58:00 2025 +0200 Commit: Noel Grandin <noelgran...@gmail.com> CommitDate: Sun Jul 13 20:14:07 2025 +0200 Revert "remove mpThreadGraphics from GDI backend" This reverts commit b5510122fd68e3d16117684f51062dfb03961b85. I was wrong, we need this for when out of process extensions call into vcl, which they will do not-the-main-thread. Unfortunately, the only unit test we have that exercises this is JunitTest_sfx2_complex and that test is not run on the Windows Jenkins boxes. Change-Id: I55582418c229a7064e27ebf408a9e719ef6dd8a4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187795 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.gran...@collabora.co.uk> diff --git a/vcl/inc/win/saldata.hxx b/vcl/inc/win/saldata.hxx index 46b94ec22dd5..9ad1a925ced3 100644 --- a/vcl/inc/win/saldata.hxx +++ b/vcl/inc/win/saldata.hxx @@ -113,6 +113,7 @@ public: sal_uInt16 mnStockPenCount; // count of static pens sal_uInt16 mnStockBrushCount; // count of static brushes WPARAM mnSalObjWantKeyEvt; // KeyEvent that should be processed by SalObj-Hook + BYTE mnCacheDCInUse; // count of CacheDC in use bool mbObjClassInit; // is SALOBJECTCLASS initialised DWORD mnAppThreadId; // Id from Application-Thread SalIcon* mpFirstIcon; // icon cache, points to first icon, NULL if none @@ -199,6 +200,8 @@ OUString ImplSalGetUniString(const char* pStr, sal_Int32 nLen = -1); #define SAL_MSG_CREATEOBJECT (WM_USER+116) // wParam == 0; lParam == pObject; #define SAL_MSG_DESTROYOBJECT (WM_USER+117) +// wParam == hWnd; lParam == 0; lResult == hDC +#define SAL_MSG_GETCACHEDDC (WM_USER+120) // wParam == hWnd; lParam == 0 #define SAL_MSG_RELEASEDC (WM_USER+121) // wParam == newParentHwnd; lParam == oldHwnd; lResult == newhWnd diff --git a/vcl/inc/win/salframe.h b/vcl/inc/win/salframe.h index 31f6b88a8cc9..9d356a11bc45 100644 --- a/vcl/inc/win/salframe.h +++ b/vcl/inc/win/salframe.h @@ -41,6 +41,9 @@ public: HCURSOR mhCursor; // cursor handle HIMC mhDefIMEContext; // default IME-Context WinSalGraphics* mpLocalGraphics; // current main thread frame graphics + /// Some threads will call vcl functions, and even though they hold the SolarMutex, the GDI + /// library will error, so we need to keep a separate graphics and DC for them. + WinSalGraphics* mpThreadGraphics; // current frame graphics for other threads (DCX_CACHE) WinSalFrame* mpNextFrame; // pointer to next frame HMENU mSelectedhMenu; // the menu where highlighting is currently going on HMENU mLastActivatedhMenu; // the menu that was most recently opened diff --git a/vcl/win/app/salinst.cxx b/vcl/win/app/salinst.cxx index dc7f93dccd3c..b0e2de5db00b 100644 --- a/vcl/win/app/salinst.cxx +++ b/vcl/win/app/salinst.cxx @@ -269,6 +269,7 @@ SalData::SalData() mnStockPenCount = 0; // count of static pens mnStockBrushCount = 0; // count of static brushes mnSalObjWantKeyEvt = 0; // KeyEvent for the SalObj hook + mnCacheDCInUse = 0; // count of CacheDC in use mbObjClassInit = false; // is SALOBJECTCLASS initialised mnAppThreadId = 0; // Id from Application-Thread mpFirstIcon = nullptr; // icon cache, points to first icon, NULL if none @@ -661,6 +662,13 @@ LRESULT CALLBACK SalComWndProc( HWND, UINT nMsg, WPARAM wParam, LPARAM lParam, b delete reinterpret_cast<SalObject*>(lParam); } break; + case (SAL_MSG_GETCACHEDDC): + { + NoYieldLockGuard g; + nRet = reinterpret_cast<LRESULT>( + GetDCEx(reinterpret_cast<HWND>(wParam), nullptr, 0x00000002L)); + } + break; case (SAL_MSG_RELEASEDC): { NoYieldLockGuard g; diff --git a/vcl/win/window/salframe.cxx b/vcl/win/window/salframe.cxx index 342fffbefe46..ddbce3e9e9ce 100644 --- a/vcl/win/window/salframe.cxx +++ b/vcl/win/window/salframe.cxx @@ -861,6 +861,7 @@ WinSalFrame::WinSalFrame() mhCursor = LoadCursor( nullptr, IDC_ARROW ); mhDefIMEContext = nullptr; mpLocalGraphics = nullptr; + mpThreadGraphics = nullptr; m_eState = vcl::WindowState::Normal; mnShowState = SW_SHOWNORMAL; mnMinWidth = 0; @@ -946,6 +947,8 @@ bool WinSalFrame::ReleaseFrameGraphicsDC( WinSalGraphics* pGraphics ) pGraphics->setHDC(nullptr); SendMessageW( pSalData->mpInstance->mhComWnd, SAL_MSG_RELEASEDC, reinterpret_cast<WPARAM>(mhWnd), reinterpret_cast<LPARAM>(hDC) ); + if ( pGraphics == mpThreadGraphics ) + pSalData->mnCacheDCInUse--; return true; } @@ -963,6 +966,14 @@ WinSalFrame::~WinSalFrame() *ppFrame = mpNextFrame; mpNextFrame = nullptr; + // destroy the thread SalGraphics + if ( mpThreadGraphics ) + { + ReleaseFrameGraphicsDC( mpThreadGraphics ); + delete mpThreadGraphics; + mpThreadGraphics = nullptr; + } + // destroy the local SalGraphics if ( mpLocalGraphics ) { @@ -998,6 +1009,7 @@ WinSalFrame::~WinSalFrame() bool WinSalFrame::InitFrameGraphicsDC( WinSalGraphics *pGraphics, HDC hDC, HWND hWnd ) { + SalData* pSalData = GetSalData(); assert( pGraphics ); pGraphics->setHWND( hWnd ); @@ -1010,6 +1022,8 @@ bool WinSalFrame::InitFrameGraphicsDC( WinSalGraphics *pGraphics, HDC hDC, HWND if ( !hDC ) return false; + if ( pGraphics == mpThreadGraphics ) + pSalData->mnCacheDCInUse++; return true; } @@ -1019,24 +1033,44 @@ SalGraphics* WinSalFrame::AcquireGraphics() return nullptr; SalData* pSalData = GetSalData(); - assert(pSalData->mpInstance->IsMainThread()); - SAL_WARN_IF(!pSalData->mpInstance->IsMainThread(), "vcl", "ERROR: pSalData->mpInstance is not main thread!"); + WinSalGraphics* pGraphics; + HDC hDC; - if ( !mpLocalGraphics ) + // Other threads get an own DC, because Windows modify in the + // other case our DC (changing clip region), when they send a + // WM_ERASEBACKGROUND message + if ( !pSalData->mpInstance->IsMainThread() ) { - HDC hDC = GetDC( mhWnd ); - if (!hDC) + // We use only three CacheDC's for all threads, because W9x is limited + // to max. 5 Cache DC's per thread + if ( pSalData->mnCacheDCInUse >= 3 ) return nullptr; - mpLocalGraphics = new WinSalGraphics(WinSalGraphics::WINDOW, true, mhWnd, this); - mpLocalGraphics->setHDC( hDC ); + if ( !mpThreadGraphics ) + mpThreadGraphics = new WinSalGraphics(WinSalGraphics::WINDOW, true, mhWnd, this); + pGraphics = mpThreadGraphics; + assert( !pGraphics->getHDC() ); + hDC = reinterpret_cast<HDC>(static_cast<sal_IntPtr>(SendMessageW( pSalData->mpInstance->mhComWnd, + SAL_MSG_GETCACHEDDC, reinterpret_cast<WPARAM>(mhWnd), 0 ))); } - mbGraphicsAcquired = true; - return mpLocalGraphics; + else + { + if ( !mpLocalGraphics ) + mpLocalGraphics = new WinSalGraphics(WinSalGraphics::WINDOW, true, mhWnd, this); + pGraphics = mpLocalGraphics; + hDC = pGraphics->getHDC(); + if ( !hDC ) + hDC = GetDC( mhWnd ); + } + + mbGraphicsAcquired = InitFrameGraphicsDC( pGraphics, hDC, mhWnd ); + return mbGraphicsAcquired ? pGraphics : nullptr; } -void WinSalFrame::ReleaseGraphics( SalGraphics* /*pGraphics*/ ) +void WinSalFrame::ReleaseGraphics( SalGraphics* pGraphics ) { + if ( mpThreadGraphics == pGraphics ) + ReleaseFrameGraphicsDC( mpThreadGraphics ); mbGraphicsAcquired = false; } @@ -1462,7 +1496,29 @@ void WinSalFrame::ImplSetParentFrame( HWND hNewParentWnd, bool bAsChild ) } // to recreate the DCs, if they were destroyed - bool bHadLocalGraphics = false; + bool bHadLocalGraphics = false, bHadThreadGraphics = false; + + HFONT hFont = nullptr; + HPEN hPen = nullptr; + HBRUSH hBrush = nullptr; + + int oldCount = pSalData->mnCacheDCInUse; + + // release the thread DC + if ( mpThreadGraphics ) + { + // save current gdi objects before hdc is gone + HDC hDC = mpThreadGraphics->getHDC(); + if ( hDC ) + { + hFont = static_cast<HFONT>(GetCurrentObject( hDC, OBJ_FONT )); + hPen = static_cast<HPEN>(GetCurrentObject( hDC, OBJ_PEN )); + hBrush = static_cast<HBRUSH>(GetCurrentObject( hDC, OBJ_BRUSH )); + } + + bHadThreadGraphics = ReleaseFrameGraphicsDC( mpThreadGraphics ); + assert( (bHadThreadGraphics && hDC) || (!bHadThreadGraphics && !hDC) ); + } // release the local DC if ( mpLocalGraphics ) @@ -1478,6 +1534,27 @@ void WinSalFrame::ImplSetParentFrame( HWND hNewParentWnd, bool bAsChild ) // succeeded ? SAL_WARN_IF( !IsWindow( hWnd ), "vcl", "WinSalFrame::SetParent not successful"); + // re-create thread DC + if( bHadThreadGraphics ) + { + HDC hDC = reinterpret_cast<HDC>(static_cast<sal_IntPtr>( + SendMessageW( pSalData->mpInstance->mhComWnd, + SAL_MSG_GETCACHEDDC, reinterpret_cast<WPARAM>(hWnd), 0 ))); + InitFrameGraphicsDC( mpThreadGraphics, hDC, hWnd ); + if ( hDC ) + { + // re-select saved gdi objects + if( hFont ) + SelectObject( hDC, hFont ); + if( hPen ) + SelectObject( hDC, hPen ); + if( hBrush ) + SelectObject( hDC, hBrush ); + + SAL_WARN_IF( oldCount != pSalData->mnCacheDCInUse, "vcl", "WinSalFrame::SetParent() hDC count corrupted"); + } + } + // re-create local DC if( bHadLocalGraphics ) InitFrameGraphicsDC( mpLocalGraphics, GetDC( hWnd ), hWnd ); @@ -2111,6 +2188,8 @@ void WinSalFrame::Flush() { if(mpLocalGraphics) mpLocalGraphics->Flush(); + if(mpThreadGraphics) + mpThreadGraphics->Flush(); GdiFlush(); }