Skip to content

Commit d2700c0

Browse files
committed
BUG: Fix launching with CTKAPPLAUNCHER_WITHOUT_CONSOLE_IO_SUPPORT on Windows
Application path and command-line arguments can now contain special characters. Note: console launcher on Windows still has not been fixed. wmain would need to be used instead of main and wide characters should be converted to utf8.
1 parent 1d61240 commit d2700c0

File tree

3 files changed

+64
-92
lines changed

3 files changed

+64
-92
lines changed

Base/ctkCommandLineParser.cpp

Lines changed: 45 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -819,106 +819,62 @@ void ctkCommandLineParser::setStrictModeEnabled(bool strictMode)
819819
this->Internal->StrictMode = strictMode;
820820
}
821821

822+
#if defined (_WIN32)
822823
// --------------------------------------------------------------------------
823824
void ctkCommandLineParser::convertWindowsCommandLineToUnixArguments(
824-
const char *cmd_line, int *argc, char ***argv)
825+
PWSTR cmd_line, int* argc, char*** argv)
825826
{
826827
if (!cmd_line || !argc || !argv)
827828
{
828829
return;
829830
}
831+
*argc = 0;
832+
*argv = nullptr;
830833

831-
// A space delimites an argument except when it is inside a quote
832-
833-
(*argc) = 1;
834-
835-
size_t cmd_line_len = strlen(cmd_line);
836-
837-
size_t i;
838-
for (i = 0; i < cmd_line_len; i++)
839-
{
840-
while (isspace(cmd_line[i]) && i < cmd_line_len)
841-
{
842-
i++;
843-
}
844-
if (i < cmd_line_len)
845-
{
846-
if (cmd_line[i] == '\"')
847-
{
848-
i++;
849-
while (cmd_line[i] != '\"' && i < cmd_line_len)
850-
{
851-
i++;
852-
}
853-
(*argc)++;
854-
}
855-
else
856-
{
857-
while (!isspace(cmd_line[i]) && i < cmd_line_len)
858-
{
859-
i++;
860-
}
861-
(*argc)++;
862-
}
863-
}
864-
}
865-
866-
(*argv) = new char* [(*argc) + 1];
867-
(*argv)[(*argc)] = NULL;
868-
869-
// Set the first arg to be the exec name
870-
871-
(*argv)[0] = new char [1024];
872-
#ifdef _WIN32
873-
::GetModuleFileName(0, (*argv)[0], 1024);
874-
#else
875-
(*argv)[0][0] = '\0';
876-
#endif
834+
// Split the command line to separate arguments
835+
int numArgs = 0;
836+
LPWSTR* wideArgs = CommandLineToArgvW(cmd_line, &numArgs);
837+
if (wideArgs == nullptr)
838+
{
839+
return;
840+
}
877841

878-
// Allocate the others
842+
// Allocate space for pointers in argv
843+
(*argc) = numArgs + 1; // +1 because the first argument is the executable name
844+
(*argv) = new char* [numArgs + 1];
845+
for (int i = 0; i < numArgs + 1; i++)
846+
{
847+
(*argv)[i] = nullptr;
848+
}
879849

880-
int j;
881-
for (j = 1; j < (*argc); j++)
850+
// Convert each argument to UTF8 and save it in argv
851+
for (int i = 0; i < numArgs; ++i)
852+
{
853+
BOOL lpUsedDefaultChar = false;
854+
// Get length
855+
int utf8length = WideCharToMultiByte(CP_UTF8, 0, wideArgs[i], -1, NULL, 0, NULL, &lpUsedDefaultChar);
856+
char* utf8buffer = new char[utf8length + 1];
857+
int retval = WideCharToMultiByte(CP_UTF8, 0, wideArgs[i], -1, utf8buffer, utf8length, NULL, &lpUsedDefaultChar);
858+
if (!SUCCEEDED(retval))
882859
{
883-
(*argv)[j] = new char [cmd_line_len + 10];
860+
// set to empty string in case of encoding error
861+
utf8buffer[0] = '\0';
862+
continue;
884863
}
864+
utf8buffer[utf8length] = '\0'; // Make sure the string is null-terminated
865+
(*argv)[i + 1] = utf8buffer; // +1 because the first argument is the executable name
866+
}
885867

886-
// Grab the args
887-
888-
size_t pos;
889-
int argc_idx = 1;
890-
891-
for (i = 0; i < cmd_line_len; i++)
892-
{
893-
while (isspace(cmd_line[i]) && i < cmd_line_len)
894-
{
895-
i++;
896-
}
897-
if (i < cmd_line_len)
898-
{
899-
if (cmd_line[i] == '\"')
900-
{
901-
i++;
902-
pos = i;
903-
while (cmd_line[i] != '\"' && i < cmd_line_len)
904-
{
905-
i++;
906-
}
907-
memcpy((*argv)[argc_idx], &cmd_line[pos], i - pos);
908-
(*argv)[argc_idx][i - pos] = '\0';
909-
argc_idx++;
910-
}
911-
else
912-
{
913-
pos = i;
914-
while (!isspace(cmd_line[i]) && i < cmd_line_len)
915-
{
916-
i++;
917-
}
918-
memcpy((*argv)[argc_idx], &cmd_line[pos], i - pos);
919-
(*argv)[argc_idx][i - pos] = '\0';
920-
argc_idx++;
921-
}
922-
}
923-
}
868+
LocalFree(wideArgs);
869+
870+
// Get the application name
871+
wchar_t wideBuffer[MAX_PATH];
872+
DWORD wideLength = GetModuleFileNameW(NULL, wideBuffer, MAX_PATH);
873+
// Convert the wide string to UTF-8
874+
int utf8length = WideCharToMultiByte(CP_UTF8, 0, wideBuffer, wideLength, NULL, 0, NULL, NULL);
875+
char* utf8buffer = new char[utf8length + 1];
876+
WideCharToMultiByte(CP_UTF8, 0, wideBuffer, wideLength, utf8buffer, utf8length, NULL, NULL);
877+
utf8buffer[utf8length] = '\0'; // Make sure the string is null-terminated
878+
(*argv)[0] = utf8buffer;
924879
}
880+
#endif

Base/ctkCommandLineParser.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#ifndef __ctkCommandLineParser_h
22
#define __ctkCommandLineParser_h
33

4+
#if defined (_WIN32)
5+
#include <windows.h>
6+
#endif
7+
48
// Qt includes
59
#include <QString>
610
#include <QStringList>
@@ -383,14 +387,18 @@ class /*CTK_CORE_EXPORT*/ ctkCommandLineParser
383387
*/
384388
void setStrictModeEnabled(bool strictMode);
385389

390+
#if defined (_WIN32)
386391
/**
387392
* Convert windows-style arguments given as a command-line string
388393
* into more traditional argc/argv arguments.
394+
* If an argument is failed to be retrieved (for example, due to character encoding error)
395+
* then the corresponding argv pointer is set to point to an empty string.
389396
*
390397
* @note argv[0] will be assigned the executable name using the ::GetModuleFileName function.
391398
*/
392399
static void convertWindowsCommandLineToUnixArguments(
393-
const char *cmd_line, int *argc, char ***argv);
400+
PWSTR cmd_line, int* argc, char*** argv);
401+
#endif
394402

395403
private:
396404
class ctkInternal;

Main.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,23 @@ int appLauncherMain(int argc, char** argv)
182182

183183
// --------------------------------------------------------------------------
184184
#ifndef CTKAPPLAUNCHER_WITHOUT_CONSOLE_IO_SUPPORT
185+
// NOTE: On Windows, wmain should be used to allow non-ASCII characters in command-line arguments
185186
int main(int argc, char *argv[])
186187
{
188+
// Uncomment the next two lines to stop at application start (to give a chance to connect with a debugger)
189+
// std::cout << "Attach debugger and hit Enter" << std::endl;
190+
// std::cin.get();
191+
187192
return appLauncherMain(argc, argv);
188193
}
189194
#elif defined Q_OS_WIN32
190-
int __stdcall WinMain(HINSTANCE hInstance,
195+
int __stdcall wWinMain(HINSTANCE hInstance,
191196
HINSTANCE hPrevInstance,
192-
LPSTR lpCmdLine, int nShowCmd)
197+
PWSTR lpCmdLine, int nShowCmd)
193198
{
199+
// Uncomment the next line to stop at application start (to give a chance to connect with a debugger)
200+
// int msgboxID = MessageBox(NULL, "Attach your debugger", "Debug", MB_ICONWARNING);
201+
194202
Q_UNUSED(hInstance);
195203
Q_UNUSED(hPrevInstance);
196204
Q_UNUSED(nShowCmd);

0 commit comments

Comments
 (0)