# Взаимодействие Java и C/C++

#Java #C #Cplusplus #JNI #NativeCode #Programming #DevZone

6 min. reading
12 декабря 2017

Java, несмотря на некоторые «недостатки», является мощным и, что особенно важно, в большинстве случаев самодостаточным языком программирования. Под самодостаточностью я понимаю возможность писать программы, решающие конкретную задачу, без привлечения других языков программирования.

Однако я намеренно отметил, что самодостаточность Java проявляется именно в большинстве случаев. Иногда невозможно написать программу полностью, не используя вспомогательные инструменты. Например, может потребоваться код, обеспечивающий низкоуровневый доступ к «железу» компьютера, на котором выполняется программа. В языке Java, который изначально проектировался как кроссплатформенный, средства для низкоуровневого доступа к аппаратной части отсутствуют.

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

Не так давно мне пришлось работать с одной Java-библиотекой. Проблема заключалась в том, что мне нужно было использовать методы, работающие с GPU (напишите в комментариях, если вам была бы интересна серия статей по CUDA), однако в Java-реализации библиотеки такой функциональности не было, и пришлось вызывать методы из программы на C. В этой статье мы поговорим о практическом применении #JNI.

## Создание класса

Для начала создадим класс, который будет содержать нативный метод.

```java
public class HelloJNI {
static {
System.loadLibrary("hello"); // Загрузка библиотеки hello.dll
}
// Объявление нативного метода sayHello(), без аргументов, возвращает void
private native void sayHello();
public static void main(String[] args) {
new HelloJNI().sayHello(); // вызов нативного метода
}
}
```

Мы объявили класс `HelloJNI`, содержащий нативный метод `sayHello`. Разберём код подробнее.

Блок `static` означает, что библиотека будет загружена во время загрузки класса. Чтобы программа смогла найти библиотеку, необходимо добавить путь к ней в `classpath`. Это можно сделать при запуске программы, добавив аргумент `-Djava.library.path=PATH_LIB`. Есть и другой вариант: вместо `loadLibrary` использовать `load`, но тогда придётся указывать полный путь к библиотеке (включая расширение `dll` или `so`).

На данном этапе у нас есть класс, но он никак не связан с библиотекой — самой библиотеки у нас пока нет.

## Создание библиотеки

Следующий шаг — компиляция файла и создание `h`-файла.

```bash
javac HelloJNI.java
javah HelloJNI
```

В результате мы получим следующий заголовочный файл:

```c
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
```

Что можно понять, глядя на этот файл? Во-первых, код на C будет подключать файл `jni.h`, который, к слову, содержит все необходимые функции для работы с JNI. Во-вторых, видно, что метод, описанный в классе `HelloJNI` как

```java
private native void sayHello();
```

в программе на C выглядит немного иначе:

```c
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
```

Как видно, функция принимает два аргумента, хотя мы их явно не указывали. Что это за аргументы?

* `JNIEnv*` — указатель на JNI-окружение, предоставляющее доступ ко всем функциям JNI;
* `jobject` — указатель на объект `this` в Java.

Определение `JNIEXPORT` в файле `jni_md.h`, который подключается из `jni.h`, выглядит так:

```c
#define JNIEXPORT __declspec(dllexport)
```

А `JNICALL` там же определяется следующим образом:

```c
#define JNICALL __stdcall
```

После этого становится понятно, что все эти «страшные» конструкции — всего лишь соглашения, используемые при вызове обычной экспортируемой функции.

## Реализация на C

Теперь реализуем описанную функцию в `c`-файле.

```c
#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
```

Как видно, функция выводит строку в консоль и возвращает `void`. *Главное — не забыть подключить заголовочный файл, созданный ранее.* Файл `jni.h` находится в каталогах `JAVA_HOME\\include` и `JAVA_HOME\\include\\win32`.

Теперь можно скомпилировать файл:

```bash
gcc -Wl --add-stdcall-alias -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" -shared -o hello.dll HelloJNI.c
```

Поясним параметры:

* `-Wl --add-stdcall-alias` — опция, предотвращающая возникновение ошибки линковщика (`UnsatisfiedLinkError`);
* `-I` — дополнительные пути для включаемых заголовков (в нашем случае — `jni.h`);
* `-shared` — генерация динамической библиотеки;
* `-o` — имя выходного файла.

Теперь можно запустить Java-программу:

```bash
java HelloJNI
Hello World!
```

*Если вы использовали метод `loadLibrary`, запускать программу нужно так:*

```bash
java -Djava.library.path=PATH_TO_LIB HelloJNI
Hello World!
```

## Заключение

В статье были рассмотрены общие принципы использования JNI. С помощью этой технологии можно также вызывать методы классов, создавать новые объекты, передавать различные параметры (массивы, строки и т. д.), возвращать значения и многое другое. Подробнее можно прочитать [в официальной документации Oracle] и [в практическом руководстве Javamex].

Использование нативных методов в Java нарушает принцип кроссплатформенности языка. Программа, использующая DLL, становится жёстко привязанной к платформе, под которую реализована эта библиотека. Нативные методы оправданы в случаях, когда основная Java-программа должна работать на разных платформах, а нативные части планируется разрабатывать отдельно для каждой из них. Если же программа предполагается к использованию только на одной платформе, возникает логичный вопрос: зачем тогда вообще кроссплатформенность?

Ещё один недостаток — из нативного метода можно получить доступ практически к любой части системы, что противоречит идеологии Java, где одним из ключевых требований является безопасность.

Тем не менее, несмотря на все минусы, выбор технологий всегда остаётся за программистом.

#JNI #JavaDevelopment #NativeMethods #CrossPlatform #SystemProgramming

From C to Java Code using Panama | Mostly nerdless

he’s right! with catalyst and SwiftUI etc. — what is “native”!?

i don’t know what a #MacAssedMacApp is anymore.

but i’m 100% certain what is NOT: a fucking webpage in chromium window (i.e. an Electron “app”) — i’m sure that’s NOT native!!!

you have to host your webpage in a WKWebView window. then you’re all good. 👍👍  

#NativeCode #ObjC #AppKit #ElectronSucks #OKNotReally #ThisIsAJokeBecauseIReallyLikeElectron #TypeScriptRules

https://github.com/BayesWitnesses/m2cgen# #FLOSS : Transform #ML models into a #NativeCode (#Java, #C, #Python, etc.) with zero dependencies
GitHub - BayesWitnesses/m2cgen: Transform ML models into a native code (Java, C, Python, Go, JavaScript, Visual Basic, C#, R, PowerShell, PHP, Dart, Haskell, Ruby, F#, Rust) with zero dependencies

Transform ML models into a native code (Java, C, Python, Go, JavaScript, Visual Basic, C#, R, PowerShell, PHP, Dart, Haskell, Ruby, F#, Rust) with zero dependencies - BayesWitnesses/m2cgen

GitHub