-- (c) The FFI task force, [2000..2001]
--
-- Pointers to external entities that are associated with finalisers, ie,
-- functions that are invoked by the storage manager when the last Haskell
-- reference to the foreign pointer disappears.

module ForeignPtr (
  ForeignPtr,             -- data ForeignPtr a, instances: Eq
  newForeignPtr,          -- :: Ptr a -> IO () -> IO (ForeignPtr a)
  addForeignPtrFinalizer, -- :: ForeignPtr a -> IO () -> IO ()
  withForeignPtr,         -- :: ForeignPtr a -> (Ptr a -> IO b) -> IO b
  foreignPtrToPtr,        -- :: ForeignPtr a -> Ptr a
  touchForeignPtr,        -- :: ForeignPtr a -> IO ()
  castForeignPtr          -- :: ForeignPtr a -> ForeignPtr b
) where

import Ptr (Ptr)

-- Pointer to external entity associated with a finalizer
--
--  * concrete implementations may choose a different representation
--
newtype ForeignPtr a = ForeignPtr (Ptr a)
                     deriving (Eq)

-- Create a new foreign pointer using the second argument as a finaliser
--
--  * There is no guarantee on how soon the finaliser is executed after the
--   last reference was dropped; this depends on the details of the Haskell
--   storage manager 
--
--  * The only guarantee given is that the finaliser runs before the program
--   terminates
--
newForeignPtr             :: Ptr a -> IO () -> IO (ForeignPtr a)
newForeignPtr p finalizer  = do
  addForeignPtrFinalizer p finalizer
  return $ ForeignPtr p

-- Add another finaliser to a foreign pointer
--
--  * No guarantees are made on the order in which multiple finalisers for a
--   single object are run
--
addForeignPtrFinalizer :: ForeignPtr a -> IO () -> IO ()
addForeignPtrFinalizer (ForeignPtr fp) finalizer =
  primAddForeignPtrFinalizer fp finalizer

-- This function ensures that the foreign object in question is alive at the
-- given place in the sequence of IO actions
--
--  * This function can be used to express liveness dependencies between
--   ForeignPtrs: for example, if the finaliser for one `ForeignPtr' touches a
--   second ForeignPtr, then it is ensured that the second `ForeignPtr' will
--   stay alive at least as long as the first. This can be useful when you
--   want to manipulate interior pointers to a foreign structure: you can use
--   `touchForeignObj' to express the requirement that the exterior pointer
--   must not be finalised until the interior pointer is no longer referenced.
--
touchForeignPtr                 :: ForeignPtr a -> IO ()
{-# notInline touchForeignPtr #-}
touchForeignPtr (ForeignPtr fo)  =
  fo `seq` return ()

-- Applies an operation to the vanilla pointer associated with a foreign
-- pointer 
--
--  * The foreign object is kept alive at least during the whole action, even
--   if it is not used directly inside. Note that it is not safe to return the
--   pointer from the action and use it after the action completes. All uses
--   of the pointer should be inside the `withForeignPtr' bracket.
--
withForeignPtr                   :: ForeignPtr a -> (Ptr a -> IO b) -> IO b
withForeignPtr (ForeignPtr fp) m  = do
  res <- m fp
  touchForeignPtr fp
  return res

-- Extract the vanilla pointer from a foreign pointer
--
--  * This is a potentially dangerous operations, as if the argument to
--   foreignPtrToPtr is the last usage occurrence of the given foreign pointer,
--   then its finaliser(s) will be run, which potentially invalidates the
--   plain pointer just obtained. Hence, `touchForeignPtr' must be used
--   wherever it has to be guaranteed that the pointer lives on - i.e., has
--   another usage occurrence. 
--
--  * To avoid subtle coding errors, hand written marshalling code should
--   preferably use `withForeignPtr' rather than combinations of
--   `foreignPtrToPtr' and `touchForeignPtr'. However, the later routines are
--   occasionally preferred in tool generated marshalling code.
--
foreignPtrToPtr                 :: ForeignPtr a -> Ptr a
foreignPtrToPtr (ForeignPtr fp)  = fp

-- Change the type parameter of a pointer
--
castForeignPtr                 :: ForeignPtr a -> ForeignPtr b
castForeignPtr (ForeignPtr fp)  = ForeignPtr fp
