我们有一个小型的C应用程序,我们通过JNI启动所有Java应用程序。这样可以实现以下功能:
- 拥有有意义的进程名称(在Windows下尤为重要)
- 拥有自己的图标(同样对Windows很重要)
- 动态构建类路径(我们解析/lib文件夹中的内容以自动包含所有jar包)
对于我们的应用程序,我们只是硬编码堆限制,但是你可以根据可用内存轻松动态配置最大堆大小。
这种小应用程序实际上非常容易实现(使用JNI是最容易的事情之一)。一个好的起点是JDK的源代码(java.exe本身有一个子文件夹,可以使用它-这就是我们所做的)。大多数人会惊讶地发现,java.exe是一个很小的应用程序(少于200行代码),它只是调用JNI并传递命令行参数(一旦你开始自己启动东西,甚至使用名为main()的方法也是相当可选的)。
这里有一段代码,不仅启动了JVM等等,而且还根据计算机可用RAM确定了最大堆空间。这是一段很长的代码,不算很漂亮,但这是经过实战考验的代码-它已经在几百个安装中使用了将近十年的时间。享受吧:
#include <windows.h>
#include <jni.h>
#include <string>
#include <sstream>
using namespace std;
#define STARTUP_CLASS "some/path/to/YourStartupClass"
void vShowError(string sErrorMessage);
void vShowJREError(string sErrorMessage);
void vShowLastError(string sErrorMessage);
void vDestroyVM(JNIEnv *env, JavaVM *jvm);
void vAddOption(string& sName);
string GetClassPath(string root);
string GetJREPath();
int getMaxHeapAvailable(int permGenMB, int maxHeapMB);
JavaVMOption* vm_options;
int mctOptions = 0;
int mctOptionCapacity = 0;
boolean GetApplicationHome(char *buf, jint sz);
typedef jint (CALLBACK *CreateJavaVM)(JavaVM
**pvm, JNIEnv **penv, void *args);
boolean PathExists(string &path)
{
DWORD dwAttr = GetFileAttributes(path.c_str());
if (dwAttr == 0xffffffff)
return FALSE;
else
return TRUE;
}
BOOL GetExceptionString(JNIEnv* jenv, string &result)
{
jthrowable ex;
if (NULL != (ex = jenv->ExceptionOccurred())) {
jenv->ExceptionClear();
jmethodID gmID = jenv->GetMethodID(
jenv->FindClass("java/lang/Throwable"),
"getMessage",
"()Ljava/lang/String;");
jstring jerrStr = (jstring)jenv->CallObjectMethod(ex,gmID);
if (jerrStr != NULL){
const char *errStr = jenv->GetStringUTFChars(jerrStr,0);
result = errStr;
jenv->ReleaseStringUTFChars(jerrStr, errStr);
} else {
result = "null";
}
return TRUE;
} else {
return FALSE;
}
}
BOOL GetJRESystemProperty(JNIEnv *env, string propname, string &propval, string &errmessage)
{
jclass cls = env->FindClass("java/lang/System");
if (cls == NULL){
errmessage = "Unable to interact with Java Virtual Machine - please visit www.java.com and confirm that your Java installation is valid.";
return FALSE;
}
jmethodID mid = env->GetStaticMethodID(cls, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
if (mid == NULL){
errmessage = "Unable to obtain Java runtime system properties - please visit www.java.net and confirm that your Java installation is valid.";
return FALSE;
}
jstring propName = env->NewStringUTF( propname.c_str() );
jstring result = (jstring) env->CallStaticObjectMethod(cls, mid, propName);
const char* utfResult = env->GetStringUTFChars( result, NULL );
if (utfResult == NULL){
errmessage = "Unable to obtain Java runtime system property " + propname + " - please visit www.java.net and confirm that your Java installation is valid.";
return FALSE;
}
propval = utfResult;
env->ReleaseStringUTFChars( result, utfResult );
return TRUE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
JNIEnv *env;
JavaVM *jvm;
jint jintVMStartupReturnValue;
jclass jclassStartup;
jmethodID midStartup;
char home[2000];
if (!GetApplicationHome(home, sizeof(home))) {
vShowError("Unable to determine application home.");
return 0;
}
string sAppHome(home);
string sOption_AppHome = "-Dapplication.home=" + sAppHome;
string sJREPath = GetJREPath();
string sRuntimePath = sJREPath + "\\bin\\client\\";
string sJVMpath = sRuntimePath + "jvm.dll";
string sBootPath = sJREPath + "\\lib";
string sOption_BootPath = "-Dsun.boot.class.path=" + sBootPath;
string cpRoot = sAppHome + "\\";
string sClassPath = GetClassPath(cpRoot);
string sOption_ClassPath = "-Djava.class.path=" + sClassPath;
string sOption_JavaLibraryPath = "-Djava.library.path=" + sAppHome + "\\lib";
int maxHeapBM = 768;
int argStart = 1;
if(__argc > 1){
string maxheapstr = __argv[1];
if (maxheapstr.substr(0, 9).compare("/maxheap=") == 0){
maxheapstr = maxheapstr.substr(9);
maxHeapBM = atoi(maxheapstr.c_str());
argStart++;
}
}
int maxHeapMB = getMaxHeapAvailable(62, maxHeapBM);
stringstream ss;
ss << "-Xmx";
ss << maxHeapMB;
ss << "m";
string sOption_HeapSpace = ss.str();
string sOption_PermSize = "-XX:MaxPermSize=62m";
string sOption_HeapDump = "-XX:+HeapDumpOnOutOfMemoryError";
if (strstr(szCmdLine, "/launcher_verbose") != NULL){
string msg = "App Home = ";
msg += sAppHome;
msg += "\nJRE Path = ";
msg += sJREPath;
msg += "\nRuntime Path = ";
msg += sRuntimePath;
msg += "\nClass Path = ";
msg += sClassPath;
msg += "\nHeap argument = ";
msg += sOption_HeapSpace;
msg += "\nPermsize argument = ";
msg += sOption_PermSize;
msg += "\nHeap dump = ";
msg += sOption_HeapDump;
msg += "\njava.library.path = ";
msg += sOption_JavaLibraryPath;
msg += "\nCommand line = ";
msg += szCmdLine;
FILE *f = fopen("launcher.txt", "w");
fprintf(f, "%s", msg.c_str());
fclose(f);
MessageBox(0, msg.c_str(), "Launcher Verbose Info", MB_OK);
}
vAddOption(sOption_ClassPath);
vAddOption(sOption_AppHome);
vAddOption(sOption_HeapSpace);
vAddOption(sOption_PermSize);
vAddOption(sOption_HeapDump);
vAddOption(sOption_JavaLibraryPath);
JavaVMInitArgs vm_args;
vm_args.version = 0x00010002;
vm_args.options = vm_options;
vm_args.nOptions = mctOptions;
vm_args.ignoreUnrecognized = JNI_TRUE;
string sBinPath = sJREPath + "\\bin";
char originalCurrentDirectory[4096];
GetCurrentDirectory(4095, originalCurrentDirectory);
SetCurrentDirectory(sBinPath.c_str());
typedef BOOL (WINAPI *LPFNSDD)(LPCTSTR lpPathname);
HINSTANCE hKernel32 = GetModuleHandle("kernel32");
LPFNSDD lpfnSetDllDirectory = (LPFNSDD)GetProcAddress(hKernel32, "SetDllDirectoryA");
if (lpfnSetDllDirectory){
lpfnSetDllDirectory(sBinPath.c_str());
}
HINSTANCE hJVM = LoadLibrary(sJVMpath.c_str());
SetCurrentDirectory(originalCurrentDirectory);
if (lpfnSetDllDirectory){
lpfnSetDllDirectory(NULL);
}
if( hJVM == NULL ){
vShowJREError("Java does not appear to be installed on this machine. Click OK to go to www.java.com where you can download and install Java");
return 0;
}
CreateJavaVM lpfnCreateJavaVM = (CreateJavaVM)
GetProcAddress(hJVM, "JNI_CreateJavaVM");
jintVMStartupReturnValue = (*lpfnCreateJavaVM)(&jvm, &env, &vm_args);
if (jintVMStartupReturnValue < 0) {
stringstream ss;
ss << "There is a problem with the 32 bit Java installation on this computer (";
ss << jintVMStartupReturnValue;
ss << "). Click OK to go to www.java.com where you can download and re-install 32 bit Java";
vShowJREError(ss.str());
return 0;
}
string version = "";
string errormsg = "";
if (!GetJRESystemProperty(env, "java.specification.version", version, errormsg)){
vShowJREError(errormsg);
vDestroyVM(env, jvm);
return 0;
}
double verf = atof(version.c_str());
if (verf < 1.599f){
string sErrorMessage = "This application requires Java Runtime version 1.6 or above, but your runtime is version " + version + "\n\nClick OK to go to www.java.com and update to the latest Java Runtime Environment";
vShowJREError(sErrorMessage);
vDestroyVM(env, jvm);
return 0;
}
string sStartupClass = STARTUP_CLASS;
jclassStartup = env->FindClass(sStartupClass.c_str());
if (jclassStartup == NULL) {
string sErrorMessage = "Unable to find startup class [" + sStartupClass + "]";
vShowError(sErrorMessage);
vDestroyVM(env, jvm);
return 0;
}
string sStartupMethod_Identifier = "main";
string sStartupMethod_TypeDescriptor =
"([Ljava/lang/String;)V";
midStartup =
env->GetStaticMethodID(jclassStartup,
sStartupMethod_Identifier.c_str(),
sStartupMethod_TypeDescriptor.c_str());
if (midStartup == NULL) {
string sErrorMessage =
"Unable to find startup method ["
+ sStartupClass + "."
+ sStartupMethod_Identifier
+ "] with type descriptor [" +
sStartupMethod_TypeDescriptor + "]";
vShowError(sErrorMessage);
vDestroyVM(env, jvm);
return 0;
}
jstring jstringExampleArg;
jclass jclassString;
jobjectArray jobjectArray_args;
jstringExampleArg = env->NewStringUTF("example string");
if (jstringExampleArg == NULL){
vDestroyVM(env, jvm);
return 0;
}
jclassString = env->FindClass("java/lang/String");
jobjectArray_args = env->NewObjectArray(__argc-argStart, jclassString, jstringExampleArg);
if (jobjectArray_args == NULL){
vDestroyVM(env, jvm);
return 0;
}
int count;
for (count = argStart; count < __argc; count++){
env->SetObjectArrayElement(jobjectArray_args, count-1, env->NewStringUTF(__argv[count]));
}
env->CallStaticVoidMethod(jclassStartup, midStartup, jobjectArray_args);
string errstr;
if (GetExceptionString(env, errstr)){
vShowError(errstr);
}
if (jvm->DetachCurrentThread() != 0) {
vShowError("Could not detach main thread.\n");
}
jvm->DestroyJavaVM();
return 0;
}
void vDestroyVM(JNIEnv *env, JavaVM *jvm)
{
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
jvm->DestroyJavaVM();
}
void vShowError(string sError) {
MessageBox(NULL, sError.c_str(), "Startup Error", MB_OK);
}
void vShowJREError(string sError) {
MessageBox(NULL, sError.c_str(), "Startup Error", MB_OK);
ShellExecute(NULL, "open", "http://www.java.com", NULL, NULL, SW_SHOWNORMAL);
}
void vShowLastError(string sLocalError) {
LPVOID lpSystemMsgBuf;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpSystemMsgBuf, 0, NULL );
string sSystemError = string((LPTSTR)lpSystemMsgBuf);
vShowError(sLocalError + " [" + sSystemError + "]");
}
void vAddOption(string& sValue) {
mctOptions++;
if (mctOptions >= mctOptionCapacity) {
if (mctOptionCapacity == 0) {
mctOptionCapacity = 3;
vm_options = (JavaVMOption*)malloc(mctOptionCapacity * sizeof(JavaVMOption));
} else {
JavaVMOption *tmp;
mctOptionCapacity *= 2;
tmp = (JavaVMOption*)malloc(mctOptionCapacity * sizeof(JavaVMOption));
memcpy(tmp, vm_options, (mctOptions-1) * sizeof(JavaVMOption));
free(vm_options);
vm_options = tmp;
}
}
vm_options[mctOptions-1].optionString = (char*)sValue.c_str();
}
jboolean GetApplicationHome(char *buf, jint sz) {
char *cp;
GetModuleFileName(0, buf, sz);
*strrchr(buf, '\\') = '\0';
if ((cp = strrchr(buf, '\\')) == 0) {
buf[0] = '\0';
return JNI_FALSE;
}
return JNI_TRUE;
}
string GetClassPath(string root){
string rootWithBackslash = root;
if (rootWithBackslash[rootWithBackslash.length()-1] != '\\')
rootWithBackslash += "\\";
string cp = rootWithBackslash + "classes\\";
string libPathWithBackslash = rootWithBackslash + "lib\\";
string searchSpec = libPathWithBackslash;
searchSpec = libPathWithBackslash + "*.jar";
WIN32_FIND_DATA fd;
HANDLE find = FindFirstFile(searchSpec.c_str(), &fd);
while (find != NULL){
cp += ";";
cp += libPathWithBackslash;
cp += fd.cFileName;
if (!FindNextFile(find, &fd)){
FindClose(find);
find = NULL;
}
}
return cp;
}
string GetJREPath(){
char home[2000];
if (!GetApplicationHome(home, sizeof(home))) {
vShowError("Unable to determine application home.");
return 0;
}
string sJREPath(home);
sJREPath += "\\jre";
if (PathExists(sJREPath)){
return sJREPath;
}
HKEY hKeyJRERoot;
HKEY hKeyJREInstance;
DWORD dwType;
DWORD dwSize;
BYTE *pData;
string valueName;
string value;
LONG regRslt;
sJREPath = "";
regRslt = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Runtime Environment", 0, KEY_READ, &hKeyJRERoot);
if (regRslt == ERROR_SUCCESS){
valueName = "CurrentVersion";
regRslt = RegQueryValueEx(hKeyJRERoot, valueName.c_str(), NULL, &dwType, NULL, &dwSize);
if (regRslt == ERROR_SUCCESS){
pData = (BYTE *)malloc(dwSize);
value = "";
regRslt = RegQueryValueEx(hKeyJRERoot, valueName.c_str(), NULL, &dwType, pData, &dwSize);
if (regRslt == ERROR_SUCCESS){
value = (LPCSTR)pData;
}
free(pData);
if (value != ""){
regRslt = RegOpenKeyEx(hKeyJRERoot, value.c_str(), 0, KEY_READ, &hKeyJREInstance);
if (regRslt == ERROR_SUCCESS){
valueName = "JavaHome";
value = "";
regRslt = RegQueryValueEx(hKeyJREInstance, valueName.c_str(), NULL, &dwType, NULL, &dwSize);
if (regRslt == ERROR_SUCCESS){
pData = (BYTE *)malloc(dwSize);
regRslt = RegQueryValueEx(hKeyJREInstance, valueName.c_str(), NULL, &dwType, pData, &dwSize);
if (regRslt == ERROR_SUCCESS){
value = (LPCSTR)pData;
sJREPath = value;
}
free(pData);
}
RegCloseKey(hKeyJREInstance);
}
}
}
RegCloseKey(hKeyJRERoot);
}
return sJREPath;
}
static const DWORD NUM_BYTES_PER_MB = 1024 * 1024;
bool canAllocate(DWORD bytes)
{
LPVOID lpvBase;
lpvBase = VirtualAlloc(NULL, bytes, MEM_RESERVE, PAGE_READWRITE);
if (lpvBase == NULL) return false;
VirtualFree(lpvBase, 0, MEM_RELEASE);
return true;
}
int getMaxHeapAvailable(int permGenMB, int maxHeapMB)
{
DWORD originalMaxHeapBytes = 0;
DWORD maxHeapBytes = 0;
int numMemChunks = 0;
SYSTEM_INFO sSysInfo;
DWORD maxPermBytes = permGenMB * NUM_BYTES_PER_MB;
DWORD numBytesNeeded = 0;
GetSystemInfo(&sSysInfo);
int card_shift = 9;
int card_size = 1 << card_shift;
DWORD alignmentBytes = sSysInfo.dwPageSize * card_size;
maxHeapBytes = maxHeapMB * NUM_BYTES_PER_MB + 50*NUM_BYTES_PER_MB;
maxHeapBytes = maxHeapBytes + (maxHeapBytes % alignmentBytes);
numMemChunks = maxHeapBytes / alignmentBytes;
originalMaxHeapBytes = maxHeapBytes;
numBytesNeeded = maxHeapBytes + maxPermBytes;
while (!canAllocate(numBytesNeeded) && numMemChunks > 0)
{
numMemChunks --;
maxHeapBytes = numMemChunks * alignmentBytes;
numBytesNeeded = maxHeapBytes + maxPermBytes;
}
if (numMemChunks == 0) return 0;
if (maxHeapBytes == originalMaxHeapBytes) return maxHeapMB;
return maxHeapBytes / NUM_BYTES_PER_MB;
}