cl -I%java_home%\include -I%java_home%\include\win32 -LD HelloWorld.cpp –Fehelloworld.dll
注 意:生成的 dll 文件名在选项 -Fe 后面配置,这里是 helloworld.dll,因为前面我们在 HelloWorld.java 文件中 loadLibary 的时候使用的名字是 helloworld。所以要保证这里的名字和前面 load 的名字一致。另外需要将 -I%java_home%\include -I%java_home%\include\win32 参数加上,因为在第四步里面编写本地方法的时候引入了 jni.h 文件,所以在这里需要加入这些头文件的路径。
完成了这些步骤之后就可以运行这个程序:java HelloWorld,运行的结果就是在控制台输出字符串“HelloWorld”。
实现窗口 Reparent
前面部分介绍了如何使用 JNI,接下来介绍如何通过 JNI 启动一个 Windows 的本地应用程序并且将其主窗口设置为指定窗口的子窗口。首先创建一个 Java 类,如下面的清单所示:
public class ReparentUtil { static{ System.loadLibrary("reparent"); } public static native int startAndReparent(int parentWnd, String command,String wndClass);}
其中 System.loadLibrary("reparent") 是用来加载名为 reparent 的动态库,我们会在这个动态库中具体实现方法 startAndReparent(…)。
startAndReparent 定义方法来启动 Windows 程序,并且将其窗口 reparent 到我们指定的窗口。其中:
int parentWnd: 父窗口句柄
String command:Windows 程序启动命令
String wndClass:Windows 程序主窗口类型
由于有的程序启动后会创建多个顶级窗口,所以我们在这里要指定一个主窗口类型来区分不同的顶级窗口。这个方法是一个本地方法,我们会用 C++ 生成为一个叫 reparent.dll 的动态库,这个方法即存在于这个动态库中。
这 个 Java 函数对应的的 C++ 函数是 Java_com_reparent_ReparentUtil_startAndReparent(JNIEnv *env, jclass classobj, jint parent, jstring command, jstring wndClass), 这个函数主要实现两部分的功能:
启动 Windows 应用程序;
获取 Windows 应用程序的主窗口句柄;
将 Windows 应用主窗口设置成指定窗口的子窗口。
启动 Windows 应用程序
下面我们来看看启动 Windows 应用程序的实现. 我们先将函数传入的 Java 字符串参数转化成 C 字符串。这个过程主要通过 GetStringChars() 来实现。
JNIEXPORT jint JNICALL Java_com_reparent_ReparentUtil_startAndReparent (JNIEnv *env, jclass classobj, jint parent, jstring command, jstring wndClass){ jboolean isCopy=FALSE; PROCESS_INFORMATION pInfo; STARTUPINFO sInfo; int hParentWnd; jsize len = ( *env ).GetStringLength(command); const jchar *commandstr = (*env).GetStringChars(command,&isCopy); const jchar *wndClassStr = NULL; char commandcstr[200]; int size = 0; size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)commandstr, len, commandcstr,(len*2+1), NULL, NULL ); (*env).ReleaseStringChars(command, commandstr); if(size==0){ return 0; } commandcstr[size] = 0; if(wndClass!=NULL){ wndClassStr = (*env).GetStringChars(wndClass,&isCopy); if(wndClassStr!=NULL){ len = (*env).GetStringLength(wndClass); size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)wndClassStr, len, wndClassName,(len*2+1), NULL, NULL ); wndClassName[size] = 0; (*env).ReleaseStringChars(wndClass, wndClassStr); } }
接着,我们使用 Windows 的 API:CreateProcess 函数来启动我们要集成的应用程序。
sInfo.cb = sizeof(STARTUPINFO); sInfo.lpReserved = NULL; sInfo.lpReserved2 = NULL; sInfo.cbReserved2 = 0; sInfo.lpDesktop = NULL; sInfo.lpTitle = NULL; sInfo.dwFlags = 0; sInfo.dwX = 0; sInfo.dwY = 0; sInfo.dwFillAttribute = 0; sInfo.wShowWindow = SW_HIDE; if(!CreateProcess(NULL,commandcstr,NULL,NULL, TRUE,0,NULL,NULL,&sInfo,&pInfo)) { printf("ERROR: Cannot launch child process\n"); release(); return 0; }
CreateProcess 函数的定义是:
BOOL CreateProcess ( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes。 LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
其中 lpApplicationName:指向一个 NULL 结尾的、用来指定可执行模块的字符串。lpCommandLine:指向一个 NULL 结尾的、用来指定要运行的命令行。lpProcessAttributes: 指向一个 SECURITY_ATTRIBUTES 结构体,这个结构体决定是否返回的句柄可以被子进程继承。lpThreadAttributes: 指向一个 SECURITY_ATTRIBUTES 结构体,这个结构体决定是否返回的句柄可以被子进程继承。bInheritHandles:指示新进程是否从调用进程处继承了句柄。 dwCreationFlags:指定附加的、用来控制优先类和进程的创建的标志。lpEnvironment:指向一个新进程的环境块。 lpCurrentDirectory:指向一个以 NULL 结尾的字符串,这个字符串用来指定子进程的工作路径。lpStartupInfo:指向一个用于决定新进程的主窗体如何显示的 STARTUPINFO 结构体。lpProcessInformation:指向一个用来接收新进程的识别信息的 PROCESS_INFORMATION 结构体。
获取应用程序的主窗口句柄
为了获取启动后的程序的主窗口句柄,在调用 CreateProcess() 之前,我们需要使用一个 Windows 的系统钩子来截获窗口创建的事件:
hHook = SetWindowsHookEx(WH_SHELL, ShellProc,(HINSTANCE)hDllHandle,NULL);
这里,我们使用的钩子类型是 WH_SHELL。这种钩子可以截获所有顶级窗口创建或者激活的事件。函数的第二个参数是事件处理函数。我们的处理函数叫 ShellProc。我们之后会介绍。
启动应用程序之后,我们需要获取应用程序的主窗口之后才能继续运行。这里需要实现进程间的同步。在我们的主进程中,我们需要等待,当应用程序的主窗口创建之后,我们发一个消息,通知我们的主进程继续执行。
我 们这里使用 Windows 的 Event 来实现同步。我们首先调用 CreateEvent 来创建一个事件,然后调用 WaitForSingleObject()等待事件的状态改变。在我们的 ShellProc 处理函数中,我们一旦获取应用程序主窗口句柄,我们会改变事件的状态以通知主进程继续执行。
以下是创建事件的代码,我们创建了一个名为 Global\WaitWindowCreatedEvent 的事件:
SECURITY_ATTRIBUTES secuAtt; secuAtt.bInheritHandle = TRUE; secuAtt.lpSecurityDescriptor = NULL; secuAtt.nLength = sizeof(SECURITY_ATTRIBUTES); hEvent = CreateEvent(&secuAtt,FALSE,FALSE,TEXT("Global\WaitWindowCreatedEvent"));






