정의 파일(Definition file)

정의 파일(Definition file)

Kotlin/Native를 사용하면 C 및 Objective-C 라이브러리를 소비해 그 기능을 Kotlin에서 사용할 수 있어요. cinterop이라는 특수 도구가 C나 Objective-C 라이브러리를 받아 그에 대응하는 Kotlin 바인딩을 생성하므로, 라이브러리의 메서드를 Kotlin 코드에서 평소처럼 사용할 수 있어요.

이 바인딩들을 생성하려면 각 라이브러리에 보통 라이브러리와 같은 이름의 정의 파일이 필요해요. 이 정의 파일은 라이브러리를 정확히 어떻게 소비해야 하는지 설명하는 프로퍼티 파일이에요. 사용 가능한 모든 프로퍼티의 목록을 확인하세요.

프로젝트를 작업할 때의 일반적인 흐름은 다음과 같아요:

  • 바인딩에 무엇을 포함할지 설명하는 .def 파일을 만들어요.
  • 생성된 바인딩을 Kotlin 코드에서 사용해요.
  • Kotlin/Native 컴파일러를 실행해 최종 실행 파일을 만들어요.

출처: Definition file

본문

정의 파일 만들기 및 구성하기

C 라이브러리용 정의 파일을 만들고 바인딩을 생성해 볼까요:

  • IDE에서 src 폴더를 선택하고 File | New | Directory로 새 디렉터리를 만들어요.
  • 새 디렉터리 이름을 nativeInterop/cinterop으로 지어요.

이것이 .def 파일 위치의 기본 규칙이지만, 다른 위치를 사용한다면 build.gradle.kts 파일에서 재정의할 수 있어요.

  • 새 하위 폴더를 선택하고 File | New | Filepng.def 파일을 만들어요.
  • 필요한 프로퍼티를 추가해요:
headers = png.h
headerFilter = png.h
package = png

compilerOpts.linux = -I/usr/include -I/usr/include/x86_64-linux-gnu
linkerOpts.osx = -L/opt/local/lib -L/usr/local/opt/png/lib -lpng
linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lpng
  • headers는 Kotlin 스텁을 생성할 헤더 파일들의 목록이에요. 여러 파일을 공백으로 구분해 추가할 수 있어요. 이 경우에는 png.h만 있어요. 참조된 파일은 지정된 경로(여기서는 /usr/include/png)에서 사용할 수 있어야 해요.
  • headerFilter는 정확히 무엇이 포함되는지를 나타내요. C에서는 한 파일이 #include 지시문으로 다른 파일을 참조할 때 모든 헤더도 함께 포함돼요. 때로는 그럴 필요가 없을 때가 있어서, 이 파라미터를 glob 패턴을 사용해 추가해서 조정할 수 있어요.

headerFilter는 외부 의존성(예: 시스템 stdint.h 헤더)을 interop 라이브러리로 가져오고 싶지 않을 때 사용할 수 있어요. 또한 라이브러리 크기 최적화와 시스템 및 제공된 Kotlin/Native 컴파일 환경 사이의 잠재적 충돌을 해결하는 데도 유용할 수 있어요.

  • 특정 플랫폼에 대한 동작을 수정해야 한다면 compilerOpts.osxcompilerOpts.linux 같은 형식을 사용해 옵션에 플랫폼별 값을 제공할 수 있어요. 이 경우에는 macOS(.osx 접미사)와 Linux(.linux 접미사)예요. 접미사가 없는 파라미터(예: linkerOpts=)도 가능하며 모든 플랫폼에 적용돼요.
  • 바인딩을 생성하려면 알림에서 Sync Now를 클릭해 Gradle 파일을 동기화해요.

Synchronize the Gradle files

바인딩이 생성된 후 IDE는 바인딩을 네이티브 라이브러리의 프록시 뷰로 사용할 수 있어요.

커맨드라인에서 cinterop 도구를 사용해 바인딩 생성을 구성할 수도 있어요.

프로퍼티

여기에 생성된 바이너리의 내용을 조정하기 위해 정의 파일에서 사용할 수 있는 전체 프로퍼티 목록이 있어요. 자세한 내용은 아래 해당 섹션을 참고하세요.

프로퍼티 설명
headers 바인딩에 포함할 라이브러리의 헤더 목록
modules 바인딩에 포함할 Objective-C 라이브러리의 Clang 모듈 목록
language 언어를 지정해요. 기본값은 C이며, 필요한 경우 Objective-C로 바꾸세요.
compilerOpts cinterop 도구가 C 컴파일러에 전달하는 컴파일러 옵션
linkerOpts cinterop 도구가 링커에 전달하는 링커 옵션
excludedFunctions 무시해야 할 함수 이름의 공백 구분 목록
staticLibraries Experimental. 정적 라이브러리를 .klib에 포함해요.
libraryPaths Experimental. cinterop 도구가 .klib에 포함할 라이브러리를 검색하는 디렉터리의 공백 구분 목록
package 생성된 Kotlin API의 패키지 접두사
headerFilter glob로 헤더를 필터링해 라이브러리를 임포트할 때 그 헤더만 포함해요.
excludeFilter 라이브러리를 임포트할 때 특정 헤더를 제외하며 headerFilter보다 우선해요.
strictEnums Kotlin enum으로 생성해야 할 enum의 공백 구분 목록
nonStrictEnums 정수 값으로 생성해야 할 enum의 공백 구분 목록
noStringConversion const char* 파라미터를 Kotlin String으로 자동 변환하지 말아야 할 함수의 공백 구분 목록
allowedOverloadsForCFunctions 기본적으로 C 함수는 고유한 이름을 가진다고 가정해요. 같은 이름의 함수가 여러 개라면 하나만 선택돼요. 하지만 allowedOverloadsForCFunctions에 이 함수들을 지정해서 이 동작을 바꿀 수 있어요.
disableDesignatedInitializerChecks non-designated Objective-C 이니셜라이저를 super() 생성자로 호출하는 것을 허용하지 않는 컴파일러 검사를 비활성화해요.
foreignExceptionMode Objective-C 코드의 예외를 ForeignException 타입의 Kotlin 예외로 감싸요.
userSetupHint 예를 들어 링커 오류 해결을 돕기 위한 사용자 지정 메시지를 추가해요.

프로퍼티 목록 외에도 정의 파일에 사용자 지정 선언을 포함할 수 있어요.

헤더 임포트하기

C 라이브러리에 Clang 모듈이 없고 대신 여러 헤더로 구성되어 있다면, headers 프로퍼티를 사용해 임포트해야 할 헤더를 지정하세요:

headers = curl/curl.h

glob로 헤더 필터링하기

.def 파일의 필터 프로퍼티를 사용해 glob로 헤더를 필터링할 수 있어요. 헤더의 선언을 포함하려면 headerFilter 프로퍼티를 사용하세요. 헤더가 glob 중 하나와 일치하면 그 선언이 바인딩에 포함돼요.

glob은 적절한 include 경로 요소에 상대적인 헤더 경로(예: time.h 또는 curl/curl.h)에 적용돼요. 따라서 라이브러리가 보통 #include <SomeLibrary/Header.h>로 포함된다면, 다음 필터로 헤더를 거를 수 있을 거예요:

headerFilter = SomeLibrary/**

headerFilter를 제공하지 않으면 모든 헤더가 포함돼요. 하지만 가능한 한 정확하게 glob를 지정해 headerFilter를 사용하길 권장해요. 그렇게 하면 생성된 라이브러리에 필요한 선언만 들어가요. 이는 개발 환경에서 Kotlin이나 도구를 업그레이드할 때 여러 문제를 피하는 데 도움이 돼요.

헤더 제외하기

특정 헤더를 제외하려면 excludeFilter 프로퍼티를 사용하세요. 지정된 헤더의 선언이 바인딩에 포함되지 않으므로, 중복되거나 문제 있는 헤더를 제거하고 컴파일을 최적화하는 데 도움이 돼요:

excludeFilter = SomeLibrary/time.h

같은 헤더가 headerFilter로 포함되고 excludeFilter로 제외되면, 지정된 헤더는 바인딩에 포함되지 않아요.

모듈 임포트하기

Objective-C 라이브러리에 Clang 모듈이 있다면, modules 프로퍼티를 사용해 임포트할 모듈을 지정하세요:

modules = UIKit

패키지 이름 설정하기

package 프로퍼티를 사용해 생성된 Kotlin API의 패키지 접두사를 지정하세요:

package = png

이 프로퍼티를 지정하지 않으면 컴파일러가 루트 패키지에 선언을 생성해요.

kotlinkotlinx.cinterop 이름은 예약되어 있어 패키지 접두사로 사용할 수 없어요.

컴파일러 및 링커 옵션 전달하기

compilerOpts 프로퍼티를 사용해 내부적으로 헤더를 분석하는 데 사용되는 C 컴파일러에 옵션을 전달하고, 최종 실행 파일을 링크하는 데 사용되는 링커에는 linkerOpts를 전달하세요. 예:

compilerOpts = -DFOO=bar
linkerOpts = -lpng

특정 타깃에만 적용되는 타깃별 옵션도 지정할 수 있어요:

compilerOpts = -DBAR=bar
compilerOpts.linux_x64 = -DFOO=foo1
compilerOpts.macos_x64 = -DFOO=foo2

이 구성으로 Linux에서는 헤더가 -DBAR=bar -DFOO=foo1로 분석되고 macOS에서는 -DBAR=bar -DFOO=foo2로 분석돼요. 어떤 정의 파일 옵션이든 공통 부분과 플랫폼별 부분을 모두 가질 수 있습니다.

특정 함수 무시하기

excludedFunctions 프로퍼티를 사용해 무시해야 할 함수 이름의 목록을 지정하세요. 헤더에 선언된 함수가 호출 가능하다고 보장할 수 없고 이를 자동으로 판단하기 어렵거나 불가능할 때 유용해요. 또한 이 프로퍼티를 사용해 interop 자체의 버그를 우회할 수도 있어요.

정적 라이브러리 포함하기

사용자 환경에 있다고 가정하는 것보다 정적 라이브러리를 제품과 함께 배포하는 것이 더 편리한 경우가 있어요. 정적 라이브러리를 .klib에 포함하려면 staticLibrarylibraryPaths 프로퍼티를 사용하세요:

headers = foo.h
staticLibraries = libfoo.a
libraryPaths = /opt/local/lib /usr/local/opt/curl/lib

위 스니펫이 주어지면 cinterop 도구는 /opt/local/lib/usr/local/opt/curl/lib에서 libfoo.a를 검색하고, 찾으면 라이브러리 바이너리를 klib에 포함해요.

이런 klib를 프로그램에서 사용하면 라이브러리가 자동으로 링크돼요.

enum 생성 구성하기

strictEnums 프로퍼티로 enum을 Kotlin enum으로 생성하거나, nonStrictEnums로 정수 값으로 생성하세요. enum이 이 두 목록 중 어디에도 없으면 휴리스틱을 기반으로 생성돼요.

문자열 변환 설정하기

noStringConversion 프로퍼티를 사용해 const char* 함수 파라미터를 Kotlin String으로 자동 변환하는 것을 비활성화하세요.

non-designated 이니셜라이저 호출 허용하기

기본적으로 Kotlin/Native 컴파일러는 non-designated Objective-C 이니셜라이저를 super() 생성자로 호출하는 것을 허용하지 않아요. 이 동작은 라이브러리에서 designated Objective-C 이니셜라이저가 제대로 표시되지 않았을 때 불편할 수 있어요. 이 컴파일러 검사를 비활성화하려면 disableDesignatedInitializerChecks 프로퍼티를 사용하세요.

Objective-C 예외 처리하기

기본적으로 Objective-C 예외가 Objective-C-to-Kotlin interop 경계에 도달해 Kotlin 코드에 들어오면 프로그램이 크래시돼요.

Objective-C 예외를 Kotlin으로 전파하려면 foreignExceptionMode = objc-wrap 프로퍼티로 래핑을 활성화하세요. 이 경우 Objective-C 예외는 ForeignException 타입을 얻게 되는 Kotlin 예외로 변환돼요.

링커 오류 해결 돕기

Kotlin 라이브러리가 C 또는 Objective-C 라이브러리에 의존할 때(예: CocoaPods 통합 사용) 링커 오류가 발생할 수 있어요. 의존하는 라이브러리가 기기 로컬에 설치되어 있지 않거나 프로젝트 빌드 스크립트에 명시적으로 구성되지 않은 경우 "Framework not found" 오류가 발생해요.

라이브러리 작성자라면 사용자 지정 메시지로 사용자들이 링커 오류를 해결하도록 도울 수 있어요. 그러려면 .def 파일에 userSetupHint=message 프로퍼티를 추가하거나 cinterop-Xuser-setup-hint 컴파일러 옵션을 전달하세요.

사용자 지정 선언 추가하기

때로는 바인딩을 생성하기 전에 라이브러리에 사용자 지정 C 선언을 추가해야 할 때가 있어요(예: 매크로의 경우). 이러한 선언이 있는 추가 헤더 파일을 만드는 대신, 구분 기호 시퀀스 ---만 포함하는 구분 줄 뒤의 .def 파일 끝에 직접 포함할 수 있어요:

headers = errno.h
---
static inline int getErrno() {
    return errno;
}

.def 파일의 이 부분은 헤더 파일의 일부로 취급되므로, 본문이 있는 함수는 static으로 선언해야 해요. 선언은 headers 목록의 파일을 포함한 후에 분석돼요.

커맨드라인을 사용해 바인딩 생성하기

정의 파일 외에도 cinterop 호출에서 대응하는 프로퍼티를 옵션으로 전달해 바인딩에 무엇을 포함할지 지정할 수 있어요.

다음은 컴파일된 png.klib 라이브러리를 생성하는 명령의 예시예요:

cinterop -def png.def -compiler-option -I/usr/local/include -o png

생성된 바인딩은 일반적으로 플랫폼별이므로, 여러 타깃을 위해 개발한다면 바인딩을 다시 생성해야 해요.

  • sysroot 검색 경로에 포함되지 않은 호스트 라이브러리의 경우 헤더가 필요할 수 있어요.
  • 구성 스크립트가 있는 일반적인 UNIX 라이브러리에서는 compilerOpts--cflags 옵션을 사용한 구성 스크립트의 출력(정확한 경로 없이)이 들어갈 가능성이 높아요.
  • --libs로 구성 스크립트의 출력을 linkerOpts 프로퍼티에 전달할 수 있어요.

다음 단계

  • C 상호 운용을 위한 바인딩
  • Swift/Objective-C와의 상호 운용

더 알아보기

  • C interop와 libcurl을 사용한 앱 만들기 – 튜토리얼
  • C, Objective-C, Swift 라이브러리 임포트