From 8734b062dc1d4c8225e243f43b4a0724b52a6751 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 6 Mar 2021 12:01:49 +0100 Subject: [PATCH] Native window decorations: avoid using C-runtime, which reduces the DLL size from 100kb to 8kb --- .gitignore | 2 + .../flatlaf-natives-windows/build.gradle.kts | 6 +- .../src/main/cpp/FlatWndProc.cpp | 35 ++-- .../src/main/cpp/FlatWndProc.h | 3 +- .../src/main/cpp/HWNDMap.cpp | 153 ++++++++++++++++++ .../src/main/cpp/HWNDMap.h | 53 ++++++ .../src/main/cpp/Runtime.cpp | 73 +++++++++ 7 files changed, 308 insertions(+), 17 deletions(-) create mode 100644 flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.cpp create mode 100644 flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.h create mode 100644 flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp diff --git a/.gitignore b/.gitignore index 34002bf5..b67ae01c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ out/ *.iml *.ipr *.iws +.vs/ +.vscode/ diff --git a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts index 0a495709..acdca5b1 100644 --- a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts @@ -55,7 +55,7 @@ library { compilerArgs.addAll( toolChain.map { when( it ) { is Gcc, is Clang -> listOf( "-O2" ) - is VisualCpp -> listOf( "/O2" ) + is VisualCpp -> listOf( "/O2", "/Zl", "/GS-" ) else -> emptyList() } } ) @@ -70,8 +70,8 @@ library { val jawt = "${org.gradle.internal.jvm.Jvm.current().javaHome}/lib/jawt" linkerArgs.addAll( toolChain.map { when( it ) { - is Gcc, is Clang -> listOf( "-l${jawt}", "-lUser32", "-lshell32", "-lAdvAPI32" ) - is VisualCpp -> listOf( "${jawt}.lib", "User32.lib", "shell32.lib", "AdvAPI32.lib" ) + is Gcc, is Clang -> listOf( "-l${jawt}", "-lUser32", "-lshell32", "-lAdvAPI32", "-lKernel32" ) + is VisualCpp -> listOf( "${jawt}.lib", "User32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "/NODEFAULTLIB" ) else -> emptyList() } } ) diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp index 1f77c5e3..8aa456a6 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp @@ -14,12 +14,14 @@ * limitations under the License. */ +// avoid inlining of printf() +#define _NO_CRT_STDIO_INLINE + #include #include #include #include #include -#include #include "FlatWndProc.h" #include "com_formdev_flatlaf_ui_FlatWindowsNativeWindowBorder_WndProc.h" @@ -50,7 +52,7 @@ jmethodID FlatWndProc::onNcHitTestMID; jmethodID FlatWndProc::isFullscreenMID; jmethodID FlatWndProc::fireStateChangedLaterOnceMID; -std::map FlatWndProc::hwndMap = std::map(); +HWNDMap* FlatWndProc::hwndMap; //---- class FlatWndProc methods ---------------------------------------------- @@ -63,21 +65,25 @@ FlatWndProc::FlatWndProc() { } HWND FlatWndProc::install( JNIEnv *env, jobject obj, jobject window ) { - initIDs( env, obj ); + initIDs( env, obj ); - if( initialized < 0 ) - return 0; + if( initialized < 0 ) + return 0; + + // create HWND map + if( hwndMap == NULL ) + hwndMap = new HWNDMap(); // get window handle HWND hwnd = getWindowHandle( env, window ); - if( hwnd == NULL || hwndMap.count( hwnd ) > 0 ) + if( hwnd == NULL || hwndMap->get( hwnd ) != NULL ) return 0; FlatWndProc* fwp = new FlatWndProc(); env->GetJavaVM( &fwp->jvm ); fwp->obj = env->NewGlobalRef( obj ); fwp->hwnd = hwnd; - hwndMap[hwnd] = fwp; + hwndMap->put( hwnd, fwp ); // replace window procedure fwp->defaultWndProc = reinterpret_cast( @@ -90,11 +96,14 @@ HWND FlatWndProc::install( JNIEnv *env, jobject obj, jobject window ) { } void FlatWndProc::uninstall( JNIEnv *env, jobject obj, HWND hwnd ) { - if( hwnd == NULL || hwndMap.count( hwnd ) == 0 ) - return; + if( hwnd == NULL ) + return; - FlatWndProc* fwp = hwndMap[hwnd]; - hwndMap.erase( hwnd ); + FlatWndProc* fwp = (FlatWndProc*) hwndMap->get( hwnd ); + if( fwp == NULL ) + return; + + hwndMap->remove( hwnd ); // restore original window procedure ::SetWindowLongPtr( hwnd, GWLP_WNDPROC, (LONG_PTR) fwp->defaultWndProc ); @@ -132,7 +141,7 @@ void FlatWndProc::updateFrame() { } LRESULT CALLBACK FlatWndProc::StaticWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { - FlatWndProc* fwp = hwndMap[hwnd]; + FlatWndProc* fwp = (FlatWndProc*) hwndMap->get( hwnd ); return fwp->WindowProc( hwnd, uMsg, wParam, lParam ); } @@ -175,7 +184,7 @@ LRESULT FlatWndProc::WmDestroy( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lPara // cleanup getEnv()->DeleteGlobalRef( obj ); - hwndMap.erase( hwnd ); + hwndMap->remove( hwnd ); delete this; // call original AWT window procedure because it may fire window closed event in AwtWindow::WmDestroy() diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.h b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.h index 583c4e85..a6d5bfbf 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.h +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.h @@ -15,6 +15,7 @@ */ #include +#include "HWNDMap.h" /** * @author Karl Tauber @@ -31,7 +32,7 @@ private: static jmethodID isFullscreenMID; static jmethodID fireStateChangedLaterOnceMID; - static std::map hwndMap; + static HWNDMap* hwndMap; JavaVM* jvm; JNIEnv* env; // attached to AWT-Windows/Win32 thread diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.cpp new file mode 100644 index 00000000..ae909ee1 --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// avoid inlining of printf() +#define _NO_CRT_STDIO_INLINE + +#include +#include "HWNDMap.h" + +#define DEFAULT_CAPACITY 20 +#define INCREASE_CAPACITY 10 + +/** + * @author Karl Tauber + */ + +class LOCK { + LPCRITICAL_SECTION lpCriticalSection; + +public: + LOCK( LPCRITICAL_SECTION lpCriticalSection ) { + this->lpCriticalSection = lpCriticalSection; + ::EnterCriticalSection( lpCriticalSection ); + } + ~LOCK() { + ::LeaveCriticalSection( lpCriticalSection ); + } +}; + + +HWNDMap::HWNDMap() { + size = 0; + capacity = DEFAULT_CAPACITY; + table = new Entry[capacity]; + + ::InitializeCriticalSection( &criticalSection ); + +// dump( "" ); +} + +LPVOID HWNDMap::get( HWND key ) { + LOCK lock( &criticalSection ); + + int index = binarySearch( key ); + return (index >= 0) ? table[index].value : NULL; +} + +void HWNDMap::put( HWND key, LPVOID value ) { + LOCK lock( &criticalSection ); + + int index = binarySearch( key ); +// printf( "put %p %p = %d --\n", key, value, index ); + if( index >= 0 ) { + // key already in map --> replace + table[index].value = value; + } else { + // insert new key + ensureCapacity( size + 1 ); + + // make roor for new entry + index = -(index + 1); + for( int i = size - 1; i >= index; i-- ) + table[i + 1] = table[i]; + size++; + + // insert entry + table[index].key = key; + table[index].value = value; + } + +// dump( "put" ); +} + +void HWNDMap::remove( HWND key ) { + LOCK lock( &criticalSection ); + + // search for key + int index = binarySearch( key ); +// printf( "remove %p = %d --\n", key, index ); + if( index < 0 ) + return; + + // remove entry + for( int i = index + 1; i < size; i++ ) + table[i - 1] = table[i]; + size--; + +// dump( "remove" ); +} + +int HWNDMap::binarySearch( HWND key ) { + int low = 0; + int high = size - 1; + + while( low <= high ) { + int mid = (low + high) >> 1; + + HWND midKey = table[mid].key; + int cmp = midKey - key; + if( cmp < 0 ) + low = mid + 1; + else if( cmp > 0 ) + high = mid - 1; + else + return mid; + } + + return -(low + 1); +} + +void HWNDMap::ensureCapacity( int minCapacity ) { + if( minCapacity <= capacity ) + return; + + // allocate new table + int newCapacity = minCapacity + INCREASE_CAPACITY; + Entry* newTable = new Entry[newCapacity]; + + // copy old table to new table + for( int i = 0; i < capacity; i++ ) + newTable[i] = table[i]; + + // delete old table + delete table; + + table = newTable; + capacity = newCapacity; +} + +/* +void HWNDMap::dump( char* msg ) { + printf( "---- %s -----------------------\n", msg ); + printf( "size %d\n", size ); + printf( "capacity %d\n", capacity ); + printf( "table %p\n", table ); + + for( int i = 0; i < capacity; i++ ) + printf( " %d: %p - %p %s\n", i, table[i].key, table[i].value, i >= size ? "UNUSED" : "" ); +} +*/ diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.h b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.h new file mode 100644 index 00000000..c94e66ec --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/HWNDMap.h @@ -0,0 +1,53 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +/** + * A simple map that uses a sorted array to store key/value pairs. + * + * @author Karl Tauber + */ + +struct Entry +{ + HWND key; + LPVOID value; +}; + +class HWNDMap +{ +private: + int size; // used entries in table + int capacity; // total size of table + Entry* table; + + // used to synchronize to make it thread safe + CRITICAL_SECTION criticalSection; + +public: + HWNDMap(); + + LPVOID get( HWND key ); + void put( HWND key, LPVOID value ); + void remove( HWND key ); + +private: + int binarySearch( HWND key ); + void ensureCapacity( int newCapacity ); + +// void dump( char* msg ); +}; diff --git a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp new file mode 100644 index 00000000..efe5f3ac --- /dev/null +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/Runtime.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// avoid inlining of printf() +#define _NO_CRT_STDIO_INLINE + +#include +#include +#include + +/** + * Methods that replace C-runtime methods and allow linking/running without C-runtime. + * + * WARNING: Constructors/destructors of static objects are not invoked! + * + * https://documentation.help/Far-Manager/msdnmag-issues-01-01-hood-default.aspx.html + * www.catch22.net/tuts/win32/reducing-executable-size#the-c-runtime-and-default-libraries + * https://www.mvps.org/user32/nocrt.html + * + * see also LIBCTINY on "Downloads" page here: http://www.wheaty.net/ + * or https://github.com/leepa/libctiny + * + * @author Karl Tauber + */ + +extern "C" +BOOL WINAPI _DllMainCRTStartup( HINSTANCE instance, DWORD reason, LPVOID reserved ) { + return TRUE; +} + +void* __cdecl operator new( size_t cb ) { + return ::HeapAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, cb ); +} + +void* __cdecl operator new[]( size_t cb ) { + return ::HeapAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, cb ); +} + +void __cdecl operator delete( void* pv, size_t cb ) { + if( pv != NULL ) + ::HeapFree( ::GetProcessHeap(), 0, pv ); +} + +/* +extern "C" +int __cdecl printf( const char* format, ... ) { + char szBuff[1024]; + int retValue; + DWORD cbWritten; + va_list argptr; + + va_start( argptr, format ); + retValue = wvsprintfA( szBuff, format, argptr ); + va_end( argptr ); + + WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ), szBuff, retValue, &cbWritten, NULL ); + + return retValue; +} +*/