diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dea1cb7..e273e984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ FlatLaf Change Log not visible. (issue #686) - "Monocai" theme: Fixed unreadable text color of default buttons. (issue #693) +- Native Windows libraries: Fixed crash when running in Java 8 and newer Java + version is installed in `PATH` environment variable and using class + `SystemInfo` before AWT initialization. (issue #673) ## 3.1.1 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLibrary.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLibrary.java index 4cb8d63d..263fbdb5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLibrary.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeLibrary.java @@ -61,13 +61,20 @@ class FlatNativeLibrary classifier = SystemInfo.isX86_64 ? "windows-x86_64" : "windows-x86"; ext = "dll"; - // In Java 8, load jawt.dll (part of JRE) explicitly because it - // is not found when running application with /bin/java.exe. - // When using /jre/bin/java.exe, it is found. - // jawt.dll is located in /jre/bin/. - // Java 9 and later do not have this problem, - // but load jawt.dll anyway to be on the safe side. - loadJAWT(); + // Do not load jawt.dll (part of JRE) here explicitly because + // the FlatLaf native library flatlaf.dll may be loaded very early on Windows + // (e.g. from class com.formdev.flatlaf.util.SystemInfo) and before AWT is + // initialized (and awt.dll is loaded). Loading jawt.dll also loads awt.dll. + // In Java 8, loading jawt.dll before AWT is initialized may load + // a wrong version of awt.dll if a newer Java version (e.g. 19) + // is in PATH environment variable. Then Java 19 awt.dll and Java 8 awt.dll + // are loaded at same time and calling JAWT_GetAWT() crashes the application. + // + // To avoid this, flatlaf.dll is not linked to jawt.dll, + // which avoids loading jawt.dll when flatlaf.dll is loaded. + // Instead flatlaf.dll dynamically loads jawt.dll when first used, + // which is guaranteed after AWT initialization. + } else if( SystemInfo.isLinux && SystemInfo.isX86_64 ) { // Linux: requires x86_64 diff --git a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts index 69490664..41942b9d 100644 --- a/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts +++ b/flatlaf-natives/flatlaf-natives-windows/build.gradle.kts @@ -71,12 +71,11 @@ tasks { val nativesDir = project( ":flatlaf-core" ).projectDir.resolve( "src/main/resources/com/formdev/flatlaf/natives" ) val is64Bit = name.contains( "64" ) val libraryName = if( is64Bit ) "flatlaf-windows-x86_64.dll" else "flatlaf-windows-x86.dll" - val jawt = if( is64Bit ) "lib/jawt-x86_64" else "lib/jawt-x86" linkerArgs.addAll( toolChain.map { when( it ) { - is Gcc, is Clang -> listOf( "-l${jawt}", "-lUser32", "-lGdi32", "-lshell32", "-lAdvAPI32", "-lKernel32", "-lDwmapi" ) - is VisualCpp -> listOf( "${jawt}.lib", "User32.lib", "Gdi32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "Dwmapi.lib", "/NODEFAULTLIB" ) + is Gcc, is Clang -> listOf( "-lUser32", "-lGdi32", "-lshell32", "-lAdvAPI32", "-lKernel32", "-lDwmapi" ) + is VisualCpp -> listOf( "User32.lib", "Gdi32.lib", "shell32.lib", "AdvAPI32.lib", "Kernel32.lib", "Dwmapi.lib", "/NODEFAULTLIB" ) else -> emptyList() } } ) diff --git a/flatlaf-natives/flatlaf-natives-windows/lib/README.md b/flatlaf-natives/flatlaf-natives-windows/lib/README.md deleted file mode 100644 index c1e55b11..00000000 --- a/flatlaf-natives/flatlaf-natives-windows/lib/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Contains libraries used to compile FlatLaf Windows 10 native libraries (DLLs). - -- `jawt-x86.lib` is `/lib/jawt.lib` from AdoptOpenJDK jdk8u282-b08 32-bit, - which is required to build 32-bit DLL -- `jawt-x86_64.lib` is `/lib/jawt.lib` from AdoptOpenJDK jdk8u282-b08 - 64-bit, which is required to build 64-bit DLL diff --git a/flatlaf-natives/flatlaf-natives-windows/lib/jawt-x86.lib b/flatlaf-natives/flatlaf-natives-windows/lib/jawt-x86.lib deleted file mode 100644 index a16a9791..00000000 Binary files a/flatlaf-natives/flatlaf-natives-windows/lib/jawt-x86.lib and /dev/null differ diff --git a/flatlaf-natives/flatlaf-natives-windows/lib/jawt-x86_64.lib b/flatlaf-natives/flatlaf-natives-windows/lib/jawt-x86_64.lib deleted file mode 100644 index d9a9c8f3..00000000 Binary files a/flatlaf-natives/flatlaf-natives-windows/lib/jawt-x86_64.lib and /dev/null differ 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 45d6b850..1c3d4cf0 100644 --- a/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp +++ b/flatlaf-natives/flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp @@ -542,10 +542,82 @@ void FlatWndProc::setMenuItemState( HMENU systemMenu, int item, bool enabled ) { ::SetMenuItemInfo( systemMenu, item, FALSE, &mii ); } +//---- window handle ---------------------------------------------------------- + +#ifdef _WIN64 +#define GETAWT_METHOD_NAME "JAWT_GetAWT" +#else +#define GETAWT_METHOD_NAME "_JAWT_GetAWT@8" +#endif + +typedef jboolean (JNICALL *JAWT_GetAWT_Type)( JNIEnv*, JAWT* ); + +static HMODULE jawtModule = NULL; +static JAWT_GetAWT_Type pJAWT_GetAWT = NULL; + + HWND getWindowHandle( JNIEnv* env, jobject window ) { + + // flatlaf.dll is not linked to jawt.dll because flatlaf.dll may be loaded + // very early on Windows (e.g. from class com.formdev.flatlaf.util.SystemInfo) and + // before AWT is initialized (and awt.dll is loaded). Loading jawt.dll also loads awt.dll. + // In Java 8, loading jawt.dll before AWT is initialized may load + // a wrong version of awt.dll if a newer Java version (e.g. 19) + // is in PATH environment variable. Then Java 19 awt.dll and Java 8 awt.dll + // are loaded at same time and calling JAWT_GetAWT() crashes the application. + // + // To avoid this, flatlaf.dll is not linked to jawt.dll, + // which avoids loading jawt.dll when flatlaf.dll is loaded. + // Instead flatlaf.dll dynamically loads jawt.dll when first used, + // which is guaranteed after AWT initialization. + // + // Load JAWT library from ${java.home}\bin\jawt.dll and use wide chars for path + // for the case that Java path uses special characters. (this is similar to JNA) + + // load JAWT library jawt.dll + if( jawtModule == NULL ) { + // invoke: javaHome = System.getProperty( "java.home" ) + jclass cls = env->FindClass( "java/lang/System" ); + jmethodID mid = (cls != NULL) ? env->GetStaticMethodID( cls, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;" ) : NULL; + jstring javaHome = (mid != NULL) ? (jstring) env->CallStaticObjectMethod( cls, mid, env->NewStringUTF( "java.home" ) ) : NULL; + if( javaHome != NULL ) { + // invoke: jawtPath = javaHome.concat( "\\bin\\jawt.dll" ) + jmethodID mid2 = env->GetMethodID( env->GetObjectClass( javaHome ), "concat", "(Ljava/lang/String;)Ljava/lang/String;" ); + jstring jawtPath = (mid2 != NULL) ? (jstring) env->CallObjectMethod( javaHome, mid2, env->NewStringUTF( "\\bin\\jawt.dll" ) ) : NULL; + if( jawtPath != NULL ) { + // convert Java UTF-8 string to Windows wide chars + const char* sjawtPath = env->GetStringUTFChars( jawtPath, NULL ); + int wstr_len = MultiByteToWideChar( CP_UTF8, 0, sjawtPath, -1, NULL, 0 ); + if( wstr_len > 0 ) { + wchar_t* wstr = new wchar_t[wstr_len]; + if( MultiByteToWideChar( CP_UTF8, 0, sjawtPath, -1, wstr, wstr_len ) == wstr_len ) { + // load jawt.dll from Java home + jawtModule = LoadLibraryExW( wstr, NULL, LOAD_WITH_ALTERED_SEARCH_PATH ); + } + delete[] wstr; + } + env->ReleaseStringUTFChars( jawtPath, sjawtPath ); + } + } + + // fallback + if( jawtModule == NULL ) + jawtModule = LoadLibraryA( "jawt.dll" ); + + if( jawtModule == NULL ) + return 0; + } + + // get address of method JAWT_GetAWT() + if( pJAWT_GetAWT == NULL ) { + pJAWT_GetAWT = (JAWT_GetAWT_Type) GetProcAddress( jawtModule, GETAWT_METHOD_NAME ); + if( pJAWT_GetAWT == NULL ) + return 0; + } + JAWT awt; awt.version = JAWT_VERSION_1_4; - if( !JAWT_GetAWT( env, &awt ) ) + if( !pJAWT_GetAWT( env, &awt ) ) return 0; jawt_DrawingSurface* ds = awt.GetDrawingSurface( env, window ); @@ -558,12 +630,15 @@ HWND getWindowHandle( JNIEnv* env, jobject window ) { return 0; } + HWND hwnd = 0; + JAWT_DrawingSurfaceInfo* dsi = ds->GetDrawingSurfaceInfo( ds ); - JAWT_Win32DrawingSurfaceInfo* wdsi = (JAWT_Win32DrawingSurfaceInfo*) dsi->platformInfo; + if( dsi != NULL ) { + JAWT_Win32DrawingSurfaceInfo* wdsi = (JAWT_Win32DrawingSurfaceInfo*) dsi->platformInfo; + hwnd = wdsi->hwnd; + ds->FreeDrawingSurfaceInfo( dsi ); + } - HWND hwnd = wdsi->hwnd; - - ds->FreeDrawingSurfaceInfo( dsi ); ds->Unlock( ds ); awt.FreeDrawingSurface( ds );