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 intstatic final String 이외의 필드

  • 배열

잠재적인 이름 충돌

Java 클래스가 C 코드에서 표현되는 방식으로 인해 매우 드물게 이름 충돌이 발생할 수 있습니다. 예를 들어 Foo 클래스 내 Foo<Bar> 클래스와 Bar 내부 클래스는 다음과 같이 C에서 같은 기호로 표현됩니다. typedef struct Foo_Bar_ Foo_Bar;

지원

라이브러리 래퍼에 문제가 있으면 Google에 알려주세요.

버그 검색 버그 신고
엔지니어링
문서