Foreign.ForeignPtr
Foreign.ForeignPtr
외국어(foreign) 객체에 대한 참조를 나타내는 ForeignPtr 타입과 파이널라이저(finalizer) 관리를 제공하는 모듈이에요.
본문
module Foreign.ForeignPtr (
ForeignPtr, FinalizerPtr, FinalizerEnvPtr, newForeignPtr,
newForeignPtr_, addForeignPtrFinalizer, newForeignPtrEnv,
addForeignPtrFinalizerEnv, withForeignPtr, finalizeForeignPtr,
unsafeForeignPtrToPtr, touchForeignPtr, castForeignPtr,
mallocForeignPtr, mallocForeignPtrBytes, mallocForeignPtrArray,
mallocForeignPtrArray0
) where
29.1 파이널라이즈된 데이터 포인터 (Finalised data pointers)
data ForeignPtr a
ForeignPtr 타입은 외국어(foreign language)로 유지되는 객체, 즉 일반적으로 Haskell 저장 관리자(storage manager)가 관리하는 데이터 구조의 일부가 아닌 객체에 대한 참조를 나타내요. ForeignPtr과 Ptr a 타입의 일반 메모리 참조 사이의 핵심 차이는 전자는 **파이널라이저(finalizer)**와 연관될 수 있다는 점이에요. 파이널라이저는 Haskell 저장 관리자가 Haskell 힙과 스택 내에서 그 ForeignPtr을 가리키는 참조가 더 이상 남아 있지 않음을 감지했을 때 호출되는 루틴이에요. 일반적으로 파이널라이저는 그때 외국어로 작성된 루틴들을 호출해 외국 객체가 묶고 있는 자원을 해제해요.
ForeignPtr은 Ptr과 같은 방식으로 매개변수화돼요. ForeignPtr의 타입 인자는 보통 Storable 클래스의 인스턴스여야 해요.
instance Eq (ForeignPtr a)
instance Ord (ForeignPtr a)
instance Show (ForeignPtr a)
type FinalizerPtr a = FunPtr (Ptr a -> IO ())
파이널라이저는 파이널라이즈 시점에, 파이널라이저와 연관된 외국 포인터의 일반 포인터(plain pointer) 변형을 인자로 받는 외국 함수에 대한 포인터로 표현돼요.
type FinalizerEnvPtr env a = FunPtr (Ptr env -> Ptr a -> IO ())
29.1.1 기본 연산 (Basic operations)
newForeignPtr :: FinalizerPtr a -> Ptr a -> IO (ForeignPtr a)
일반 메모리 참조를 외국 포인터로 바꾸고, 그 참조에 파이널라이저를 연관시켜요. 파이널라이저는 외국 객체에 대한 마지막 참조가 사라진 뒤에 실행돼요. 즉시 실행이 보장되지는 않지만, 프로그램이 종료되기 전에는 파이널라이저가 실행돼요.
newForeignPtr_ :: Ptr a -> IO (ForeignPtr a)
일반 메모리 참조를, addForeignPtrFinalizer를 사용해 파이널라이저와 연관시킬 수 있는 외국 포인터로 바꿔요.
addForeignPtrFinalizer :: FinalizerPtr a -> ForeignPtr a -> IO ()
이 함수는 주어진 외국 객체에 파이널라이저를 추가해요. 이 파이널라이저는 같은 객체에 대해 이미 등록된 다른 파이널라이저들보다 먼저 실행돼요.
newForeignPtrEnv :: FinalizerEnvPtr env a
-> Ptr env -> Ptr a -> IO (ForeignPtr a)
newForeignPtr의 이 변형은 파이널라이즈된 포인터 외에 환경(environment)도 기대하는 파이널라이저를 추가해요. 파이널라이저에 전달될 환경은 newForeignPtrEnv의 두 번째 인자로 고정돼요.
addForeignPtrFinalizerEnv :: FinalizerEnvPtr env a
-> Ptr env -> ForeignPtr a -> IO ()
addForeignPtrFinalizer와 같지만, 파이널라이저에 추가 환경 매개변수를 전달할 수 있어요. 파이널라이저에 전달되는 환경은 addForeignPtrFinalizerEnv의 두 번째 인자로 고정돼요.
withForeignPtr :: ForeignPtr a -> (Ptr a -> IO b) -> IO b
이것은 외국 객체 안에 살고 있는 포인터를 들여다보는 방법이에요. 이 함수는 그 포인터에 적용되는 함수를 받아서, 그 결과 IO 액션을 실행해요. 외국 객체는 그 액션 전체 동안, 심지어 그 안에서 직접 사용되지 않더라도 살아 있게 유지돼요. 액션이 완료된 뒤에는 그 포인터를 반환해 사용하는 것이 안전하지 않다는 점에 유의해요. 포인터의 모든 사용은 withForeignPtr 괄호 안에서 이루어져야 해요. 이것이 안전하지 않은 이유는 아래의 unsafeForeignPtrToPtr와 같아요. 즉, 컴파일러는 ForeignPtr 객체의 사용만 추적할 수 있고, 그것으로부터 만들어진 Ptr 객체는 추적할 수 없기 때문에 파이널라이저가 예상보다 일찍 실행될 수 있어요.
이 함수는 보통 Storable 클래스의 연산을 사용해 ForeignPtr이 가리키는 객체로/로부터 데이터를 마샬링할 때 사용돼요.
finalizeForeignPtr :: ForeignPtr a -> IO ()
외국 포인터와 연관된 파이널라이저들이 즉시 실행되도록 해요.
29.1.2 저수준 연산 (Low-level operations)
unsafeForeignPtrToPtr :: ForeignPtr a -> Ptr a
이 함수는 외국 포인터의 포인터 구성 요소를 추출해요. 이는 잠재적으로 위험한 연산인데, 만약 unsafeForeignPtrToPtr의 인자가 주어진 외국 포인터의 마지막 사용 지점이라면 그 파이널라이저(들)가 실행되어 방금 얻은 일반 포인터를 무효화할 수 있기 때문이에요. 따라서 포인터가 살아 있음 — 즉 또 다른 사용 지점이 있음 — 을 보장해야 하는 곳마다 touchForeignPtr를 사용해야 해요.
미묘한 코딩 오류를 피하기 위해, 손으로 작성한 마샬링 코드는 unsafeForeignPtrToPtr와 touchForeignPtr의 조합보다는 Foreign.ForeignPtr.withForeignPtr를 사용하는 것이 좋아요. 다만 도구가 생성한 마샬링 코드에서는 후자의 루틴들이 가끔 선호되기도 해요.
touchForeignPtr :: ForeignPtr a -> IO ()
이 함수는 해당 외국 객체가 IO 액션 수열의 주어진 위치에서 살아 있도록 보장해요. 특히 withForeignPtr는 사용자 액션을 실행한 뒤 touchForeignPtr을 수행해요.
이 함수는 ForeignPtr들 사이의 파이널라이저 의존성을 표현하는 데 사용해서는 안 된다는 점에 유의해요. 예를 들어 ForeignPtr F1의 파이널라이저가 두 번째 ForeignPtr F2에 대해 touchForeignPtr를 호출한다면, 보장되는 것은 F2의 파이널라이저가 F1의 파이널라이저보다 먼저 시작되지 않는다는 것뿐이에요. 예를 들어 F1과 F2가 모두 그 외에는 도달 불가능하다면, 둘은 함께 시작될 수도 있어요.
일반적으로, 서로 다른 객체들에 대해 순서 제약이 있는 파이널라이저를 사용하는 것은 권장되지 않아요. 순서를 강건하게 표현하려면 파이널라이저들 사이의 명시적인 동기화가 필요해요.
castForeignPtr :: ForeignPtr a -> ForeignPtr b
이 함수는 한 타입으로 매개변수화된 ForeignPtr을 다른 타입으로 캐스트(cast)해요.
29.1.3 관리 메모리 할당 (Allocating managed memory)
mallocForeignPtr :: Storable a => IO (ForeignPtr a)
약간의 메모리를 할당하고 그것에 대한 ForeignPtr을 돌려줘요. ForeignPtr이 버려지면 메모리는 자동으로 해제돼요.
mallocForeignPtr은 다음과 동등해요.
do { p <- malloc; newForeignPtr finalizerFree p }
다만 내부적으로는 다르게 구현될 수 있어요. mallocForeignPtr이 돌려준 메모리가 Foreign.Marshal.Alloc.malloc으로 할당되었다고 가정해서는 안 돼요.
mallocForeignPtrBytes :: Int -> IO (ForeignPtr a)
이 함수는 필요한 메모리 크기를 바이트 수로 명시적으로 주는 점을 제외하면 mallocForeignPtr과 비슷해요.
mallocForeignPtrArray :: Storable a => Int -> IO (ForeignPtr a)
이 함수는 Foreign.Marshal.Array.mallocArray와 비슷하지만, 메모리 영역을 해제하는 파이널라이저가 붙은 메모리 영역을 산출해요. mallocForeignPtr처럼, 이 메모리 블록이 Foreign.Marshal.Alloc.malloc에 의해 할당되었다는 보장은 없어요.
mallocForeignPtrArray0 :: Storable a => Int -> IO (ForeignPtr a)
이 함수는 Foreign.Marshal.Array.mallocArray0와 비슷하지만, 메모리 영역을 해제하는 파이널라이저가 붙은 메모리 영역을 산출해요. mallocForeignPtr처럼, 이 메모리 블록이 Foreign.Marshal.Alloc.malloc에 의해 할당되었다는 보장은 없어요.
더 알아보기 (Learn more)
- 파이널라이저가 사용하는
finalizerFree와 같은 할당·해제 루틴은 31장 Foreign.Marshal.Alloc에서 다뤄요. - 배열 할당에 쓰이는
mallocArray·mallocArray0은 32장 Foreign.Marshal.Array에서 다뤄요.