Skip to content

JNI в Windows

Синопсис

В этом посте поговорим о вызовах функции библиотеки c++ из java класса. Это довольно интересная тема, я достаточно много времени потратил чтобы понять как осуществляется взаимодействие кода выполняющегося в виртуальной машине джава с нативным кодом.
В этом посте, тестировать взаимодействие будем на платформе Windows, а в следующем сделаем тоже самое, но только в Linux.

Похожие посты

  • Как подключить разделяемую библиотеку к проекту в Visual Stuido
  • Что пилим

    Идея такая, допустим мы создаем джава приложение которое будет делать некое вычисления по каким-то формулам. Допустим по ряду причин мы не можем сами написать функции делающие вычисления по этим формулам (нам лень), но мы знаем, что кто-то когда-то, когда еще не было джавы, что-то подобное уже делал. Порывшись в яндексе мы нашли и выкачали библиотеку сишных функций, которые делают именно то, что нам нужно. И вот теперь у нас встал вопрос, как из джава приложения делать колы к функциям из сишной dll библиотеки.
    Для этого существует JNI — Java Native Interface, это такой механизм джавы, который позволяет виртуальной джава машине вызывать нативный код.

    Готовый проект можно взять с gitHub по ссылке: https://github.com/dev-blogs/JNIProject_windows

    Архитектура проекта

    На диаграмме ниже представлена схема интеракции джава приложения и сишной dll либы. Сначала запускается джава приложение com.task.JNIProject, которое стартует с вызова метода main. Далее метод main обращается к нативному методу sum. Этот нативный метод не настоящий сишный метод, через него джава машина пробрасывает вызов к функции Java_com_task_JNIProject_sum() (такое страшное сгенеренное название пока оставим без комментариев) которая находится в библиотеке JNIProject.dll, так вот, код этой функции и есть вызываемый нативный сишный код. А вот функция Java_com_task_JNIProject_sum вызывает уже функцию sum из библиотеки MathExpressions.dll:
    6

    Critical warning
    Я не зря пометил в круглых скобках разрядность выполняемых приложений и сред в которых они выполняются. В данном случае, для компиляции нативных приложений применялся 32 разрядный компилятор g++, из MinGW который идет вместе с CodeBlocks (это я об Windows, ибо в Linux менеджер пакетов подтянет g++ и все иже с ним). Эти 32 битные нативные коды могут запускаться как в 32 так и 64 разрядной среде. Но в зависимости от разрядности нативных кодов нужно выбирать соответствующую разрядность виртуальной джава машины для обращения к ним. Если джава программа будет обращаться к 32 разрядному нативному приложению, то она должна быть запущена на 32 разрядной виртуальной джава машине. Из 64 разрядной джава машины обращаться к 32 разрядным нативным кодам нельзя.

    Библиотека JNIProject.dll в этой цепочке, это посредник, предназначенный для связывания джава приложения с целевой либой, той которая предоставляет математические операции, нужные нашему джава приложению. На прямую джава приложение не может обращаться к произвольной сишной библиотеке, потому что сама по себе та библиотека не предназначена, чтобы она вызывалась из других джава приложений, из джава приложений возможны вызовы только тех сишных файлов, которые скомпилированы с использованием заголовочного файла jni.h, а так же сгенеренного джава средствами хэдера, который нужно включить в тот сишный цпп файл, к которому будет обращаться джава приложение.

    so vs dll

    Динаически разделяемая библиотека (Dynamic shared library) представляет из себя отдельную независимую либу (ибо она сама представляет из себя зависимость 🙂 ), в которую вынесена некая общая, для некоторых приложений, функциональность. Это делается для того, чтобы облегчить приложение (в весе). Например в разделяемой либе можно держать функции ввода-вывода, математические функции, в общем любые функции, которые могут пригодиться в каком-нибудь множестве приложений. В таком случае все эти часто пригождающиеся функции нет смысла хранить вместе с самими прилжожениями. Если, например, какая-нибудь либа из часто требуемых функций размером, скажем, в 1024 килобайт, будет поставляться с каждым приложением, и таких приложений будет, например, 1024 штук, то только для библиотек потребуется на диске 1024×1024=1Гб места, вместо того, чтобы все 1024 приложений юзали одну либу размером в метр. Недостаток здесь в том, что приложение не сможет запуститься в энвайренменте не поддерживающей все необходимые ему либы или поддерживающей, но не тех версий.

    Структура проекта

    Ниже представлены две структуры проекта, до компиляции и после, точнее те каталоги, которые добавятся после компиляции. К тем каталогам, которые должны быть видимы для джава машины во время компиляции, другими словами те которые должны быть добавлены в класспас, я промаркировал classpath (compile), а те которые должны быть видимы джава машиной во время выполнения, промаркированы classpath (runtime):

    JNIProject <--classpath (compile)
        ├─src
        │   ├─java
        │   │  └─com
        │   │     └─task
        │   │        └─JNIProject.java
        │   └──c
        │      ├─com_task_JNIProject.h (generated)
        │      ├─JNIProject.cpp
        │      ├─MathExpressions.h
        │      └─mathExpressions.cpp
        └──build.bat
    

    После того как мы запустим скрипт build.bat, в корне проекта появится еще два каталога build_java и build_c:

    ─build-java <--classpath (runtime)
        └─com
           └─task
              └─JNIProject
    ─build-c <--java.library.path (runtime)
        ├─com_task_JNIProject.h
        ├─JNIProject.dll
        ├─MathExpressions.h
        └─MathExpressions.dll
    

    Запускаться будет приложение com.task.JNIProject которое находится в каталоге build_java (туда же установлен класспас. Напоминаю, все каталоги, которые расположены ниже класспаса по дереву иерархии, относятся к имени класса) которое будет обращаться к сишной библиотеке JNIProject.dll из каталога build_c (путь к этому каталогу указан в параметре утилиты java java.library.path), которая, в свою очередь, будет обращаться к библиотеке MathExpressions.dll. А теперь добавим все необходимые классы.

    Java код

    HelloJNI.java

    package com.task;
    
    public class JNIProject {
    	static {
    		System.loadLibrary("JNIProject");	// Load native library at runtime
    											// hello.dll (Windows) or libhello.so (Unixes)
    	}
    	
    	private native int initialize();
    	private native int sum(int a, int b);
    	private native int difference(int a, int b);
    	private native int multiply(int a, int b);
    	private native int divide(int a, int b);
    
    	public static void main(String [] args) {
    		int a = 3;
    		int b = 2;
    		JNIProject jniProject = new JNIProject();
    		jniProject.initialize();
    		System.out.println(a + " + " + b + " = " + jniProject.sum(a, b));
    		System.out.println(a + " - " + b + " = " + jniProject.difference(a, b));
    		System.out.println(a + "*" + b + " = " + jniProject.multiply(a, b));
    		System.out.println(a + "/" + b + " = " + jniProject.divide(a, b));
    	}
    }
    

    C++ код

    Сгенеренный файл

    com_task_JNIProject.h (относится к JNIProject.dll)

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_task_JNIProject */
    
    #ifndef _Included_com_task_JNIProject
    #define _Included_com_task_JNIProject
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_task_JNIProject
     * Method:    initialize
     * Signature: ()I
     */
    JNIEXPORT jint JNICALL Java_com_task_JNIProject_initialize
      (JNIEnv *, jobject);
    
    /*
     * Class:     com_task_JNIProject
     * Method:    sum
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL Java_com_task_JNIProject_sum
      (JNIEnv *, jobject, jint, jint);
    
    /*
     * Class:     com_task_JNIProject
     * Method:    difference
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL Java_com_task_JNIProject_difference
      (JNIEnv *, jobject, jint, jint);
    
    /*
     * Class:     com_task_JNIProject
     * Method:    multiply
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL Java_com_task_JNIProject_multiply
      (JNIEnv *, jobject, jint, jint);
    
    /*
     * Class:     com_task_JNIProject
     * Method:    divide
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL Java_com_task_JNIProject_divide
      (JNIEnv *, jobject, jint, jint);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    JNIProject.cpp (относится к JNIProject.dll)

    #include <windows.h>
    #include "com_task_JNIProject.h"
    
    HINSTANCE h;
    int (* psum) (int, int);
    int (* pdifference) (int, int);
    int (* pmultiply) (int, int);
    int (* pdivide) (int, int);
    
    JNIEXPORT jint JNICALL Java_com_task_JNIProject_initialize(JNIEnv *env, jobject thisObj)
    {
    	h = LoadLibrary("C:\\practice\\JNI\\JNIProject\\build_c\\MathExpressions.dll");
    	if (!h)
    	{
    		printf("%d Error - can't find library MathExpressions.dll\n", h);
    		return 1;
    	}
    	psum=(int (*)(int, int)) GetProcAddress(h, "sum");
    	pdifference=(int (*)(int, int)) GetProcAddress(h, "difference");
    	pmultiply=(int (*)(int, int)) GetProcAddress(h, "multiply");
    	pdivide=(int (*)(int, int)) GetProcAddress(h, "divide");
    	if (!psum)
    	{
    		printf("Error psum\n");
    		return 2;
    	}
    	if (!pdifference)
    	{
    		printf("Error pdifference\n");
    		return 2;
    	}
    	if (!pmultiply)
    	{
    		printf("Error pmultiply\n");
    		return 2;
    	}
    	if (!pdivide)
    	{
    		printf("Error pdivide\n");
    		return 2;
    	}
    }
    
    JNIEXPORT jint JNICALL Java_com_task_JNIProject_sum(JNIEnv *env, jobject thisObj, jint a, jint b)
    {	
    	return psum(a, b);
    }
    
    JNIEXPORT jint JNICALL Java_com_task_JNIProject_difference(JNIEnv *env, jobject thisObj, jint a, jint b)
    {
    	return pdifference(a, b);
    }
    
    JNIEXPORT jint JNICALL Java_com_task_JNIProject_multiply(JNIEnv *env, jobject thisObj, jint a, jint b)
    {
    	return pmultiply(a, b);
    }
    
    JNIEXPORT jint JNICALL Java_com_task_JNIProject_divide(JNIEnv *env, jobject thisObj, jint a, jint b)
    {
    	return pdivide(a, b);
    }
    

    MathExpressions.h (относится к MathExpressions.dll)

    extern "C"
    {
    	int sum(int a, int b);
    	int difference(int a, int b);
    	int multiply(int a, int b);
    	int divide(int a, int b);
    }
    

    mathExpressions.cpp (относится к MathExpressions.dll)

    #include "MathExpressions.h"
    
    int sum(int a, int b)
    {
    	return a + b;
    }
    
    int difference(int a, int b)
    {
    	return a - b;
    }
    
    int multiply(int a, int b)
    {
    	return a*b;
    }
    
    int divide(int a, int b)
    {
    	return a/b;
    }
    

    Сборка проекта

    build.bat

    set BUILD_JAVA=build_java
    set BUILD_C=build_c
    set SRC_JAVA=src\java
    set SRC_C=src\c
    set JAVA_NAME=JNIProject
    set HEADER_NAME=%JAVA_NAME%
    set DLL_NAME=JNIProject
    set HEADERS_FROM_JAVA_DIR=C:\bin\Java\32\jdk1.7.0_71\include
    
    :: clear %BUILD_JAVA% directory
    rd /s /q %BUILD_JAVA%
    if exist %BUILD_JAVA% rd /s /q %BUILD_JAVA%
    
    :: make %BUILD_JAVA% and %GENERATED_DIR% directories
    mkdir %BUILD_JAVA%
    
    :: clear %BUILD_C% directory
    rd /s /q %BUILD_C%
    if exist %BUILD_C% rd /s /q %BUILD_C%
    
    ::make %BUILD_C% and %GENERATED_DIR% directories
    mkdir %BUILD_C%
    
    :: compilation and generate header
    javac -d %BUILD_JAVA% %SRC_JAVA%\com\task\%JAVA_NAME%.java
    :: generate header
    javah -d %SRC_C% -cp %BUILD_JAVA% com.task.%JAVA_NAME%
    
    :: create MathExpressions.dll
    g++ -shared -o %BUILD_C%\MathExpressions.dll %SRC_C%\mathExpressions.cpp
    
    :: create JNIProject.dll lib which java application works with
    g++ -Wl,--add-stdcall-alias -I%HEADERS_FROM_JAVA_DIR% -I%HEADERS_FROM_JAVA_DIR%\win32 -shared -o %BUILD_C%\%DLL_NAME%.dll %SRC_C%\%DLL_NAME%.cpp
    
    :: copy headers to %BUILD_C% directory
    copy %SRC_C%\MathExpressions.h %BUILD_C%
    copy %SRC_C%\com_task_JNIProject.h %BUILD_C%
    
    :: run java program
    java -Djava.library.path=%BUILD_C% -cp %BUILD_JAVA% com.task.%JAVA_NAME%
    

    Ссылки

    Работа с DLL в C++ (Microsoft Visual C++)

    Поделиться в социальных сетях

    Опубликовать в Google Plus
    Опубликовать в LiveJournal
    Опубликовать в Мой Мир
    Опубликовать в Одноклассники
    Опубликовать в Яндекс