Android API용 라이브러리 래퍼 Android Game Development Kit의 일부
라이브러리 래퍼는 Java로 작성된 Android API용 C 언어 래퍼 코드를 생성하는 명령줄 도구(CLI)입니다. 네이티브 Android 앱에서 이 코드를 사용하면 Java 네이티브 인터페이스(JNI)를 수동으로 만들지 않고도 Java API를 호출할 수 있습니다. 이 도구를 사용하면 주로 C 또는 C++로 작성된 Android 앱 개발을 간소화할 수 있습니다.
이 도구는 개발자가 제공하는 Java 보관 파일(JAR) 또는 도구의 구성 파일에 정의된 클래스에 포함되거나 둘 다에 포함된 공개 기호의 C 코드를 생성하는 방식으로 작동합니다. 도구에서 생성된 코드는 Java API를 대체하지 않습니다. 대신 C 코드와 Java 간의 다리 역할을 합니다. 래핑하는 Java 라이브러리를 프로젝트에 계속 포함해야 합니다.
다운로드
라이브러리 래퍼 보관 파일을 다운로드하고 그 콘텐츠를 원하는 디렉터리에 압축해제합니다.
문법
라이브러리 래퍼 도구에는 다음과 같은 명령줄 문법이 있습니다.
java -jar lw.jar \
[-i jar-file-to-be-wrapped] \
[-o output-path] \
[-c config-file] \
[-fa allow-list-file] \
[-fb block-list-file] \
[--skip_deprecated_symbols]
매개변수 | 설명 |
---|---|
-i jar-file-to-be-wrapped |
JAR 파일이며, C 래퍼 코드를 생성합니다. 여러 JAR을 지정할 수 있습니다. 예를 들면 다음과 같습니다. -i first_library.jar -i second_library.jar...
|
-o output-path |
생성된 코드의 파일 시스템 위치입니다. |
-c config-file |
라이브러리 래퍼 구성 파일의 파일 시스템 경로입니다. 자세한 내용은 구성 섹션을 참고하세요. |
-fa allow-list-file |
도구에서 래핑할 기호를 지정할 수 있는 필터 파일의 경로입니다. 자세한 내용은 필터 섹션을 참고하세요. |
-fb block-list-file |
래핑에서 제외된 기호가 포함된 필터 파일의 경로입니다. 자세한 내용은 필터 섹션을 참고하세요. |
--skip_deprecated_symbols |
래퍼 도구에서 @Deprecated 기호를 건너뛰도록 지시합니다. |
래퍼 구성 파일
라이브러리 래퍼 구성은 개발자가 코드 생성 프로세스를 제어할 수 있는 JSON 파일입니다. 파일은 다음 구조를 사용합니다.
{
// An array of type-specific configs. A type config is useful when a user wants to map
// a Java type to a manually defined C type without generating the code. For example, when a developer
// has their own implementation of the "java.lang.String" class, they can tell the generator to use it
// instead of generating it.
"type_configs": [
{
// [Required] Name of a fully qualified Java type.
"java_type": "java.lang.String",
// The C type that the java_type will be mapped to.
"map_to": "MyOwnStringImplementation",
// A header file that contains the declaration of the "map_to" type.
"source_of_definition": "my_wrappers/my_own_string_implementation.h",
// Controls if a value should be passed by pointer or value.
"pass_by_value": false
}
],
// An array of package-specific configs.
"package_configs": [
{
// [Required] A name of a Java package that this section regards. A wildchar * can be used at the
// end of the package name to apply this config to all packages whose name starts with this value.
"package_name": "androidx.core.app*",
// A subdirectory relative to the root directory where the generated code will be located.
"sub_directory": "androidx_generated/",
// If true, the generated file structure reflects the package name. For example, files generated
// for the package com.google.tools will be placed in the directory com/google/tools/.
"file_location_by_package_name": true,
// A prefix added to all class names from this package.
"code_prefix": "Gen",
// A prefix added to all generated file names from this package.
"file_prefix": = "gen_"
}
],
// An array of manually defined classes for wrapping. Defining classes manually is useful when a
// jar file with desired classes are not available or a user needs to wrap just a small part of an SDK.
"custom_classes": [
{
// [Required] A fully-qualified Java class name. To define inner class, use symbol "$", for example
// "class com.example.OuterClass$InnerClass".
"class_name": "class java.util.ArrayList<T>",
// List of methods.
"methods": [
"ArrayList()", // Example of a constructor.
"boolean add(T e)", // Example of a method that takes a generic parameter.
"T get(int index)", // Example of a method that returns a generic parameter.
"int size()" // Example of parameterless method.
]
},
]
}
필터 파일
래핑하려는 JAR 파일에서 일부 기호를 제외하는 것이 유용할 수 있습니다. 구성에서 필터 파일을 지정하여 기호를 제외할 수 있습니다. 필터 파일은 각 행이 래핑할 기호를 정의하는 간단한 텍스트 파일입니다. 필터 파일은 다음 문법을 사용합니다.
java-symbol-name java-jni-type-signature
다음은 필터 파일의 예입니다.
# Class filter
java.util.ArrayList Ljava.util.ArrayList;
# Method filter
java.util.ArrayList.lastIndexOf (Ljava.lang.Object;)I
# Field filter
android.view.KeyEvent.KEYCODE_ENTER I
-fa
매개변수를 사용하여 허용되는 기호를 지정하고 -fb
매개변수를 사용하여 차단된 기호를 지정하는 필터 파일을 구성에 제공합니다. 두 매개변수를 동시에 사용할 수 있습니다. 두 필터가 모두 제공되는 경우 기호는 허용 필터 파일에 정의되고 차단 필터 파일에 없으면 래핑됩니다.
시나리오 예
다음 클래스가 포함된 ChatLibrary.jar
JAR 파일을 래핑해야 한다고 가정해 보겠습니다.
public class ChatManager {
public static void sendMessage(int userId, String message) {...}
}
C 프로젝트에서는 이 JAR의 네이티브 래퍼를 생성하여 네이티브 Android 앱이 런타임 중에 이를 호출할 수 있도록 해야 합니다. 다음 명령어와 함께 라이브러리 래퍼를 사용하여 이 코드를 생성합니다.
java -jar lw.jar -i ChatLibrary.jar -o ./generated_code/
위의 명령어는 ./generated_code
디렉터리에 C 소스 코드를 생성합니다. 생성된 파일 chat_manager.h
에는 다음과 유사한 코드가 포함되어 있어 프로젝트에서 라이브러리를 호출할 수 있습니다.
#include "java/lang/string.h"
typedef struct ChatManager_ ChatManager;
void ChatManager_sendMessage(int32_t user_id, String* message);
상세한 시나리오 예는 라이브러리 래퍼 가이드를 참고하세요.
도구 세부정보
다음 섹션에서는 라이브러리 래퍼의 기능을 자세히 설명합니다.
출력 디렉터리 구조
모든 C 소스 및 헤더 파일은 래핑된 Java 클래스의 패키지 이름을 반영하는 하위 디렉터리에 있습니다. 예를 들어 지정된 JAR java.lang.Integer
의 래퍼 코드는 ./java/lang/integer.[h/cc]
디렉터리에 생성됩니다.
도구의 구성 파일을 사용하여 이 출력 동작을 제어할 수 있습니다.
객체 수명 주기
Java 객체는 C 코드에서 래퍼라고 하는 불투명 포인터로 표현됩니다. 래퍼는 상응하는 Java 객체의 JNI 참조를 관리합니다. 래퍼는 다음 시나리오에서 만들 수 있습니다.
MyClass_wrapJniReference(jobject jobj)
함수를 호출하여 기존 JNI 참조를 래핑합니다. 이 함수는 제공된 참조의 소유권을 가져가지 않고 자체 전역 JNI 참조를 만듭니다.- Java에서 생성자를 호출하는 것과 동일한 새 객체를 만듭니다.
MyClass_construct()
- 함수에서 새 래퍼를 반환합니다. 예:
Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)
래퍼가 더 이상 사용되지 않으면 모두 제거해야 합니다. 이렇게 하려면 전용 destroy()
함수 MyClass_destroy(MyClass* instance)
를 호출합니다.
래퍼를 반환하는 함수는 래퍼가 동일한 Java 인스턴스를 나타내는 경우에도 모든 호출에서 새 메모리를 할당합니다.
예를 들어 Java 메서드 Singleton.getInstance()
가 항상 동일한 인스턴스를 반환하는 경우 C 측의 상응하는 함수가 동일한 Java 인스턴스에 관한 래퍼의 새 인스턴스를 만듭니다.
Singleton* singleton_a = Singleton_getInsance();
Singleton* singleton_b = Singleton_getInsance();
// singleton_a and singleton_b are different pointers, even though they represent the same Java instance.
참조되지 않은 클래스 처리
제공된 JAR에서 클래스를 찾을 수 없는 경우 라이브러리 래퍼는 불투명 포인터와 다음 메서드로 구성된 기본 구현을 만듭니다.
wrapJniReference()
getJniReference()
destroy()
코드 생성 세부정보
실행하면 라이브러리 래퍼는 개발자가 도구에 제공한 JAR 파일의 공개 기호를 기반으로 C 코드를 생성합니다. 생성된 C 코드는 래핑된 Java 코드와 차이가 있을 수 있습니다. 예를 들어 C는 OOP나 일반 유형, 메서드 오버로드, 기타 Java 기능과 같은 기능을 지원하지 않습니다.
이러한 상황을 반영하는 생성된 C 코드는 C 개발자가 예상하는 코드 유형과 다를 수 있습니다. 다음 섹션의 예는 도구가 Java 코드에서 C를 생성하는 방법에 관한 컨텍스트를 제공합니다. 참고: 코드 스니펫의 다음 예에는 C/C++ 및 Java 코드 스니펫이 포함되어 있습니다. 이러한 스니펫은 지정된 각 상황에 맞는 코드를 도구에서 생성하는 방법을 보여주기 위한 것입니다.
클래스
클래스는 C에서 불투명 포인터로 표현됩니다.
C/C++
typedef struct MyClass_ MyClass;
Java
public class MyClass { ... }
불투명 포인터 인스턴스는 래퍼로 참조됩니다. 래퍼 도구는 각 클래스의 추가 지원 함수를 생성합니다. 위에 예로 든 클래스 MyClass
에서는 다음 함수가 생성됩니다.
// Wraps a JNI reference with MyClass. The 'jobj' must represent MyClass on the Java side.
MyClass* MyClass_wrapJniReference(jobject jobj);
// Return JNI reference associated with the 'MyClass' pointer.
jobject MyClass_getJniReference(const MyClass* object);
// Destroys the object and releases underlying JNI reference.
void MyClass_destroy(const MyClass* object);
생성자
공개 또는 기본 생성자가 있는 클래스는 특수 함수를 사용하여 표현됩니다.
C/C++
MyClass* MyClass_construct(String* data);
Java
public class MyClass {
public MyClass(String data) { ... }
}
메서드
메서드는 일반 함수로 표현됩니다. 함수 이름에는 원래 클래스 이름이 포함됩니다. 비정적 인스턴스 메서드를 나타내는 함수에는 함수 호출 대신 Java 객체를 나타내는 구조체를 가리키는 포인터가 첫 번째 매개변수로 있습니다. 이 접근 방식은 this
포인터와 유사합니다.
C/C++
Result* MyClass_doAction(const MyClass* my_class_instance, int32_t action_id, String* data);
int32_t MyClass_doAction(int32_t a, int32_t b);
Java
public class MyClass {
public Result doAction(int actionId, String data) { ... }
public static int doCalculations(int a, int b) { ... }
}
내부 클래스
내부 클래스는 일반 클래스와 밀접하게 표현됩니다. 단, 해당하는 C 구조의 이름에는 외부 클래스의 체이닝된 이름이 포함됩니다.
C/C++
typedef struct MyClass_InnerClass_ MyClass_InnerClass;
Java
public class MyClass {
public class InnerClass {...}
}
내부 클래스 메서드
내부 클래스 메서드는 다음과 같이 표현됩니다.
C/C++
bool MyClass_InnerClass_setValue(MyClass_InnerClass* my_class_inner_class_instance, int32_t value);
Java
public class MyClass {
public class InnerClass {
public boolean setValue(int value) { ... }
}
}
일반 유형
라이브러리 래퍼는 일반 유형을 직접 래핑하지 않습니다. 대신 이 도구는 일반 유형 인스턴스화를 위한 래퍼만 생성합니다.
예를 들어 클래스 MyGeneric<T>
가 API에 있고 이 클래스의 인스턴스화가 두 개라면(예: MyGeneric<Integer>
및 MyGeneric<String>
) 이 두 인스턴스화의 래퍼가 생성됩니다. 즉, 다른 유형 구성을 사용하여 MyGeneric<T>
유형의 새 인스턴스화를 만들 수 없습니다. 아래 예를 참고하세요.
C/C++
// result.h
typedef struct Result_Integer_ Result_Integer;
typedef struct Result_Float_ Result_Float;
Integer* Result_Integer_getResult(const Result_Integer* instance);
Float* Result_Float_getResult(const Result_Float* instance);
// data_processor.h
typedef struct DataProcessor_ DataProcessor;
Result_Integer* DataProcessor_processIntegerData(const DataProcessor* instance);
Result_Float* DataProcessor_processFloatData(constDataProcessor* instance);
Java
public class Result<T> {
public T getResult();
}
public class DataProcessor {
public Result<Integer> processIntegerData();
public Result<Float> processFloatData();
}
인터페이스 구현
implementInterface()
를 호출하고 각 인터페이스 메서드의 콜백 함수를 제공하여 C 인터페이스를 구현합니다. 인터페이스만 이러한 방식으로 구현할 수 있습니다. 클래스와 추상 클래스는 지원되지 않습니다. 아래 예를 참고하세요.
C/C++
// observer.h
typedef struct Observer_ Observer;
typedef void (*Observer_onAction1Callback)();
typedef void (*Observer_onAction2Callback)(int32_t data);
Observer* Observer_implementInterface(
Observer_onAction1Callback observer_on_action1_callback,
Observer_onAction2Callback observer_on_action2_callback);
Java
public interface Observer {
void onAction1();
void onAction2(int data);
}
public class Subject {
public void registerObserver(Observer observer);
}
사용 예는 다음과 같습니다.
void onAction1() {
// Handle action 1
}
void onAction2(int32_t data) {
// Handle action 2
}
Observer* observer = Observer_implementInterface(onAction1, onAction2);
Subject_registerObserver(subject, observer);
제한사항
라이브러리 래퍼 도구는 베타 버전입니다. 다음과 같은 제한사항이 발생할 수 있습니다.
지원되지 않는 Java 구조
라이브러리 래퍼 베타는 다음 구조를 지원하지 않습니다.
메서드 오버로드
C 언어에서는 이름이 같은 두 함수를 선언할 수 없습니다. 클래스가 메서드 오버로드를 사용하면 생성된 C 코드가 컴파일되지 않습니다. 해결 방법은 충분한 매개변수 집합을 가진 메서드 하나만 사용하는 것입니다. 나머지 함수는 필터를 사용하여 필터링할 수 있습니다. 이는 생성자에도 적용됩니다.
템플릿 형식 메서드
static final int
및static final String
이외의 필드배열
잠재적인 이름 충돌
Java 클래스가 C 코드에서 표현되는 방식으로 인해 매우 드물게 이름 충돌이 발생할 수 있습니다. 예를 들어 Foo
클래스 내 Foo<Bar>
클래스와 Bar
내부 클래스는 다음과 같이 C에서 같은 기호로 표현됩니다. typedef struct Foo_Bar_ Foo_Bar;
지원
라이브러리 래퍼에 문제가 있으면 Google에 알려주세요.
버그 검색 | 버그 신고 |
---|---|
엔지니어링 | bug_report |
문서 | bug_report |