Bundle jdk with my game - can't find Netbeans installer

hello guys,

I have created a game in which compile at runtime is used. The problem is that the compiler can be found and be used only if the game is executed through the Jmonkey. The reason for this is that when you create a jar file of the game using “clean and build” this jar file uses the JRE that is installed on your PC (which does not include the compiler) instead of the JDK that Jmonkey uses. Of course you could tell the user to install the JDK and then “link” your game with this JDK but this is not user friendly and can couse a lot of trouble.

So I was thinking of creating an installer that installes my game with its JDK bundled (just like the Jmonkey itself wich has its jdk in its directory). I found a tutorial that uses the netbeans installer but it seems that it is not installed in the netbeans version of jmonkey.

Is there an easy way to do that?

If you are using the SDK, just download the latest 3.1 and in the project’s properties you choose the distributables you want to use. The sdk will add the jre for you. It worked for me with Dominoes.

Other alternatives are either using launch4j or buy a licence of excelsior

Just replace the JRE in the distro with a JDK?

Don’t you need a license to bundle the JDK together with your software? And besides, your customers will need to accept the JDK license too…

Maybe you don’t need the full JDK, and can achieve the same result with scripts or something… just to not complicate matters.

Note: groovy is very similar to Java and compiles its own scripts to classes at runtime… no JDK required.

I used “launch4j " and then " Inno setup” to make the installer. I don’t know about the license though. Could I just copy the license?

Also groovy would be a good solution but my game is for my master degree and I the scripts must be in java. I can’t change that now.

Have you thought about including the eclipse compiler (standalone) instead, and use it to compile?

Do they have to be compiled?

BeanShell can run Java code at runtime with generally almost no change whatsoever.

You can try Janino to compile java code within the same application:

Janino is a super-small, super-fast Java™ compiler. Not only can it compile a set of source files to a set of class files like JAVAC, but also can it compile a Java™ expression, block, class body or source file in memory, load the bytecode and execute it directly in the same JVM.

hello again,

I just saw your messages. I like your ideas. I ll try a few

since @Pesegato brought up licenses …
check with your supervisor which contexts your code needs to be used in.
Is it for internal use only, or will it be published in some way?
The eclipse compiler is probably licensed under the Eclipse Public License, which is not as nice to use as the BSD one jme is licensed under.
But as pspeed put it so well, “you would not ask your lawyer to debug your shader code”.

1 Like

If you know C# or another visual studio language you can use Visual Studio to write a custom installer. I’ve written installers that basically just unpack a zip embedded in the executable as well as installers that check for an installed compatible version of Java and if it’s not found downloads and launches the appropriate JRE or JDK installer.

When writing an installer like that you need to be sure when checking for installed Java that you look at both the 32 bit and, if available, 64 bit registries because it’s possible someone on a 64 bit Windows installed 32 bit Java. Also if your installer automatically launches the JRE or JDK installer it will need admin privaledges which can cause anti virus software to warn people that your installer may be dangerous.

Why should anyone add another heavyweight dependency such as .Net for an installer?

How does this behave on Linux and OSX systems? Do you bundle a Mono Runtime and a JDK for the game? You cannot use the WPF for UI on those systems, so you need to bundle Gtk or Qt as well.

Also on Windows 7 you don’t necessarily have a .net Runtime Environment.
Beginning with Windows 8, you do not know which version of .Net is installed.

So you basically add another layer of the problem to the problem you are trying to solve.

On Linux and OSX you would use different installers which is basically how every cross platform software distribution I’ve ever used works. For instance jmonkey engine uses an executable installer for Windows and a shell script installer for Linux, don’t know about OSX because I don’t have a Mac.

I’ve never encountered any problems with people using incompatible .net installations. I think Windows actually detects if a user is trying to run an application that requires a newer version of .net and offers to download and install it.

Nonetheless you could also opt to use the open source NullSoft Installation System, NSIS. I think there are plugins available to download and install JREs with an NSIS installer. Personally I prefer to customize my installers by coding them myself.

P.S. There’s an Eclipse plugin for NSIS BTW. Of course NSIS installers only work on Windows.

By the by here’s how I detect if the end user has a compatible version of Java installed using C# and .NET 2.0 methods:

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace YourNamespaceHere
{
    public static class OSOps
    {
        private static readonly string javaKey = "SOFTWARE\\JavaSoft\\Java Runtime Environment";

        public static Boolean isJavaCompatible(float targetJava)
        {
            if (Is64BitOS())
            {
                return java64Compatible(targetJava);
            }
            else
            {
                return java32Compatible(targetJava);
            }
        }

        private static Boolean java32Compatible(float targetJava)
        {
            string currentVersion = GetRegKey32(HKEY_LOCAL_MACHINE, javaKey, "CurrentVersion");
            if (currentVersion != null)
            {
                if (float.Parse(currentVersion) >= targetJava)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        private static Boolean java64Compatible(float targetJava)
        {
            string currentVersion = GetRegKey64(HKEY_LOCAL_MACHINE, javaKey, "CurrentVersion");
            if (currentVersion != null)
            {
                if (float.Parse(currentVersion) >= targetJava)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return java32Compatible(targetJava);
            }
        }

        public static string getJavaPath(float targetJava)
        {
            if (Is64BitOS())
            {
                return getJava64(targetJava);
            }
            else
            {
                return getJava32(targetJava);
            }
        }

        private static string getJava32(float targetJava)
        {
            string currentVersion = GetRegKey32(HKEY_LOCAL_MACHINE, javaKey, "CurrentVersion");
            if (currentVersion != null)
            {
                if (float.Parse(currentVersion) >= targetJava)
                {
                    return GetRegKey32(HKEY_LOCAL_MACHINE, javaKey + "\\" + currentVersion, "JavaHome");
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        private static string getJava64(float targetJava)
        {
            string currentVersion = GetRegKey64(HKEY_LOCAL_MACHINE, javaKey, "CurrentVersion");
            if (currentVersion != null)
            {
                if (float.Parse(currentVersion) >= targetJava)
                {
                    return GetRegKey64(HKEY_LOCAL_MACHINE, javaKey + "\\" + currentVersion, "JavaHome");
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return getJava32(targetJava);
            }
        }

        //check for 64-bit OS
        internal static bool Is64BitOS()
        {
            if (IntPtr.Size == 8)
            {
                return true;
            }
            else
            {
                bool flag;
                return ((DoesWin32MethodExist("kernel32.dll", "IsWow64Process") && IsWow64Process(GetCurrentProcess(), out flag)) && flag);
            }
        }

        static bool DoesWin32MethodExist(string moduleName, string methodName)
        {
            IntPtr moduleHandle = GetModuleHandle(moduleName);
            if (moduleHandle == IntPtr.Zero)
            {
                return false;
            }
            return (GetProcAddress(moduleHandle, methodName) != IntPtr.Zero);
        }

        [DllImport("kernel32.dll")]
        static extern IntPtr GetCurrentProcess();

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        static extern IntPtr GetModuleHandle(string moduleName);

        [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)]string procName);

        [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool IsWow64Process(IntPtr hProcess, out bool wow64Process);

        //check 32 or 64 bit registry with .NET 2.0 or higher
        private enum RegSAM
        {
            QueryValue = 0x0001,
            SetValue = 0x0002,
            CreateSubKey = 0x0004,
            EnumerateSubKeys = 0x0008,
            Notify = 0x0010,
            CreateLink = 0x0020,
            WOW64_32Key = 0x0200,
            WOW64_64Key = 0x0100,
            WOW64_Res = 0x0300,
            Read = 0x00020019,
            Write = 0x00020006,
            Execute = 0x00020019,
            AllAccess = 0x000f003f
        }

        private static UIntPtr HKEY_LOCAL_MACHINE = new UIntPtr(0x80000002u);
        private static UIntPtr HKEY_CURRENT_USER = new UIntPtr(0x80000001u);

        [DllImport("Advapi32.dll")]
        static extern uint RegOpenKeyEx(
           UIntPtr hKey,
           string lpSubKey,
           uint ulOptions,
           int samDesired,
           out int phkResult);

        [DllImport("Advapi32.dll")]
        static extern uint RegCloseKey(int hKey);

        [DllImport("advapi32.dll", EntryPoint = "RegQueryValueEx")]
        public static extern int RegQueryValueEx(
           int hKey, string lpValueName,
           int lpReserved,
           ref uint lpType,
           System.Text.StringBuilder lpData,
           ref uint lpcbData);

        private static string GetRegKey64(UIntPtr inHive, String inKeyName, String inPropertyName)
        {
            return GetRegKey64(inHive, inKeyName, RegSAM.WOW64_64Key, inPropertyName);
        }

        private static string GetRegKey32(UIntPtr inHive, String inKeyName, String inPropertyName)
        {
            return GetRegKey64(inHive, inKeyName, RegSAM.WOW64_32Key, inPropertyName);
        }

        private static string GetRegKey64(UIntPtr inHive, String inKeyName, RegSAM in32or64key, String inPropertyName)
        {
            int hkey = 0;

            try
            {
                uint lResult = RegOpenKeyEx(inHive, inKeyName, 0, (int)RegSAM.QueryValue | (int)in32or64key, out hkey);
                if (lResult != 0) return null;
                uint lpType = 0;
                uint lpcbData = 1024;
                StringBuilder AgeBuffer = new StringBuilder(1024);
                RegQueryValueEx(hkey, inPropertyName, 0, ref lpType, AgeBuffer, ref lpcbData);
                string Age = AgeBuffer.ToString();
                return Age;
            }
            finally
            {
                if (hkey != 0) RegCloseKey(hkey);
            }
        }
    }
}

usage:

private static readonly float javaVersion = 1.8f;

if (OSOps.isJavaCompatible(javaVersion))
{
    //Install your application
}
else
{
    //Prompt to download JRE/JDK and launch JRE/JDK installer.
//You could also bundle the JRE/JDK installer with the executable
//and just launch that rather than downloading it.
//Or you could just bundle the JRE/JDK with your installer so
//that, like jME, it is extracted into your applications
//install directory rather than being installed system wide.
}

P.S. Oh yeah, the getJavaPath method returns the path to installed Java or null if it’s not found.

So if you were going to launch an application using installed Java you might:

using System;
using System.IO;

String javaHome = OSOps.getJavaPath();
if (javaHome != null)
{
    Process prc = new Process();
    prc.StartInfo = new ProcessStartInfo(Path.Combine(javaHome, "bin\\java.exe"));
    
    string exeDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
    prc.StartInfo.WorkingDirectory = exeDir;
    prc.StartInfo.Arguments = @"-jar pathtomyappRelativeToExe\myapp.jar";
        
    prc.StartInfo.UseShellExecute = false;
    prc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
    prc.StartInfo.CreateNoWindow = true;

    prc.StartInfo.RedirectStandardOutput = true;
    prc.StartInfo.RedirectStandardError = true;

    prc.Start();
    StreamReader sr = prc.StandardOutput;
    StreamReader err = prc.StandardError;
    
    //redirects output from the launched application's
    //console to the launching application's console
    while (prc.HasExited == false)
    {
        string output = sr.ReadLine();
        if (output != null)
        {
            if (output.Length > 0)
            {
                Console.WriteLine(output);
            }
        }
        string eOutput = err.ReadLine();
        if (eOutput != null)
        {
            if (eOutput.Length > 0)
            {
                Console.WriteLine(eOutput);
            }
        }
    }
}

And that’s something you might use when your application installer has finished successfully and you want to give the user the option to launch the newly installed application.

Edit: Now the above shows how to detect if the JRE is installed, but in reading I found that the JDK apparently does not make use of the system registry so there’s no way to know if the JDK is installed using the registry. Though I suppose you could use the Java path provided by the registry and look at its parent directory to see if it’s in the JDK directory.

Other than that I suppose you’d just install the JDK into your application directory like jME does and use that to launch your application. With a Windows executable you could modify the code I provided and swap out OSOps.getJavaPath(); with the path to the JDK you want to use.

Relative to the executable launcher:

string exeDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string javaHome = Path.Combine(exeDir, "pathtoJDK_RelativeToExe");

Now with Linux you could instruct the user to always launch the application from the application directory and/or install a .desktop launcher to /home/username/.local/share/applications that might look something like:

[Desktop Entry]
Name=MyApp
Comment=A Java application
Exec=/path/to/app/jdk/bin/java -jar “/path/to/app/myapp.jar”
Icon=/path/to/app/icon.png
Terminal=false
Type=Application
Categories=Game

As for OSX, I don’t have a Mac.