Implemented single instance handling.

This commit is contained in:
LoRd_MuldeR 2020-09-29 19:58:33 +02:00
parent b9bf24c87a
commit 6858ffe791
5 changed files with 82 additions and 7 deletions

View File

@ -58,7 +58,7 @@ All of the above Launch5j variants are available as `i586` (32-Bit) and `x86-64`
Launch5j comes with a *default* executable icon and a *default* splash screen bitmap. These just server as an example and you probably want to replace them with your own *application-specific* graphics.
It is ***not*** necessary to re-build the executable files for that purpose. Instead, you can simply use a resource editor, such as [**XN Resource Editor**](https://stefansundin.github.io/xn_resource_editor/) or [**Resource Hacker™**](http://www.angusj.com/resourcehacker/), to *modify* the pre-compiled executable files as needed:
It is ***not*** necessary to re-build the executable files for that purpose. Instead, you can simply use a resource editor, such as [**XN Resource Editor**](https://web.archive.org/web/20100419201225/http://www.wilsonc.demon.co.uk/d10resourceeditor.htm) ([mirror](https://stefansundin.github.io/xn_resource_editor/)) or [**Resource Hacker™**](http://www.angusj.com/resourcehacker/), to *modify* the pre-compiled executable files as needed:
![reshack](etc/reshacker-example.png)
@ -90,17 +90,30 @@ Some options can be configured via the launcher executable's [STRINGTABLE](https
Specifies the ***maximum*** supported JRE version, in the **`w.x.y.z`** format (e.g. `12.0.0.0`).
This values is *exclusive*, i.e. only JRE versions *older* than the specified JRE version will be accepted.
If not specified, then there is **no** upper limit on the supported JRE version.
*Hint:* Old-style `1.x.y.z` Java versions are automatically translated to the `x.y.z.0` format!
*Hint:* Old-style `1.x.y.z` Java versions are automatically translated to the `x.y.z.0` format!
(This option only applies to the `registry` variant of Launch5j)
* **`ID_STR_BITNESS` (#6)**
Specifies the required ***bitness*** of the JRE. This can be either **`32`** (x86, aka i586) or **`64`** (x86-64).
If not specified, 32-Bit *and* 64-Bit JREs are accepted, with a preference to 64-Bit.
(This option only applies to the `registry` variant of Launch5j)
* **`ID_STR_BITNESS` (#6)**
Specifies the required ***bitness*** of the JRE. This can be either **`32`** (x86, aka i586) or **`64`** (x86-64).
If not specified, 32-Bit *and* 64-Bit JREs are accepted, with a preference to 64-Bit.
(This option only applies to the `registry` variant of Launch5j)
* **`ID_STR_MUTEXID` (#7)**
Specifies the application ID to be used when creating the [*single-instance*](http://www.bcbjournal.org/articles/vol3/9911/Single-instance_applications.htm) mutex.
The ID **must** be at least 5 characters in length and **should** be a *unique* string for each application!
If not specified, then **no** mutex will be created and thus *multiple* instances will be allowed.
*Hint:* If the specified application ID *starts* with an **`@`** character, then Launch5j will **not** show a message box when the application is already running; the **`@`** character is *not* considered a part of the actual ID.
*Note:* We use the convention that the default resource string value `"?"` is used to represent an “undefined” value, because resource strings cannot be empty. You can replace the default value as needed!
# Build instructions

View File

@ -27,7 +27,7 @@ for %%m in (32,64) do (
echo.
)
echo ALL IS COMPLETED.
echo ALL IS DONE.
:build_completed
pause

View File

@ -40,6 +40,7 @@ BEGIN
ID_STR_HEADING L"?" /*custom application title*/
ID_STR_JVMARGS L"?" /*additional JVM args*/
ID_STR_CMDARGS L"?" /*additional command-line args*/
ID_STR_MUTEXID L"?" /*single instance mutex ID*/
END
/////////////////////////////////////////////////////////////////////////////

View File

@ -48,6 +48,7 @@
// Const
static const wchar_t *const JRE_RELATIVE_PATH = L"runtime\\bin\\javaw.exe";
static const wchar_t *const JRE_DOWNLOAD_LINK = L"https://adoptopenjdk.net/";
static const size_t MIN_MUTEXID_LENGTH = 5U;
static const DWORD SPLASH_SCREEN_TIMEOUT = 30000U;
/* ======================================================================== */
@ -918,6 +919,47 @@ static void show_jre_download_notice(const HWND hwnd, const wchar_t *const title
}
}
/* ======================================================================== */
/* Single instance */
/* ======================================================================== */
static ULONGLONG hash_code(const BYTE *const message, const size_t message_len)
{
ULONGLONG hash = 0xCBF29CE484222325ull;
for (size_t iter = 0U; iter < message_len; ++iter)
{
hash ^= message[iter];
hash *= 0x00000100000001B3ull;
}
return hash;
}
static BOOL initialize_mutex(HANDLE *const handle, const wchar_t *const mutex_name)
{
static const char *const BUILD_TIME = __DATE__ " " __TIME__;
const ULONGLONG hashcode_0 = hash_code((const BYTE*)BUILD_TIME, sizeof(wchar_t) * strlen(BUILD_TIME));
const ULONGLONG hashcode_1 = hash_code((const BYTE*)mutex_name, sizeof(wchar_t) * wcslen(mutex_name));
const wchar_t *const mutex_uuid = awprintf(L"l5j.%016llX%016llX", hashcode_0, hashcode_1);
if (!mutex_uuid)
{
return TRUE; /*better safe than sorry*/
}
BOOL result = TRUE;
if ((*handle = CreateMutexW(NULL, TRUE, mutex_uuid)) != NULL)
{
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
result = FALSE;
}
}
free((void*)mutex_uuid);
return result;
}
/* ======================================================================== */
/* Utilities */
/* ======================================================================== */
@ -959,7 +1001,8 @@ static wchar_t *const DEFAULT_HEADING = L"Launch5j";
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
int result = -1;
const wchar_t *app_heading = NULL, *executable_path = NULL, *executable_directory = NULL, *jarfile_path = NULL, *java_runtime_path = NULL, *jvm_extra_args = NULL, *cmd_extra_args = NULL, *command_line = NULL;
const wchar_t *app_heading = NULL, *mutex_name = NULL, *executable_path = NULL, *executable_directory = NULL, *jarfile_path = NULL, *java_runtime_path = NULL, *jvm_extra_args = NULL, *cmd_extra_args = NULL, *command_line = NULL;
HANDLE mutex_handle = NULL;
DWORD java_required_bitness = 0U;
ULONGLONG java_required_ver_min = 0ULL, java_required_ver_max = 0ULL;
HGDIOBJ splash_image = NULL;
@ -976,6 +1019,21 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine
// Create the window
HWND hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TOPMOST, L"STATIC", APP_HEADING, WS_POPUP | SS_BITMAP, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
// Single instance
mutex_name = load_string(hInstance, ID_STR_MUTEXID);
if (AVAILABLE(mutex_name) && (wcslen(mutex_name) >= MIN_MUTEXID_LENGTH + ((mutex_name[0U] == L'@') ? 0U : 1U)))
{
if(!initialize_mutex(&mutex_handle, (mutex_name[0U] == L'@') ? mutex_name + 1U : mutex_name))
{
if(mutex_name[0U] != L'@')
{
show_message(hwnd, MB_ICONWARNING | MB_TOPMOST, APP_HEADING, L"The application is already running.\n\n"
L"If you see this message even though the application does not appear to be running, try restarting your computer!");
}
goto cleanup;
}
}
// Show the splash screen
#if ENABLE_SPLASH
if (splash_image = LoadImage(hInstance, MAKEINTRESOURCE(ID_BITMAP_SPLASH), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE))
@ -1113,6 +1171,7 @@ cleanup:
close_handle(&process_info.hThread);
close_handle(&process_info.hProcess);
close_handle(&mutex_handle);
destroy_window(&hwnd);
delete_object(&splash_image);
@ -1124,6 +1183,7 @@ cleanup:
free((void*)jarfile_path);
free((void*)executable_directory);
free((void*)executable_path);
free((void*)mutex_name);
free((void*)app_heading);
return result;

View File

@ -24,3 +24,4 @@
#define ID_STR_JAVAMIN 4
#define ID_STR_JAVAMAX 5
#define ID_STR_BITNESS 6
#define ID_STR_MUTEXID 7