외부 함수 인터페이스

외부 함수 인터페이스 (Foreign Function Interface)

Haskell만으로는 잘 안 되는 일이 있어요. 이미 C로 짜여진 라이브러리를 그대로 쓰고 싶다든가, 속도가 중요한 코드를 저수준으로 짜야 한다든가 할 때요. 이 장은 Haskell에서 C 같은 다른 언어의 함수를 부르고, 또 Haskell 함수를 다른 언어가 부를 수 있게 하는 외부 함수 인터페이스(FFI) 를 다뤄요. foreign 키워드로 쓰는 FFI 선언의 문법과 규칙이 여기 있어요.

출처: Haskell 2010 언어 리포트

본문

이 장은 Haskell과 외부 세계(주로 C) 사이의 인터페이스를 정의하는 **외부 함수 인터페이스(Foreign Function Interface, FFI)**를 설명해요. FFI는 두 방향으로 동작해요: Haskell이 외부 언어 함수를 호출하고, 외부 언어가 Haskell 함수를 호출하는 것.

topdecl → foreign fdecl
fdecl   → import callconv [safety] impent var :: ftype
         | export callconv expent var :: ftype

FFI 선언은 foreign 키워드로 시작해요. 4장의 최상위 선언 문법에서 foreign fdecl이 최상위 선언의 한 형태로 허용돼요.

  • foreign import는 외부 함수를 Haskell로 가져와(import) 쓰겠다는 뜻이에요.
  • foreign export는 Haskell 함수를 외부 세계가 부를 수 있게 내보내겠다는 뜻이에요.

callconv는 호출 규약(calling convention)을 지정해요. 대부분의 구현은 ccall(C 호출)을 지원해요. 추가로 stdcall(Win32) 같은 규약을 지원할 수 있어요.

callconv → ccall | stdcall | ...
safety   → safe | unsafe
impent   → [string]
expent   → [string]

safetyforeign import에만 의미가 있어요. safe는 Haskell 런타임과 안전하게 상호작용할 수 있는 호출이고, unsafe는 더 빠르지만 외부 함수가 다시 Haskell로 콜백하지 못하게 제한하는 호출이에요.

impent/expent는 선택적 문자열로, 기본값(예: C 심볼 이름이 변수 var와 같다고 가정)을 재정의할 때 써요.

변수 var의 타입 ftype은 다음 규칙을 만족해야 해요:

  • foreign import에서 var는 타입 ftype을 가져야 하고, 보통 IO-안의 타입이어야 해요. 호출 규약과 기본 타입(마샬링)은 구현이 정해요.
  • foreign export에서 var는 타입 IO t이거나 그에 해당하는 형태여야 해요.

예시

C의 strlen을 Haskell에서 부르려면:

foreign import ccall unsafe "strlen" c_strlen :: CString -> IO CSize

다른 모듈이 Haskell 함수 foo를 부르게 하려면:

foreign export ccall "foo" foo :: Int -> IO Int
foreign export ccall foo :: Int -> IO Int

새 이름 문자열 "foo"를 제공하면 C 쪽에서 그 이름으로 참조할 수 있어요.

기본 브릿지 타입과 마샬링

FFI로 주고받는 타입은 언어 사이에서 기계적으로 표현 가능한 **기본 타입(primitive types)**이에요. 예를 들어 Int, Word, Float, Double, Char, 포인터 타입 등이 여기에 속해요. Foreign.C.Types 같은 라이브러리 모듈이 C 타입에 대응하는 타입들을 정의해요. Foreign 모듈은 CString, Ptr a, FunPtr a 같은 타입을 제공해요.

올바른 FFI 사용은 보통 Foreign, Foreign.C.Types, Foreign.Marshal.* 같은 모듈을 함께 쓰는 식이에요. 자세한 표준 라이브러리 사양은 Part II에 있어요.

더 알아보기 (Learn more)