Calling C and Fortran Code

대부분의 코드는 Julia로 작성할 수 있지만, 이미 C와 Fortran으로 작성된 고품질의 성숙한 수치 계산 라이브러리가 많이 있습니다. 이러한 기존 코드를 쉽게 사용할 수 있도록 Julia는 C와 Fortran 함수를 호출하는 것을 간단하고 효율적으로 만듭니다. Julia는 "보일러플레이트 없음" 철학을 가지고 있습니다: 함수는 "접착" 코드, 코드 생성 또는 컴파일 없이 Julia에서 직접 호출할 수 있습니다 - 심지어 대화형 프롬프트에서도 가능합니다. 이는 적절한 호출을 @ccall 매크로(또는 덜 편리한 ccall 구문)를 사용하여 수행됩니다. 자세한 내용은 ccall syntax section를 참조하십시오.

코드가 호출되려면 공유 라이브러리로 제공되어야 합니다. 대부분의 C 및 Fortran 라이브러리는 이미 공유 라이브러리로 컴파일되어 제공되지만, GCC(또는 Clang)를 사용하여 코드를 직접 컴파일하는 경우 -shared-fPIC 옵션을 사용해야 합니다. Julia의 JIT에 의해 생성된 기계 명령어는 네이티브 C 호출과 동일하므로, 결과적인 오버헤드는 C 코드에서 라이브러리 함수를 호출하는 것과 동일합니다. [1]

기본적으로, Fortran 컴파일러는 generate mangled names (예: 함수 이름을 소문자 또는 대문자로 변환하고, 종종 언더스코어를 추가함)로 이름을 맹글링합니다. 따라서 Fortran 함수를 호출하려면 사용 중인 Fortran 컴파일러가 따르는 규칙에 해당하는 맹글링된 식별자를 전달해야 합니다. 또한, Fortran 함수를 호출할 때 모든 입력은 힙 또는 스택에 할당된 값에 대한 포인터로 전달되어야 합니다. 이는 일반적으로 힙에 할당되는 배열 및 기타 가변 객체뿐만 아니라, 일반적으로 스택에 할당되고 C 또는 Julia 호출 규약을 사용할 때 레지스터에 전달되는 정수 및 부동 소수점과 같은 스칼라 값에도 적용됩니다.

@ccall를 사용하여 라이브러리 함수 호출을 생성하는 구문은 다음과 같습니다:

  @ccall library.function_name(argvalue1::argtype1, ...)::returntype
  @ccall function_name(argvalue1::argtype1, ...)::returntype
  @ccall $function_pointer(argvalue1::argtype1, ...)::returntype

여기서 library는 문자열 상수 또는 리터럴입니다(하지만 아래의 Non-constant Function Specifications를 참조하십시오). 라이브러리는 생략할 수 있으며, 이 경우 함수 이름은 현재 프로세스에서 해결됩니다. 이 형식은 C 라이브러리 함수, Julia 런타임의 함수 또는 Julia에 연결된 애플리케이션의 함수를 호출하는 데 사용할 수 있습니다. 라이브러리에 대한 전체 경로도 지정할 수 있습니다. 또는 @ccall을 사용하여 Libdl.dlsym에 의해 반환된 것과 같은 함수 포인터 $function_pointer를 호출할 수도 있습니다. argtype는 C 함수 시그니처에 해당하고 argvalue는 함수에 전달될 실제 인수 값입니다.

Note

아래를 참조하여 map C types to Julia types하는 방법을 확인하세요.

완전하지만 간단한 예로, 다음은 대부분의 유닉스 계열 시스템에서 표준 C 라이브러리의 clock 함수를 호출합니다:

julia> t = @ccall clock()::Int32
2292761

julia> typeof(t)
Int32

clock는 인수를 받지 않으며 Int32를 반환합니다. 환경 변수의 값에 대한 포인터를 얻기 위해 getenv 함수를 호출하려면 다음과 같이 호출합니다:

julia> path = @ccall getenv("SHELL"::Cstring)::Cstring
Cstring(@0x00007fff5fbffc45)

julia> unsafe_string(path)
"/bin/bash"

실제로, 특히 재사용 가능한 기능을 제공할 때, 일반적으로 @ccall 사용을 인수 설정 및 C 또는 Fortran 함수가 지정하는 방식으로 오류를 확인하는 Julia 함수로 래핑합니다. 오류가 발생하면 일반 Julia 예외로 발생합니다. 이는 C 및 Fortran API가 오류 조건을 나타내는 방식에 대해 일관성이 없기 때문에 특히 중요합니다. 예를 들어, getenv C 라이브러리 함수는 다음과 같은 Julia 함수로 래핑되며, 이는 env.jl의 실제 정의의 단순화된 버전입니다:

function getenv(var::AbstractString)
    val = @ccall getenv(var::Cstring)::Cstring
    if val == C_NULL
        error("getenv: undefined variable: ", var)
    end
    return unsafe_string(val)
end

C getenv 함수는 C_NULL을 반환하여 오류를 나타내지만, 다른 표준 C 함수들은 -1, 0, 1 및 기타 특별한 값을 반환하는 등 다른 방식으로 오류를 나타냅니다. 이 래퍼는 호출자가 존재하지 않는 환경 변수를 가져오려고 할 경우 문제를 나타내는 예외를 발생시킵니다:

julia> getenv("SHELL")
"/bin/bash"

julia> getenv("FOOBAR")
ERROR: getenv: undefined variable: FOOBAR

여기 로컬 머신의 호스트 이름을 발견하는 약간 더 복잡한 예제가 있습니다.

function gethostname()
    hostname = Vector{UInt8}(undef, 256) # MAXHOSTNAMELEN
    err = @ccall gethostname(hostname::Ptr{UInt8}, sizeof(hostname)::Csize_t)::Int32
    Base.systemerror("gethostname", err != 0)
    hostname[end] = 0 # ensure null-termination
    return GC.@preserve hostname unsafe_string(pointer(hostname))
end

이 예제는 먼저 바이트 배열을 할당합니다. 그런 다음 C 라이브러리 함수 gethostname을 호출하여 배열을 호스트 이름으로 채웁니다. 마지막으로 호스트 이름 버퍼에 대한 포인터를 가져오고, 이를 널 종료 C 문자열로 가정하여 Julia 문자열로 변환합니다.

C 라이브러리에서 호출자가 호출자에게 전달할 메모리를 할당하고 이를 채우도록 요구하는 패턴은 일반적입니다. Julia에서 이렇게 메모리를 할당하는 것은 일반적으로 초기화되지 않은 배열을 생성하고 그 데이터에 대한 포인터를 C 함수에 전달함으로써 이루어집니다. 이것이 우리가 여기서 Cstring 유형을 사용하지 않는 이유입니다: 배열이 초기화되지 않았기 때문에 null 바이트를 포함할 수 있습니다. @ccall의 일환으로 Cstring으로 변환하면 포함된 null 바이트를 검사하므로 변환 오류가 발생할 수 있습니다.

pointer(hostname)unsafe_string으로 역참조하는 것은 안전하지 않은 작업입니다. 이는 hostname에 할당된 메모리에 접근해야 하는데, 이 메모리는 그 사이에 가비지 컬렉션에 의해 수거되었을 수 있습니다. 매크로 GC.@preserve는 이러한 일이 발생하는 것을 방지하며, 따라서 유효하지 않은 메모리 위치에 접근하는 것을 막습니다.

마지막으로, 경로를 통해 라이브러리를 지정하는 예가 있습니다. 다음 내용을 포함하여 공유 라이브러리를 생성합니다.

#include <stdio.h>

void say_y(int y)
{
    printf("Hello from C: got y = %d.\n", y);
}

gcc -fPIC -shared -o mylib.so mylib.c로 컴파일합니다. 그런 다음 라이브러리 이름으로 (절대) 경로를 지정하여 호출할 수 있습니다:

julia> @ccall "./mylib.so".say_y(5::Cint)::Cvoid
Hello from C: got y = 5.

Creating C-Compatible Julia Function Pointers

Julia 함수를 함수 포인터 인수를 받는 네이티브 C 함수에 전달하는 것이 가능합니다. 예를 들어, 다음과 같은 C 프로토타입과 일치하도록:

typedef returntype (*functiontype)(argumenttype, ...)

매크로 @cfunction는 줄리아 함수에 대한 C 호환 함수 포인터를 생성합니다. 4d61726b646f776e2e436f64652822222c2022406366756e6374696f6e2229_40726566의 인자는:

  1. 줄리아 함수
  2. 함수의 반환 유형
  3. 함수 시그니처에 해당하는 입력 유형의 튜플
Note

@ccall와 마찬가지로, 반환 유형과 입력 유형은 리터럴 상수여야 합니다.

Note

현재 플랫폼 기본 C 호출 규약만 지원됩니다. 이는 @cfunction으로 생성된 포인터가 32비트 Windows에서 WINAPI가 stdcall 함수를 기대하는 호출에 사용할 수 없지만, WIN64에서는 사용할 수 있음을 의미합니다(여기서 stdcall은 C 호출 규약과 통합됩니다).

Note

@cfunction를 통해 노출된 콜백 함수는 오류를 발생시켜서는 안 됩니다. 이는 예기치 않게 Julia 런타임으로 제어를 반환하게 하여 프로그램을 정의되지 않은 상태로 남길 수 있습니다.

전형적인 예는 표준 C 라이브러리의 qsort 함수로, 다음과 같이 선언됩니다:

void qsort(void *base, size_t nitems, size_t size,
           int (*compare)(const void*, const void*));

base 인자는 길이가 nitems인 배열에 대한 포인터로, 각 요소는 size 바이트입니다. compare는 두 요소 ab에 대한 포인터를 받아들이고, ab보다 앞에/뒤에 나타나야 하는 경우 0보다 작은/큰 정수를 반환하며 (어떤 순서도 허용되는 경우 0을 반환합니다).

이제, 우리가 정렬하고자 하는 값들의 1차원 배열 A가 있다고 가정해 보겠습니다. 우리는 qsort 함수를 사용하여 정렬할 것입니다(줄리아의 내장 sort 함수 대신). qsort를 호출하고 인수를 전달하기 전에, 비교 함수를 작성해야 합니다:

julia> function mycompare(a, b)::Cint
           return (a < b) ? -1 : ((a > b) ? +1 : 0)
       end;

qsort는 C int를 반환하는 비교 함수를 기대하므로, 반환 유형을 Cint로 주석을 달아야 합니다.

이 함수를 C로 전달하기 위해 매크로 @cfunction을 사용하여 그 주소를 얻습니다:

julia> mycompare_c = @cfunction(mycompare, Cint, (Ref{Cdouble}, Ref{Cdouble}));

@cfunction는 세 개의 인자를 요구합니다: 줄리아 함수(mycompare), 반환 타입(Cint), 그리고 입력 인자 타입의 리터럴 튜플, 이 경우 Cdouble(Float64) 요소로 배열을 정렬하기 위한 것입니다.

최종 qsort 호출은 다음과 같습니다:

julia> A = [1.3, -2.7, 4.4, 3.1];

julia> @ccall qsort(A::Ptr{Cdouble}, length(A)::Csize_t, sizeof(eltype(A))::Csize_t, mycompare_c::Ptr{Cvoid})::Cvoid

julia> A
4-element Vector{Float64}:
 -2.7
  1.3
  3.1
  4.4

예제에서 보여주듯이, 원래의 Julia 배열 A는 이제 정렬되었습니다: [-2.7, 1.3, 3.1, 4.4]. Julia takes care of converting the array to a Ptr{Cdouble}), 요소 유형의 크기를 바이트 단위로 계산하는 것과 같은 작업을 수행합니다.

재미로, mycompareprintln("mycompare($a, $b)") 라인을 삽입해 보세요. 이렇게 하면 qsort가 수행하는 비교를 볼 수 있고, 실제로 전달한 Julia 함수를 호출하고 있는지 확인할 수 있습니다.

Mapping C Types to Julia

선언된 C 타입과 Julia에서의 선언이 정확히 일치하는 것이 중요합니다. 불일치가 발생하면 한 시스템에서 제대로 작동하는 코드가 다른 시스템에서는 실패하거나 불확실한 결과를 생성할 수 있습니다.

C 함수 호출 과정에서 C 헤더 파일이 어디에서도 사용되지 않음을 유의하십시오: Julia 유형과 호출 서명이 C 헤더 파일의 내용을 정확하게 반영하도록 하는 것은 귀하의 책임입니다.[2]

Automatic Type Conversion

줄리아는 각 인수를 지정된 유형으로 변환하기 위해 Base.cconvert 함수에 대한 호출을 자동으로 삽입합니다. 예를 들어, 다음 호출:

@ccall "libfoo".foo(x::Int32, y::Float64)::Cvoid

이렇게 작성된 것처럼 행동할 것입니다:

c_x = Base.cconvert(Int32, x)
c_y = Base.cconvert(Float64, y)
GC.@preserve c_x c_y begin
    @ccall "libfoo".foo(
        Base.unsafe_convert(Int32, c_x)::Int32,
        Base.unsafe_convert(Float64, c_y)::Float64
    )::Cvoid
end

Base.cconvert는 일반적으로 convert를 호출하지만, C에 전달하기에 더 적합한 임의의 새로운 객체를 반환하도록 정의할 수 있습니다. 이는 C 코드에서 접근할 메모리의 모든 할당을 수행하는 데 사용되어야 합니다. 예를 들어, 이는 객체(예: 문자열)의 Array를 포인터 배열로 변환하는 데 사용됩니다.

Base.unsafe_convertPtr 유형으로 변환하는 기능을 처리합니다. 이는 객체를 네이티브 포인터로 변환하면 가비지 컬렉터로부터 객체가 숨겨져 조기에 해제될 수 있기 때문에 안전하지 않은 것으로 간주됩니다.

Type Correspondences

먼저, 관련된 줄리아 타입 용어를 검토해 봅시다:

Syntax / KeywordExampleDescription
mutable structBitSet"Leaf Type" :: A group of related data that includes a type-tag, is managed by the Julia GC, and is defined by object-identity. The type parameters of a leaf type must be fully defined (no TypeVars are allowed) in order for the instance to be constructed.
abstract typeAny, AbstractArray{T, N}, Complex{T}"Super Type" :: A super-type (not a leaf-type) that cannot be instantiated, but can be used to describe a group of types.
T{A}Vector{Int}"Type Parameter" :: A specialization of a type (typically used for dispatch or storage optimization).
"TypeVar" :: The T in the type parameter declaration is referred to as a TypeVar (short for type variable).
primitive typeInt, Float64"Primitive Type" :: A type with no fields, but a size. It is stored and defined by-value.
structPair{Int, Int}"Struct" :: A type with all fields defined to be constant. It is defined by-value, and may be stored with a type-tag.
ComplexF64 (isbits)"Is-Bits" :: A primitive type, or a struct type where all fields are other isbits types. It is defined by-value, and is stored without a type-tag.
struct ...; endnothing"Singleton" :: a Leaf Type or Struct with no fields.
(...) or tuple(...)(1, 2, 3)"Tuple" :: an immutable data-structure similar to an anonymous struct type, or a constant array. Represented as either an array or a struct.

Bits Types

특별한 유형이 몇 가지 있으며, 다른 유형이 동일하게 동작하도록 정의될 수 없다는 점에 유의해야 합니다:

  • Float32

    C(또는 Fortran의 REAL*4)의 float 타입과 정확히 일치합니다.

  • Float64

    C(또는 Fortran의 REAL*8)에서 double 유형과 정확히 일치합니다.

  • 복소수F32

    C(또는 Fortran의 COMPLEX*8)에서 complex float 유형과 정확히 일치합니다.

  • 복소수F64

    C(또는 Fortran의 COMPLEX*16)에서 complex double 유형과 정확히 일치합니다.

  • 서명됨

    정확히 C의 signed 타입 주석(또는 Fortran의 모든 INTEGER 타입)에 해당합니다. Signed의 하위 타입이 아닌 모든 Julia 타입은 부호 없는 것으로 간주됩니다.

  • Ref{T}

    Ptr{T}처럼 동작하며 Julia GC를 통해 메모리를 관리할 수 있습니다.

  • Array{T,N}

    배열이 C에 Ptr{T} 인수로 전달될 때, 재해석 캐스트되지 않습니다: Julia는 배열의 요소 유형이 T와 일치해야 하며, 첫 번째 요소의 주소가 전달됩니다.

    따라서, Array에 잘못된 형식의 데이터가 포함되어 있다면, trunc.(Int32, A)와 같은 호출을 사용하여 명시적으로 변환해야 합니다.

    배열 A를 데이터를 미리 변환하지 않고(예: Float64 배열을 해석되지 않은 바이트에서 작동하는 함수에 전달하기 위해) 다른 유형의 포인터로 전달하려면, 인수를 Ptr{Cvoid}로 선언할 수 있습니다.

    배열의 eltype Ptr{T}Ptr{Ptr{T}} 인수로 전달되면, Base.cconvert는 각 요소가 자신의 4d61726b646f776e2e436f64652822222c2022426173652e63636f6e766572742229_40726566 버전으로 대체된 배열의 널 종료 복사본을 먼저 만들려고 시도합니다. 이를 통해 예를 들어, Vector{String} 유형의 argv 포인터 배열을 Ptr{Ptr{Cchar}} 유형의 인수로 전달할 수 있습니다.

현재 지원하는 모든 시스템에서 기본 C/C++ 값 유형은 다음과 같이 Julia 유형으로 변환될 수 있습니다. 모든 C 유형은 C로 접두사가 붙은 동일한 이름의 해당 Julia 유형을 가지고 있습니다. 이는 이식 가능한 코드를 작성할 때 도움이 될 수 있습니다(그리고 C의 int가 Julia의 Int와 같지 않다는 것을 기억하는 데 도움이 됩니다).

시스템 독립형 타입

C nameFortran nameStandard Julia AliasJulia Base Type
unsigned charCHARACTERCucharUInt8
bool (_Bool in C99+)CucharUInt8
shortINTEGER*2, LOGICAL*2CshortInt16
unsigned shortCushortUInt16
int, BOOL (C, typical)INTEGER*4, LOGICAL*4CintInt32
unsigned intCuintUInt32
long longINTEGER*8, LOGICAL*8ClonglongInt64
unsigned long longCulonglongUInt64
intmax_tCintmax_tInt64
uintmax_tCuintmax_tUInt64
floatREAL*4iCfloatFloat32
doubleREAL*8CdoubleFloat64
complex floatCOMPLEX*8ComplexF32Complex{Float32}
complex doubleCOMPLEX*16ComplexF64Complex{Float64}
ptrdiff_tCptrdiff_tInt
ssize_tCssize_tInt
size_tCsize_tUInt
voidCvoid
void and [[noreturn]] or _NoreturnUnion{}
void*Ptr{Cvoid} (or similarly Ref{Cvoid})
T* (where T represents an appropriately defined type)Ref{T} (T may be safely mutated only if T is an isbits type)
char* (or char[], e.g. a string)CHARACTER*NCstring if null-terminated, or Ptr{UInt8} if not
char** (or *char[])Ptr{Ptr{UInt8}}
jl_value_t* (any Julia Type)Any
jl_value_t* const* (a reference to a Julia value)Ref{Any} (const, since mutation would require a write barrier, which is not possible to insert correctly)
va_argNot supported
... (variadic function specification)T... (where T is one of the above types, when using the ccall function)
... (variadic function specification); va_arg1::T, va_arg2::S, etc. (only supported with @ccall macro)

Cstring 유형은 본질적으로 Ptr{UInt8}의 동의어입니다. 단, Cstring으로 변환할 때 Julia 문자열에 임베디드 널 문자가 포함되어 있으면 오류가 발생합니다(이는 C 루틴이 널을 종료 문자로 처리할 경우 문자열이 조용히 잘리게 됩니다). 널 종료를 가정하지 않는 C 루틴에 char*를 전달하는 경우(예: 명시적인 문자열 길이를 전달하는 경우) 또는 Julia 문자열에 널이 포함되어 있지 않다고 확신하고 검사를 건너뛰고 싶다면 Ptr{UInt8}를 인수 유형으로 사용할 수 있습니다. Cstringccall 반환 유형으로도 사용할 수 있지만, 이 경우에는 명백히 추가 검사를 도입하지 않으며 호출의 가독성을 향상시키기 위한 것입니다.

시스템 의존형

C nameStandard Julia AliasJulia Base Type
charCcharInt8 (x86, x86_64), UInt8 (powerpc, arm)
longClongInt (UNIX), Int32 (Windows)
unsigned longCulongUInt (UNIX), UInt32 (Windows)
wchar_tCwchar_tInt32 (UNIX), UInt16 (Windows)
Note

Fortran을 호출할 때, 모든 입력은 힙 또는 스택에 할당된 값에 대한 포인터로 전달되어야 하므로, 위의 모든 타입 대응은 해당 타입 사양 주위에 추가적인 Ptr{..} 또는 Ref{..} 래퍼를 포함해야 합니다.

Warning

문자열 인수(char*)의 경우, 줄리아 타입은 Cstring이어야 합니다(널 종료 데이터가 예상되는 경우). 그렇지 않으면 Ptr{Cchar} 또는 Ptr{UInt8} 중 하나여야 합니다(이 두 포인터 타입은 동일한 효과를 가집니다). 배열 인수(T[] 또는 T*)의 경우에도 줄리아 타입은 다시 Ptr{T}여야 하며, Vector{T}가 아닙니다.

Warning

줄리아의 Char 타입은 32비트로, 모든 플랫폼에서 와이드 캐릭터 타입(wchar_t 또는 wint_t)과 동일하지 않습니다.

Warning

Union{}의 반환 유형은 함수가 반환하지 않음을 의미합니다. 즉, C++11의 [[noreturn]] 또는 C11의 _Noreturn과 같습니다 (예: jl_throw 또는 longjmp). 반환값이 없는 함수(void)에 대해서는 이 유형을 사용하지 마십시오. 그런 경우에는 대신 Cvoid를 사용하십시오.

Note

wchar_t* 인수의 경우, Julia 타입은 Cwstring (C 루틴이 널 종료 문자열을 기대하는 경우) 또는 그렇지 않으면 Ptr{Cwchar_t} 여야 합니다. 또한 Julia의 UTF-8 문자열 데이터는 내부적으로 널 종료되어 있으므로, 복사 없이 널 종료 데이터를 기대하는 C 함수에 전달할 수 있습니다 (하지만 Cwstring 타입을 사용하면 문자열 자체에 널 문자가 포함되어 있을 경우 오류가 발생합니다).

Note

C 함수는 char** 유형의 인수를 사용하여 호출할 수 있으며, Julia에서는 Ptr{Ptr{UInt8}} 유형을 사용하여 호출할 수 있습니다. 예를 들어, 다음과 같은 형태의 C 함수:

int main(int argc, char **argv);

다음 Julia 코드를 통해 호출될 수 있습니다:

argv = [ "a.out", "arg1", "arg2" ]
@ccall main(length(argv)::Int32, argv::Ptr{Ptr{UInt8}})::Int32
Note

Fortran 함수가 가변 길이 문자열인 character(len=*) 유형을 사용할 때 문자열 길이는 숨겨진 인수로 제공됩니다. 이러한 인수의 유형과 위치는 컴파일러에 따라 다르며, 컴파일러 공급업체는 일반적으로 Csize_t를 유형으로 사용하고 숨겨진 인수를 인수 목록의 끝에 추가하는 것을 기본값으로 설정합니다. 일부 컴파일러(GNU)의 경우 이 동작은 고정되어 있지만, 다른 컴파일러(Intel, PGI)는 선택적으로 숨겨진 인수를 문자 인수 바로 뒤에 배치할 수 있도록 허용합니다. 예를 들어, 다음과 같은 형태의 Fortran 서브루틴

subroutine test(str1, str2)
character(len=*) :: str1,str2

다음 Julia 코드를 통해 호출할 수 있으며, 길이가 추가됩니다.

str1 = "foo"
str2 = "bar"
ccall(:test, Cvoid, (Ptr{UInt8}, Ptr{UInt8}, Csize_t, Csize_t),
                    str1, str2, sizeof(str1), sizeof(str2))
Warning

Fortran 컴파일러는 포인터, 가정된 형태(:) 및 가정된 크기(*) 배열에 대해 다른 숨겨진 인수를 추가할 수 있습니다. 이러한 동작은 ISO_C_BINDING을 사용하고 서브루틴 정의에 bind(c)를 포함시킴으로써 피할 수 있으며, 이는 상호 운용 가능한 코드에 강력히 권장됩니다. 이 경우 숨겨진 인수가 없지만, 일부 언어 기능(예: character(len=1)만 문자열을 전달하는 것이 허용됨)의 대가를 치르게 됩니다.

Note

C 함수가 Cvoid를 반환하도록 선언되면, Julia에서는 nothing 값을 반환합니다.

Struct Type Correspondences

C나 Fortran90의 TYPE (또는 F77의 일부 변형에서 STRUCTURE / RECORD)와 같은 복합 유형은 동일한 필드 레이아웃을 가진 struct 정의를 생성하여 Julia에서 미러링할 수 있습니다.

재귀적으로 사용될 때, isbits 타입은 인라인으로 저장됩니다. 다른 모든 타입은 데이터에 대한 포인터로 저장됩니다. C에서 다른 구조체 내부에 값으로 사용되는 구조체를 미러링할 때, 필드를 수동으로 복사하려고 시도하지 않는 것이 중요합니다. 이렇게 하면 올바른 필드 정렬이 유지되지 않습니다. 대신, isbits 구조체 타입을 선언하고 그것을 사용해야 합니다. 이름이 없는 구조체는 Julia로의 변환에서 불가능합니다.

줄리아에서는 패킹된 구조체 및 공용체 선언이 지원되지 않습니다.

union의 근사값을 얻으려면, 사전에 가장 큰 크기를 가질 필드를 알고 있어야 합니다(패딩을 포함할 수 있음). 필드를 Julia로 변환할 때, Julia 필드를 해당 유형만으로 선언하십시오.

매개변수의 배열은 NTuple로 표현할 수 있습니다. 예를 들어, C 표기법으로 작성된 구조체는 다음과 같습니다.

struct B {
    int A[3];
};

b_a_2 = B.A[2];

줄리아로 작성할 수 있습니다.

struct B
    A::NTuple{3, Cint}
end

b_a_2 = B.A[3]  # note the difference in indexing (1-based in Julia, 0-based in C)

크기 미정 배열(C99 준수 가변 길이 구조체는 [] 또는 [0]로 지정됨)은 직접 지원되지 않습니다. 이러한 배열을 처리하는 가장 좋은 방법은 바이트 오프셋을 직접 다루는 것입니다. 예를 들어, C 라이브러리가 적절한 문자열 유형을 선언하고 이에 대한 포인터를 반환하는 경우:

struct String {
    int strlen;
    char data[];
};

줄리아에서는 문자열의 일부에 독립적으로 접근하여 해당 문자열의 복사본을 만들 수 있습니다:

str = from_c::Ptr{Cvoid}
len = unsafe_load(Ptr{Cint}(str))
unsafe_string(str + Core.sizeof(Cint), len)

Type Parameters

@ccall@cfunction에 대한 타입 인수는 사용이 정의될 때 정적으로 평가됩니다. 따라서 이들은 변수 대신 리터럴 튜플의 형태를 가져야 하며, 지역 변수를 참조할 수 없습니다.

이것은 이상한 제한처럼 들릴 수 있지만, C는 Julia와 같은 동적 언어가 아니기 때문에 그 함수는 정적으로 알려진 고정된 시그니처를 가진 인수 유형만 허용한다는 점을 기억하세요.

그러나 C ABI를 계산하기 위해서는 타입 레이아웃이 정적으로 알려져야 하며, 함수의 정적 매개변수는 이 정적 환경의 일부로 간주됩니다. 함수의 정적 매개변수는 타입 레이아웃에 영향을 미치지 않는 한 호출 서명에서 타입 매개변수로 사용될 수 있습니다. 예를 들어, f(x::T) where {T} = @ccall valid(x::Ptr{T})::Ptr{T}는 유효합니다. 왜냐하면 Ptr는 항상 단어 크기의 원시 타입이기 때문입니다. 그러나 g(x::T) where {T} = @ccall notvalid(x::T)::T는 유효하지 않습니다. 왜냐하면 T의 타입 레이아웃이 정적으로 알려져 있지 않기 때문입니다.

SIMD Values

참고: 이 기능은 현재 64비트 x86 및 AArch64 플랫폼에서만 구현되어 있습니다.

C/C++ 루틴에 인수나 반환 값으로 네이티브 SIMD 유형이 있는 경우, 해당하는 Julia 유형은 SIMD 유형에 자연스럽게 매핑되는 VecElement의 동질적인 튜플입니다. 구체적으로:

  • 튜플은 SIMD 타입과 동일한 크기여야 합니다. 예를 들어, x86에서 __m128을 나타내는 튜플은 16바이트의 크기를 가져야 합니다.
  • 튜플의 요소 유형은 VecElement{T}의 인스턴스여야 하며, 여기서 T는 1, 2, 4 또는 8 바이트의 원시 유형입니다.

예를 들어, AVX 인트린식을 사용하는 이 C 루틴을 고려해 보십시오:

#include <immintrin.h>

__m256 dist( __m256 a, __m256 b ) {
    return _mm256_sqrt_ps(_mm256_add_ps(_mm256_mul_ps(a, a),
                                        _mm256_mul_ps(b, b)));
}

다음 Julia 코드는 ccall을 사용하여 dist를 호출합니다:

const m256 = NTuple{8, VecElement{Float32}}

a = m256(ntuple(i -> VecElement(sin(Float32(i))), 8))
b = m256(ntuple(i -> VecElement(cos(Float32(i))), 8))

function call_dist(a::m256, b::m256)
    @ccall "libdist".dist(a::m256, b::m256)::m256
end

println(call_dist(a,b))

호스트 머신은 필수 SIMD 레지스터를 갖추고 있어야 합니다. 예를 들어, 위의 코드는 AVX 지원이 없는 호스트에서는 작동하지 않습니다.

Memory Ownership

malloc/free

메모리 할당 및 해제는 사용 중인 라이브러리의 적절한 정리 루틴을 호출하여 처리해야 하며, 이는 모든 C 프로그램과 마찬가지입니다. Julia에서 Libc.free로부터 받은 객체를 해제하려고 하지 마십시오. 이는 잘못된 라이브러리를 통해 free 함수가 호출되어 프로세스가 중단될 수 있습니다. 반대로, Julia에서 할당된 객체를 외부 라이브러리에서 해제하도록 전달하는 것도 마찬가지로 유효하지 않습니다.

When to use T, Ptr{T} and Ref{T}

In Julia code wrapping calls to external C routines, ordinary (non-pointer) data should be declared to be of type T inside the @ccall, as they are passed by value. For C code accepting pointers, Ref{T} should generally be used for the types of input arguments, allowing the use of pointers to memory managed by either Julia or C through the implicit call to Base.cconvert. In contrast, pointers returned by the C function called should be declared to be of the output type Ptr{T}, reflecting that the memory pointed to is managed by C only. Pointers contained in C structs should be represented as fields of type Ptr{T} within the corresponding Julia struct types designed to mimic the internal structure of corresponding C structs.

줄리아 코드에서 외부 포트란 루틴을 호출할 때, 모든 입력 인자는 Ref{T} 유형으로 선언해야 합니다. 포트란은 모든 변수를 메모리 위치에 대한 포인터로 전달하기 때문입니다. 반환 유형은 포트란 서브루틴의 경우 Cvoid이거나, 유형 T를 반환하는 포트란 함수의 경우 T여야 합니다.

Mapping C Functions to Julia

@ccall / @cfunction argument translation guide

C 인수 목록을 Julia로 변환하기 위해:

  • T, 여기서 T는 기본 데이터 유형 중 하나입니다: char, int, long, short, float, double, complex, enum 또는 이들의 typedef 동등물

    • T, 여기서 T는 위의 표에 따른 동등한 Julia Bits Type입니다.
    • Tenum인 경우, 인수 유형은 Cint 또는 Cuint와 동일해야 합니다.
    • 인수 값이 복사됩니다 (값에 의해 전달됨)
  • struct T (구조체에 대한 typedef 포함)

    • T, 여기서 T는 Julia 리프 타입입니다.
    • 인수 값이 복사됩니다 (값에 의해 전달됨)
  • void*

    • 이 매개변수가 어떻게 사용되는지에 따라 다릅니다. 먼저 이것을 의도된 포인터 유형으로 번역한 다음, 이 목록의 나머지 규칙을 사용하여 줄리아 동등성을 결정합니다.
    • 이 인자는 실제로 알 수 없는 포인터라면 Ptr{Cvoid}로 선언될 수 있습니다.
  • jl_value_t*

    • 모든
    • 인수 값은 유효한 줄리아 객체여야 합니다.
  • jl_value_t* const*

    • Ref{아무}
    • 인수 목록은 유효한 Julia 객체여야 합니다 (또는 C_NULL).
    • 출력 매개변수로 사용할 수 없으며, 사용자가 객체를 GC(가비지 컬렉션)로 보존하도록 별도로 조치할 수 없는 경우입니다.
  • T*

    • Ref{T}, 여기서 TT에 해당하는 Julia 타입입니다.
    • 인수 값은 inlinealloc 유형일 경우 복사됩니다(여기에는 isbits가 포함됨). 그렇지 않으면 값은 유효한 줄리아 객체여야 합니다.
  • T (*)(...) (예: 함수에 대한 포인터)

    • Ptr{Cvoid} (이 포인터를 생성하려면 @cfunction를 명시적으로 사용해야 할 수 있습니다)
  • ... (예: 가변 인자)

    • [for ccall]: T..., 여기서 T는 나머지 모든 인수의 단일 Julia 타입입니다.
    • [for @ccall]: ; va_arg1::T, va_arg2::S, etc, 여기서 TS는 줄리아 타입입니다 (즉, 일반 인수와 가변 인수를 ;로 구분합니다)
    • 현재 @cfunction에 의해 지원되지 않음
  • va_arg

    • ccall 또는 @cfunction에 의해 지원되지 않음

@ccall / @cfunction return type translation guide

C 반환 유형을 줄리아로 변환하기 위해:

  • 무효

    • Cvoid (이것은 단일 인스턴스 nothing::Cvoid를 반환합니다)
  • T, 여기서 T는 기본 데이터 유형 중 하나입니다: char, int, long, short, float, double, complex, enum 또는 이들의 typedef 동등물

    • T, 여기서 T는 위의 표에 따른 동등한 Julia Bits Type입니다.
    • Tenum인 경우, 인수 유형은 Cint 또는 Cuint와 동일해야 합니다.
    • 인수 값이 복사됩니다 (값에 의해 반환됨)
  • struct T (구조체에 대한 typedef 포함)

    • T, 여기서 T는 Julia Leaf Type입니다.
    • 인수 값이 복사됩니다 (값에 의해 반환됨)
  • void*

    • 이 매개변수가 어떻게 사용되는지에 따라 다릅니다. 먼저 이것을 의도된 포인터 유형으로 번역한 다음, 이 목록의 나머지 규칙을 사용하여 줄리아 동등성을 결정합니다.
    • 이 인자는 실제로 알 수 없는 포인터라면 Ptr{Cvoid}로 선언될 수 있습니다.
  • jl_value_t*

    • 모든
    • 인수 값은 유효한 줄리아 객체여야 합니다.
  • jl_value_t**

    • Ptr{Any} (Ref{Any}는 반환 타입으로 유효하지 않습니다)
  • T*

    • 메모리가 이미 Julia에 의해 소유되었거나 isbits 타입이며 널이 아님이 알려진 경우:

      • Ref{T}, 여기서 TT에 해당하는 Julia 타입입니다.
      • Ref{Any}의 반환 유형은 유효하지 않습니다. Any(즉, jl_value_t*에 해당) 또는 Ptr{Any}(즉, jl_value_t**에 해당) 중 하나여야 합니다.
      • C 절대 Tisbits 타입인 경우 Ref{T}를 통해 반환된 메모리를 수정해서는 안 됩니다.
    • C가 메모리를 소유하는 경우:

      • Ptr{T}, 여기서 TT에 해당하는 Julia 타입입니다.
  • T (*)(...) (예: 함수에 대한 포인터)

    • Ptr{Cvoid}를 사용하여 이를 Julia에서 직접 호출하려면, 이를 @ccall의 첫 번째 인수로 전달해야 합니다. Indirect Calls를 참조하세요.

Passing Pointers for Modifying Inputs

C는 여러 개의 반환 값을 지원하지 않기 때문에, 종종 C 함수는 함수가 수정할 데이터에 대한 포인터를 받습니다. @ccall 내에서 이를 수행하려면, 먼저 적절한 유형의 Ref{T} 안에 값을 캡슐화해야 합니다. 이 Ref 객체를 인수로 전달하면, Julia는 자동으로 캡슐화된 데이터에 대한 C 포인터를 전달합니다:

width = Ref{Cint}(0)
range = Ref{Cfloat}(0)
@ccall foo(width::Ref{Cint}, range::Ref{Cfloat})::Cvoid

반환 시, widthrange의 내용은 foo에 의해 변경되었다면 width[]range[]를 통해 검색할 수 있습니다. 즉, 이들은 제로 차원 배열처럼 작동합니다.

C Wrapper Examples

간단한 Ptr 타입을 반환하는 C 래퍼의 예제로 시작해 보겠습니다:

mutable struct gsl_permutation
end

# The corresponding C signature is
#     gsl_permutation * gsl_permutation_alloc (size_t n);
function permutation_alloc(n::Integer)
    output_ptr = @ccall "libgsl".gsl_permutation_alloc(n::Csize_t)::Ptr{gsl_permutation}
    if output_ptr == C_NULL # Could not allocate memory
        throw(OutOfMemoryError())
    end
    return output_ptr
end

GNU Scientific Library (여기서는 :libgsl을 통해 접근할 수 있다고 가정함) 은 C 함수 gsl_permutation_alloc의 반환 타입으로 불투명 포인터 gsl_permutation *를 정의합니다. 사용자 코드가 gsl_permutation 구조체 내부를 들여다볼 필요가 없기 때문에, 해당하는 줄리아 래퍼는 내부 필드가 없는 새로운 타입 선언 gsl_permutation만 필요하며, 이 타입은 Ptr 타입의 타입 매개변수에 배치되는 것이 유일한 목적입니다. ccall의 반환 타입은 Ptr{gsl_permutation}으로 선언되며, 이는 output_ptr이 가리키는 메모리가 C에 의해 제어되기 때문입니다.

입력 n은 값으로 전달되므로 함수의 입력 시그니처는 ::Csize_t로 간단히 선언되며 RefPtr이 필요하지 않습니다. (만약 래퍼가 Fortran 함수를 호출하고 있었다면, 해당 함수의 입력 시그니처는 ::Ref{Csize_t}가 되었을 것입니다. 왜냐하면 Fortran 변수는 포인터로 전달되기 때문입니다.) 또한, nCsize_t 정수로 변환 가능한 모든 유형이 될 수 있습니다; ccall는 암묵적으로 Base.cconvert(Csize_t, n)를 호출합니다.

여기 해당 소멸자를 감싸는 두 번째 예제가 있습니다:

# The corresponding C signature is
#     void gsl_permutation_free (gsl_permutation * p);
function permutation_free(p::Ptr{gsl_permutation})
    @ccall "libgsl".gsl_permutation_free(p::Ptr{gsl_permutation})::Cvoid
end

여기 줄리아 배열을 전달하는 세 번째 예제가 있습니다:

# The corresponding C signature is
#    int gsl_sf_bessel_Jn_array (int nmin, int nmax, double x,
#                                double result_array[])
function sf_bessel_Jn_array(nmin::Integer, nmax::Integer, x::Real)
    if nmax < nmin
        throw(DomainError())
    end
    result_array = Vector{Cdouble}(undef, nmax - nmin + 1)
    errorcode = @ccall "libgsl".gsl_sf_bessel_Jn_array(
                    nmin::Cint, nmax::Cint, x::Cdouble, result_array::Ref{Cdouble})::Cint
    if errorcode != 0
        error("GSL error code $errorcode")
    end
    return result_array
end

C 함수 wrapped는 정수 오류 코드를 반환하며, Bessel J 함수의 실제 평가 결과는 Julia 배열 result_array를 채웁니다. 이 변수는 Ref{Cdouble}로 선언되며, 그 메모리는 Julia에 의해 할당되고 관리됩니다. Base.cconvert(Ref{Cdouble}, result_array)에 대한 암묵적인 호출은 Julia 포인터를 C에서 이해할 수 있는 형태로 Julia 배열 데이터 구조로 언팩합니다.

Fortran Wrapper Example

다음 예제는 ccall을 사용하여 공통 Fortran 라이브러리(libBLAS)의 함수를 호출하여 내적을 계산합니다. 인수 매핑이 위와 약간 다르다는 점에 유의하십시오. Julia에서 Fortran으로 매핑해야 합니다. 모든 인수 유형에 대해 Ref 또는 Ptr을 지정합니다. 이 이름 맹글링 규칙은 특정 Fortran 컴파일러 및 운영 체제에 따라 다를 수 있으며 문서화되어 있지 않을 가능성이 높습니다. 그러나 각 인수를 Ref(또는 동등한 경우 Ptr)로 감싸는 것은 Fortran 컴파일러 구현의 일반적인 요구 사항입니다.

function compute_dot(DX::Vector{Float64}, DY::Vector{Float64})
    @assert length(DX) == length(DY)
    n = length(DX)
    incx = incy = 1
    product = @ccall "libLAPACK".ddot(
        n::Ref{Int32}, DX::Ptr{Float64}, incx::Ref{Int32}, DY::Ptr{Float64}, incy::Ref{Int32})::Float64
    return product
end

Garbage Collection Safety

데이터를 @ccall에 전달할 때는 pointer 함수를 사용하는 것을 피하는 것이 좋습니다. 대신 Base.cconvert 메서드를 정의하고 변수를 @ccall에 직접 전달하세요. @ccall은 모든 인수가 호출이 반환될 때까지 가비지 컬렉션에서 보존되도록 자동으로 정리합니다. C API가 Julia에 의해 할당된 메모리에 대한 참조를 저장할 경우, @ccall이 반환된 후에도 객체가 가비지 컬렉터에 보이도록 해야 합니다. 이를 수행하는 권장 방법은 이러한 값을 보관하기 위해 Array{Ref,1} 유형의 전역 변수를 만드는 것입니다. C 라이브러리가 작업을 마쳤다고 알릴 때까지 이 값을 유지하세요.

줄리아 데이터에 대한 포인터를 생성할 때는 포인터 사용이 끝날 때까지 원본 데이터가 존재하는지 확인해야 합니다. unsafe_loadString와 같은 줄리아의 많은 메서드는 버퍼의 소유권을 가져오는 대신 데이터를 복사하므로, 원본 데이터를 안전하게 해제(또는 변경)할 수 있습니다. 줄리아에 영향을 주지 않습니다. 성능상의 이유로 unsafe_wrap는 기본 버퍼를 공유(또는 소유권을 가져오도록 지시할 수 있음)하는 주목할 만한 예외입니다.

가비지 수집기는 최종화의 순서를 보장하지 않습니다. 즉, ab에 대한 참조를 포함하고 있고 ab 모두 가비지 수집 대상이라면, ba 이후에 최종화될 것이라는 보장이 없습니다. a의 적절한 최종화가 b가 유효한 것에 의존한다면, 다른 방법으로 처리해야 합니다.

Non-constant Function Specifications

일부 경우, 필요한 라이브러리의 정확한 이름이나 경로가 사전에 알려져 있지 않으며 실행 시간에 계산되어야 합니다. 이러한 경우를 처리하기 위해 라이브러리 구성 사양은 함수 호출이 될 수 있습니다. 예를 들어, find_blas().dgemm과 같습니다. 호출 표현식은 ccall 자체가 실행될 때 실행됩니다. 그러나 라이브러리 위치는 결정된 후에는 변경되지 않는 것으로 가정되므로 호출의 결과는 캐시되어 재사용될 수 있습니다. 따라서 표현식이 실행되는 횟수는 명시되지 않으며, 여러 호출에 대해 서로 다른 값을 반환하는 것은 명시되지 않은 동작을 초래합니다.

더 많은 유연성이 필요하다면, eval를 통해 단계적으로 함수 이름으로 계산된 값을 사용할 수 있습니다:

@eval @ccall "lib".$(string("a", "b"))()::Cint

이 표현식은 string을 사용하여 이름을 구성한 다음, 이 이름을 새로운 @ccall 표현식에 대체하여 평가합니다. eval은 최상위에서만 작동하므로, 이 표현식 내에서는 지역 변수가 사용 불가능합니다(값이 $로 대체되지 않는 한). 이러한 이유로 eval은 일반적으로 많은 유사한 함수를 포함하는 라이브러리를 래핑할 때와 같이 최상위 정의를 형성하는 데만 사용됩니다. @cfunction에 대한 유사한 예를 구성할 수 있습니다.

그러나 이렇게 하면 매우 느리고 메모리 누수가 발생하므로 일반적으로 이를 피하고 대신 계속 읽는 것이 좋습니다. 다음 섹션에서는 간접 호출을 사용하여 유사한 효과를 효율적으로 달성하는 방법에 대해 설명합니다.

Indirect Calls

@ccall의 첫 번째 인수는 런타임에 평가되는 표현식일 수도 있습니다. 이 경우, 표현식은 호출할 네이티브 함수의 주소로 사용될 Ptr로 평가되어야 합니다. 이 동작은 첫 번째 @ccall 인수가 지역 변수, 함수 인수 또는 비상수 전역 변수와 같은 비상수에 대한 참조를 포함할 때 발생합니다.

예를 들어, dlsym을 통해 함수를 조회한 다음, 해당 세션에 대한 공유 참조에 캐시할 수 있습니다. 예를 들어:

macro dlsym(lib, func)
    z = Ref{Ptr{Cvoid}}(C_NULL)
    quote
        let zlocal = $z[]
            if zlocal == C_NULL
                zlocal = dlsym($(esc(lib))::Ptr{Cvoid}, $(esc(func)))::Ptr{Cvoid}
                $z[] = zlocal
            end
            zlocal
        end
    end
end

mylibvar = Libdl.dlopen("mylib")
@ccall $(@dlsym(mylibvar, "myfunc"))()::Cvoid

Closure cfunctions

@cfunction의 첫 번째 인수는 $로 표시할 수 있으며, 이 경우 반환 값은 인수를 클로즈하는 struct CFunction이 됩니다. 이 반환 객체는 모든 사용이 완료될 때까지 유지되어야 합니다. cfunction 포인터의 내용과 코드는 이 참조가 삭제되고 atexit에서 finalizer를 통해 지워집니다. 이는 일반적으로 필요하지 않지만, 별도의 클로저 환경 매개변수를 제공하지 않는 잘못 설계된 API를 처리하는 데 유용할 수 있습니다.

function qsort(a::Vector{T}, cmp) where T
    isbits(T) || throw(ArgumentError("this method can only qsort isbits arrays"))
    callback = @cfunction $cmp Cint (Ref{T}, Ref{T})
    # Here, `callback` isa Base.CFunction, which will be converted to Ptr{Cvoid}
    # (and protected against finalization) by the ccall
    @ccall qsort(a::Ptr{T}, length(a)::Csize_t, Base.elsize(a)::Csize_t, callback::Ptr{Cvoid})
    # We could instead use:
    #    GC.@preserve callback begin
    #        use(Base.unsafe_convert(Ptr{Cvoid}, callback))
    #    end
    # if we needed to use it outside of a `ccall`
    return a
end
Note

Closure @cfunction는 모든 플랫폼에서 사용할 수 없는 LLVM 트램폴린에 의존합니다(예: ARM 및 PowerPC).

Closing a Library

때때로 라이브러리를 닫고(언로드) 다시 로드하는 것이 유용할 수 있습니다. 예를 들어, Julia와 함께 사용할 C 코드를 개발할 때, C 코드를 컴파일하고 Julia에서 호출한 다음, 라이브러리를 닫고, 수정한 후, 다시 컴파일하고 새로운 변경 사항을 로드해야 할 수 있습니다. Julia를 다시 시작하거나 Libdl 함수를 사용하여 라이브러리를 명시적으로 관리할 수 있습니다.

lib = Libdl.dlopen("./my_lib.so") # Open the library explicitly.
sym = Libdl.dlsym(lib, :my_fcn)   # Get a symbol for the function to call.
@ccall $sym(...) # Use the pointer `sym` instead of the library.symbol tuple.
Libdl.dlclose(lib) # Close the library explicitly.

@ccall을 사용할 때 입력(예: @ccall "./my_lib.so".my_fcn(...)::Cvoid)에 유의하세요. 라이브러리는 암묵적으로 열리며 명시적으로 닫히지 않을 수 있습니다.

Variadic function calls

가변 인자를 갖는 C 함수를 호출할 때, 세미콜론을 사용하여 필수 인자와 가변 인자를 구분할 수 있습니다. 아래는 printf 함수의 예입니다:

julia> @ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint
foo = 3
8

ccall interface

@ccall에 대한 또 다른 대체 인터페이스가 있습니다. 이 인터페이스는 약간 덜 편리하지만 calling convention을 지정할 수 있습니다.

ccall에 대한 인자는:

  1. A (:function, "library") 쌍 (가장 일반적임),

    또는

    :function 이름 기호 또는 "function" 이름 문자열 (현재 프로세스 또는 libc의 기호에 대해),

    또는

    함수 포인터(예: dlsym에서).

  2. 함수의 반환 유형

  3. 함수 시그니처에 해당하는 입력 유형의 튜플입니다. 일반적인 실수 중 하나는 인수 유형의 1-튜플이 후행 쉼표와 함께 작성되어야 한다는 것을 잊는 것입니다.

  4. 함수에 전달될 실제 인수 값, 있는 경우; 각각은 별도의 매개변수입니다.

Note

(:function, "library") 쌍, 반환 유형 및 입력 유형은 리터럴 상수여야 합니다 (즉, 변수일 수 없지만 Non-constant Function Specifications를 참조하십시오).

나머지 매개변수는 포함된 메서드가 정의될 때 컴파일 타임에 평가됩니다.

아래에 매크로와 함수 인터페이스 간의 번역 표가 제공됩니다.

@ccallccall
@ccall clock()::Int32ccall(:clock, Int32, ())
@ccall f(a::Cint)::Cintccall(:a, Cint, (Cint,), a)
@ccall "mylib".f(a::Cint, b::Cdouble)::Cvoidccall((:f, "mylib"), Cvoid, (Cint, Cdouble), (a, b))
@ccall $fptr.f()::Cvoidccall(fptr, f, Cvoid, ())
@ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint<unavailable>
@ccall printf("%s = %s\n"::Cstring ; "2 + 2"::Cstring, "5"::Cstring)::Cintccall(:printf, Cint, (Cstring, Cstring...), "%s = %s\n", "2 + 2", "5")
<unavailable>ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))

Calling Convention

ccall의 두 번째 인수(반환 유형 바로 앞)는 선택적으로 호출 규칙 지정자일 수 있습니다(@ccall 매크로는 현재 호출 규칙을 지정하는 것을 지원하지 않습니다). 지정자가 없으면 플랫폼 기본 C 호출 규칙이 사용됩니다. 지원되는 다른 규칙은 stdcall, cdecl, fastcall, 및 thiscall입니다(64비트 Windows에서는 효과가 없습니다). 예를 들어(base/libc.jl에서) 위와 동일한 gethostnameccall을 볼 수 있지만 Windows에 대한 올바른 서명이 있습니다:

hn = Vector{UInt8}(undef, 256)
err = ccall(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))

자세한 내용은 LLVM Language Reference를 참조하십시오.

추가적인 특별 호출 규약 llvmcall가 있으며, 이는 LLVM 내장 함수를 직접 호출할 수 있도록 허용합니다. 이는 GPGPU와 같은 특이한 플랫폼을 대상으로 할 때 특히 유용할 수 있습니다. 예를 들어, CUDA의 경우, 스레드 인덱스를 읽을 수 있어야 합니다:

ccall("llvm.nvvm.read.ptx.sreg.tid.x", llvmcall, Int32, ())

ccall과 마찬가지로, 인수 서명을 정확하게 맞추는 것이 필수적입니다. 또한, Core.Intrinsics에 의해 노출된 동등한 Julia 함수와 달리, 내재 함수가 현재 대상에서 의미가 있고 작동하도록 보장하는 호환성 계층이 없다는 점에 유의해야 합니다.

Accessing Global Variables

네이티브 라이브러리에서 내보낸 전역 변수는 cglobal 함수를 사용하여 이름으로 접근할 수 있습니다. 4d61726b646f776e2e436f64652822222c202263676c6f62616c2229_40726566의 인자는 ccall에서 사용되는 기호 사양과 동일하며, 변수에 저장된 값을 설명하는 유형입니다:

julia> cglobal((:errno, :libc), Int32)
Ptr{Int32} @0x00007f418d0816b8

결과는 값의 주소를 제공하는 포인터입니다. 이 포인터를 통해 unsafe_loadunsafe_store!를 사용하여 값을 조작할 수 있습니다.

Note

errno 기호는 "libc"라는 라이브러리에서 찾을 수 없을 수 있으며, 이는 시스템 컴파일러의 구현 세부 사항입니다. 일반적으로 표준 라이브러리 기호는 이름으로만 접근해야 하며, 컴파일러가 올바른 기호를 채워 넣을 수 있도록 해야 합니다. 그러나 이 예제에서 보여지는 errno 기호는 대부분의 컴파일러에서 특별하며, 따라서 여기서 보이는 값은 아마도 당신이 기대하거나 원하는 값이 아닐 것입니다. 멀티 스레드 기능을 지원하는 시스템에서 C로 동등한 코드를 컴파일하면 일반적으로 다른 함수를 호출하게 되며(매크로 전처리기 오버로딩을 통해), 여기서 인쇄된 레거시 값과는 다른 결과를 제공할 수 있습니다.

Accessing Data through a Pointer

다음 방법들은 "안전하지 않다"고 설명되며, 잘못된 포인터나 타입 선언이 줄리아를 갑자기 종료시킬 수 있습니다.

주어진 Ptr{T}에 대해, 타입 T의 내용은 일반적으로 unsafe_load(ptr, [index])를 사용하여 참조된 메모리에서 줄리아 객체로 복사할 수 있습니다. 인덱스 인자는 선택 사항이며(기본값은 1), 줄리아 관례에 따라 1 기반 인덱싱을 따릅니다. 이 함수는 의도적으로 getindexsetindex!의 동작과 유사합니다(예: [] 접근 구문).

반환 값은 참조된 메모리의 내용을 복사하여 포함하도록 초기화된 새로운 객체가 됩니다. 참조된 메모리는 안전하게 해제하거나 릴리스할 수 있습니다.

TAny인 경우, 메모리는 Julia 객체에 대한 참조(jl_value_t*)를 포함하고 있는 것으로 가정되며, 결과는 이 객체에 대한 참조가 되고 객체는 복사되지 않습니다. 이 경우 객체가 항상 가비지 컬렉터에 보이도록 해야 하며(포인터는 포함되지 않지만 새로운 참조는 포함됩니다) 메모리가 조기에 해제되지 않도록 주의해야 합니다. 객체가 원래 Julia에 의해 할당되지 않은 경우, 새로운 객체는 절대 Julia의 가비지 컬렉터에 의해 최종화되지 않습니다. Ptr 자체가 실제로 jl_value_t*인 경우, unsafe_pointer_to_objref(ptr)를 사용하여 다시 Julia 객체 참조로 변환할 수 있습니다. (Julia 값 vpointer_from_objref(v)를 호출하여 Ptr{Cvoid} 포인터로 변환할 수 있습니다.)

역방향 작업(Ptr{T}에 데이터 쓰기)은 unsafe_store!(ptr, value, [index])를 사용하여 수행할 수 있습니다. 현재 이는 원시 타입 또는 다른 포인터가 없는(isbits) 불변 구조체 타입에 대해서만 지원됩니다.

오류를 발생시키는 모든 작업은 현재 구현되지 않았을 가능성이 높으며, 해결될 수 있도록 버그로 게시해야 합니다.

관심 있는 포인터가 일반 데이터 배열(원시 유형 또는 불변 구조체)인 경우, 함수 unsafe_wrap(Array, ptr,dims, own = false)가 더 유용할 수 있습니다. 마지막 매개변수는 Julia가 기본 버퍼의 "소유권을 가져가고" 반환된 Array 객체가 최종화될 때 free(ptr)를 호출해야 하는 경우 true여야 합니다. own 매개변수가 생략되거나 false인 경우, 호출자는 모든 접근이 완료될 때까지 버퍼가 존재하도록 보장해야 합니다.

Julia의 Ptr 타입에서의 산술 연산(예: + 사용)은 C의 포인터 산술과 동일하게 작동하지 않습니다. Julia에서 Ptr에 정수를 더하면 항상 포인터가 바이트 단위로 이동하며, 요소 단위로 이동하지 않습니다. 이렇게 하면 포인터 산술에서 얻은 주소 값은 포인터의 요소 타입에 의존하지 않습니다.

Thread-safety

일부 C 라이브러리는 다른 스레드에서 콜백을 실행하며, Julia는 스레드 안전하지 않기 때문에 추가적인 주의가 필요합니다. 특히, 두 계층 시스템을 설정해야 합니다: C 콜백은 "실제" 콜백의 실행을 예약해야 합니다(줄리아의 이벤트 루프를 통해). 이를 위해 AsyncCondition 객체를 생성하고 그 위에서 wait를 호출하세요:

cond = Base.AsyncCondition()
wait(cond)

C에 전달하는 콜백은 ccall:uv_async_send에 실행해야 하며, 인수로 cond.handle을 전달해야 하며, Julia 런타임과의 할당이나 다른 상호작용을 피해야 합니다.

이벤트가 병합될 수 있으므로, uv_async_send에 대한 여러 호출이 조건에 대한 단일 깨움 알림으로 이어질 수 있음을 유의하십시오.

More About Callbacks

C 라이브러리에 콜백을 전달하는 방법에 대한 자세한 내용은 이 링크를 참조하세요: blog post.

C++

C++ 바인딩을 생성하기 위한 도구는 CxxWrap 패키지를 참조하세요.

  • 1Non-library function calls in both C and Julia can be inlined and thus may have even less overhead than calls to shared library functions. The point above is that the cost of actually doing foreign function call is about the same as doing a call in either native language.
  • 2The Clang package can be used to auto-generate Julia code from a C header file.