創建進程外可執行程序exe,java進程中創建64或32位JDK的新進程

最近在公司做的一個需求,項目進程是64爲JDK,但是所依賴的額外程序是基於32位的dll開發的。所以就需要通過COM組件的方式來調用外部可執行文件。

但是在註冊COM組件時,因爲要修改註冊表,我們我們必須是管理員的身份去運行這個EXE程序。

在網上找了很多辦法,都試過,沒用。

最後在stackoverflow上找到一篇文章講解如何以管理員身份調用外部可執行文件

https://stackoverflow.com/questions/11041509/elevating-a-processbuilder-process-via-uac

使用ProcessBuilder無法做到這一點,必須得調用Windows API。

使用JNA通過類似於以下代碼的代碼來實現此目的

/**
	 * @param command
	 * @param args
	 * @DesktopJavaDocable disable
	 */
	public static void executeAsAdministrator(String command, String args) {
		Shell32X.SHELLEXECUTEINFO execInfo = new Shell32X.SHELLEXECUTEINFO();
		execInfo.lpFile = new WString(command);
		if (args != null) {
			execInfo.lpParameters = new WString(args);
		}
		execInfo.nShow = Shell32X.SW_SHOWDEFAULT;
		execInfo.fMask = Shell32X.SEE_MASK_NOCLOSEPROCESS;
		execInfo.lpVerb = new WString("runas");
		boolean result = Shell32X.INSTANCE.ShellExecuteEx(execInfo);

		if (!result) {
			int lastError = Kernel32.INSTANCE.GetLastError();
			String errorMessage = Kernel32Util.formatMessageFromLastErrorCode(lastError);
			throw new RuntimeException("Error performing elevation: " + lastError + ": " + errorMessage + " (apperror=" + execInfo.hInstApp + ")");
		}
	}

	public interface Shell32X extends Shell32 {

		// https://stackoverflow.com/questions/11041509/elevating-a-processbuilder-process-via-uac
		// 不要刪除無用的字段,給反射用的

		Shell32X INSTANCE = Native.loadLibrary("shell32", Shell32X.class, W32APIOptions.UNICODE_OPTIONS);

		int SW_HIDE = 0;
		int SW_MAXIMIZE = 3;
		int SW_MINIMIZE = 6;
		int SW_RESTORE = 9;
		int SW_SHOW = 5;
		int SW_SHOWDEFAULT = 10;
		int SW_SHOWMAXIMIZED = 3;
		int SW_SHOWMINIMIZED = 2;
		int SW_SHOWMINNOACTIVE = 7;
		int SW_SHOWNA = 8;
		int SW_SHOWNOACTIVATE = 4;
		int SW_SHOWNORMAL = 1;

		/**
		 * File not found.
		 */
		int SE_ERR_FNF = 2;

		/**
		 * Path not found.
		 */
		int SE_ERR_PNF = 3;

		/**
		 * Access denied.
		 */
		int SE_ERR_ACCESSDENIED = 5;

		/**
		 * Out of memory.
		 */
		int SE_ERR_OOM = 8;

		/**
		 * DLL not found.
		 */
		int SE_ERR_DLLNOTFOUND = 32;

		/**
		 * Cannot share an open file.
		 */
		int SE_ERR_SHARE = 26;


		int SEE_MASK_NOCLOSEPROCESS = 0x00000040;


		int ShellExecute(int i, String lpVerb, String lpFile, String lpParameters, String lpDirectory, int nShow);

		boolean ShellExecuteEx(SHELLEXECUTEINFO lpExecInfo);


		class SHELLEXECUTEINFO extends Structure {
        /*
  DWORD     cbSize;
  ULONG     fMask;
  HWND      hwnd;
  LPCTSTR   lpVerb;
  LPCTSTR   lpFile;
  LPCTSTR   lpParameters;
  LPCTSTR   lpDirectory;
  int       nShow;
  HINSTANCE hInstApp;
  LPVOID    lpIDList;
  LPCTSTR   lpClass;
  HKEY      hkeyClass;
  DWORD     dwHotKey;
  union {
    HANDLE hIcon;
    HANDLE hMonitor;
  } DUMMYUNIONNAME;
  HANDLE    hProcess;
         */

			public int cbSize = size();
			public int fMask;
			public WinDef.HWND hwnd;
			public WString lpVerb;
			public WString lpFile;
			public WString lpParameters;
			public WString lpDirectory;
			public int nShow;
			public WinDef.HINSTANCE hInstApp;
			public Pointer lpIDList;
			public WString lpClass;
			public WinReg.HKEY hKeyClass;
			public int dwHotKey;

			/*
			 * Actually:
			 * union {
			 *  HANDLE hIcon;
			 *  HANDLE hMonitor;
			 * } DUMMYUNIONNAME;
			 */
			public WinNT.HANDLE hMonitor;
			public WinNT.HANDLE hProcess;

			protected List getFieldOrder() {
				return Arrays.asList("cbSize", "fMask", "hwnd", "lpVerb", "lpFile", "lpParameters",
						"lpDirectory", "nShow", "hInstApp", "lpIDList", "lpClass",
						"hKeyClass", "dwHotKey", "hMonitor", "hProcess");
			}
		}

	}

這樣我們就可以調用executeAsAdministrator方法來達到我們的目的!!!

 

 

如果我們想在當前進程中啓動一個新的進程。這個進程入口也是一個main函數的話,我們就需要先將這個main函數編譯之後的class文件進行打包成jar。

然後可以使用java 的ProcessBuilder來新啓一個進程

/**
	 * 啓動一個新的進程
	 *
	 * @param params    傳入口類參數
	 * @param isDebug   是否Debug模式
	 * @param className 主入口的類
	 * @return
	 */
	public static SubProcessThread startProcess(String[] params, boolean isDebug, String className) {
		SubProcessThread thread = new SubProcessThread(getArguments(params, isDebug, className, false));
		thread.start();
		return thread;
	}
className就是新進程的main函數的全類名
package com.supermap.process;

/**
 * @author wangchao
 * @DesktopJavaDocable disable
 */
public class TestNewProcess {
	public static void main(String[] args) {
		System.out.println("this is a origin process!!!");
		System.out.println("paramter is a " + args[0]);
		System.out.println(System.getProperty("sun.arch.data.model"));
	}
}

我們封裝了一個SubProcessThread這個類,他是繼承於Thread,因爲新進程需要我們傳遞一些參數過去。

package com.supermap.process;

import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.Executors;

/**
 * @author wangchao
 * @DesktopJavaDocable disable
 */
public class SubProcessThread extends Thread {

	private ArrayList<String> arguments;
	private String environmentPath = "";

	private Process process;

	/**
	 * @param arguments
	 */
	public SubProcessThread(ArrayList<String> arguments) {
		this.arguments = arguments;
	}


	public SubProcessThread(ArrayList<String> arguments, String environmentPath) {
		this.arguments = arguments;
		this.environmentPath = environmentPath;
	}

	@Override
	public void run() {
		try {
			ProcessBuilder processBuilder = new ProcessBuilder();
			//// 修改下環境變量中地址的值,調用32位組件架需要
			if (!environmentPath.isEmpty()) {
				processBuilder.environment().put("path", environmentPath);
			}
			processBuilder.directory(new File("d:\\"));
			processBuilder.command(arguments);
			processBuilder.redirectErrorStream(false);
			process = processBuilder.start();
			final InputStream errorStream = process.getErrorStream();
			final InputStream intputStream = process.getInputStream();
			Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2 + 2).execute(() -> {
				while (true) {
					try {
						int value = errorStream.read();
						if (value != -1) {
							System.out.print((char) value);
						} else {
							break;
						}
					} catch (IOException e) {
						e.printStackTrace();
						break;
					}
				}
			});
			Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2 + 2).execute(() -> {
				while (true) {
					try {
						BufferedInputStream in = new BufferedInputStream(intputStream);
						BufferedReader br = new BufferedReader(new InputStreamReader(in));
						String s;
						while ((s = br.readLine()) != null) {
							System.out.println(s);
						}

						int value = intputStream.read();
						if (value == -1) {
							break;
						}
					} catch (IOException e) {
						e.printStackTrace();
						break;
					}
				}
			});

		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

通過以下的代碼獲取我們需要的參數

/**
	 * 將啓動進程的傳入參數組合
	 *
	 * @param params    傳入口類參數
	 * @param isDebug   是否Debug模式
	 * @param className 主入口的類
	 * @return
	 */
	private static ArrayList<String> getArguments(String[] params, boolean isDebug, String className, boolean bin32) {
		ArrayList<String> arguments = new ArrayList<>();
		try {

			String javaExeHome;
			String javaExe = "java.exe";
			if (!bin32) {
				javaExeHome = System.getProperty("java.home");
			} else {
				javaExeHome = BIN_32_PATH;
			}
			if (javaExeHome.isEmpty()) {
				javaExeHome = "." + File.separator + "jre" + File.separator + "bin";
				javaExeHome = javaExeHome + File.separator + "java.exe";
			} else if (!javaExeHome.endsWith("bin")) {
				javaExeHome = javaExeHome + File.separator + "bin" + File.separator + javaExe;
			}
			arguments.add(javaExeHome);
			//開啓debug模式,可以在idea中使用remote來調試新進程
			if (isDebug) {
				//需要一個沒有被佔用的端口
				String portStr = String.valueOf((int) ((Math.random() * 9 + 1) * 10000));
				System.out.println("debug " + className + " at " + portStr + " port");
				arguments.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=" + portStr);
			}

			File classpathJarFile = createClasspathJarFile("D:\\testProcess\\target\\classes");
			String jarFilePath = classpathJarFile.getAbsolutePath();
			arguments.add("-classpath");
			arguments.add(jarFilePath);
			arguments.add(className);
			if (null != params) {
				for (String param1 : params) {
					String param = param1.endsWith(File.separator) ? param1.substring(0, param1.length() - 1) : param1;
					arguments.add("\"" + param + "\"");
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return arguments;
	}

有個參數叫做 bin32,用這個來控制新進程是64位JDK還是32位JDK啓動

還有一個isDebug參數,用可以設置新進程是否可以被調試

這個方法存在createClasspathJarFile方法使用來把我們所需要的class文件添加到jar中

//將編譯好的class文件打成jar包
	private static File createClasspathJarFile(String classPath) throws IOException {
		JarOutputStream out = null;
		final File jar = File.createTempFile("storm-", ".jar", new File(System.getProperty("java.io.tmpdir")));
		System.out.println("jar包路徑:" + jar.getAbsolutePath());
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				//運行完成後刪除jar包
				jar.delete();
			}
		});
		try {
			File path = new File(classPath);
			Manifest manifest = new Manifest();
			manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
			manifest.getMainAttributes().putValue("Created-By", "JarPackageUtil");
			out = new JarOutputStream(new FileOutputStream(jar), manifest);
			writeBaseFile(out, path, "");
		} finally {
			out.flush();
			out.close();
		}
		return jar;
	}

	private static void writeBaseFile(JarOutputStream out, File file, String base) throws IOException {
		if (file.isDirectory()) {
			File[] fl = file.listFiles();
			if (base.length() > 0) {
				base = base + "/";
			}
			for (File value : fl) {
				writeBaseFile(out, value, base + value.getName());
			}
		} else {
			out.putNextEntry(new JarEntry(base));
			try (FileInputStream in = new FileInputStream(file)) {
				byte[] buffer = new byte[1024];
				int n = in.read(buffer);
				while (n != -1) {
					out.write(buffer, 0, n);
					n = in.read(buffer);
				}
			}
		}
	}

 

 如果需要啓動一個32位JDK 的進程,我們新增一個方法即可

private static String BIN_32_PATH = "C:\\Program Files\\Java\\jdk1.8_32\\jre";

public static SubProcessThread start32Process(String[] params, boolean isDebug, String className) {
		SubProcessThread thread = new SubProcessThread(getArguments(params, isDebug, className, true), BIN_32_PATH);
		thread.start();
		return thread;
	}

BIN_32_PATH 是32位JDK所在的目錄,這裏我們需要寫到jre目錄

最後執行一下

public class main {
	public static void main(String[] args) {
		System.out.println("this is a original process!!!");
		ProcessUtilities.startProcess(new String[]{"SUCCESS"}, true, "com.supermap.process.TestNewProcess");
		System.out.println();
		ProcessUtilities.start32Process(new String[]{"32 SUCCESS"}, true, "com.supermap.process.Test32NewProcess");
	}
}

得到的結果就是:

Listening for transport dt_socket at address: 39066
this is a origin process!!!
paramter is a SUCCESS
64
Listening for transport dt_socket at address: 23965
this is a new process!!!
paramter is a 32 SUCCESS
32

這裏提供一個源碼的地址

https://github.com/15982224307/testProcess.git

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章