WinRegistry not working anymore in Processing 4

I was using java code to do file association for my software in Window platform. The class is called WinRegistry which can be commonly found online. The code worked well when I develop my software in Processing 3.5.4 but fails to work when i migrate my system to Processing 4.1.1

From here: regedit - JAVA : ERROR Java.util.prefs.WindowsPreferences.WindowsRegOpenKey - Stack Overflow I know that I should change some data type from int to long to adapt the change of JDK. I did so, like this:

regOpenKey = userClass.getDeclaredMethod(“WindowsRegOpenKey”, new Class[ ] { long.class, byte[ ].class, int.class });
regOpenKey.setAccessible(true);

I also adjusted the rest of the code accordingly. However it still did not work and the error message is:
RuntimeException: java.lang.reflect.InaccessibleObjectException: Unable to make private static native long[ ] java.util.prefs.WindowsPreferences.WindowsRegOpenKey(long,byte[ ],int) accessible: module java.prefs does not “opens java.util.prefs” to unnamed module @2a22d168

Since I develop my software in Processing so I suppose the way to solve this problem depends on Processing rather than by Java. Is it because of the upgrade of JDK in Processing 4? I have TOTALLY no idea how to handle this. Can anyone help please? The full code of WinRegistry is here:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.Preferences;

public class WinRegistry
{
public static final long HKEY_CURRENT_USER = 0x80000001;
public static final long HKEY_LOCAL_MACHINE = 0x80000002;
public static final int REG_SUCCESS = 0;
public static final int REG_NOTFOUND = 2;
public static final int REG_ACCESSDENIED = 5;
private static final int KEY_ALL_ACCESS = 0xf003f;
private static final int KEY_READ = 0x20019;
private static final Preferences userRoot = Preferences.userRoot();
private static final Preferences systemRoot = Preferences.systemRoot();
private static final Class<? extends Preferences> userClass = userRoot.getClass();
private static final Method regOpenKey;
private static final Method regCloseKey;
private static final Method regQueryValueEx;
private static final Method regEnumValue;
private static final Method regQueryInfoKey;
private static final Method regEnumKeyEx;
private static final Method regCreateKeyEx;
private static final Method regSetValueEx;

static {

 try {
   regOpenKey = userClass.getDeclaredMethod("WindowsRegOpenKey", new Class[] { long.class, byte[].class, int.class });
     regOpenKey.setAccessible(true);
    regCloseKey = userClass.getDeclaredMethod("WindowsRegCloseKey", new Class[] { long.class });
     regCloseKey.setAccessible(true);
    regQueryValueEx = userClass.getDeclaredMethod("WindowsRegQueryValueEx", new Class[] { long.class, byte[].class });
     regQueryValueEx.setAccessible(true);
    regEnumValue = userClass.getDeclaredMethod("WindowsRegEnumValue", new Class[] { long.class, int.class, int.class });
    regEnumValue.setAccessible(true);
    regQueryInfoKey = userClass.getDeclaredMethod("WindowsRegQueryInfoKey1", new Class[] { long.class });
     regQueryInfoKey.setAccessible(true);
     regEnumKeyEx = userClass.getDeclaredMethod("WindowsRegEnumKeyEx", new Class[] { long.class, int.class,  int.class });  
     regEnumKeyEx.setAccessible(true);
     regCreateKeyEx = userClass.getDeclaredMethod( "WindowsRegCreateKeyEx", new Class[] { long.class, byte[].class });  
     regCreateKeyEx.setAccessible(true);  
     regSetValueEx = userClass.getDeclaredMethod( "WindowsRegSetValueEx", new Class[] { long.class, byte[].class, byte[].class });  
     regSetValueEx.setAccessible(true);  
   }
   catch (Exception e) {
     throw new RuntimeException(e);
   }
 }

 WinRegistry() {  }


 public static void createKey(long hkey, String key) 
   throws IllegalArgumentException, IllegalAccessException,
   InvocationTargetException 
 {
   long [] ret;
   if (hkey == HKEY_LOCAL_MACHINE) {
     ret = createKey(systemRoot, hkey, key);    
     regCloseKey.invoke(systemRoot, new Object[] { ret[0] });
  }
   else if (hkey == HKEY_CURRENT_USER) {
     ret = createKey(userRoot, hkey, key);
     regCloseKey.invoke(userRoot, new Object[] { ret[0] });
   }
   else {
     throw new IllegalArgumentException("hkey=" + hkey);
   }
   if (ret[1] != REG_SUCCESS) {
     throw new IllegalArgumentException("rc=" + ret[1] + "  key=" + key);
   }
 }


 public static void writeStringValue
   (long hkey, String key, String valueName, String value) 
   throws IllegalArgumentException, IllegalAccessException,
   InvocationTargetException 
 {
   if (hkey == HKEY_LOCAL_MACHINE)
     writeStringValue(systemRoot, hkey, key, valueName, value);
   else if (hkey == HKEY_CURRENT_USER)
     writeStringValue(userRoot, hkey, key, valueName, value);      
   else
     throw new IllegalArgumentException("hkey=" + hkey);
 }

 private static long [] createKey(Preferences root, long hkey, String key)
   throws IllegalArgumentException, IllegalAccessException,
   InvocationTargetException 
 {
   return  (long[]) regCreateKeyEx.invoke(root,
       new Object[] { hkey, toCstr(key) });
 }

 private static void writeStringValue 
   (Preferences root, long hkey, String key, String valueName, String value) 
   throws IllegalArgumentException, IllegalAccessException,
   InvocationTargetException 
 {
   long[] handles = (long[]) regOpenKey.invoke(root, new Object[] {
       hkey, toCstr(key), new Integer(KEY_ALL_ACCESS) });          // is here the problem???

   regSetValueEx.invoke(root,  
       new Object[] { 
         handles[0], toCstr(valueName), toCstr(value) 
         }); 
   regCloseKey.invoke(root, new Object[] { handles[0] });
 }

 // utility
 private static byte[] toCstr(String str) 
 {
   byte[] result = new byte[str.length() + 1];

   for (int i = 0; i < str.length(); i++) {
     result[i] = (byte) str.charAt(i);
  }
   result[str.length()] = 0;
   return result;
 } 
}

And the way to execute WinRegistry is something like this, assuming the extension of the file is ‘.abc’ and the ProgramID is ‘abc_File’:

WinRegistry W = new WinRegistry();
W.createKey(W.HKEY_CURRENT_USER, “Software\Classes\.abc”);
W.writeStringValue(W.HKEY_CURRENT_USER, “Software\Classes\.abc”, “”, “abc_File”);

Hi @Martin_The_Sun,

this is not a processing issue, but rather a jdk one, as the reflection handling has changed.
As the error message says, it is (by default) not allowed anymore, hence …

InaccessibleObjectException: Unable to make private static ... accessible.

Maybe you can read here about the background …

https://openjdk.org/jeps/396
https://openjdk.org/jeps/403

Cheers
— mnse

2 Likes

Hi @Martin_The_Sun,

on your own risk you can change the run.options= of your preferences.txt file.

run.options=--add-opens java.prefs/java.util.prefs=ALL-UNNAMED

this will allow you to bypass the restrictions …

Cheers
—mnse

2 Likes

Hi @mnse , thanks so much for your suggestion. I tried adding the code

run.options=--add-opens java.prefs/java.util.prefs=ALL-UNNAMED

in preferences.txt. Yes, it works and Processing bypasses the error and key registration can be done in regedit.

Unfortunately, if I export my processing program as application (i.e. .exe file) and run it. I found that the app stops running in the step of initializing an instance of WinRegistry, i.e. this step:

WinRegistry W = new WinRegistry();

Besides, regedit has no any path update so i confirm that key registration is not done. Since i don’t know how to trace the program flow inside java code when running the app, I don’t know where the problem is, but i suspect that the handling problem still restrict the operation when the program is exported as app.

Apart from bypassing the error, i wonder if there is other approaches we can do to modify the codes in WinRegistry directly to adapt the new change in JDK?

Hi @Martin_The_Sun,

Sorry! Too long to explain all the background information, but …
…you need to create an additional configuration file beside your executable which let it know that you want to use an additional parameter for execution. It must be in the same directory as you executable!

  • go to the directory where you have your executable.
  • assuming you exe is called: myapp.exe
  • Create a file named: myapp.l4j.ini
  • content of the file should be in your case a single line with the required option
    --add-opens java.prefs/java.util.prefs=ALL-UNNAMED
  • save the ini file.
  • Afterwards you can run the exe and it would/should work. :slight_smile:

There are several approaches but none of them are really, let’s say, the holy grail, but rather work-arounds to the issue.

Cheers
— mnse

2 Likes

Hi @mnse ,

oh yes, it works for .exe now after i prepared that ini file. Many thanks to your help.

just a minor question, isn’t it possible to hide this ini fle in a subfolder instead of putting it next to my exe file? Right now the ini file is just next to my exe file in my software directory and i am afraid user will feel a litter bit confused to click the exe file since both files has similar filename.

For example, moving the ini file to a subfolder ‘ini’ (or whatever name) and my exe file can still refer to that ini when it is executed. Is this possible?

query

Hi @Martin_The_Sun,

No! It is not possible to move the ini file to a different location as the process searches it next to the exe file, resp. in the directory of the exe file. Also the naming convention can’t be changed.
The process simple uses the exe path, remove the .exe extension apply the .l4j.ini and search for it.

There are more advanced things you can do, but for that you need to patch the exe itself by ie. a resource hacking tool.

Cheers
— mnse

You can “hide” the file via the attrib command-line:
attrib +h +s +r *.ini

2 Likes

@GoToLoop , yes, this is a good suggestion, will consider this approach, thanks!
@mnse , just one more question. If I bypass the error checking for java.util.prefs, potentially what kinds of risks I might come across? I would like to have an idea so that I can be more well-prepared in the future of developing the software.

Hi @Martin_The_Sun,

Imo, actually none! The background of all is that jep 403 makes the whole jdk more secure, resp. more specific in case of using reflection. Before it all was permitted by default without let the user know that it is used internally. Now you have to declare and allow it explicitly if you want to use.

Cheers
— mnse

1 Like