Set Windows System Variables with Java - java

is there a way to add a specific directory to the Windows systemvariable %PATH%?
This doesn't seem to work:
String[] cmd = { "cmd", "/c", "set", "PATH=\"%PATH%;c:\\test\"" };
Runtime.getRuntime().exec( cmd );
c:\test\ doesn't appear in System.getenv("PATH"); or in the output of
String[] cmd = { "cmd", "/c", "echo", "%PATH%" };
Runtime.getRuntime().exec( cmd );
What I need is to modify the %PATH%-variable for the current Java-Process under Windows. The reason is, that I need to load some native dll-files which cross-reference each other. So I'd like to add the application-path to the Windows environment.
The next thing I tried was a small JNI-Wrapper for the C-Function "putenv" which looks like this:
JNIEXPORT void JNICALL Java_com_splitscreen_AppletTest_PutEnv_putEnv
(JNIEnv *env, jobject jobj, jstring val) {
jboolean iscopy;
const char *mvalue = (*env)->GetStringUTFChars(
env, val, &iscopy);
putenv(mvalue);
}
This is how I call it:
final String curPath = System.getenv( "PATH" );
final PutEnv pe = new PutEnv();
pe.putEnv( "PATH=" + curPath + ";c:\test" );
final String newPath = System.getenv( "PATH" );
System.out.println( newPath );
But the pathes are equal. I'm not sure whether the Map of the Java-System-Environment isn't updated or whether putenv didn't work. Is there a way to check this?

The reason this doesn't work is that the two exec() invocations start two different shells; the one you set the path in isn't the one you check it in.
It's difficult to change the permanent, systemwide path setting. But you can change the path for the duration of the invocation of one or more programs that you need it for.
Specifically, the thing to do is to write yourself a batch file (.CMD or .BAT, as you please), set the PATH near the beginning, follow that with whatever DOS/Windows commands you'd like executed with that path, and then exec() that script file.
Updating the PATH for the current Java process seems pretty pointless. Java, once running, doesn't care about the path. Or are you running some library code that does?
If you are running DOS/Windows commands from Java using exec(), the above trick will work.
Update: OK, you have library code that for reasons of its own wants the PATH set just so, and you want to give it what it wants.
What I would consider here is to fire up a new JVM. You can use exec(cmd, envp) to start up a new Java application ("yourself," in a pinch) with a custom set of environment variables in envp. Just copy the ones that are already there and manipulate the contents of PATH, if any.
The standard way to start up a new Java app is to create a new ClassLoader, and there are various descriptions on how to accomplish that. But I'm not sure you can use that procedure to come up with a new environment - so exec-ing the JVM may not only be simpler, but possibly the only way.

This is not possible with just running a batch file. See here for details.
Your solution doesn't work, because it only modifies the environmental variable in the process level and not in system level.

You can pass paths to where the native libraries are located via the -Djava.library.path option if you are using JNI extensions, this may also work for your exec case. The other option is to launch the java app from a batch file and edit the PATH settings in the command interpreter "before" you launch the java app, the java app will inherit this PATH settings.
NASA WorldWind uses native libraries and can be run as an Applet, here is a howto on setting this up with JNLPAppletLauncher. What this basically does is detect the OS, fetch appropriate native libraries, save them in a location in default jvm path and execute. Calling exec from a Java applet violates all sorts of sane security and sandboxing principles and I would really avoid it.

Related

Can you create a new JVM in a c++ function called from java using JNI?

So my setup is that I have a .dll which is developed by me (A.dll) which in the original application is called from an external process which is basically just a .exe file that I do not have the source code for (B.exe). The purpose of A.dll is to communicate with a .jar file, which is also developed by me (C.jar). So in the application, the "communication flows" as shown below
B.exe -> A.dll -> (through JNI) -> C.jar
Now, what i want to do is to add the calls going between A.dll and C.jar as a part of my test suite in the development environment for C.jar. What I have so far is that I have created another .dll (D.dll) which mirrors all functions in A.dll, but with JNIEXPORT, and simply makes direct call to the respective function in A.dll. So the "communication flow" in this situation will be as follows:
Unit test in C.jar development framework -> (through JNI) -> D.dll -> A.dll -> (through JNI) -> C.jar
At this point, a very simple function call that simply prints out something in C.jar works through the whole chain; all the way from the unit test call and into C.jar. The problem however arises when i call the function in A.dll which creates a new JVM using CreateJavaVM(), which produces the following error:
Error occurred during initialization of VM
Unable to load native library: The specified procedure could not be found
So basically I'm wondering if it is actually possible to do this, or is it just simply impossible to call CreateJavaVM() when there is already a running JVM in the same process? I know that you can't call CreateJavaVM() several times within the same process, but in this situation it is only called once but a JVM already exists in the process - can you even have several JVMs running in the same process?
SOLUTION:
Thanks to #apangin's answer the code snippet below solved my problem:
jsize nVMs = 0;
JavaVM** buffer;
jni_GetCreatedJavaVMs = (GetCreatedJavaVMs) GetProcAddress(GetModuleHandle(
TEXT("jvm.dll")), "JNI_GetCreatedJavaVMs");
if (jni_GetCreatedJavaVMs == NULL) {
// stuff
CreateJavaVM(&jvm, (void **) &env, &args);
} else {
jni_GetCreatedJavaVMs(NULL, 0, &nVMs); // 1. just get the required array length
JavaVM** buffer = new JavaVM*[nVMs];
jni_GetCreatedJavaVMs(buffer, nVMs, &nVMs); // 2. get the data
buffer[0]->GetEnv((void **) &env, jni_version); // 3. get environment
jvm = buffer[0];
}
Current JNI specification explicitly states that creation of multiple VMs in a single process is not supported, and this is actually asserted in HotSpot source code.
Even if your dll calls JNI_CreateJavaVM only once, it does not mean that this is the very first call within the whole process. In fact, JNI_CreateJavaVM is first called by java.exe or by another launcher of your IDE (idea.exe, eclipse.exe, netbeans.exe etc).
Therefore, instead of creating Java VM blindly, A.dll should check first if JVM already exists in current process by calling JNI_GetCreatedJavaVMs. If the function returns nonempty array, then use GetEnv or AttachCurrentThread to obtain JNIEnv* for the existing VM, otherwise create a new VM.

Make JAR as a standalone executable

Is there a way to convert JAR lib into JAR standalone?
I need to find a standalone java executable that convert PDF into TIFF and I've found these JARs: http://www.icefaces.org/JForum/posts/list/17504.page
Any ideas?
Easiest might be to create another Jar with a Main() entry point, and then just use the java.exe executable to run it:
e.g.
> java.exe -cp MyJarMain.jar;MyPDFJar.jar com.mydomain.MyMain myPDF.pdf
Where MyMain is a class with a Main static method.
You'll need something with a main entry point to pass in and interpret some command line arguments (myPDF.pdf in my made-up example)
You could do an assembly (are you using maven?) and make sure the Main-Class entry in the manifest.mf points to the main class.
Since there is no main-Method, you have to write one, or write a whole new class to call the class/method TiffConver.convertPDF .
The question is, how you're going to use it. From the command line, you need no executable jar. From the Gui, maybe you want to pass a file to be converted by drag and drop? Then you should take the parameter(s) passed to main as Input-PDF-Names (if they end in .pdf) and pass the names iteratively to TiffConverter, for "a.pdf b.pdf" =>
TiffConver.convertPDF ("a.pdf", "a.tiff");
TiffConver.convertPDF ("b.pdf", "b.tiff");
TiffCoverter will silently overwrite existing tiffs, so check that before or change the code there - this is clearly bad habit, and look out for more such things - I didn't.
/*
* Remove target file if exists
*/
File f = new File(tif);
if (f.exists()) {
f.delete();
}
Maybe you wan't to write a swing-wrapper, which let's you choose Files interactively to be converted. This would be a nice idee, if no filename is given.
If the user passes "a.pdf xy.tiff" you could rename the converted file to xy, as additional feature.
Without a main-class, however, a standalone jar would be magic.
However, building a native executale is almost always a bad idea. You loose portability, you don't profit from security- and performance improvements to the JVM or fixed bugs. For multiple programs you need always an independend bugfix, which you might have to manage yourself, if you don't have a package-management as most linux distros have.
after clearing some questions:
public static void main (String [] args) {
if (args.length == 1 && args[0].endsWith (".pdf")) {
String target = args[0].replaceAll (".pdf$", ".tif");
convertPDF (args[0], target);
}
}
This method you put into TiffConvert. It will allow you to convert a simple pdf-File, and generate a tif-File with the same basename but ending in .tif, silently overwriting an existing one of the same name.
I guess you now need to know how to start it?

Java, inherit classpath with Runtime.exec()

I have a program that will create a child process, and I want it inherit all the classpath from its parent. In javadoc, it says:
public Process exec(String[] cmdarray,
String[] envp)
throws IOException
Executes the specified command and arguments in a separate process with the specified environment.
Given an array of strings cmdarray, representing the tokens of a command line, and an array of strings envp, representing "environment" variable settings, this method creates a new process in which to execute the specified command.
If envp is null, the subprocess inherits the environment settings of the current process.
When I set envp to null, it didn't inherit anything.
Here is the code:
System.out.print("Debug system path: "+System.getProperty("java.class.path"));
startTime();
Process proc = Runtime.getRuntime().exec(cmd,null);
I can see the path information, but these path information is not inherited by the new created process.
How did you specify the classpath of your application? If it was not through the CLASSPATH environment variable, it will not be inherited.
Runtime.exec method can invoke any native application, and the envp here refers to system environment, not your java environment.
If you want to pass your classpath to the child java process, you can do so explicitly:
String[] cmdarray = new String[] {
"java", "-classpath", System.getProperty("java.class.path"), "com.example.MyChildApp", "appParam"};
Process p = Runtime.getRuntime().exec(cmdarray);
No can do. Your 'classpath' at the time you call exec is whatever is hiding away in your current class loader at the time you call it. You can't, in general, ask a class loader to tell you the class path. It could be fetching classes from a database, or the planet Mars.
Reading java.class.path will tell you what was going on when your application started, but not what's going on at the time you go to launch something else.
Finally I have to insert "-cp System.getProperty("java.class.path")" into the cmd to make it work.
Is there any better way to do that?

How can I make OS X recognize drive letters?

I know. Heresy. But I'm in a bind. I have a lot of config files that use absolute path names, which creates an incompatibility between OS X and Windows. If I can get OS X (which I'm betting is the more flexible of the two) to recognize Q:/foo/bar/bim.properties as a valid absolute file name, it'll save me days of work spelunking through stack traces and config files.
In the end, I need this bit of Java test code to print "SUCCESS!" when it runs:
import java.io.*;
class DriveLetterTest {
static public void main(String... args) {
File f = new File("S:");
if (f.isDirectory()) {
System.out.println("SUCCESS!");
} else {
System.out.println("FAIL!");
}
}
}
Anyone know how this can be done?
UPDATE: Thanks for all the feedback, everyone. It's now obvious to me I really should have been clearer in my question.
Both the config files and the code that uses them belong to a third-party package I cannot change. (Well, I can change them, but that means incurring an ongoing maintenance load, which I want to avoid if at all possible.)
I'm in complete agreement with all of you who are appalled by this state of affairs. But the fact remains: I can't change the third-party code, and I really want to avoid forking the config files.
Short answer: No.
Long answer: For Java you should use System.getProperties(XXX).
Then you can load a Properties file or Configuration based on what you find in os.name.
Alternate Solution just strip off the S: when you read the existing configuration files on non-Windows machines and replace them with the appropriate things.
Opinion: Personally I would bite the bullet and deal with the technical debt now, fix all the configuration files at build time when the deployment for OSX is built and be done with it.
public class WhichOS
{
public static void main(final String[] args)
{
System.out.format("System.getProperty(\"os.name\") = %s\n", System.getProperty("os.name"));
System.out.format("System.getProperty(\"os.arch\") = %s\n", System.getProperty("os.arch"));
System.out.format("System.getProperty(\"os.version\") = %s\n", System.getProperty("os.version"));
}
}
the output on my iMac is:
System.getProperty("os.name") = Mac OS X
System.getProperty("os.arch") = x86_64
System.getProperty("os.version") = 10.6.4
Honestly, don't hard-code absolute paths in a program, even for a single-platform app. Do the correct thing.
The following is my wrong solution, saved to remind myself not to repeat giving a misdirected advice ... shame on me.
Just create a symbolic link named Q: just at the root directory / to / itself.
$ cd /
$ ln -s / Q:
$ ln -s / S:
You might need to use sudo. Then, at the start of your program, just chdir to /.
If you don't want Q: and S: to show up in the Finder, perform
$ /Developer/Tools/SetFile -P -a V Q:
$ /Developer/Tools/SetFile -P -a V S:
which set the invisible-to-the-Finder bit of the files.
The only way you can replace java.io.File is to replace that class in rt.jar.
I don't recommend that, but the best way to do this is to grab a bsd-port of the OpenJDK code, make necessary changes, build it and redistribute the binary with your project. Write a shell script to use your own java binary and not the built-in one.
PS. Just change your config files! Practice your regex skills and save yourself a lot of time.
If you are not willing to change your config file per OS, what are they for in first place?
Every installation should have its own set of config files and use it accordingly.
But if you insist.. you just have to detect the OS version and if is not Windows, ignore the letter:
Something along the lines:
boolean isWindows = System.getProperty("os.name").toLowerCase()
.contains("windows");
String folder = "S:";
if (isWindows && folder.matches("\\w:")) {
folder = "/";
} else if (isWindows && folder.matches("\\w:.+")) {
folder = folder.substring(2);// ignoring the first two letters S:
}
You get the idea
Most likely you'd have to provide a different java.io.File implementation that can parse out the file paths correctly, maybe there's one someone already made.
The real solution is to put this kind of stuff (hard-coded file paths) in configuration files and not in the source code.
Just tested something out, and discovered something interesting: In Windows, if the current directory is on the same logical volume (i.e. root is the same drive letter), you can leave off the drive letter when using a path. So you could just trim off all those drive letters and colons and you should be fine as long as you aren't using paths to items on different disks.
Here's what I finally ended up doing:
I downloaded the source code for the java.io package, and tweaked the code for java.io.File to look for path names that start with a letter and a colon. If it finds one, it prepends "/Volumes/" to the path name, coughs a warning into System.err, then continues as normal.
I've added symlinks under /Volumes to the "drives" I need mapped, so I have:
/Volumes/S:
/Volumes/Q:
I put it into its own jar, and put that jar at the front of the classpath for this project only. This way, the hack affects only me, and only this project.
Net result: java.io.File sees a path like "S:/bling.properties", and then checks the OS. If the OS is OS X, it prepends "/Volumes/", and looks for a file in /Volumes/S:/bling.properties, which is fine, because it can just follow the symlink.
Yeah, it's ugly as hell. But it gets the job done for today.

Launching java classes via windows drag-and-drop

I have a java class file with a main method. In Windows, I would like to be able to drag files onto a desktop icon/short/etc that would call supply the filenames to my main method. Basically, I want to allow users to drag-and-drop files at program execution instead of having type them on the command line.
Any thoughts?
To build on daub815's answer, in Windows, you can use a batch file to pass
arguments to another command. In this case, we'll use the java launcher to
launch your class with the main method.
I did a quick Google search on how to do write a batch file to take multiple arguments,
and found a page with a batch file to pass arguments to another command. Adapting from
the example, here is what you can do:
#ECHO OFF
:Loop
IF "%1" == "" GOTO Done
java YourClass %1
SHIFT
GOTO Loop
:Done
Save the above file as a batch file (with a ".bat" extension), and then you can drag-and-drop
files onto it, and it will be passed as arguments.
Also, you can call the batch file from the command line and pass arguments as well.
Edit: It appears that the batch file will not work with quoted arguments which contain spaces. Using a workaround presented in the site I've linked to will split the spaces contained in the quoted full path of the file into separate arguments, so that won't work either. If anyone has a good idea how to fix this, please either edit this entry, or post another answer. I will make this a community wiki.
PhiLho's answer works perfectly if you pack the classes in an executable JAR file (it's how you're meant to do it anyway) and make a .reg file that looks like the one below. Then just double-click that .reg file to merge it into the registry and you're good to go. This lets you both double-click a JAR file to run it, and starting it by Drag & Drop.
Do remember to change the path to where your Java executable is installed.
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.jar]
#="jarfile"
[HKEY_CLASSES_ROOT\jarfile\DefaultIcon]
#="C:\\Java\\jdk1.7.0\\bin\\java.exe,1"
[HKEY_CLASSES_ROOT\jarfile\shell\open]
#="Run Java Program"
[HKEY_CLASSES_ROOT\jarfile\shell\open\command]
#="\"C:\\Java\\jdk1.7.0\\bin\\java.exe\" -jar \"%1\" %*"
[HKEY_CLASSES_ROOT\jarfile\shellex\DropHandler]
#="{86C86720-42A0-1069-A2E8-08002B30309D}"
OK, I made it work... The base knowledge is to use DropHandler UUID in the registry. I made a base setting, as follow:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.class]
#="JavaClass"
[HKEY_CLASSES_ROOT\JavaClass\DefaultIcon]
#="C:\\Java\\jdk1.6.0_05\\bin\\java.exe,1"
[HKEY_CLASSES_ROOT\JavaClass\shell\open]
#="Run Java class"
[HKEY_CLASSES_ROOT\JavaClass\shell\open\command]
#="\"C:\\Java\\jdk1.6.0_05\\bin\\java.exe\" \"%1\" %*"
[HKEY_CLASSES_ROOT\JavaClass\shellex\DropHandler]
#="{86C86720-42A0-1069-A2E8-08002B30309D}"
and... it didn't work!
I just forgot that java.exe wants a class name, not a file name! But I see no way to do that in the registry.
Fortunately, there is a workaround, which still need a script file if we want to be generic, to work on any/all class files (with static main function, of course!). Not batch, I avoid them when I can. I chose to use WSH, as it should be available on any modern Windows system. I also chose to make a JS script, it could have been a VB script as well.
So I made the following script (LaunchJavaClass.js):
if (WScript.Arguments.count() == 0)
{
WScript.StdOut.Write("No parameters");
WScript.Quit(1);
}
var className = WScript.Arguments.Item(0);
//~ WScript.StdOut.Write(className + "\n");
var m = className.match(/^(.*)\\(.+?)\.class$/);
if (m == null)
{
WScript.StdOut.Write("Not a class file");
WScript.Quit(1);
}
var classPath = m[1];
className = m[2];
//~ WScript.StdOut.Write(classPath + " >>> " + className + "\n");
var params = new Array();
for (i = 1; i < WScript.Arguments.count(); i++)
{
params[params.length] = WScript.Arguments.Item(i);
}
var cmd = "cmd /c cd /D " + classPath +
" & C:/Java/jdk1.6.0_05/bin/java.exe " +
className + " " + params.join(" ");
//~ WScript.StdOut.Write(cmd + "\n");
var shell = WScript.CreateObject("WScript.Shell");
//~ var exec = shell.Exec(cmd); // Can be used to get stdout
shell.Run(cmd, 0);
I left some output, not useful in this context, but usable for debugging (run with cscript).
Of course, the path to the JRE must be adjusted.
And I changed the command in the registry, as follow:
[HKEY_CLASSES_ROOT\JavaClass\shell\open\command]
#="\wscript -b "D:\\_PhiLhoSoft\\WSH\\LaunchJavaClass.js\" %1 %*"
Of course, adjust path, and keep the above other lines.
Now, if I drag'n'drop some files to a .class file, it gets the short file paths as arguments of the main() function.
import java.io.*;
class TestDnD
{
public static void main(String[] args)
{
Writer output = null;
try
{
output = new BufferedWriter(new FileWriter(new File("LogFile.txt")));
for (String arg : args)
{
output.write(arg + "\n");
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
return;
}
finally
{
try { output.close(); } catch (IOException e) {}
}
}
}
I think the first version of the .reg file can be used for something else, eg. to drag'n'drop on .jar files (adapting it, of course).
This technique has limited use: we rarely make one-class programs in Java! But it looked like a good and interesting challenge, so I didn't resist to solve it. Note: you can add stuff like -Djava.ext.dirs="some path;another path" if you ever need to use external libraries (in jar files).
Adding onto Adiel A. If you create a batch file, which launches your a Java window using Swing. You would have the user drop the files onto that window. You could then be able to root through those dropped files.
So there's no way to have windows itself pass the args into main() via drag and drop?

Categories

Resources