https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html


자바 프로그래밍 튜토리얼

Java Native Interface (JNI)

1. 소개

때로는 Java의 메모리 관리 및 성능 제한을 극복하기 위해 원시 (비 Java) 코드 (예 : C / C ++)를 사용해야합니다. Java는 Java Native Interface (JNI)를 통해 원시 코드를 지원합니다.

JNI는 두 언어와 런타임이 포함되어 있기 때문에 어렵습니다.

나는 당신이 익숙하다고 가정 할 것이다.

  1. 자바.
  2. C / C ++ 및 GCC 컴파일러 ( " GCC 및 Make " 읽기 )
  3. (Windows) Cygwin 또는 MinGW ( " Cygwin 및 MinGW 설치 방법 "참조).

2. 시작하기

2.1 C와 JNI

1 단계 : C 코드를 사용 하는 Java 클래스 HelloJNI.java 작성하기
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
public class HelloJNI {   // HelloJNI.java로 저장합니다.
   정적 {
      System.loadLibrary ( "hello"); // 원시 라이브러리 hello.dll (Windows) 또는 libhello.so (유닉스)를로드 
                                   // 런타임에 
                                   // ) (이 라이브러리의 sayHello라는 기본 방법을 포함
   }
 
   // 매개 변수를받지 않고 void를 반환하는 인스턴스 네이티브 메소드 sayHello ()를 선언합니다.
   개인 네이티브 무효 sayHello ();
 
   // 드라이버 테스트
   public static void main (String [] args) {
      새로운 HelloJNI (). sayHello ();  // 인스턴스를 만들고 네이티브 메서드를 호출합니다.
   }
}

정적 초기화가 호출 System.loadLibrary()네이티브 라이브러리 "로드 hello(호출 네이티브 메소드 포함" sayHello()클래스 로딩 동안을). hello.dllWindows의 "에 매핑됩니다 또는 "libhello.so"Unixes / Mac OS X에서.이 라이브러리는 자바의 라이브러리 경로에 포함될 것입니다 ( Java 시스템 변수에 보관 됨 java.library.path). VM 인수를 통해 Java 라이브러리 경로에 라이브러리를 포함 할 수 있습니다. 라이브러리 가 발견되지 않으면 프로그램은 a를 던집니다. 런타임에.-Djava.library.path=/path/to/libUnsatisfiedLinkError

다음 으로이 메소드가 다른 언어로 구현되었음을 나타내는 sayHello()키워드 native를 통해 메소드를 원시 인스턴스 메소드 로 선언합니다 원시 메소드에는 본문이 들어 있지 않습니다. 이 sayHello()라이브러리는로드 된 네이티브 라이브러리에서 찾을 수 있습니다.

이 main()메소드는 인스턴스를 할당 HelloJNI하고 원시 메소드를 호출합니다 sayHello().

스텝 2 : Java 프로그램의 컴파일 HelloJNI.java & C / C ++ 헤더 파일의 생성 HelloJNI.h

JDK 8부터는 " javac -h"을 사용하여 Java 프로그램을 컴파일하고 HelloJNI.h다음과 같이 호출 되는 C / C ++ 헤더 파일을 생성 해야합니다.

> javac -h. HelloJNI.java

"옵션은 C / C ++ 헤더를 생성하고 지정된 디렉토리 (위의 예 에서 현재 디렉토리에 대해)에 저장합니다.-h dir'.'

JDK 8 이전 에는 다음과 같이 javac전용 javah유틸리티를 사용 하여 Java 프로그램을 컴파일하고 C / C ++ 헤더를 생성 해야합니다. 이 javah유틸리티는 JDK 10에서 더 이상 사용할 수 없습니다.

> javac HelloJNI.java 
> javah HelloJNI

헤더 파일을 검사하십시오 HelloJNI.h.

1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
/ *이 파일을 편집하지 마십시오 - 기계가 생성됩니다 * /
#include <jni.h>
/ * HelloJNI 클래스의 헤더 * /
 
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C"{
#endif
/ *
 * 클래스 : HelloJNI
 * 방법 : sayHello
 * 서명 : () V
 * /
JNIEXPORT void JNICALL Java_HelloJNI_sayHello (JNIEnv *, jobject);
 
#ifdef __cplusplus
}
#endif
#endif

헤더는 Java_HelloJNI_sayHello다음과 같이 C 함수 를 선언 합니다.

JNIEXPORT void JNICALL Java_HelloJNI_sayHello (JNIEnv *, jobject);

C 함수의 이름 지정 규칙은 다음과 같습니다 Java_{package_and_classname}_{function_name}(JNI_arguments)패키지 이름의 점은 밑줄로 대체됩니다.

인수는 다음과 같습니다.

  • JNIEnv*: JNI 환경에 대한 참조. 모든 JNI 기능에 액세스 할 수 있습니다.
  • jobjectthisJava 객체에 대한 참조 입니다.

이 예제에서는이 인수를 사용하지 않지만 나중에 사용합니다. 매크로를 무시 JNIEXPORT하고 JNICALL당분간.

는 extern "C" 오직 C ++ 컴파일러에 의해 인식됩니다. 이 함수는 C ++ 명명 프로토콜 대신 C의 함수 명명 프로토콜을 사용하여 컴파일된다는 것을 C ++ 컴파일러에 알립니다. C와 C ++은 C ++ 지원 함수 오버로딩과 같은 다른 함수 명명 프로토콜을 가지고 있으며 오버로드 된 함수를 구별하기 위해 이름 맹 글링 스키마를 사용합니다. Name Mangling "을 읽으십시오 .

3 단계 : C 프로그램 구현 HelloJNI.c
1
2
4
5
6
7
8
9
10
// "HelloJNI.c"로 저장 
#include <jni.h>         // JDK가 제공하는 JNI 헤더 
#include <stdio.h>       // C 표준 IO 헤더 
#include "HelloJNI.h"    // Generated
 
// 네이티브 메소드 sayHello ()의 구현
JNIEXPORT void JNICALL Java_HelloJNI_sayHello (JNIEnv * env, jobject thisObj) {
   printf ( "Hello World! \ n");
   반환;
}

C 프로그램을 " HelloJNI.c"(으) 로 저장하십시오 .

의 JNI 헤더 " jni.h"JDK에서 제공은 아래에 있습니다 " <JAVA_HOME>\include"와 " <JAVA_HOME>\include\win32"(Windows의 경우) 또는 " <JAVA_HOME>\include\linux(우분투)"디렉토리 [맥 OS X를 점검] <JAVA_HOME>( "예를 들어, 당신의 JDK 설치 디렉토리 c:\program files\java\jdk10.0.xWindows 용") .

C 함수는 단순히 "Hello world!"라는 메시지를 출력합니다. 콘솔에.

4 단계 : C 프로그램 컴파일 HelloJNI.c

JDK (32 비트, 64 비트) 용 운영 플랫폼 (Windows, Mac OS X, Ubuntu)에 적합한 컴파일러를 찾고 올바른 컴파일러 옵션을 찾는 것이 JNI를 작동시키는 가장 어려운 부분입니다 !!!

(Windows) 64 비트 JDK

우리는 Cygwin을 사용할 것입니다. Windows의 경우 다음을주의해야합니다.

  • Windows / Intel은 다음 명령어 세트를 사용합니다. x86은 32 비트 명령어 세트입니다. i868은 x86 (또한 32 비트)의 향상된 버전입니다. x86_64 (또는 amd64)는 64 비트 명령어 세트입니다.
  • 32 비트 컴파일러는 32 비트 또는 64 비트 (역 호환) Windows에서 실행될 수 있지만 64 비트 컴파일러는 64 비트 Windows에서만 실행될 수 있습니다.
  • 64 비트 컴파일러는 32 비트 또는 64 비트 대상을 생성 할 수 있습니다.
  • Cygwin의 GCC를 사용하는 경우 대상은 Windows 또는 Cygwin이 될 수 있습니다. 대상이 네이티브 Windows 인 경우 Windows에서 코드를 배포하고 실행할 수 있습니다. 그러나 대상이 Cygwin 인 경우, 배포하려면 Cygwin 런타임 환경 ( cygwin1.dll을 배포해야합니다 이것은 Cygwin이 Windows에서 유닉스 에뮬레이터이기 때문입니다.
  • 위의 내용은 Cygwin에서 GCC의 많은 버전을 설명합니다.

64 비트 JDK의 경우 64 비트 기본 Windows를 생성하는 컴파일러를 찾아야합니다. 이것은 MinGW-W64에서 제공됩니다. mingw64-x86_64-gcc-core"(C 컴파일러)와 " mingw64-x86_64-gcc-g++"(C ++ 컴파일러) 를 선택하여 Cygwin에서 MinGW-W64를 설치할 수 있습니다 실행 파일은 각각 "x86_64-w64-mingw32-gcc"(C 컴파일러) "x86_64-w64-mingw32-g++및 "(C ++ 컴파일러)입니다.

먼저 JAVA_HOMEJDK가 설치된 디렉토리 (예 : " c:\program files\java\jdk10.0.x") 를 가리 키 도록 환경 변수 를 설정하십시오 여기에 나온 단계를 따르 십시오 .

다음, 컴파일하려면 다음 명령을 사용 HelloJNI.c으로 hello.dllWindows에서는 명령에서와 JAVA_HOME같이 환경 변수 를 참조합니다 %JAVA_HOME%.

> x86_64-w64-mingw32-gcc -I "% JAVA_HOME % \ include"-I "% JAVA_HOME % \ include \ win32"-shared -o hello.dll HelloJNI.c

사용되는 컴파일러 옵션은 다음과 같습니다.

  • -IheaderDir: 헤더 디렉토리를 지정합니다. 이 경우 " jni.h"(in " %JAVA_HOME%\include") 및 " jni_md.h"(in " %JAVA_HOME%\include\win32"JAVA_HOME은 JDK가 설치된 디렉토리로 설정된 환경 변수입니다.
  • -shared: 공유 라이브러리를 생성합니다.
  • -o outputFilename: 출력 파일 이름을 " hello.dll설정합니다 .

다음 두 단계로 컴파일하고 링크 할 수도 있습니다.

// 컴파일 전용 "HelloJNI.c"와 -c 플래그. 출력은 "HElloJNI.o" 
> x86_64- w64 -mingw32-gcc -c -I "% JAVA_HOME % \ include"-I "% JAVA_HOME % \ include \ win32"HelloJNI.c
 
// "HelloJNO.o"를 공유 라이브러리 "hello.dll"에 링크 
> x86_64- w64 -mingw32-gcc -shared -o hello.dll HelloJNI.o

fileHello.dll"이 (가) 64 비트 (x86_64) 기본 Windows DLL 임을 나타내는 유틸리티 를 통해 결과 파일 유형을 확인해야합니다 .

> hello.dll 
hello.dll 파일 : PE32 + 실행 파일 (DLL) (콘솔) x86-64, MS Windows 용

시도 nm, 공유 라이브러리의 모든 기호를 나열하고 sayHello()함수를 찾습니다 Java_HelloJNI_sayHello유형 "T"(정의)이 있는 함수 이름 을 확인하십시오 .

> nm hello.dll | grep say 
00000000624014a0 T Java_HelloJNI_sayHello
(Windows) 32 비트 JDK [더 이상 사용되지 않는 기능?]

32 비트 JDK의 경우 32 비트 기본 Windows를 생성하는 32/64 비트 컴파일러를 찾아야합니다. 이것은 MinGW-W64 (및 구형 MinGW)가 제공합니다. mingw64-i686-gcc-core"(C 컴파일러)와 " mingw64-i686-gcc-g++"(C ++ 컴파일러) 를 선택하여 Cygwin에서 MinGW-W64를 설치할 수 있습니다 실행 파일은 각각 "i886-w64-mingw32-gcc"(C 컴파일러) "i686-w64-mingw32-g++및 "(C ++ 컴파일러)입니다.

먼저 JAVA_HOMEJDK가 설치된 디렉토리 (예 : " c:\program files\java\jdk9.0.x") 를 가리 키 도록 환경 변수 를 설정하십시오 여기에 나온 단계를 따르 십시오 .

다음, 컴파일하려면 다음 명령을 사용 HelloJNI.c에 hello.dll:

> i886-w64-mingw32-gcc -Wl, - add-stdcall-alias -I "% JAVA_HOME % \ include"-I "% JAVA_HOME % \ include \ win32"-shared -o hello.dll HelloJNI.c

사용되는 컴파일러 옵션은 다음과 같습니다.

  • -Wl다음은 -Wl링커 옵션을 전달하는 --add-stdcall-alias것을 방지하기 위해 UnsatisfiedLinkError(a stdcall을 접미사 문자 ( @nn그대로 접미사가 벗겨진도 함께) 내보낼). (일부 사람들은 사용을 제안했습니다 -Wl,--kill-at.)
  • -I: 헤더 파일 디렉토리를 지정합니다. 이 경우 " jni.h"(in " %JAVA_HOME%\include") 및 " jni_md.h"(in " %JAVA_HOME%\include\win32"%JAVA_HOME%은 JDK가 설치된 디렉토리로 설정된 환경 변수입니다.
  • -shared: 공유 라이브러리를 생성합니다.
  • -o: 출력 파일 이름을 " hello.dll설정합니다 .
  • -D __int64"long long": 유형을 정의하십시오 ( "unknown type name '__int64'"오류 인 경우 앞에이 옵션을 추가하십시오)
(우분투) 64 비트 JDK
  1. JAVA_HOMEJDK가 설치된 디렉토리를 가리 키도록 환경 변수 를 설정하십시오 include다음 단계에서 사용할 하위 디렉토리 포함 ).
    $ export JAVA_HOME = / 사용자 / java / installed / dir
     $ echo $ JAVA_HOME
  2. C 프로그램 컴파일 HelloJNI.c공유 모듈에 libhello.so사용하는 gcc모든 유닉스에 포함된다 :
    $ gcc -fPIC -I "$ JAVA_HOME / include"-I "$ JAVA_HOME / include / linux"-shared -o libhello.so HelloJNI.c
  3. Java 프로그램 실행 :
    $ java -Djava.library.path =. HelloJNI
(Mac OS X) 64 비트 JDK
  1. JAVA_HOMEJDK가 설치된 디렉토리를 가리 키도록 환경 변수 를 설정하십시오 include다음 단계에서 사용할 하위 디렉토리 포함 ).
    $ export JAVA_HOME = / your / java / installed / dir
        // 내 컴퓨터 용 @ /Library/Java/JavaVirtualMachines/jdk1.8.0_xx.jdk/Contents/Home 
    $ echo $ JAVA_HOME
  2. C 프로그램 컴파일 HelloJNI.c동적 공유 모듈에 libhello.dylib사용하는 gcc모든 유닉스 / 맥 OS에 포함된다 :
    $ gcc -I "$ JAVA_HOME / include"-I "$ JAVA_HOME / include / darwin"-dynamiclib -o libhello.dylib HelloJNI.c
  3. Java 프로그램 실행 :
    $ java -Djava.library.path =. HelloJNI
4 단계 : Java 프로그램 실행
> java HelloJNI

아래와 같이 VM 옵션을 통해 hello.dll"(Windows), " libHello.so"(Unixes), " libhello.dylib"(Mac OS X) Java 라이브러리 경로를 명시 적으로 지정해야 할 수도 있습니다. 이 예제에서 네이티브 라이브러리는 현재 디렉토리에 보관됩니다 .-Djava.library.path=/path/to/lib'.'

> java -Djava.library.path =. HelloJNI

2.2 C ++의 JNI

C 프로그램 대신 HelloJNI.cpp위의 예제 에서 C ++ 프로그램을 사용할 수 있습니다 .

1
2
4
5
6
7
8
9
10
11
// "HelloJNI.cpp"로 저장 
#include <jni.h>        // JDK가 제공하는 JNI 헤더 
#include <iostream>     // C ++ 표준 IO 헤더 
#include "HelloJNI.h"   // Generated
네임 스페이스를 사용하여 표준;

// 네이티브 메소드 sayHello ()의 구현
JNIEXPORT void JNICALL Java_HelloJNI_sayHello (JNIEnv * env, jobject thisObj) {
	cout << "Hello World from C ++!" << endl;
   반환;
}

C ++ 프로그램을 다음과 같이 공유 라이브러리로 컴파일하십시오. 자세한 내용은 "JNI with C"섹션을 참조하십시오.

(Windows) 64 비트 JDK

Cygwin에서 mingw64-x86-gcc-g++패키지 를 설치해야 합니다.

> x86_64-w64-mingw32-g ++ -I "% JAVA_HOME % \ include"-I "% JAVA_HOME % \ include \ win32"-shared -o hello.dll HelloJNI.cpp

(우분투) 64 비트 JDK

$ g ++ -fPIC -I "$ JAVA_HOME / include"-I "$ JAVA_HOME / include / linux"-shared -o libhello.so HelloJNI.cpp

(Mac OS X) 64 비트 JDK

[할 것]

Java 프로그램 실행
> 자바 HelloJNI 
또는 
> 자바 = -Djava.library.path. HelloJNI

Notes : " java.lang.UnsatisfiedLinkError: hello.dll: Can't find dependent libraries"을 발견하면 종속 라이브러리를 추적하는 "DLL 종속성 워커"를 찾아야합니다. Cygwin에서 라이브러리를 검색하고 환경 변수에 라이브러리를 포함하십시오 PATH필자의 경우, 종속 라이브러리는 " libstdc++-6.dll"에 있습니다 cygwin64\usr\x86_64-w64-mingw32\sys-root\mingw\bin.

2.3 C / C ++ 혼합 JNI

1 단계 : 네이티브 코드를 사용하는 Java 클래스 작성 - HelloJNICpp.java
1
2
4
5
6
7
8
9
10
11
12
13
public class HelloJNICpp {
   정적 {
      System.loadLibrary ( "hello"); // hello.dll (Windows) 또는 libhello.so (Unixes)
   }
 
   // 네이티브 메소드 선언
   개인 네이티브 무효 sayHello ();
 
   // 드라이버 테스트
   public static void main (String [] args) {
      새로운 HelloJNICpp (). sayHello ();  // 네이티브 메소드를 호출한다.
   }
}
2 단계 : Java 프로그램 컴파일 및 C / C ++ 헤더 파일 생성 HelloJNICpp.h
javac -h. HelloJNICpp

결과 헤더 파일 HelloJNICpp.h은 네이티브 함수를 다음과 같이 선언합니다 :

JNIEXPORT void JNICALL Java_HelloJNICpp_sayHello (JNIEnv *, jobject);
3 단계 : C / C ++ 구현 - HelloJNICppImpl.h , HelloJNICppImpl.cpp 및 HelloJNICpp.c

우리는 C ++ ( " HelloJNICppImpl.h"및 " HelloJNICppImpl.cpp")로 프로그램을 구현 하지만 C 프로그램 ( " HelloJNICpp.c")을 사용하여 Java와 인터페이스합니다.

C ++ 헤더 - " HelloJNICppImpl.h"

1
2
4
5
6
7
8
9
10
11
12
#ifndef _HELLO_JNI_CPP_IMPL_H
#define _HELLO_JNI_CPP_IMPL_H
 
#ifdef __cplusplus
        extern "C"{
#endif
        무효 sayHello ();
#ifdef __cplusplus
        }
#endif
 
#endif

C ++ 구현 - " HelloJNICppImpl.cpp"

1
2
4
5
6
7
8
9
#include "HelloJNICppImpl.h"
#include <iostream>
 
네임 스페이스를 사용하여 표준;
 
void sayHello () {
    cout << "Hello World from C ++!" << endl;
    반환;
}

C Java와의 인터페이스 - " HelloJNICpp.c"

1
2
4
5
6
7
8
#include <jni.h>
#include "HelloJNICpp.h"
#include "HelloJNICppImpl.h"
 
JNIEXPORT void JNICALL Java_HelloJNICpp_sayHello (JNIEnv * env, jobject thisObj) {
    sayHello ();  // C ++ 함수 호출
    반환;
}

C / C ++ 프로그램을 공유 라이브러리 ( hello.dllWindows의 경우 ") 로 컴파일하십시오 .

(Windows) 64 비트 JDK

Cygwin에서 mingw64-x86-gcc-g++패키지 를 설치해야 합니다.

> x86_64-w64-mingw32-g ++ -I "% JAVA_HOME % \ include"-I "% JAVA_HOME % \ include \ win32"-shared -o hello.dll HelloJNICpp.c HelloJNICppImpl.cpp

(우분투) 64 비트 JDK

$ g ++ -fPIC -I "$ JAVA_HOME / include"-I "$ JAVA_HOME / include / linux"-shared -o libhello.so HelloJNICpp.c HelloJNICppImpl.cpp
4 단계 : Java 프로그램 실행
> 자바 HelloJNICpp 
또는 
> 자바 = -Djava.library.path. HelloJNICpp

Notes : " java.lang.UnsatisfiedLinkError: hello.dll: Can't find dependent libraries"을 발견하면 종속 라이브러리를 추적하는 DLL 종속성 워커를 찾아야합니다. Cygwin에서 라이브러리를 검색하고에 라이브러리를 포함하십시오 PATH필자의 경우 의존 라이브러리는 " libstdc++-6.dll"에 있습니다 cygwin64\usr\x86_64-w64-mingw32\sys-root\mingw\bin.

2.4 패키지 내의 JNI

프로덕션의 경우 모든 Java 클래스는 기본 no-name 패키지 대신 적절한 패키지에 보관됩니다.

1 단계 : JNI 프로그램 - myjni \ HelloJNI.java
1
2
4
5
6
7
8
9
10
11
12
13
패키지 myjni;
 
public class HelloJNI {
   정적 {
      System.loadLibrary ( "hello"); // hello.dll (Windows) 또는 libhello.so (Unixes)
   }
   // 아무것도받지 않고 void를 반환하는 네이티브 메소드입니다.
   개인 네이티브 무효 sayHello ();
 
   public static void main (String [] args) {
      new myjni.HelloJNI (). sayHello ();  // 네이티브 메소드를 호출한다.
   }
}

이 JNI 클래스는 패키지 " myjni"로 저장되어 " myjni\HelloJNI.java로 저장됩니다 .

2 단계 : JNI 프로그램 컴파일 및 C / C ++ 헤더 생성
// 디렉토리를 패키지 기본 디렉토리로 변경 
> javac -h include myjni \ HelloJNI

컴파일 결과는 " myjni\HelloJNI.class"입니다.

이 예제에서 헤더 파일을 include하위 디렉토리 에두기로 결정했습니다 생성 된 출력은 " include\myjni_HelloJNI.h"입니다.

헤더 파일은 원시 함수를 선언합니다.

JNIEXPORT void JNICALL Java_myjni_HelloJNI_sayHello (JNIEnv *, jobject);

네이티브 함수 명명 규칙을 메모 해 두십시오 : 점은 밑줄로 대체됩니다.Java_<fully-qualified-name>_methodName

3 단계 : C 구현 - HelloJNI.c
1
2
4
5
6
7
8
#include <jni.h>
#include <stdio.h>
#include "include \ myjni_HelloJNI.h"
 
JNIEXPORT void JNICALL Java_myjni_HelloJNI_sayHello (JNIEnv * env, jobject thisObj) {
   printf ( "Hello World! \ n");
   반환;
}

C 프로그램 컴파일 :

// Windows 64 비트 용 
> x86_64-w64-mingw32-gcc -I "% JAVA_HOME % \ include"-I "% JAVA_HOME % \ include \ win32"-shared -o hello.dll HelloJNI.c

이제 JNI 프로그램을 실행할 수 있습니다.

> java -Djava.library.path =. myjni.HelloJNI

2.5 모듈 내의 JNI (JDK 9)

[할 것]

2.6 Eclipse의 JNI [확인]

이클립스에서 JNI를 작성하면 NDK가있는 Android 앱을 개발할 때 편리합니다.

Eclipse 및 Eclipse CDT (C / C ++ Development Tool) 플러그인을 설치해야합니다. CDT 설치 방법은 Eclipse for C / C ++ "를 읽으십시오 .

1 단계 : 자바 프로젝트 만들기

새 Java 프로젝트를 생성하고 ( HelloJNI다음) Java 클래스 " HelloJNI.java"를 작성하십시오.

public class HelloJNI {
   정적 {
      System.loadLibrary ( "hello"); // hello.dll (Windows) 또는 libhello.so (Unixes)
   }
 
   // 네이티브 메소드 선언
   개인 네이티브 무효 sayHello ();
 
   // 드라이버 테스트
   public static void main (String [] args) {
      새로운 HelloJNI (). sayHello ();  // 인스턴스를 할당하고 네이티브 메소드를 호출합니다.
   }
}
2 단계 : Java 프로젝트를 C / C ++ Makefile 프로젝트로 변환

HelloJNI자바 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 ⇒ 새로 만들기 ⇒ 기타 ... ⇒ C / C ++ 프로젝트로 변환 (C / C ++ 속성 추가) ⇒ 다음.

"C / C ++ 프로젝트로 변환"대화 상자가 나타납니다. "Project type"에서 " Makefile Project "를 선택하십시오. ⇒ "Toolchains"에서 "MinGW GCC"를 선택하십시오.

이제이 프로젝트를 Java 및 C / C ++ 프로젝트로 실행할 수 있습니다.

3 단계 : C / C ++ 헤더 파일 생성 (Pre JDK-10)

jni프로젝트 아래의 디렉토리를 생성 하여 프로젝트를 마우스 오른쪽 버튼으로 클릭하여 모든 C / C ++ 코드를 유지하십시오. ⇒ 신규 ⇒ 폴더 ⇒ "폴더 이름"에 ""를 입력하십시오 jni.

"폴더 에서 마우스 오른쪽 버튼을 클릭 makefile하여 " jni"디렉토리를 생성하십시오. jni⇒ 새 ⇒ 파일 ⇒ "파일 이름"에서 " makefile"를 입력하십시오. ⇒ 다음 코드를 입력하십시오. 들여 쓰기에는 탭 대신 공백 대신 탭을 사용해야합니다.

# classpath에 대한 변수 정의
CLASS_PATH = ../bin

# bin 디렉토리의 .class에 대한 가상 경로를 정의하십시오
vpath % .class $ (CLASS_PATH)

# $ *는 확장명이없는 대상 파일 이름과 일치합니다.
# Pre JDK-10. JDK 10은 javah 유틸리티를 제거하고 대신 javac -h를 사용해야합니다 [TO CHECK]
HelloJNI.h : HelloJNI.class
	javah -classpath $ (CLASS_PATH) $ *

이 메이크 파일 HelloJNI.h은 종속성 " HelloJNI.class이 있는 대상 " 을 만들고 대상 헤더 파일을 작성 하려면 javah유틸리티를 HelloJNI.class(under -classpath에 호출 합니다.

메이크 파일을 마우스 오른쪽 버튼으로 클릭 ⇒ 목표물 만들기 ⇒ 만들기 ⇒ "대상 이름"에 ""를 입력하십시오 HelloJNI.h.

makefile을 HelloJNI.h마우스 오른쪽 버튼으로 클릭 하여 대상 " "에 대한 makefile을 실행합니다. ⇒ 대상 만들기 ⇒ 빌드 ⇒ 대상 " HelloJNI.h"⇒ 빌드를 선택 하십시오. 헤더 파일 " HelloJNI.h"은 jni디렉토리에 생성됩니다 필요하면 새로 고침 (F5)하십시오. 출력은 다음과 같습니다.

HelloJNI.h를 만든다. 
javah -classpath ../bin HelloJNI

makefile에 대한 자세한 내용은 GCC 및 Make "를 읽으십시오 .

또는 CMD 셸을 사용하여 make 파일을 실행할 수도 있습니다.

// makefile을 포함하는 디렉토리로 디렉토리를 변경한다. 
> HelloJNI.h를 만든다.

CMD 셸을 사용하여 javah(Pre JDK-10) 을 실행할 수도 있습니다 .

> javah -classpath ../bin HelloJNI
4 단계 : C 구현 - HelloJNI.c

HelloJNI.cjni"폴더 를 마우스 오른쪽 버튼으로 클릭하여 라는 C 프로그램을 만듭니다. ⇒ 새로 만들기 ⇒ 소스 파일 ⇒ "소스 파일"에 ""를 입력하십시오 HelloJNI.c다음 코드를 입력하십시오 :

#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"
 
JNIEXPORT void JNICALL Java_HelloJNI_sayHello (JNIEnv * env, jobject thisObj) {
   printf ( "Hello World! \ n");
   반환;
}

makefile공유 라이브러리 " hello.dll를 생성하려면 다음과 같이 "을 수정하십시오 (다시 탭을 사용하여 줄을 들여 씁니다.)

# classpath에 대한 변수 정의
CLASS_PATH = ../bin

# bin 디렉토리의 .class에 대한 가상 경로를 정의하십시오
vpath % .class $ (CLASS_PATH)

all : hello.dll

# $ @은 대상과 일치하고, $ <는 첫 번째 종속성과 일치합니다 
. hello.dll : HelloJNI.o
	gcc -Wl, - add-stdcall-alias -shared -o $ @ $ <

# $ @은 대상과 일치하고, $ <는 첫 번째 종속성 인 
HelloJNI.o 와 일치합니다 . HelloJNI.c HelloJNI.h
	gcc -I "D : \ bin \ jdk1.7 \ include"-I "D : \ bin \ jdk1.7 \ include \ win32"-c $ <-o $ @

# $ *는 확장명이없는 대상 파일 이름과 일치합니다.
HelloJNI.h : HelloJNI.class
	javah -classpath $ (CLASS_PATH) $ *

깨끗한 :
	rm HelloJNI.h HelloJNI.o hello.dll

makefile"대상을 마우스 오른쪽 단추로 클릭하고 "대상 이름"에 ""를 입력하십시오 allclean대상을 만들기 위해 반복하십시오 .

makefile을 all마우스 오른쪽 버튼으로 클릭 하여 대상 " "에 대한 makefile을 실행합니다. ⇒ 대상 만들기 ⇒ 빌드 ⇒ 대상 " all"⇒ 빌드를 선택 하십시오. 출력은 다음과 같습니다.

모두를 만들다.
javah -classpath ../bin HelloJNI
gcc -I "D : \ bin \ jdk1.7 \ include"-I "D : \ bin \ jdk1.7 \ include \ win32"-c HelloJNI.c -o HelloJNI.o
gcc -Wl, - add-stdcall-alias -shared -o hello.dll HelloJNI.o

hello.dll공유 라이브러리 가 jni디렉토리 에 생성되었습니다 .

5 단계 : Java JNI 프로그램 실행

Java JNI 프로그램을 실행할 수 있습니다 HelloJNI그러나 라이브러리 경로를 " hello.dll에 제공해야합니다 이것은 VM 인수를 통해 수행 할 수 있습니다 -Djava.library.path프로젝트를 마우스 오른쪽 버튼으로 클릭하고 Â 실행을 클릭 한 다음 구성을 실행합니다 ⇒ "Java 응용 프로그램"을 선택합니다 ⇒ "Main"탭에서 메인 클래스 " HelloJNI"를 입력합니다 ⇒ "Arguments", "VM Arguments"에서 -Djava.library.path=jni"⇒ Run "을 입력 합니다.

출력은 "Hello World!"입니다. 콘솔에 표시됩니다.

2.7 NetBeans의 JNI

[할 것]

3. JNI 기초

JNI는 네이티브 시스템에서 Java 유형에 해당하는 다음 JNI 유형을 정의합니다.

  1. 자바 프리미티브 : jintjbytejshortjlongjfloatjdoublejcharjboolean의 원시 자바에 대한 intbyteshortlongfloatdoublechar와 boolean, 각각.
  2. Java 참조 유형 : jobjectfor java.lang.Object또한 다음과 같은 하위 유형 s 도 정의합니다 .
    1. jclass를 위해 java.lang.Class.
    2. jstring를 위해 java.lang.String.
    3. jthrowable를 위해 java.lang.Throwable.
    4. jarrayJava 배열 용. Java 배열은 원시 배열이 8 개이고 배열이 하나 인 참조 유형입니다 Object따라서,이 프리미티브 여덟 개 배열되어 jintArrayjbyteArrayjshortArrayjlongArrayjfloatArrayjdoubleArrayjcharArray및 jbooleanArray및 하나의 객체 배열 jobjectArray.

원시 함수는 상기 JNI 유형의 주장을 수신하고, JNI 타입 값 (예 반환 jstringjintArray). 그러나 네이티브 함수는 자체 네이티브 형식 (예 : C 문자열, C int[])에서 작동합니다. 따라서, JNI 형과 네이티브 형을 변환 (또는 변환) 할 필요가 있습니다.

네이티브 프로그램 :

  1. JNI 형식의 인수를 수신합니다 (Java 프로그램에서 전달됨).
  2. 참조 JNI 유형의 경우, 변환하거나, 예를 들어, 지역 고유 유형에 인수를 복사 jstring하는 C 문자열로 jintArrayC의에 int[]등등합니다. 원시 JNI 유형 등 jint및 jdouble변환을 필요로하지 않고 직접 조작 할 수 있습니다.
  3. 로컬 네이티브 형식으로 작업을 수행합니다.
  4. JNI 형으로 돌려 주어지는 오브젝트를 작성해, 돌려 주어지는 오브젝트에 그 결과를 카피합니다.
  5. 반환.

JNI 프로그래밍에서 가장 복잡하고 어려운 작업을 전환 (또는 변환) JNI 사이의 참조 유형 (예컨대 jstringjobjectjintArrayjobjectArray) 및 기본 타입 ( C-stringint[]). JNI 환경 인터페이스는, 변환을 실시하는 많은 기능을 제공합니다.

JNI는 객체 지향이 아닌 C 인터페이스입니다. 그것은 실제로 물건을 전달하지 않습니다.

[C ++ 객체 지향 인터페이스 ?!]

4. Java 및 네이티브 프로그램 간의 인수 및 결과 전달

4.1 패스 프리미티브

자바 프리미티브를 전달하는 것은 간단합니다. jxxx유형 즉, 기본 시스템에 정의되어 ,. jintjbytejshortjlongjfloatjdoublejchar그리고 jboolean자바의 기본 요소 각각에 대해 intbyteshortlongfloatdoublechar와 boolean, 각각.

Java JNI 프로그램 : TestJNIPrimitive.java
1
2
4
5
6
7
8
9
10
11
12
13
공공 클래스 TestJNIPrimitive {
   정적 {
      System.loadLibrary ( "myjni"); // myjni.dll (Windows) 또는 libmyjni.so (Unixes)
   }
 
   // 두 개의 int를 받고 평균을 포함하는 double을 반환하는 네이티브 메소드 average ()를 선언합니다.
   개인 기본 이중 평균 (int n1, int n2);
 
   // 드라이버 테스트
   public static void main (String args []) {
      System.out.println ( "자바에서는 평균값이"+ new TestJNIPrimitive (). average (3, 2));
   }
}

이 JNI 프로그램은 공유 라이브러리 myjni.dll(Windows) 또는 libmyjni.so(Unixes)를 로드합니다 그것은 선언 native방법 average()개의 수신 int년대들 및 반환 double이들의 평균치가 포함 int년대한다. 이 main()메소드는를 호출합니다 average().

Java 프로그램을 컴파일하여 TestJNIPrimitive.classC / C ++ 헤더 파일 " TestJNIPrimitive.h"을 생성하십시오 .

javac -h. TestJNIPrimitive.java
C 구현 - TestJNIPrimitive.c

헤더 파일 TestJNIPrimitive.h에는, (JNI 환경 인터페이스에 액세스하기위한) a (this를 참조하기위한 ), two (Java 네이티브 메소드의 2 개의 인수), 및 a (Java 네이티브 메소드의 return 형) Java_TestJNIPrimitive_average()를 취하는 함수 선언이 포함됩니다 .JNIEnv*jobjectobjectjintjdouble

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average (JNIEnv *, jobject, jint, jint);

의 JNI 유형 jint과 jdouble자바의 유형에 해당 int하고 double각각.

jni.h"및 " win32\jni_mh.h"(플랫폼에 따라 다름)에는 typedef여덟 개의 JNI 프리미티브와 추가 항목에 대한 이러한 명령문 이 포함 jsize됩니다.

흥미로운 점 은 C (16 비트 일 수 있음 ) 대신 C 적어도 32 비트)에 jint매핑 된다는 점입니다 따라서 C 프로그램에서 단순히 사용하는 대신에 사용하는 것이 중요합니다 Cygwin은 지원하지 않습니다 .longintjintint__int64

// "win \ jni_mh.h"에서 - 머신에 의존하는 머신 헤더
typedef 긴 jint;
typedef __int64 jlong;
typedef signed char jbyte;
 
// "jni.h"
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef 짧은 jshort;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize;

구현 TestJNIPrimitive.c은 다음과 같습니다.

1
2
4
5
6
7
8
9
10
11
12
#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitive.h"
 
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv * env, jobject thisObj, jint n1, jint n2) {
   jdouble 결과;
   printf ( "C에서 숫자는 % d 및 % d \ n", n1, n2);
   결과 = ((jdouble) n1 + n2) / 2.0;
   // jint는 int에 매핑되고, jdouble은 double에 매핑됩니다.
   반환 결과;
}

C 프로그램을 공유 라이브러리 ( jni.dll로 컴파일하십시오 .

gcc -I "% JAVA_HOME % \ include"-I "% JAVA_HOME % \ include \ win32"-shared -o myjni.dll TestJNIPrimitive.c

이제 Java 프로그램을 실행합니다.

java -Djava.library.path =. TestJNIPrimitive
C ++ 구현 - TestJNIPrimitive.cpp
1
2
4
5
6
7
8
9
10
11
12
13
#include <jni.h>
#include <iostream>
#include "TestJNIPrimitive.h"
네임 스페이스를 사용하여 표준;
 
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv * env, jobject obj, jint n1, jint n2) {
   jdouble 결과;
   cout << "C ++에서 숫자는"<< n1 << "및"<< n2 << endl입니다.
   결과 = ((jdouble) n1 + n2) / 2.0;
   // jint는 int에 매핑되고, jdouble은 double에 매핑됩니다.
   반환 결과;
}

C ++ 프로그램 컴파일 :

g ++ -I "% JAVA_HOME % \ include"-I "% JAVA_HOME % \ include \ win32"-shared -o myjni.dll TestJNIPrimitive.cpp

4.2 문자열 전달하기

Java JNI 프로그램 : TestJNIString.java
1
2
4
5
6
7
8
9
10
11
12
공공 클래스 TestJNIString {
   정적 {
      System.loadLibrary ( "myjni"); // myjni.dll (Windows) 또는 libmyjni.so (Unixes)
   }
   // Java String을 받아 Java String을 반환하는 네이티브 메소드
   private 네이티브 String sayHello (String msg);
 
   public static void main (String args []) {
      String result = new TestJNIString (). sayHello ( "Hello from Java");
      System.out.println ( "Java에서 반환되는 문자열은 다음과 같습니다."+ result);
   }
}

이 JNI 프로그램은 Java를 수신하고 Java 를 리턴하는 native메소드 sayHello()를 선언합니다 이 메소드는를 호출합니다 .StringStringmain()sayHello()

Java 프로그램을 컴파일하고 C / C ++ 헤더 파일 " TestJNIString.h"을 생성하십시오 .

javac -h. TestJNIString.java
C 구현 - TestJNIString.c

헤더 파일 TestJNIString.h에는 다음 함수 선언이 포함되어 있습니다.

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello (JNIEnv *, jobject, jstring);

JNI jstring는 자바를 나타내는 유형을 정의했습니다 String(JNI 형의 jstring마지막 인수 는 Java String가 C 프로그램에 전달 된 것입니다. 반환 유형도 있습니다 jstring.

문자열을 전달하는 것은 Java String가 객체 (참조 유형)이기 때문에 C 문자열이 NULL로 끝나는 char배열 이기 때문에 기본 요소를 전달하는 것보다 복잡 합니다 Java String(JNI로 표시 jstring)와 C-string ( char*사이를 변환해야합니다 .

JNI 환경 (인수를 통해 액세스 JNIEnv*)은 변환을위한 함수를 제공합니다.

  1. char*JNI string ( jstring에서 C-string ( 을 얻으려면 method를 호출하십시오 const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*).
  2. jstringC- 문자열 ( )에서 JNI 문자열 ( 을 가져 오려면 char*메소드를 호출하십시오 jstring NewStringUTF(JNIEnv*, char*).

C 구현 TestJNIString.c은 다음과 같습니다.

  1. 그것은 JNI 문자열 (수신 jstring)와, C-문자열 (로 변환을 char*통해) GetStringUTFChars().
  2. 그런 다음 의도 한 작업을 수행합니다. 수신 된 문자열을 표시하고 반환 할 다른 문자열을 묻습니다.
  3. 반환 된 C- 문자열 ( char*)을 jstring통해 JNI 문자열 ( 로 변환 NewStringUTF()하고를 반환합니다 jstring.
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
21
#include <jni.h>
#include <stdio.h>
#include "TestJNIString.h"
 
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello (JNIEnv * env, jobject thisObj, jstring inJNIStr) {
   // 1 단계 : JNI 문자열 (jstring)을 C 문자열 (char *)로 변환
   const char * inCStr = (* env) -> GetStringUTFChars (env, inJNIStr, NULL);
   if (NULL == inCStr) NULL을 반환합니다.
 
   // 2 단계 : 의도 한 작업 수행
   printf ( "C에서 수신 된 문자열은 % s \ n", inCStr);
   (* env) -> ReleaseStringUTFChars (env, inJNIStr, inCStr);  // 리소스를 해제하십시오.
 
   // 사용자에게 C- 문자열을 묻습니다.
   char outCStr [128];
   printf ( "문자열 입력 :");
   scanf ( "% s", outCStr);    // 127자를 넘지 않음
 
   // 3 단계 : C 문자열 (char *)을 JNI 문자열 (jstring)로 변환하고 반환
   return (* env) -> NewStringUTF (env, outCStr);
}

C 프로그램을 공유 라이브러리로 컴파일하십시오.

gcc -I "<JAVA_HOME> \ include"-I "<JAVA_HOME> \ include \ win32"-shared -o myjni.dll TestJNIString.c

이제 Java 프로그램을 실행합니다.

java -Djava.library.path =. TestJNIString
C에서 수신 된 문자열은 Hello from Java입니다.
문자열 입력 : 테스트
Java에서 반환되는 문자열은 다음과 같습니다.
JNI 네이티브 문자열 함수

JNI는 유니 코드 (16 비트 문자) 및 UTF-8 (1-3 바이트로 인코딩 된) 문자열에 대한 변환을 지원합니다. UTF-8 문자열 char은 C / C ++ 프로그램에서 사용해야하는 null로 끝나는 C 문자열 ( 배열) 과 같은 역할을 합니다.

JNI string ( jstring) 함수는 다음과 같습니다.

// UTF-8 문자열 (1-3 바이트로 인코딩 됨, 7 비트 ASCII와 역 호환 가능)
// null로 끝나는 문자 배열에 매핑 가능 C- 문자열 
const char * GetStringUTFChars (JNIEnv * env, jstring string, jboolean * isCopy);
   // 수정 된 UTF-8 인코딩으로 문자열을 나타내는 바이트 배열에 대한 포인터를 반환합니다. 
void ReleaseStringUTFChars (JNIEnv * env, jstring 문자열, const char * utf);
   // 원시 코드가 더 이상 utf에 액세스 할 필요가 없다는 것을 VM에 알립니다. 
jstring NewStringUTF (JNIEnv * env, const char * bytes);
   // 수정 된 UTF-8 인코딩으로 문자 배열로부터 새로운 java.lang.String 객체를 생성합니다. 
jsize GetStringUTFLength (JNIEnv * env, jstring string);
   // 문자열의 수정 된 UTF-8 표현의 길이를 바이트 단위로 반환합니다. 
void GetStringUTFRegion (JNIEnv * env, jstring str, jsize start, jsize 길이, char * buf);
   // 오프셋 시작에서 시작하는 len 개의 유니 코드 문자를 수정 된 UTF-8 인코딩으로 변환합니다.
   // 결과를 주어진 버퍼 buf에 저장한다.
  
// 유니 코드 문자열 (16 비트 문자) 
const jchar * GetStringChars (JNIEnv * env, jstring string, jboolean * isCopy);
   // 유니 코드 문자 배열에 대한 포인터를 반환합니다. 
void ReleaseStringChars (JNIEnv * env, jstring string, const jchar * chars);
   // 원시 코드가 더 이상 char에 액세스 할 필요가 없다는 것을 VM에 알립니다. 
jstring NewString (JNIEnv * env, const jchar * 유니 코드, jsize 길이);
   // 유니 코드 문자의 배열로부터 새로운 java.lang.String 객체를 생성합니다. 
jsize GetStringLength (JNIEnv * env, jstring string);
   // Java 문자열의 길이 (유니 코드 문자 수)를 반환합니다. GetStringRegion (JNIEnv * env, jstring str, jsize start, jsize 길이, jchar * buf);
   // 오프셋 시작에서 시작하여 len 개의 유니 코드 문자를 주어진 버퍼 buf에 복사합니다.
UTF-8 문자열 또는 C 문자열

이 GetStringUTFChars()함수는 char*주어진 자바로부터 새로운 C-string ( 을 생성하는데 사용될 수있다 jstringNULL메모리를 할당 할 수없는 경우 함수가 반환 됩니다. 확인하는 것은 항상 좋은 습관 NULL입니다.

"in-out"매개 변수 인 세 번째 매개 변수 isCopy(of jboolean*JNI_TRUE는 반환 된 문자열이 원래 java.lang.String인스턴스 의 복사본 인 경우 로 설정됩니다 JNI_FALSE반환 된 문자열이 원래 String인스턴스에 대한 직접 포인터 인 경우이 값으로 설정됩니다 .이 경우 네이티브 코드는 반환 된 문자열의 내용을 수정하지 않습니다. 가능한 경우 JNI 런타임은 직접 포인터를 반환하려고 시도합니다. 그렇지 않으면 사본을 리턴합니다. 그럼에도 불구하고 기본 문자열을 수정하는 데 거의 관심이 없으며 종종 NULL포인터를 전달합니다 .

가비지 수집 될 수 있도록 메모리와 참조를 해제하기 ReleaseStringUTFChars()위해 반환 된 문자열이 필요하지 않을 때마다 항상 호출하십시오 GetStringUTFChars().

이 NewStringUTF()함수 jstring는 주어진 C 문자열을 사용하여 새 JNI 문자열 ( )을 만듭니다 .

JDK 1.2 소개 GetStringUTFRegion(), 이는 복사 jstring(또는 발 부분 start의 length은 "으로) 사전 할당은" C의 char배열. 그들은 대신 사용할 수 있습니다 GetStringUTFChars()은 isCopy은 C의 배열 인으로 필요하지 않은 미리 할당 .

JDK 1.2는 또한 Get/ReleaseStringCritical()함수를 소개합니다 비슷하게 GetStringUTFChars(), 가능한 경우 직접 포인터를 반환합니다. 그렇지 않으면 사본을 리턴합니다. 네이티브 메소드는 pair a GetStringCritical()와 ReleaseStringCritical()call 사이를 차단해서는 안된다 .

자세한 설명은 항상 "Java Native Interface Specification"@ http://docs.oracle.com/javase/7/docs/technotes/guides/jni/index.html을 참조하십시오 .

유니 코드 문자열

대신에 char*jchar*를 사용하여 유니 코드 문자를 저장합니다.

C ++ 구현 - TestJNIString.cpp
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
21
22
23
#include <jni.h>
#include <iostream>
#include <string>
#include "TestJNIString.h"
네임 스페이스를 사용하여 표준;
 
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello (JNIEnv * env, jobject thisObj, jstring inJNIStr) {
   // 1 단계 : JNI 문자열 (jstring)을 C 문자열 (char *)로 변환
   const char * inCStr = env-> GetStringUTFChars (inJNIStr, NULL);
   if (NULL == inCStr) NULL을 반환합니다.
 
   // 2 단계 : 의도 한 작업 수행
   cout << "C ++에서 수신 된 문자열은 다음과 같습니다."<< inCStr << endl;
   env-> ReleaseStringUTFChars (inJNIStr, inCStr);  // 리소스를 해제하십시오.
 
   // 사용자에게 C ++ 문자열을 묻습니다.
   문자열 outCppStr;
   cout << "문자열 입력 :";
   cin >> outCppStr;
 
   // Step 3 : C ++ 문자열을 C-string으로 변환 한 다음 JNI String (jstring)으로 변환하여 반환합니다.
   return env-> NewStringUTF (outCppStr.c_str ());
}

g++C ++ 프로그램을 컴파일 할 때 사용 합니다 :

g ++ -I "<JAVA_HOME> \ include"-I "<JAVA_HOME> \ include \ win32"-shared -o myjni.dll TestJNIString.cpp

C ++ 네이티브 문자열 함수는 C와는 다른 구문을 사용합니다. C ++에서는 " env->"대신에 " (env*)->"을 사용할 수 있습니다. 또한 JNIEnv*C ++ 함수에서 인수 가 필요하지 않습니다 .

또한 C ++에서 string클래스를 지원한다는 점에 유의하십시오 <string>보다 사용자 친화적 인 머리글 및 레거시 C 문자열 (문자 배열) 아래).

[TODO] C ++ string클래스가 직접 지원됩니까?

4.3 프리미티브 배열 전달하기

JNI 프로그램 - TestJNIPrimitiveArray.java
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
공용 클래스 TestJNIPrimitiveArray {
   정적 {
      System.loadLibrary ( "myjni"); // myjni.dll (Windows) 또는 libmyjni.so (Unixes)
   }
 
   // int []를 받고 
   // [0]이 sum이고 double이 [1] 인 배열을 평균으로 반환 하는 네이티브 메소드 인 sumAndAverage ()를 선언합니다.
   private 네이티브 double [] sumAndAverage (int [] numbers);
 
   // 드라이버 테스트
   public static void main (String args []) {
      int [] numbers = {22, 33, 33};
      double [] results = 새 TestJNIPrimitiveArray (). sumAndAverage (numbers);
      System.out.println ( "Java에서는 합계가"+ results [0] "입니다.
      System.out.println ( "Java에서 평균은"+ results [1]입니다.
   }
}
C 구현 - TestJNIPrimitiveArray.c

헤더 " TestJNIPrimitiveArray.h"에는 다음 함수 선언이 포함되어 있습니다.

JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_average (JNIEnv *, jobject, jintArray);

Java에서 array는 클래스와 비슷한 참조 유형 입니다. Java 배열에는 9 가지 유형이 있으며, 하나는 각각 8 개의 프리미티브와 배열입니다 java.lang.ObjectJNI는 즉, 팔 개 자바 원시적 배열의 각 유형을 정의하고, jintArrayjbyteArrayjshortArrayjlongArrayjfloatArrayjdoubleArrayjcharArrayjbooleanArray자바의 원시의 배열 intbyteshortlongfloatdoublechar및 boolean각각. 또한 jobjectArrayJava 배열을 정의합니다 Object(나중에 설명 함).

다시 말하지만, 당신은 사이 JNI 배열 및 기본 배열, 예를 들면 사이의 변환합니다 jintArray및 C의 jint[]또는 jdoubleArray과 C의 jdouble[]JNI 환경 인터페이스는 변환을위한 일련의 함수를 제공합니다.

  1. jint[]JNI에서 C 네이티브를 가져 오려면 jintArray호출하십시오 jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy).
  2. JNI 얻으려면 jintArrayC 출신의를 jint[]먼저 호출 jintArray NewIntArray(JNIEnv *env, jsize len)할당 한 후 사용 void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)(가) 복사하기 jint[]에 jintArray.

8 개의 Java 프리미티브 각각에 대해 하나씩 위의 함수가 8 세트 있습니다.

기본 프로그램은 다음을 수행해야합니다.

  1. 수신 JNI 배열 (예 :)을 수신하고 jintArrayC의 기본 배열 (예 :)로 변환합니다 jint[].
  2. 의도 된 작업을 수행하십시오.
  3. 반환 값 C의 네이티브 배열 (예 jdouble[]:)를 JNI 배열 (예 jdoubleArray:)로 변환하고 JNI 배열을 반환합니다.

C 구현 TestJNIPrimitiveArray.c은 다음과 같습니다.

1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
21
22
23
24
25 명
26 세
27
28
#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitiveArray.h"
 
JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage
          (JNIEnv * env, jobject thisObj, jintArray inJNIArray) {
   // 1 단계 : 수신 JNI jintarray를 C의 jint []로 변환합니다.
   jint * inCArray = (* env) -> GetIntArrayElements (env, inJNIArray, NULL);
   if (NULL == inCArray)는 NULL을 반환합니다.
   jsize length = (* env) -> GetArrayLength (env, inJNIArray);
 
   // 2 단계 : 의도 한 작업 수행
   jint sum = 0;
   int i;
   for (i = 0; i <길이; i ++) {
      sum + = inCArray [i];
   }
   jdouble 평균 = (jdouble) 합계 / 길이;
   (* env) -> ReleaseIntArrayElements (env, inJNIArray, inCArray, 0); // 리소스를 해제하십시오.
 
   jdouble outCArray [] = {합계, 평균};
 
   // Step 3 : C의 네이티브 jdouble []를 JNI jdoublearray로 변환하고 jdoubleArray를 반환 
   합니다. outJNIArray = (* env) -> NewDoubleArray (env, 2);  // allocate
   if (NULL == outJNIArray)가 NULL를 돌려 준다.
   (* env) -> SetDoubleArrayRegion (env, outJNIArray, 0, 2, outCArray);  // 복사
   returnNJIArray;
}
JNI 원시적 배열 함수

JNI의 배열 프리미티브 ( jintArrayjbyteArrayjshortArrayjlongArrayjfloatArrayjdoubleArrayjcharArray및 jbooleanArray) 함수이다 :

// ArrayType : jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
// PrimitiveType : int, byte, short, long, float, double, char, boolean
// NativeType : jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean 
NativeType * Get < PrimitiveType > ArrayElements (JNIEnv * env, ArrayType 배열, jboolean * isCopy);
void Release < PrimitiveType > ArrayElements (JNIEnv * env, ArrayType 배열, NativeType * elems, jint 모드);
void < PrimitiveType > ArrayRegion (JNIEnv * env, ArrayType 배열, jsize start, jsize 길이, NativeType * 버퍼)를 취득합니다 .
void Set < PrimitiveType > ArrayRegion (JNIEnv * env, ArrayType 배열, jsize start, jsize 길이, const NativeType * 버퍼);
ArrayType  새로운 < PrimitiveType > Array (JNIEnv * env, jsize length);
void * GetPrimitiveArrayCritical (JNIEnv * env, jarray 배열, jboolean * isCopy);
void ReleasePrimitiveArrayCritical (JNIEnv * env, jarray 배열, void * carray, jint 모드)입니다.

는 새로운 C의 기본 배열을 만드는 데 사용할 수있는 지정된 Java에서을 등본에 사용될 수있다 (또는 발 부분 의가 와주고 사전 할당 C 기본 배열 .GET|Release<PrimitiveType>ArrayElements()jxxx[]jxxxArrayGET|Set<PrimitiveType>ArrayRegion()jxxxArraystartlengthjxxx[]

은 New<PrimitiveType>Array()새로운를 할당하는 데 사용할 수있는 jxxxArray주어진 크기의. 그런 다음이 함수를 사용하여 기본 배열에서 내용을 채울 수 있습니다 .Set<PrimitiveType>ArrayRegion()jxxx[]

이 Get|ReleasePrimitiveArrayCritical()함수는 get과 release 사이에서 호출을 블로킹 할 수 없습니다.

5. 객체 변수에 접근하기 및 콜백 메소드

5.1 객체의 인스턴스 변수에 접근하기

JNI 프로그램 - TestJNIIstanceVariable.java
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
공공 클래스 TestJNIInstanceVariable {
   정적 {
      System.loadLibrary ( "myjni"); // myjni.dll (Windows) 또는 libmyjni.so (Unixes)
   }
 
   // 인스턴스 변수
   개인 int 번호 = 88;
   전용 String message = "Hello from Java";
 
   // 인스턴스 변수를 수정하는 네이티브 메소드 선언
   private 네이티브 void modifyInstanceVariable ();
 
   // 드라이버 테스트   
   public static void main (String args []) {
      TestJNIInstanceVariable 테스트 = 새 TestJNIInstanceVariable ();
      test.modifyInstanceVariable ();
      System.out.println ( "Java에서 int는"+ test.number입니다.
      System.out.println ( "Java에서는 String이"+ test.message "입니다.
   }
}

이 클래스는 두 개의 private인스턴스 변수를 포함한다 : int호출 된 프리미티브 number와 String호출된다 message또한 인스턴스 변수의 내용을 수정할 수있는 고유 메소드를 선언합니다.

C 구현 - TestJNIInstanceVariable.c
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
21
22
23
24
25 명
26 세
27
28
29
30
31
32
33
34
35 세
36
37 세
38 세
39 세
40
41
42
43
44
#include <jni.h>
#include <stdio.h>
#include "TestJNIInstanceVariable.h"
 
JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable
          (JNIEnv * env, jobject thisObj) {
   //이 객체의 클래스에 대한 참조를 가져옵니다.
   jclass thisClass = (* env) -> GetObjectClass (env, thisObj);
 
   // int 
   // 인스턴스 변수 "number"의 필드 ID를 가져옵니다.
   jfieldID fidNumber = (* env) -> GetFieldID (env, thisClass, "number", "I");
   if (NULL == fidNumber) return;
 
   // 필드 ID가 주어진 int를 가져옵니다.
   jint number = (* env) -> GetIntField (env, thisObj, fidNumber);
   printf ( "C에서 int는 % d \ n", 숫자);
 
   // 변수를 변경합니다.
   number = 99;
   (* env) -> SetIntField (env, thisObj, fidNumber, number);
 
   // 인스턴스 변수 "message"의 필드 ID를 가져옵니다.
   jfieldID fidMessage = (* env) -> GetFieldID (env, thisClass, "message", "Ljava / lang / String;");
   if (NULL == fidMessage) return;
 
   // String 
   // Field ID가 지정된 객체를 가져옵니다.
   jstring message = (* env) -> GetObjectField (env, thisObj, fidMessage);
 
   // JNI 문자열을 사용하여 C 문자열 만들기
   const char * cStr = (* env) -> GetStringUTFChars (env, message, NULL);
   if (NULL == cStr) return;
 
   printf ( "C에서 문자열은 % s입니다. \ n", cStr);
   (* env) -> ReleaseStringUTFChars (env, message, cStr);
 
   // 새로운 C- 문자열을 생성하고 JNI 문자열에 할당
   message = (* env) -> NewStringUTF (env, "Hello from C");
   if (NULL == message) return;
 
   // 인스턴스 변수를 수정합니다.
   (* env) -> SetObjectField (env, thisObj, fidMessage, message);
}

객체의 인스턴스 변수에 액세스하려면 :

  1. 를 통해이 객체의 클래스에 대한 참조를 가져옵니다 GetObjectClass().
  2. GetFieldID()클래스 참조로부터 액세스 할 인스턴스 변수의 필드 ID를 가져옵니다. 변수 이름과 필드 설명자 (또는 서명)를 제공해야합니다. Java 클래스의 경우, 필드 기술자는 " L<fully-qualified-name>;의 형식이며 점은 슬래시 ( /로 대체됩니다 . 예를 들어 클래스 설명 String자는 " Ljava/lang/String;"입니다. 프리미티브를 들어, 사용 "I"을 위해 int"B"대한 byte"S"대한 short"J"대한 long"F"대한 float"D"대한 double"C"대한 char, 그리고 "Z"에 boolean배열의 경우 접두사 "["(예 : " [Ljava/lang/Object;")를 배열에 포함하십시오 Object"[I"배열의 int.
  3. 필드 ID를 기반으로 GetObjectField()또는을 통해 인스턴스 변수를 검색합니다 .Get<primitive-type>Field()
  4. 인스턴스 변수를 업데이트하려면 SetObjectField()또는 함수를 사용 하여 필드 ID를 제공하십시오.Set<primitive-type>Field()

인스턴스 변수에 액세스하기위한 JNI 함수는 다음과 같습니다.

jclass GetObjectClass (JNIEnv * env, jobject obj);
   // 객체의 클래스를 반환합니다.
   
jfieldID GetFieldID (JNIEnv * env, jclass cls, const char * name, const char * sig);
  // 클래스의 인스턴스 변수에 대한 필드 ID를 반환합니다.
 
NativeType get < type > 필드 (JNIEnv * env, jobject obj, jfieldID fieldID);
void set < type > Field (JNIEnv * env, jobject obj, jfieldID fieldID, NativeType 값);
  // 객체의 인스턴스 변수 값 가져 오기 / 설정
  // < type >은 Object와 함께 8 개의 기본 유형을 각각 포함합니다.

5.2 클래스의 정적 변수에 접근하기

정적 변수 액세스하면 다음과 같은 기능을 사용하는 것을 제외하고는, 액세스 인스턴스 변수와 유사하다 GetStaticFieldID()Get|SetStaticObjectField(),가 .Get|SetStatic<Primitive-type>Field()

JNI 프로그램 - TestJNIStaticVariable.java
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
공공 클래스 TestJNIStaticVariable {
   정적 {
      System.loadLibrary ( "myjni"); // nyjni.dll (Windows) 또는 libmyjni.so (Unixes)
   }
 
   // 정적 변수
   개인 정적 이중 숫자 = 55.66;
 
   // 정적 변수를 수정하는 네이티브 메소드 선언
   private 네이티브 void modifyStaticVariable ();
 
   // 드라이버 테스트
   public static void main (String args []) {
      TestJNIStaticVariable 테스트 = 새 TestJNIStaticVariable ();
      test.modifyStaticVariable ();
      System.out.println ( "Java에서는 double이"+ 숫자입니다.
   }
}
C 구현 - TestJNIStaticVariable.c
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
#include <jni.h>
#include <stdio.h>
#include "TestJNIStaticVariable.h"
 
JNIEXPORT void JNICALL Java_TestJNIStaticVariable_modifyStaticVariable
          (JNIEnv * env, jobject thisObj) {
   //이 객체의 클래스에 대한 참조를 가져옵니다.
   jclass cls = (* env) -> GetObjectClass (env, thisObj);
 
   // int static 변수를 읽고 값을 수정합니다.
   jfieldID fidNumber = (* env) -> GetStaticFieldID (env, cls, "number", "D");
   if (NULL == fidNumber) return;
   jdouble number = (* env) -> GetStaticDoubleField (env, cls, fidNumber);
   printf ( "C에서 double은 % f \ n", 숫자);
   수 = 77.88;
   (* env) -> SetStaticDoubleField (env, cls, fidNumber, number);
}

정적 변수에 액세스하기위한 JNI 함수는 다음과 같습니다.

jfieldID GetStaticFieldID (JNIEnv * env, jclass cls, const char * 이름, const char * sig);
  // 클래스의 정적 변수에 대한 필드 ID를 반환합니다.
 
NativeType GetStatic < type > 필드 (JNIEnv * env, jclass clazz, jfieldID fieldID);
void SetStatic < type > Field (JNIEnv * env, jclass clazz, jfieldID fieldID, NativeType 값)입니다.
  // 클래스의 정적 변수 값을 가져 오거나 설정합니다.
  // < type >은 Object와 함께 8 개의 기본 유형을 각각 포함합니다.

5.3 콜백 인스턴스 메소드와 정적 메소드

네이티브 코드에서 인스턴스 및 정적 메서드를 콜백 할 수 있습니다.

JNI 프로그램 - TestJNICallBackMethod.java
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
21
22
23
24
25 명
26 세
27
28
29
30
31
공용 클래스 TestJNICallBackMethod {
   정적 {
      System.loadLibrary ( "myjni"); // myjni.dll (Windows) 또는 libmyjni.so (Unixes)
   }
 
   // 아래의 자바 메소드를 호출하는 네이티브 메소드 선언
   private 네이티브 무효 nativeMethod ();
 
   // 네이티브 코드에 의해 콜백된다.
   개인 무효 콜백 () {
      System.out.println ( "Java에서");
   }
 
   개인 무효 콜백 (문자열 메시지) {
      System.out.println ( "Java with"+ message);
   }
 
   private double callbackAverage (int n1, int n2) {
      return ((double) n1 + n2) / 2.0;
   }
 
   // 콜백 될 정적 메소드
   개인 정적 문자열 callbackStatic () {
      return "정적 Java 메소드에서";
   }

   // 드라이버 테스트 
   public static void main (String args []) {
      새로운 TestJNICallBackMethod (). nativeMethod ();
   }
}

이 클래스는 호출 된 native메소드를 선언하고 nativeMethod()이것을 호출합니다 nativeMethod()또한 nativeMethod(),이 클래스에 정의 된 다양한 인스턴스 및 정적 메서드를 호출합니다.

C 구현 - TestJNICallBackMethod.c
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
21
22
23
24
25 명
26 세
27
28
29
30
31
32
33
34
35 세
36
37 세
38 세
39 세
#include <jni.h>
#include <stdio.h>
#include "TestJNICallBackMethod.h"
 
JNIEXPORT void JNICALL Java_TestJNICallBackMethod_nativeMethod
          (JNIEnv * env, jobject thisObj) {
 
   //이 객체에 대한 클래스 참조 가져 오기
   jclass thisClass = (* env) -> GetObjectClass (env, thisObj);
 
   // arg를 취하지 않고 void를 반환하는 "callback"메서드의 메서드 ID를 가져옵니다.
   jmethodID midCallBack = (* env) -> GetMethodID (env, thisClass, "콜백", "() V");
   if (NULL == midCallBack) return;
   printf ( "C에서는 Java 콜백 ()을 콜백합니다. \ n");
   // 메서드 ID (void)를 반환하고 메소드 ID를 baed합니다.
   (* env) -> CallVoidMethod (env, thisObj, midCallBack);
 
   jmethodID midCallBackStr = (* env) -> GetMethodID (env, thisClass,
                               "콜백", "(Ljava / lang / String;) V");
   if (NULL == midCallBackStr) return;
   printf ( "C에서는 콜백 자바 (String) \ n");
   jstring message = (* env) -> NewStringUTF (env, "Hello from C");
   (* env) -> CallVoidMethod (env, thisObj, midCallBackStr, message);
 
   jmethodID midCallBackAverage = (* env) -> GetMethodID (env, thisClass,
                                  "callbackAverage", "(II) D");
   if (NULL == midCallBackAverage) return;
   jdouble average = (* env) -> CallDoubleMethod (env, thisObj, midCallBackAverage, 2, 3);
   printf ( "C에서 평균은 % f \ n", 평균);
 
   jmethodID midCallBackStatic = (* env) -> GetStaticMethodID (env, thisClass,
                                 "callbackStatic", "() Ljava / lang / String;");
   if (NULL == midCallBackStatic) return;
   jstring resultJNIStr = (* env) -> CallStaticObjectMethod (env, thisClass, midCallBackStatic);
   const char * resultCStr = (* env) -> GetStringUTFChars (env, resultJNIStr, NULL);
   if (NULL == resultCStr) return;
   printf ( "C에서 반환 된 문자열은 % s \ n", resultCStr);
   (* env) -> ReleaseStringUTFChars (env, resultJNIStr, resultCStr);
}

네이티브 코드에서 인스턴스 메서드를 호출하려면 :

  1. 를 통해이 객체의 클래스에 대한 참조를 가져옵니다 GetObjectClass().
  2. 클래스 참조에서 메소드 ID를 가져옵니다 GetMethodID()메소드 이름과 서명을 제공해야합니다. 서명은 " 형식 입니다. 유틸리티 (클래스 파일 디스어셈블러)와 (인쇄 서명) 및 (개인 회원 표시 를 통해 Java 프로그램의 메소드 서명을 나열 할 수 있습니다 (parameters)return-typejavap-s-p
    > javap --help 
    > javap -s -p TestJNICallBackMethod
      .......
      private void callback ();
        서명 : () V
     
      개인 무효 콜백 (java.lang.String);
        서명 : (Ljava / lang / String;) V
     
      private double callbackAverage (int, int);
        서명 : (II) D
     
      private static java.lang.String callbackStatic ();
        서명 : () Ljava / lang / String;
      .......
  3. 메서드 ID를 기반으로, 당신은 호출 할 수 Call<Primitive-type>Method()또는 CallVoidMethod()또는 CallObjectMethod()수익 형이고, 무효 및 각각. 인수 목록 앞에 인수가 있으면 추가하십시오. 비 반환형의 경우이 메서드는 값을 반환합니다.<Primitive-type>Objectvoid

콜백하는 static방법, 사용 GetMethodID(), CallStatic<Primitive-type>Method()CallStaticVoidMethod()또는 CallStaticObjectMethod().

인스턴스 메소드 및 정적 메소드를 호출하기위한 JNI 함수는 다음과 같습니다.

jmethodID GetMethodID (JNIEnv * env, jclass cls, const char * 이름, const char * sig);
   // 클래스 또는 인터페이스의 인스턴스 메서드에 대한 메서드 ID를 반환합니다.
   
NativeType Call < type > 메서드 (JNIEnv * env, jobject obj, jmethodID methodID, ...);
NativeType Call < type > MethodA (JNIEnv * env, jobject obj, jmethodID methodID, const jvalue * args);
NativeType Call < type > MethodV (JNIEnv * env, jobject obj, jmethodID methodID, va_list args);
   // 객체의 인스턴스 메소드를 호출합니다.
   // <type>은 8 개의 프리미티브와 Object를 각각 포함합니다.
   
jmethodID GetStaticMethodID (JNIEnv * env, jclass cls, const char * 이름, const char * sig);
   // 클래스 또는 인터페이스의 인스턴스 메서드에 대한 메서드 ID를 반환합니다.
   
NativeType CallStatic < type > 메서드 (JNIEnv * env, jclass clazz, jmethodID methodID, ...);
NativeType CallStatic < type > MethodA (JNIEnv * env, jclass clazz, jmethodID methodID, const jvalue * args);
NativeType CallStatic < type > MethodV (JNIEnv * env, jclass clazz, jmethodID methodID, va_list args);
   // 객체의 인스턴스 메소드를 호출합니다.
   // <type>은 8 개의 프리미티브와 Object를 각각 포함합니다.

5.4 콜백 오버라이드 된 수퍼 클래스의 인스턴스 메소드

JNI는 CallNonvirtual<Type>Method()이 클래스에서 오버라이드 된 수퍼 클래스의 인스턴스 메소드를 호출 하는 함수 세트를 제공합니다 Java 서브 클래스 내부의 호출 과 비슷 함 ).super.methodName()

  1. 를 통해 메소드 ID를 가져옵니다 GetMethodID().
  2. 메소드 ID를 기반으로, CallNonvirtual<Type>Method()object, superclass W arguments와 함 2를 호출하십시오.

오버라이드 (override) 된 수퍼 클래스의 인스턴스 메소드를 호출하기위한 JNI 함수는 다음과 같습니다.

NativeType CallNonvirtual < type > 메서드 (JNIEnv * env, jobject obj, jclass cls, jmethodID methodID, ...);
NativeType CallNonvirtual < type > MethodA (JNIEnv * env, jobject obj, jclass cls, jmethodID methodID, const jvalue * args);
NativeType CallNonvirtual < type > MethodV (JNIEnv * env, jobject obj, jclass cls, jmethodID methodID, va_list args);

6. 객체 및 객체 배열 만들기

당신은 구성 할 수 jobject와 jobjectArray네이티브 코드 내부를 통해 NewObject()및 newObjectArray()기능, 다시 자바 프로그램을 전달합니다.

6.1 네이티브 코드에서 새 Java 객체를 생성하기위한 생성자 콜백

콜백 생성자는 메서드를 다시 호출하는 것과 비슷합니다. 먼저 <init>메서드 이름으로 " V"을, 반환 유형으로 "를 전달하여 생성자의 메서드 ID를 가져옵니다 그런 다음 NewObject()생성자를 호출하여 새 Java 객체를 만드는 것과 같은 메소드를 사용할 수 있습니다 .

JNI 프로그램 - TestJavaConstructor.java
1
2
4
5
6
7
8
9
10
11
12
13
14
공공 클래스 TestJNIConstructor {
   정적 {
      System.loadLibrary ( "myjni"); // myjni.dll (Windows) 또는 libmyjni.so (Unixes)
   }
 
   // 생성자를 다시 호출하고 생성 된 객체를 반환하는 네이티브 메서드입니다.
   // 지정된 int로 Integer 객체를 반환합니다.
   private 네이티브 Integer getIntegerObject (int number);
 
   public static void main (String args []) {
      TestJNIConstructor obj = new TestJNIConstructor ();
      System.out.println ( "Java에서 숫자는 다음과 같습니다."+ obj.getIntegerObject (9999));
   }
}

이 클래스는 native메서드를 선언합니다 getIntegerObject()네이티브 코드는 제공된 인수를 기반으로 Integer 객체를 만들고 반환해야합니다.

C 구현 - TestJavaConstructor.c
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
21
22
23
24
#include <jni.h>
#include <stdio.h>
#include "TestJNIConstructor.h"
 
JNIEXPORT jobject JNICALL Java_TestJNIConstructor_getIntegerObject
          (JNIEnv * env, jobject thisObj, jint 번호) {
   // java.lang.Integer에 대한 클래스 참조 얻기
   jclass cls = (* env) -> FindClass (env, "java / lang / Integer");
 
   // int를 취하는 생성자의 메서드 ID를 가져옵니다.
   jmethodID midInit = (* env) -> GetMethodID (env, cls, "<init>", "(I) V");
   if (NULL == midInit) NULL을 반환합니다.
   // int 인자를 사용하여 생성자를 호출하여 새 인스턴스를 할당합니다.
   jobject newObj = (* env) -> NewObject (env, cls, midInit, number);
 
   //이 새로 생성 된 객체에서 toString ()을 실행 해 봅니다.
   jmethodID midToString = (* env) -> GetMethodID (env, cls, "toString", "() Ljava / lang / String;");
   if (NULL == midToString)가 NULL을 반환합니다.
   jstring resultStr = (* env) -> CallObjectMethod (env, newObj, midToString);
   const char * resultCStr = (* env) -> GetStringUTFChars (env, resultStr, NULL);
   printf ( "C에서 : 숫자는 % s입니다. \ n", resultCStr);
 
   return newObj;
}

object ( jobject를 작성하기위한 JNI 함수 는 다음과 같습니다.

jclass FindClass (JNIEnv * env, const char * name);
 
jobject NewObject (JNIEnv * env, jclass cls, jmethodID methodID, ...);
jobject NewObjectA (JNIEnv * env, jclass cls, jmethodID methodID, const jvalue * args);
jobject NewObjectV (JNIEnv * env, jclass cls, jmethodID methodID, va_list args);
   // 새로운 Java 객체를 생성합니다. 메서드 ID는 호출 할 생성자 메서드를 나타냅니다.
 
jobject AllocObject (JNIEnv * env, jclass cls);
  // 객체의 생성자를 호출하지 않고 새 Java 객체를 할당합니다.

6.2 객체 배열

JNI 프로그램 - TestJNIObjectArray.java
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
import java.util.ArrayList;
 
공용 클래스 TestJNIObjectArray {
   정적 {
      System.loadLibrary ( "myjni"); // myjni.dll (Windows) 또는 libmyjni.so (Unixes)
   }
   // Integer []를 받고 
   // [0]이 평균이고 [1]이 평균 인 Double [2]를 반환하는 네이티브 메서드
   개인 네이티브 Double [] sumAndAverage (정수 [] numbers);
 
   public static void main (String args []) {
      정수 [] numbers = {11, 22, 32};  // 자동 상자
      Double [] results = 새 TestJNIObjectArray (). sumAndAverage (numbers);
      System.out.println ( "Java에서는 합계가"+ results [0] "입니다.  // auto-unbox
      System.out.println ( "Java에서 평균은"+ results [1]입니다.
   }
}

예를 들어,이 클래스는 native배열을 취하고 Integer그 합과 평균 및 반환 값을 배열로 취하는 메서드를 선언합니다 Double오브젝트의 배열은 네이티브 메소드의 안팎으로 전달됩니다.

C 구현 - TestJNIObjectArray.c
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
21
22
23
24
25 명
26 세
27
28
29
30
31
32
33
34
35 세
36
37 세
38 세
39 세
40
41
42
43
#include <jni.h>
#include <stdio.h>
#include "TestJNIObjectArray.h"
 
JNIEXPORT jobjectArray JNICALL Java_TestJNIObjectArray_sumAndAverage
          (JNIEnv * env, jobject thisObj, jobjectArray inJNIArray) {
   // java.lang.Integer에 대한 클래스 참조 얻기
   jclass classInteger = (* env) -> FindClass (env, "java / lang / Integer");
   // Integer.intValue ()를 사용하여 int를 검색합니다.
   jmethodID midIntValue = (* env) -> GetMethodID (env, classInteger, "intValue", "() I");
   if (NULL == midIntValue) NULL을 반환합니다.
 
   // 배열의 각 Integer 객체의 값을 가져옵니다.
   jsize length = (* env) -> GetArrayLength (env, inJNIArray);
   jint sum = 0;
   int i;
   for (i = 0; i <길이; i ++) {
      jobject objInteger = (* env) -> GetObjectArrayElement (env, inJNIArray, i);
      if (NULL == objInteger)가 NULL를 돌려 준다.
      jint 값 = (* env) -> CallIntMethod (env, objInteger, midIntValue);
      합계 + = 값;
   }
   double average = (double) sum / length;
   printf ( "C에서 합계는 % d \ n", 합계입니다.);
   printf ( "C에서 평균은 % f \ n", 평균);
 
   // java.lang.Double의 클래스 참조를 가져옵니다.
   jclass classDouble = (* env) -> FindClass (env, "java / lang / Double");
 
   // 2의 jobjectArray를 할당한다. java.lang.Double
   jobjectArray outJNIArray = (* env) -> NewObjectArray (env, 2, classDouble, NULL);
 
   // 생성자를 호출하여 2 개의 Double 객체를 만듭니다.
   jmethodID midDoubleInit = (* env) -> GetMethodID (env, classDouble, "<init>", "(D) V");
   if (NULL == midDoubleInit) NULL을 반환합니다.
   jobject objSum = (* env) -> NewObject (env, classDouble, midDoubleInit, (double) sum);
   jobject objAve = (* env) -> NewObject (env, classDouble, midDoubleInit, average);
   // jobjectArray로 설정합니다.
   (* env) -> SetObjectArrayElement (env, outJNIArray, 0, objSum);
   (* env) -> SetObjectArrayElement (env, outJNIArray, 1, objAve);
 
   returnNJIArray;
}

객체 배열의 경우 대량으로 처리 할 수있는 프리미티브 배열과 달리 Get|SetObjectArrayElement()각 요소를 처리 하는 데는을 사용해야합니다 .

객체 배열 ( jobjectArray을 생성하고 조작하기위한 JNI 함수 는 다음과 같습니다.

jobjectArray 를 NewObjectArray (ENV의 JNIEnv *, jsize 길이 JCLASS elementClass, initialElement하는 jobject);
   // elementClass 클래스의 객체를 포함하는 새로운 배열을 생성합니다.
   // 모든 요소는 초기에 initialElement로 설정됩니다.
 
jobject GetObjectArrayElement (JNIEnv * env, jobjectArray 배열, jsize 인덱스);
   // 객체 배열의 요소를 반환합니다.
 
void SetObjectArrayElement (JNIEnv * env, jobjectArray 배열, jsize 인덱스, jobject 값).
   // 객체 배열의 요소를 설정합니다.

7. 지역 및 세계 참고 문헌

효율적인 프로그램을 작성하는 데 참조 자료 관리가 중요합니다. 예를 들어, 우리는 자주 사용하는 FindClass()GetMethodID()GetFieldID()를 검색 jclassjmethodID그리고 jfieldID기본 기능 내부. 반복되는 호출을 수행하는 대신 오버 헤드를 제거하기 위해 값을 한 번 가져 와서 다음에 사용하기 위해 캐싱해야합니다.

JNI는 jobject네이티브 코드가 사용하는 객체 참조 (for )를 로컬 및 글로벌 참조의 2 개의 카테고리로 나눕니다.

  1. 로컬 기준은 네이티브 이내있어서, 상기 방법이 종료되면 해제된다. 원시 메소드의 지속 기간 동안 유효합니다. JNI 함수 DeleteLocalRef()를 사용해 로컬 참조를 명시 적으로 무효화 할 수 있기 때문에, 중간에 가베지 컬렉션이 가능하게됩니다. 객체는 로컬 참조로 원시 메소드에 전달됩니다. jobjectJNI 함수에 의해 반환 된 모든 Java 객체 ( )는 로컬 참조입니다.
  2. 글로벌 참조 가 명시 통해 프로그래머가 해제 될 때까지 유지 DeleteGlobalRef()JNI 기능. JNI 함수를 통해 로컬 참조에서 새 전역 참조를 만들 수 있습니다 NewGlobalRef().
1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
21
공공 클래스 TestJNIReference {
   정적 {
      System.loadLibrary ( "myjni"); // myjni.dll (Windows) 또는 libmyjni.so (Unixes)
   }
 
   // 지정된 int로 java.lang.Integer를 돌려주는 네이티브 메소드.
   private 네이티브 Integer getIntegerObject (int number);
 
   // 지정된 int로 java.lang.Integer를 리턴하는 또 다른 원시 메소드.
   private 네이티브 Integer anotherGetIntegerObject (int number);
 
   public static void main (String args []) {
      TestJNIReference 테스트 = 새 TestJNIReference ();
      System.out.println (test.getIntegerObject (1));
      System.out.println (test.getIntegerObject (2));
      System.out.println (test.anotherGetIntegerObject (11));
      System.out.println (test.anotherGetIntegerObject (12));
      System.out.println (test.getIntegerObject (3));
      System.out.println (test.anotherGetIntegerObject (13));
   }
}

위의 JNI 프로그램은 두 가지 고유 메소드를 선언합니다. 둘 다 java.lang.Integer객체를 만들고 반환 합니다.

C 구현 java.lang.Integer에서는 via를 통해 클래스 참조를 가져와야 FindClass()합니다. 그런 다음에 생성자의 메소드 ID를 찾고 생성자를 Integer호출합니다. 그러나 반복 호출에 사용될 클래스 참조와 메소드 ID를 모두 캐시하려고합니다.

다음 C 구현은 작동하지 않습니다!

1
2
4
5
6
7
8
9
10
11
12
13
14
15 명
16
17
18
19
20
21
22
23
24
25 명
26 세
27
28
29
30
31
32
33
34
35 세
36
37 세
38 세
39 세
#include <jni.h>
#include <stdio.h>
#include "TestJNIReference.h"
 
// Java 클래스 "java.lang.Integer"에 대한 전역 참조
static jclass classInteger;
static jmethodID midIntegerInit;
 
jobject getInteger (JNIEnv * env, jobject thisObj, jint 번호) {
 
   // 누락 된 경우 java.lang.Integer에 대한 클래스 참조 가져 오기
   if (NULL == classInteger) {
      printf ( "Find java.lang.Integer \ n");
      classInteger = (* env) -> FindClass (env, "java / lang / Integer");
   }
   if (NULL == classInteger)가 NULL을 돌려 준다.
 
   // 누락 된 경우 Integer 생성자의 Method ID를 가져옵니다.
   if (NULL == midIntegerInit) {
      printf ( "java.lang.Integer의 생성자에 대한 메소드 ID 가져 오기 \ n");
      midIntegerInit = (* env) -> GetMethodID (env, classInteger, "<init>", "(I) V");
   }
   if (NULL == midIntegerInit) NULL을 반환합니다.
 
   // int 인자를 사용하여 생성자를 호출하여 새 인스턴스를 할당합니다.
   jobject newObj = (* env) -> NewObject (env, classInteger, midIntegerInit, number);
   printf ( "C에서 숫자가 % d 인 정수 생성 java.lang.Integer \ n", number);
   return newObj;
}
 
JNIEXPORT jobject JNICALL Java_TestJNIReference_getIntegerObject
          (JNIEnv * env, jobject thisObj, jint 번호) {
   return getInteger (env, thisObj, number);
}
 
JNIEXPORT jobject JNICALL Java_TestJNIReference_anotherGetIntegerObject
          (JNIEnv * env, jobject thisObj, jint 번호) {
   return getInteger (env, thisObj, number);
}

위의 프로그램에서에 FindClass()대한 클래스 참조를 찾고 java.lang.Integer글로벌 정적 변수에 저장했습니다. 그럼에도 불구하고 다음 호출에서이 참조는 더 이상 유효하지 않으며 NULL이 아닙니다. FindClass()메서드가 종료되면 무효화되는 로컬 참조를 반환 하기 때문 입니다.

문제를 극복하기 위해,에 의해 반환 된 지역 참조로부터 전역 참조를 생성해야합니다 FindClass()그러면 로컬 참조를 해제 할 수 있습니다. 수정 된 코드는 다음과 같습니다.

   // 누락 된 경우 java.lang.Integer에 대한 클래스 참조 가져 오기
   if (NULL == classInteger) {
      printf ( "Find java.lang.Integer \ n");
      // FindClass는 로컬 참조를 반환합니다.
      jclass classIntegerLocal = (* env) -> FindClass (env, "java / lang / Integer");
      // 로컬 참조에서 전역 참조를 만듭니다.
      classInteger = (* env) -> NewGlobalRef (env, classIntegerLocal);
      // 더이상 로컬 참조가 필요 없으므로 자유롭게 사용하십시오!
      (* env) -> DeleteLocalRef (env, classIntegerLocal);
   }

가지고 점에 유의 jmethodID하고 jfieldID아닌 jobject, 글로벌 참조를 만들 수 없습니다.

8. JNI 프로그램 디버깅

[할 것]

참고 문헌 및 자료

  1. Java Native Interface Specification @ http://docs.oracle.com/javase/7/docs/technotes/guides/jni/index.html .
  2. Wiki "Java Native Interface"@ http://en.wikipedia.org/wiki/Java_Native_Interface .
  3. Liang, "The Java Native Interface - 프로그래머 가이드 및 스펙", Addison Wesley, 1999, 온라인 @ http://java.sun.com/docs/books/jni/html/jniTOC.html .
  4. JNI Tips @ http://developer.android.com/guide/practices/jni.html .