/*
* Given a path to a jre to execute, this routine checks if this process
* is indeed that jre. If not, it exec's that jre.
*
* We want to actually check the paths rather than just the version string
* built into the executable, so that given version specification will yield
* the exact same Java environment, regardless of the version of the arbitrary
* launcher we start with.
*/
void
ExecJRE(char *jre, char **argv) {
jint len;
char path[MAXPATHLEN + 1];
//program name:java
const char *progname = GetProgramName();
/*
* Resolve the real path to the currently running launcher.
*/
//获取当前进程已加载模块的文件的完整路径,该模块必须由当前进程加载,存入path变量中
len = GetModuleFileName(NULL, path, MAXPATHLEN + 1);
if (len == 0 || len > MAXPATHLEN) {
JLI_ReportErrorMessageSys(JRE_ERROR9, progname);
exit(1);
}
//ExecJRE: old: C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe
JLI_TraceLauncher("ExecJRE: old: %s\n", path);
//ExecJRE: new: C:\Tools\Java\open-jre1.8.0_202
JLI_TraceLauncher("ExecJRE: new: %s\n", jre);
/*
* If the path to the selected JRE directory is a match to the initial
* portion of the path to the currently executing JRE, we have a winner!
* If so, just return.
*/
//判断项目中是否包含jre运行时环境
//比较jre, path,比较长度:strlen(jre),如果相等直接return
if (JLI_StrNCaseCmp(jre, path, JLI_StrLen(jre)) == 0)
return; /* I am the droid you were looking for */
/*
* If this isn't the selected version, exec the selected version.
*/
//拼接java命令绝对路径:C:\Tools\Java\open-jre1.8.0_202\bin\java.exe
JLI_Snprintf(path, sizeof(path), "%s\\bin\\%s.exe", jre, progname);
/*
* Although Windows has an execv() entrypoint, it doesn't actually
* overlay a process: it can only create a new process and terminate
* the old process. Therefore, any processes waiting on the initial
* process wake up and they shouldn't. Hence, a chain of pseudo-zombie
* processes must be retained to maintain the proper wait semantics.
* Fortunately the image size of the launcher isn't too large at this
* time.
*
* If it weren't for this semantic flaw, the code below would be ...
*
* execv(path, argv);
* JLI_ReportErrorMessage("Error: Exec of %s failed\n", path);
* exit(1);
*
* The incorrect exec semantics could be addressed by:
*
* exit((int)spawnv(_P_WAIT, path, argv));
*
* Unfortunately, a bug in Windows spawn/exec impementation prevents
* this from completely working. All the Windows POSIX process creation
* interfaces are implemented as wrappers around the native Windows
* function CreateProcess(). CreateProcess() takes a single string
* to specify command line options and arguments, so the POSIX routine
* wrappers build a single string from the argv[] array and in the
* process, any quoting information is lost.
*
* The solution to this to get the original command line, to process it
* to remove the new multiple JRE options (if any) as was done for argv
* in the common SelectVersion() routine and finally to pass it directly
* to the native CreateProcess() Windows process control interface.
*/
{
char *cmdline;
char *p;
char *np;
char *ocl;
char *ccl;
char *unquoted;
DWORD exitCode;
STARTUPINFO si;
PROCESS_INFORMATION pi;
/*
* The following code block gets and processes the original command
* line, replacing the argv[0] equivalent in the command line with
* the path to the new executable and removing the appropriate
* Multiple JRE support options. Note that similar logic exists
* in the platform independent SelectVersion routine, but is
* replicated here due to the syntax of CreateProcess().
*
* The magic "+ 4" characters added to the command line length are
* 2 possible quotes around the path (argv[0]), a space after the
* path and a terminating null character.
*/
//命令行返回数据结构:char *argv[],查看MSDN《Parsing C Command-Line Arguments》
ocl = GetCommandLine();
np = ccl = JLI_StringDup(ocl);
//丢弃第一个参数:argv[0] = java
p = nextarg(&np); /* Discard argv[0] */
cmdline = (char *)JLI_MemAlloc(JLI_StrLen(path) + JLI_StrLen(np) + 4);
if (JLI_StrChr(path, (int)' ') == NULL && JLI_StrChr(path, (int)'\t') == NULL)
cmdline = JLI_StrCpy(cmdline, path);
else
cmdline = JLI_StrCat(JLI_StrCat(JLI_StrCpy(cmdline, "\""), path), "\"");
while (*np != (char)0) { /* While more command-line */
//获取命令行参数arg[i](i >= 1),处理反斜杠转义字符
p = nextarg(&np);
if (*p != (char)0) { /* If a token was isolated */
//移除双引号
unquoted = unquote(p);
//拼接以'-'开始的命令行参数
if (*unquoted == '-') { /* Looks like an option */
//argv[7] = -classpath
//argv[8] = xx1.jar;xx2.jar;...
//argv[9] = com.johnjoe.study.Test
//argv[10] = -cp
//argv[11] = xxx.jar
//因为argv中-classpath和-cp的特殊处理
if (JLI_StrCmp(unquoted, "-classpath") == 0 ||
JLI_StrCmp(unquoted, "-cp") == 0) { /* Unique cp syntax */
//拼接命令行参数
cmdline = JLI_StrCat(JLI_StrCat(cmdline, " "), p);
p = nextarg(&np);
if (*p != (char)0) /* If a token was isolated */
//拼接参数配置的lib(jar)包目录
cmdline = JLI_StrCat(JLI_StrCat(cmdline, " "), p);
//过滤参数:-version, -jre-restrict-search, -no-jre-restrict-search
//其余参数全部原样拼接
} else if (JLI_StrNCmp(unquoted, "-version:", 9) != 0 &&
JLI_StrCmp(unquoted, "-jre-restrict-search") != 0 &&
JLI_StrCmp(unquoted, "-no-jre-restrict-search") != 0) {
cmdline = JLI_StrCat(JLI_StrCat(cmdline, " "), p);
}
} else { /* End of options */
//拼接当前循环取出的命令行参数
cmdline = JLI_StrCat(JLI_StrCat(cmdline, " "), p);
//拼接剩余命令行参数并退出循环
cmdline = JLI_StrCat(JLI_StrCat(cmdline, " "), np);
JLI_MemFree((void *)unquoted);
break;
}
JLI_MemFree((void *)unquoted);
}
}
JLI_MemFree((void *)ccl);
if (JLI_IsTraceLauncher()) {
np = ccl = JLI_StringDup(cmdline);
p = nextarg(&np);//p与cmdline相等
//ReExec Command: C:\Tools\Java\open-jre1.8.0_202\bin\java.exe (C:\Tools\Java\open-jre1.8.0_202\bin\java.exe)
printf("ReExec Command: %s (%s)\n", path, p);
//ReExec Args: -jar test.jar
printf("ReExec Args: %s\n", np);
JLI_MemFree((void *)ccl);
}
//刷新stdou, stderr流缓冲区
(void)fflush(stdout);
(void)fflush(stderr);
/*
* The following code is modeled after a model presented in the
* Microsoft Technical Article "Moving Unix Applications to
* Windows NT" (March 6, 1994) and "Creating Processes" on MSDN
* (Februrary 2005). It approximates UNIX spawn semantics with
* the parent waiting for termination of the child.
*/
memset(&si, 0, sizeof(si));
si.cb =sizeof(STARTUPINFO);
memset(&pi, 0, sizeof(pi));
//启动新的进程执行命令行
if (!CreateProcess((LPCTSTR)path, /* executable name */
(LPTSTR)cmdline, /* command line */
(LPSECURITY_ATTRIBUTES)NULL, /* process security attr. */
(LPSECURITY_ATTRIBUTES)NULL, /* thread security attr. */
(BOOL)TRUE, /* inherits system handles */
(DWORD)0, /* creation flags */
(LPVOID)NULL, /* environment block */
(LPCTSTR)NULL, /* current directory */
(LPSTARTUPINFO)&si, /* (in) startup information */
(LPPROCESS_INFORMATION)&pi)) { /* (out) process information */
JLI_ReportErrorMessageSys(SYS_ERROR1, path);
exit(1);
}
//等待执行完成,超时时间设置为INT.MAX_VALUE: 0xFFFFFFFF
if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_FAILED) {
//获取退出代码
if (GetExitCodeProcess(pi.hProcess, &exitCode) == FALSE)
exitCode = 1;
} else {
JLI_ReportErrorMessage(SYS_ERROR2);
exitCode = 1;
}
//关闭线程、进程句柄
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
//终止新的进程
exit(exitCode);
}
}
src/windows/bin/java_md.c::nextarg
/*
* Local helper routine to isolate a single token (option or argument)
* from the command line.
*
* This routine accepts a pointer to a character pointer. The first
* token (as defined by MSDN command-line argument syntax) is isolated
* from that string.
*
* Upon return, the input character pointer pointed to by the parameter s
* is updated to point to the remainding, unscanned, portion of the string,
* or to a null character if the entire string has been consummed.
*
* This function returns a pointer to a null-terminated string which
* contains the isolated first token, or to the null character if no
* token could be isolated.
*
* Note the side effect of modifying the input string s by the insertion
* of a null character, making it two strings.
*
* See "Parsing C Command-Line Arguments" in the MSDN Library for the
* parsing rule details. The rule summary from that specification is:
*
* * Arguments are delimited by white space, which is either a space or a tab.
*
* * A string surrounded by double quotation marks is interpreted as a single
* argument, regardless of white space contained within. A quoted string can
* be embedded in an argument. Note that the caret (^) is not recognized as
* an escape character or delimiter.
*
* * A double quotation mark preceded by a backslash, \", is interpreted as a
* literal double quotation mark (").
*
* * Backslashes are interpreted literally, unless they immediately precede a
* double quotation mark.
*
* * If an even number of backslashes is followed by a double quotation mark,
* then one backslash (\) is placed in the argv array for every pair of
* backslashes (\\), and the double quotation mark (") is interpreted as a
* string delimiter.
*
* * If an odd number of backslashes is followed by a double quotation mark,
* then one backslash (\) is placed in the argv array for every pair of
* backslashes (\\) and the double quotation mark is interpreted as an
* escape sequence by the remaining backslash, causing a literal double
* quotation mark (") to be placed in argv.
*
* 在MSDN Library转换规则明细查看“C命令行参数转换”。根据规范,总结规则如下:
*
* * 参数被空格(' ')或制表符('\t')等空白符号分割。
*
* * 被双引号包围起来的字符串被认为是一个参数,无论里面是否包含空白符号(' ' | '\t')。
* 被引起来的字符串可以嵌入到参数中。注意:插入符(^)不会被当作转义符或分隔符。
*
* * 双引号前面加一个反斜杠,组成转义字符,转义成单个双引号(")。
*
* * 反斜杠按字面解释,除非放在双引号之前。
*
* * 如果偶数个反斜杠后面跟一个双引号, 则每对反斜杠组成转义字符,
* 转义成单个反斜杠存储在 argv 数组中。
*
* * 如果奇数个反斜杠后面跟一个双引号, 则每对反斜杠组成转义字符,
* 转义成单个反斜杠存储在 argv 数组中。双引号作为字符串分隔符。
*/
// ******** 移除第一个命令行参数的斜杠 *********
static char*
nextarg(char** s) {
char *p = *s;
char *head;
int slashes = 0;//斜杠数量
int inquote = 0;//是否在引号中
/*
* Strip leading whitespace, which MSDN defines as only space or tab.
* (Hence, no locale specific "isspace" here.)
*/
//移除最前面的空白字符(' ' | '\t'), 并设为返回结果
while (*p != (char)0 && (*p == ' ' || *p == '\t'))
p++;
head = p; /* Save the start of the token to return */
/*
* Isolate a token from the command line.
*/
//根据空白字符(' ' | '\t')逐个剥离命令行参数
while (*p != (char)0 && (inquote || !(*p == ' ' || *p == '\t'))) {
if (*p == '\\' && *(p+1) == '"' && slashes % 2 == 0)
p++;
else if (*p == '"')
inquote = !inquote;
slashes = (*p++ == '\\') ? slashes + 1 : 0;
}
/*
* If the token isolated isn't already terminated in a "char zero",
* then replace the whitespace character with one and move to the
* next character.
* 如果剩余的命令行未以(char)0结束,用(char)0替换空白字符并将指针移动到下一个字符。???
*/
//如果字符串指针首字符不为空字符,指针后移一个字符并赋值为空字符 (Dev C++执行抛异常)
if (*p != (char)0)
*p++ = (char)0;
/*
* Update the parameter to point to the head of the remaining string
* reflecting the command line and return a pointer to the leading
* token which was isolated from the command line.
*/
//s指向剥离一个命令行参数后的字符串
*s = p;
return (head);
}
src/windows/bin/java_md.c::unquote
/*
* Local helper routine to return a string equivalent to the input string
* s, but with quotes removed so the result is a string as would be found
* in argv[]. The returned string should be freed by a call to JLI_MemFree().
*
* The rules for quoting (and escaped quotes) are:
*
* 1 A double quotation mark preceded by a backslash, \", is interpreted as a
* literal double quotation mark (").
*
* 2 Backslashes are interpreted literally, unless they immediately precede a
* double quotation mark.
*
* 3 If an even number of backslashes is followed by a double quotation mark,
* then one backslash (\) is placed in the argv array for every pair of
* backslashes (\\), and the double quotation mark (") is interpreted as a
* string delimiter.
*
* 4 If an odd number of backslashes is followed by a double quotation mark,
* then one backslash (\) is placed in the argv array for every pair of
* backslashes (\\) and the double quotation mark is interpreted as an
* escape sequence by the remaining backslash, causing a literal double
* quotation mark (") to be placed in argv.
*/
static char*
unquote(const char *s) {
const char *p = s; /* Pointer to the tail of the original string */
char *un = (char*)JLI_MemAlloc(JLI_StrLen(s) + 1); /* Ptr to unquoted string */
char *pun = un; /* Pointer to the tail of the unquoted string */
//移除双引号并返回
while (*p != '\0') {
if (*p == '"') {
//双引号直接跳过,指向下一字符
p++;
} else if (*p == '\\') {
//JLI_StrSpn从第一个字符开始匹配,此处可以直接简写成1
//如果反斜杠后紧跟双引号直接跳过双引号,只输出反斜杠
const char *q = p + JLI_StrSpn(p,"\\");
if (*q == '"')
do {
*pun++ = '\\';
p += 2;
} while (*p == '\\' && p < q);
else
while (p < q)
*pun++ = *p++;
} else {
//其它字符直接输出
*pun++ = *p++;
}
}
//将pun指针的值设为空串
*pun = '\0';
return un;
}