📜 ⬆️ ⬇️

Autonomous cross-platform monolithic Java program

I love desktop apps. Admitting to this now seems more embarrassing than in relations with foreign intelligence, but it is. No, this does not mean that I do not like Internet technology. Moreover, I not only respect some, but even more or less know. But, nevertheless, I miss the time when the program was written on one computer, then compiled and run on other, different computers. Then everywhere (almost) there was one system - Windows with the same API, there were almost no compatibility issues at the application level, no one was motivated by browser developers - everybody took care of WinAPI developers, who managed to create conflicts even inside it. But this is, of course, ironic, but seriously - sometimes even now I just want to write a desktop application, so much so that it works on all popular systems. Difficult? If you think and dig, it is not very.

I also like high-level languages ​​with neat architecture and strong typing. My favorites are Java and C #. Both of them provide the developer with many advantages compared to C ++, both relieve from a number of concerns. What have to pay? By dragging along a heavy deck called the Oracle JVM, .NET or mono. All three decks weigh hundreds of megabytes and have a license such that each user has to download this thing himself, without confusing the bitness of his computer, and most importantly, the Java program cannot be compatible with all versions of JVM at once, is it? And so - we come to the fact that just throwing the program to a friend (or a million friends) and not worrying that it does not start for him does not come out. We have to make clever setups, drive crutches, and I have not mentioned it yet. NET - once I saw a friend with 3 installed versions at once, and all three were needed by different applications ...

Stop! And let's write a Java program, but in such a way that it does not require installation of any JVM on the machine, with one touch going under Windows, Linux and OS X and at the same time taking up quite a bit; so that no one even understood that it was written, say, not in C. Impossible? Quite the contrary! (And no, I mean not gcj, which deprives Java of all its charms. Reflection will work and even third-party jar you can run).


Of course, I'm not a magician. I just found one magic artifact. It is called Avian, lies at oss.readytalk.com/avian and is a lightweight, but full-fledged third-party JVM implementation, which Oracle may not have even heard of. He supports a bunch of platforms and architectures, has a “take and do what you want” license, and - no, I have no relation to this project, I am not even a contributor, I just learned how to use it and want to share this powerful knowledge with respected inhabitants of Habr. It is also worth noting that it is a JIT compiler, that is, it has a competitive high performance (although I haven’t measured it yet).
')
Avian can be embedded in your application along with its very standardized class library, which is tolerant in functionality, and the program is only a megabyte heavy with hooks. Let's put together such a program.

0. Wednesday


To build, we first need the command line utilities of a unix developer, in particular, the g ++ compiler. It is most difficult for Windows users. I used to use MinGW32 for Windows with its excellent MSYS environment, which emulates a unix terminal. The compilers included in MinGW32 are 32-bit, which in some way limits the resulting program. In the comments I was told that there is a convenient mingw-w64 for a long time, which is regularly updated and in which there is not only MSYS, but even git.

Here and further, I hid platform-specific instructions under spoilers for convenience.

Windows
To download MinGW, go to sourceforge.net/projects/mingwbuilds and download two archives there: x64- XXX -release-posix-seh-rev X .7z and external-binary-packages / msys + 7za + wget + svn + git + mercurial + cvs-rev X .7z

After they are downloaded, unpack both of them in convenient directories. Next, you need to tell the MSYS environment where MinGW is located. To do this, go to msys/etc and write the path to /mingw in the fstab file as shown in the fstab.sample file fstab.sample I did it like this:
 c:/mingw64 /mingw 


After installing all of the above, you open the MSYS terminal. To do this, run the file msys\msys.bat (next to it are two icons). All further actions we will do from this terminal, since, firstly, it supports the format of unix-commands and unix-ways, and, secondly, it contains all the necessary environmental parameters.

OS X
Under OS X you have to download Xcode 4 and in its settings, in the Downloads section, install the Command Line Tools. Then you simply open a terminal window through the Launcher.

Linux
In linian based on debian, simply open the terminal and write:

 > sudo apt-get install build-essential 

After that, you need to close the terminal window and open it again to load the new environment settings.


The bottom line in each operating system should be what you type on the command line.

 > g++ 

and in response you see something like

 g++: fatal error: no input files 

Such a message means that the compiler is ready for battle and is eager to receive the source files.

1. Avian


For a start, let's offer our g ++ to build Avian.
Open: oss.readytalk.com/avian . Select the status link. On the page that opens, download Avian 0.6 . Despite the modest version number, the program is absolutely stable (in any case, I have never managed to drop it, and in their bugtracker there are quite intricate bugs, meaning high stability of what is).

Unpack the Avian source file we downloaded into some folder (let it be ~ / Projects).

 > cd ~/Projects 

Under Windows, the "~" folder in the MSYS environment is not attached to the user's home folder, but to the C: \ MinGW64 \ msys \ home \ username folder. In our case, since we want to maximally distance ourselves from the platform, this is even an advantage.

Suppose the downloaded archive is called avian-0.6.tar.bz2 and is in ~ / Downloads, then unpack it into the current folder by typing

 > tar -xjf ~/Downloads/avian-0.6.tar.bz2 

On Windows, the path must be specified in the mingw format with straight slashes in the form of /c/Users/username/Downloads/avian-0.6.tar.bz2. Of course, you can use one of a hundred alternative ways to unpack the archive - the main thing is to get it into the current folder. As a result, the avian subfolder unpacked from the archive will appear in it. Go to her:

 > cd avian 

Now you can try to run the make command, but if you run the build right now, most likely we will get a message that zlib was not found. Something like zlib.h: No such file or directory.

Windows
Under Windows, you will have to sweat a little and use the method proposed by the Avian authors, namely, slip the zlib library from their special auxiliary win64 repository. To do this, you will need git installed on your system. Fortunately, git is included in the msys build that we installed. Unfortunately, at the time of this writing, the build is crooked - it lacks the msys-crypto-0.9.8.dll file, which will have to be found on Google and put next to its useless brother, msys-crypto-1.0.0.dll, which This assembly is complete.

Next, you need to run the command from the avian folder

 > git clone git://oss.readytalk.com/win64.git ../win64 

which will put the win64 folder next to the avian folder with all the libraries that avian may need, in particular, with zlib.

OS X
Under OS X, as I recall, this library is installed automatically (possibly with the developer’s toolkit, which we have already installed).

Linux
Under linux, this problem is solved by a simple

 > sudo apt-get install zlib1g-dev 


However, by installing zlib and typing make again, we will get another error - the Avian collector does not find the / bin / javac program. Java developers are likely to recognize this program - this is a Java compiler. Since Avian is only a virtual machine, we still use the official compiler from Oracle. When building the VM itself, it is needed in order to build the Avian classes of the small standard Avian library from the source java files, such as System , ArrayList or HashMap . Accordingly, the developer’s machine should still have the JDK - both when building Avian, and when building applications that will use it. And it is desirable to put JDK7, which is compatible with Avian 0.6. The user of your applications, like the JRE, will no longer need it (actually, for the sake of it we are trying).

Windows
On Windows, go to the Oracle site and download the desired distribution, and then install it.

OS X
In OS X, as in Windows, go to the Oracle website and download the desired distribution, and then install it.

Linux
In Linux, we manage the usual mantra

 > sudo apt-get install openjdk-7-jdk 

(Perhaps your distribution will not have OpenJDK or the package will be called something else. But Linux is Linux - look for it and find it.)

In order to see where you just put your java-environment for developers, make reads the JAVA_HOME environment variable, which we now need to set correctly. We need the same variable with the same value later on in order to build our own project.

Windows
Your path to MinGW for Windows is likely to be something like / c / Program \ Files / Java / jdk1.7.0_07 / (you specified it when installing JDK7).

OS X
Under OS X, your JDK7 will install in /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home

Linux
On Linux, it looks like this:

 > update-java-alternatives -l java-1.7.0-openjdk-amd64 1071 /usr/lib/jvm/java-1.7.0-openjdk-amd64 > export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-amd64 


And now, finally, the hour of triumph - we collect Avian:

 > make 

If you did everything correctly, you will see a sequence of lines like compiling build / <your platform_name> / <some file>, and then linking build / <your_ platform_name> / <some file>. At the end of the build process, we will get a lot of files in the build / <your platform_name> folder, but we’ll be interested only in this:



2. Cross-platform independent monolithic hello in Java


We compiled all the necessary third-party code. Now let's create our own.

The task is to make a program that will be written in Java, which at the same time will contain as few platform-specific bookmarks as possible and which will be assembled into one exe-file that does not require special installation by itself and runs on any "clean" system without install any dependencies.

2.1. A little about JNI

Let's start with the theory. Discuss how the JVM interacts with the system. Any virtual machine is created, first of all, in order to abstract from the external environment. Therefore, it is not surprising that the call of system functions is the bottleneck in the VM implementation. A modern program cannot even "sneeze" without the OS. Read / write to disk - system function. Text output to the console is a system function. Draw a window on the screen - what do you think?

In fact, the only thing that an application can do “inside itself” is calculations and decision making. These actions — arithmetic and logic — are VM functions. As soon as something else needs to be done, she calls for the external environment. But how? In the case of Java, there is a JNI (Java Native Interface) for this. Its essence is very simple. A program written in Java contains a function header marked with the native modifier. For example,

 package packagename; { class ClassName { void native foo(); } } 

Such a function is understood by the Java compiler as a function that is called from loaded libraries of ordinary (non-virtual) code. In one of these libraries there must be something like

 extern "C" JNIEXPORT void JNICALL Java_packagename_ClassName_foo(JNIEnv * env, jobject caller) { … } 

When we call the foo() function in Java code, we actually call a function from the native library, passing it a pointer to the JNIEnv environment — an object that allows to “communicate” with data and code inside the VM, and a pointer to the object from which the function was called, jobject caller (if the function were static, instead of the object descriptor, there would be a jclass caller_class class descriptor). People who are familiar with Java, but have not studied JNI, can explain this principle of interaction as follows: JNI allows external native-code to perform reflection on a Java program. If you want to study this technology in more detail, you are welcome in a special section on the official Oracle website .

2.2. JNI "set"

Why was all this educational program? Then, that at the moment we face a very interesting, almost inverse, task. We need to run a native-executable file, which, being statically linked to the libavian.a library, will contain the JVM right inside it. In addition, it will contain all the necessary java-classes, including the “entry point” - a class like

 class Application { public static void main(String... args) { … } } 

It all sounds pretty scary, but the task is quite simple. You need to write a fairly simple C code that will pull out the Avian class library (with our Application class added to it) from within its own binary file and feed it to the JVM along with command line parameters using the same JNI. Then we link this C-file in a special way so that everything is in its place, and enjoy the result.

2.3. New project and libraries

Now we will drag and lay down all the components we need for further work. What I will describe here is my own approach. Of course, you are free to do everything differently, as you please. But if you want to end up with exactly what I posted on GitHub (the link will be at the end), try to do everything exactly.

Create a crossbase folder where we want (I created it in Projects, next to avian and win32)

 > mkdir crossbase && cd crossbase 

Inside we create a subfolder libs

 > mkdir lib && cd lib 

Inside we create a subfolder with the name of your current OS. It should be “linux”, “win32” or “osx”.

 > mkdir win-x86_64 && cd win-x86_64 

In this folder you need to copy the libavian.a, which we collected earlier. It looks like this to me:

 > cp ../../../avian/build/windows-i386/libavian.a ./ 

In addition, in a Windows system where there is no zlib, libz.a will also need to be copied to the same folder:

 > cp ../../../win-x86_64/lib/libz.a ./ 

Thus, we have collected the minimum required libraries. That's enough for the simplest program.

In addition to libraries, we will need classpath.jar, which was also compiled with avian.

 > cd .. > mkdir java && cd java > cp ../../../avian/build/windows-i386/classpath.jar ./ 

And now it's time to reveal the purpose of the mysterious binaryToObject. We need it to convert our jar file to a special object file, which will then be transferred to the linker and added to our program by it. Since this procedure must be carried out with each assembly, it must also be dragged into our new project.

 > cd ../.. 

(we are again in the crossbase folder where we created the lib)

 > mkdir -p tools/win-x86_64 && cd tools/win-x86_64 

The name win-x86_64 is assigned to the internal folder on the same principle as last time. We throw here binaryToObject. (in Windows, of course, it has an exe extension)

 > cp ../../../avian/build/windows-i386/binaryToObject/binaryToObject.exe ./ 

You can run it and see usage:

 usage: c:\Users\imizus\Projects\crossbase\crossbase\tools\win32\binaryToObject.exe <input file> <output file> <start name> <end name> <platform> <architecture> [<alignment> [{writable|executable}...]] 


2.4. Program code

Now let's start writing the code. Create a new C ++ source file (you can use any text editor you like, I use eclipse, where you can edit both C ++ and Java in one project, although this will have to be slightly customized).

 > mkdir -p src/cpp && cd src/cpp 

Inside we create the main.cpp file with the following content (I will give it in its entirety, and then I will explain what is there for what):

 #include <stdint.h> #include <string.h> #ifdef __MINGW32__ #include <windows.h> #endif #include <jni.h> #if (defined __MINGW32__) # define EXPORT __declspec(dllexport) #else # define EXPORT __attribute__ ((visibility("default"))) \ __attribute__ ((used)) #endif #if (! defined __x86_64__) && (defined __MINGW32__) # define SYMBOL(x) binary_boot_jar_##x #else # define SYMBOL(x) _binary_boot_jar_##x #endif extern "C" { extern const uint8_t SYMBOL(start)[]; extern const uint8_t SYMBOL(end)[]; EXPORT const uint8_t* bootJar(unsigned* size) { *size = SYMBOL(end) - SYMBOL(start); return SYMBOL(start); } } // extern "C" int main(int argc, const char** argv) { #ifdef __MINGW32__ // For Windows: Getting command line as a wide string int wac = 0; wchar_t** wav; wav = CommandLineToArgvW(GetCommandLineW(), &wac); #else // For other OS: Getting command line as a plain string (encoded in UTF8) int wac = argc; const char** wav = argv; #endif JavaVMInitArgs vmArgs; vmArgs.version = JNI_VERSION_1_2; vmArgs.nOptions = 1; vmArgs.ignoreUnrecognized = JNI_TRUE; JavaVMOption options[vmArgs.nOptions]; vmArgs.options = options; options[0].optionString = const_cast<char*>("-Xbootclasspath:[bootJar]"); JavaVM* vm; void* env; JNI_CreateJavaVM(&vm, &env, &vmArgs); JNIEnv* e = static_cast<JNIEnv*>(env); jclass c = e->FindClass("crossbase/Application"); if (not e->ExceptionCheck()) { jmethodID m = e->GetStaticMethodID(c, "main", "([Ljava/lang/String;)V"); if (not e->ExceptionCheck()) { jclass stringClass = e->FindClass("java/lang/String"); if (not e->ExceptionCheck()) { jobjectArray a = e->NewObjectArray(wac - 1, stringClass, 0); if (not e->ExceptionCheck()) { for (int i = 1; i < wac; ++i) { #ifdef __MINGW32__ // For Windows: Sending wide string to Java int arglen = wcslen(wav[i]); jstring arg = e->NewString((jchar*) (wav[i]), arglen); #else // For other OS: Sending UTF8-encoded string to Java int arglen = strlen(wav[i]); jstring arg = e->NewStringUTF((char*) (wav[i])); #endif e->SetObjectArrayElement(a, i - 1, arg); } e->CallStaticVoidMethod(c, m, a); } } } } int exitCode = 0; if (e->ExceptionCheck()) { exitCode = -1; e->ExceptionDescribe(); } vm->DestroyJavaVM(); return exitCode; } 


__MINGW32__ is the preprocessor symbol, which (as unexpectedly!) Is automatically set inside the MinGW32 environment. It allows us to distinguish between Windows, which, as you, I think, have already noticed, is very different from all other systems. In particular, only under Windows we need a special system API, which we connect with the line #include <windows.h> . On the other platforms, we manage the standard POSIX and ANSI C ++ libraries. Why do you need? It will become clear a little later. We will view the code in order.

 #if (defined __MINGW32__) # define EXPORT __declspec(dllexport) #else # define EXPORT __attribute__ ((visibility("default"))) \ __attribute__ ((used)) #endif 

This code is familiar and understandable to anyone who wrote cross-platform dynamic libraries using gcc. Its essence is that in different operating systems functions that should be exported from a library are described differently. “And here is a dynamic library, because we are collecting the executable file?” - you ask. In response, I remind you that Avian interacts with the platform-specific code through the JNI mechanism, which implies a function call from the library. In other words, for your Java code, an executable file is not only a launch program, but also a dynamic library of functions.

The next part is weird magic:
 #if (! defined __x86_64__) && (defined __MINGW32__) # define SYMBOL(x) binary_boot_jar_##x #else # define SYMBOL(x) _binary_boot_jar_##x #endif extern "C" { extern const uint8_t SYMBOL(start)[]; extern const uint8_t SYMBOL(end)[]; EXPORT const uint8_t* bootJar(unsigned* size) { *size = SYMBOL(end) - SYMBOL(start); return SYMBOL(start); } } // extern "C" 

Let's see. We declare an export function (look at extern "C" and EXPORT directive that we just introduced. The function name is bootJar . bootJar this name and see what it does. If we mentally parse the preprocessor directives, we see that it calculates the distance between some _binary_boot_jar_start and _binary_boot_jar_end (In MinGW32, they will not have an underscore at the beginning.) These characters themselves are declared as extern , that is, they must be substituted by the linker. Mysterious activity, isn't it?

In fact, as we will see below, it's pretty simple, if you know what to do. Since Avian was designed to be embedded in applications, the authors provided the ability to add a class library directly to the executable file and then download it from there. To do this, you just need to convert the library into an object file. Yes, yes, I was surprised at first, but this is a very elegant idea. In the object file containing our jar, when we create it, 2 symbols will be declared, indicating the beginning ( _binary_boot_jar_start ) and the end ( _binary_boot_jar_end ) of this jar-file. And the bootJar function will be used by Avian to find out where it starts and how long it has. Looking ahead, I’ll say that the name of this function is passed as a string.

 options[0].optionString = const_cast<char*>("-Xbootclasspath:[bootJar]"); 

Finally we got to the entry point - the main function. Her task includes:


Let's go from the beginning of the function:

 #ifdef __MINGW32__ // For Windows: Getting command line as a wide string int wac = 0; wchar_t** wav; wav = CommandLineToArgvW(GetCommandLineW(), &wac); #else // For other OS: Getting command line as a plain string (encoded in UTF8) int wac = argc; const char** wav = argv; #endif 

Here, as always, Windows distinguished itself. When it was universally decided to switch from old uncomfortable single-byte encodings to more complex ones, all OS switched to convenient UTF-8, and Microsoft’s favorite brainchild switched to fixed two-byte one. At the same time, they didn’t take care of the encoding used, for example, in file names. But the coding doesn’t bother us now either. We need to pass a string of parameters to Java (in which the two-byte char is also passed). Therefore, for Windows, we call the API function (for which we dragged windows.h), which will give us the parameter string in the correct two-byte encoding. So we will get the opportunity, for example, to open Cyrillic files in the title. In all other systems, we simply read the parameters from the arguments of the main function.

The following is the creation of a Java virtual machine:

 JavaVMInitArgs vmArgs; vmArgs.version = JNI_VERSION_1_2; vmArgs.nOptions = 1; vmArgs.ignoreUnrecognized = JNI_TRUE; JavaVMOption options[vmArgs.nOptions]; vmArgs.options = options; options[0].optionString = const_cast<char*>("-Xbootclasspath:[bootJar]"); JavaVM* vm; void* env; JNI_CreateJavaVM(&vm, &env, &vmArgs); JNIEnv* e = static_cast<JNIEnv*>(env); 

We also pull out a pointer to a JNIEnv object, which we will use to command the newly created Java machine.

The following code reads Mayakovsky's verse, if you only know a little JNI.

 jclass c = e->FindClass("crossbase/Application"); if (not e->ExceptionCheck()) { jmethodID m = e->GetStaticMethodID(c, "main", "([Ljava/lang/String;)V"); if (not e->ExceptionCheck()) { jclass stringClass = e->FindClass("java/lang/String"); if (not e->ExceptionCheck()) { jobjectArray a = e->NewObjectArray(wac - 1, stringClass, 0); if (not e->ExceptionCheck()) { for (int i = 1; i < wac; ++i) { #ifdef __MINGW32__ // For Windows: Sending wide string to Java int arglen = wcslen(wav[i]); jstring arg = e->NewString((jchar*) (wav[i]), arglen); #else // For other OS: Sending UTF8-encoded string to Java int arglen = strlen(wav[i]); jstring arg = e->NewStringUTF((char*) (wav[i])); #endif e->SetObjectArrayElement(a, i - 1, arg); } e->CallStaticVoidMethod(c, m, a); } } } } int exitCode = 0; if (e->ExceptionCheck()) { exitCode = -1; e->ExceptionDescribe(); } 

Take the crossbase/Application class. If we could, we will find in it the static method main with the signature ([Ljava/lang/String;)V If we could, we will get the class java/lang/String from the standard library. If we could, we will create an array of objects of this class (they will be parameters). , java- , UTF-8 , Windows , .

- , .

, , « ». Java. , , crossbase.Application public static void main(String... args) .

crossbase/src java, — crossbase ( — ), Application.java :

 package crossbase; public class Application { public static void main(String... args) { System.out.println("This is a crossplatform monolith application with Java code inside. Freedom to Java apps!"); for (int i = 0; i < args.length; i++) { System.out.println("args[" + i + "] = " + args[i]); } } } 

Java, , , . , Avian ( , , OpenJDK).

2.5. Assembly


We now turn to the task of assembling our project. I use make because it is always and wherever there is gcc. It is also powerful enough to write almost any automated build system on it. No, really.It’s possible to list on fingers what I couldn’t do on make and these were hardly vital things. Our Makefile will be located directly in the crossbase folder and it will look like this:

 UNAME := $(shell uname) ARCH := $(shell uname -m) SRC = src BIN = bin OBJ = obj JAVA_SOURCE_PATH = $(SRC)/java JAVA_CLASSPATH = $(BIN)/java CPP_SOURCE_PATH = $(SRC)/cpp OBJECTS = $(OBJ) DEBUG_OPTIMIZE = -O3 #-O0 -g ifeq ($(UNAME), Darwin) # OS X PLATFORM_ARCH = darwin x86_64 PLATFORM_LIBS = osx-x86_64 PLATFORM_GENERAL_INCLUDES = -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/darwin" PLATFORM_GENERAL_LINKER_OPTIONS = -framework Carbon PLATFORM_CONSOLE_OPTION = EXE_EXT= STRIP_OPTIONS=-S -x RDYNAMIC=-rdynamic else ifeq ($(UNAME) $(ARCH), Linux x86_64) # linux on PC PLATFORM_ARCH = linux x86_64 PLATFORM_LIBS = linux-x86_64 PLATFORM_GENERAL_INCLUDES = -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/linux" PLATFORM_GENERAL_LINKER_OPTIONS = -lpthread -ldl PLATFORM_CONSOLE_OPTION = EXE_EXT= STRIP_OPTIONS=--strip-all RDYNAMIC=-rdynamic else ifeq ($(OS), Windows_NT) # Windows PLATFORM_ARCH = windows x86_64 PLATFORM_LIBS = win-x86_64 PLATFORM_GENERAL_INCLUDES = -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/win32" PLATFORM_GENERAL_LINKER_OPTIONS = -static -lmingw32 -lmingwthrd -lws2_32 -mwindows -static-libgcc -static-libstdc++ PLATFORM_CONSOLE_OPTION = -mconsole EXE_EXT=.exe STRIP_OPTIONS=--strip-all RDYNAMIC= endif JAVA_FILES = $(shell cd $(JAVA_SOURCE_PATH); find . -name \*.java | awk '{ sub(/.\//,"") }; 1') JAVA_CLASSES := $(addprefix $(JAVA_CLASSPATH)/,$(addsuffix .class,$(basename $(JAVA_FILES)))) CPP_FILES = $(shell cd $(CPP_SOURCE_PATH); find . -name \*.cpp | awk '{ sub(/.\//,"") }; 1') CPP_OBJECTS := $(addprefix $(OBJECTS)/,$(addsuffix .o,$(basename $(CPP_FILES)))) all: $(BIN)/crossbase $(JAVA_CLASSPATH)/%.class: $(JAVA_SOURCE_PATH)/%.java @echo $(PLATFORM_GENERAL_INCLUDES) if [ ! -d "$(dir $@)" ]; then mkdir -p "$(dir $@)"; fi "$(JAVA_HOME)/bin/javac" -sourcepath "$(JAVA_SOURCE_PATH)" -classpath "$(JAVA_CLASSPATH)" -d "$(JAVA_CLASSPATH)" $< $(OBJ)/%.o: $(SRC)/cpp/%.cpp @echo $(PLATFORM_GENERAL_INCLUDES) mkdir -p $(OBJ) g++ $(DEBUG_OPTIMIZE) -D_JNI_IMPLEMENTATION_ -c $(PLATFORM_GENERAL_INCLUDES) $< -o $@ $(BIN)/crossbase: $(JAVA_CLASSES) $(CPP_OBJECTS) mkdir -p $(BIN); @echo $(PLATFORM_GENERAL_INCLUDES) # Extracting libavian objects ( \ cd $(OBJ); \ mkdir -p libavian; \ cd libavian; \ ar x ../../lib/$(PLATFORM_LIBS)/libavian.a; \ ) # Making the java class library cp lib/java/classpath.jar $(BIN)/boot.jar; \ ( \ cd $(BIN); \ "$(JAVA_HOME)/bin/jar" u0f boot.jar -C java .; \ ) # Making an object file from the java class library tools/$(PLATFORM_LIBS)/binaryToObject $(BIN)/boot.jar $(OBJ)/boot.jar.o _binary_boot_jar_start _binary_boot_jar_end $(PLATFORM_ARCH); \ g++ $(RDYNAMIC) $(DEBUG_OPTIMIZE) -Llib/$(PLATFORM_LIBS) $(OBJ)/boot.jar.o $(CPP_OBJECTS) $(OBJ)/libavian/*.o $(PLATFORM_GENERAL_LINKER_OPTIONS) $(PLATFORM_CONSOLE_OPTION) -lm -lz -o $@ strip $(STRIP_OPTIONS) $@$(EXE_EXT) clean: rm -rf $(OBJ) rm -rf $(BIN) .PHONY: all 

Be careful!Do not confuse tabs with spaces, in make tabs are allocated commands inside the build rule, and the space is not a syntax element. Let's take a closer look at how it works. The only more or less cerebral construct is the purpose of these variables:

 JAVA_FILES = $(shell cd $(JAVA_SOURCE_PATH); find . -name \*.java | awk '{ sub(/.\//,"") }; 1') JAVA_CLASSES := $(addprefix $(JAVA_CLASSPATH)/,$(addsuffix .class,$(basename $(JAVA_FILES)))) CPP_FILES = $(shell cd $(CPP_SOURCE_PATH); find . -name \*.cpp | awk '{ sub(/.\//,"") }; 1') CPP_OBJECTS := $(addprefix $(OBJECTS)/,$(addsuffix .o,$(basename $(CPP_FILES)))) 

unix- find .java $(JAVA_SOURCE_PATH) . . .class , $(JAVA_CLASSPATH) , , , .. . .cpp .o . makefile :

 $(JAVA_CLASSPATH)/%.class: $(JAVA_SOURCE_PATH)/%.java @echo $(PLATFORM_GENERAL_INCLUDES) if [ ! -d "$(dir $@)" ]; then mkdir -p "$(dir $@)"; fi "$(JAVA_HOME)/bin/javac" -sourcepath "$(JAVA_SOURCE_PATH)" -classpath "$(JAVA_CLASSPATH)" -d "$(JAVA_CLASSPATH)" $< $(OBJ)/%.o: $(SRC)/cpp/%.cpp @echo $(PLATFORM_GENERAL_INCLUDES) mkdir -p $(OBJ) g++ $(DEBUG_OPTIMIZE) -D_JNI_IMPLEMENTATION_ -c $(PLATFORM_GENERAL_INCLUDES) $< -o $@ 

, . , ,

 $(BIN)/crossbase: $(JAVA_CLASSES) $(CPP_OBJECTS) ... 

. makefile , java cpp , .

:



— $(BIN)/crossbase: $(JAVA_CLASSES) $(CPP_OBJECTS) . libavian.a, . , . Windows - ( ). classpath.jar, bin/java bin/boot.jar. binaryToObject, boot.jar obj/boot.jar.o _binary_boot_jar_start _binary_boot_jar_end ( main.o). , , . , , strip, , , OS X, , MinGW Linux. — . crossbase 9 , — .

3. The moment of triumph


Going into the crossbase / bin folder, we launch our crossbase from the console, passing parameters to it.

 > ./crossbase  ! This is a crossplatform monolith application with Java code inside. Freedom to Java apps! args[0] =  args[1] = ! 


The project we got is on my GitHub .

4. Results and meaning


It's hard for me to appreciate the benefits of this article. If I at least get an invite for it, it will mean that she, at least, is not uninteresting. I can only say that with seeming complexity, this method pays off well compared to writing a program, say, in pure C ++. Java becomes very convenient when a project grows to at least a couple of dozen classes. Even if to be extremely careful when writing code in C ++, there are still loopholes for monstrously difficult to find errors. Therefore, I would advise everyone to write control code (not requiring super-performance) in Java. The code that requires maximum speed can be written in C ++, and then very easily and carefully wrapped the C ++ class with Java. Maybe I'll write how to make it beautiful and not run into a rake.

, «» SWT (, Eclipse), , . , . Thank you for attention!

PS
, . .

Source: https://habr.com/ru/post/176237/


All Articles