最近在公司做的一個需求,項目進程是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