diff --git a/haskell-gi-base.cabal b/haskell-gi-base.cabal
--- a/haskell-gi-base.cabal
+++ b/haskell-gi-base.cabal
@@ -1,5 +1,5 @@
 name:                haskell-gi-base
-version:             0.14
+version:             0.15
 synopsis:            Foundation for libraries generated by haskell-gi
 description:         Foundation for libraries generated by haskell-gi
 homepage:            https://github.com/haskell-gi/haskell-gi-base
diff --git a/src/Data/GI/Base/BasicTypes.hsc b/src/Data/GI/Base/BasicTypes.hsc
--- a/src/Data/GI/Base/BasicTypes.hsc
+++ b/src/Data/GI/Base/BasicTypes.hsc
@@ -38,6 +38,7 @@
     , BoxedEnum(..)
     , BoxedFlags(..)
     , GObject(..)
+    , WrappedPtr(..)
     , UnexpectedNullPointerReturn(..)
     , NullToNothing(..)
 
@@ -212,6 +213,17 @@
 -- | Flags with an associated `GType`.
 class BoxedFlags a where
     boxedFlagsType :: Proxy a -> IO GType
+
+-- | Pointers to structs/unions without an associated `GType`.
+class ForeignPtrNewtype a => WrappedPtr a where
+    -- | Allocate a zero-initialized block of memory for the given type.
+    wrappedPtrCalloc :: IO (Ptr a)
+    -- | Make a copy of the given pointer.
+    wrappedPtrCopy   :: Ptr a -> IO (Ptr a)
+    -- | A pointer to a function for freeing the given pointer, or
+    -- `Nothing` is the memory associated to the pointer does not need
+    -- to be freed.
+    wrappedPtrFree   :: Maybe (FunPtr (Ptr a -> IO ()))
 
 -- | A wrapped `GObject`.
 class ForeignPtrNewtype a => GObject a where
diff --git a/src/Data/GI/Base/ManagedPtr.hs b/src/Data/GI/Base/ManagedPtr.hs
--- a/src/Data/GI/Base/ManagedPtr.hs
+++ b/src/Data/GI/Base/ManagedPtr.hs
@@ -35,6 +35,7 @@
     , freeBoxed
     , wrapPtr
     , newPtr
+    , copyPtr
     ) where
 
 #if !MIN_VERSION_base(4,8,0)
@@ -47,7 +48,8 @@
 import Foreign (poke)
 import Foreign.C (CInt(..))
 import Foreign.Ptr (Ptr, FunPtr, castPtr, nullPtr)
-import Foreign.ForeignPtr (ForeignPtr, newForeignPtr, newForeignPtrEnv, touchForeignPtr)
+import Foreign.ForeignPtr (ForeignPtr, newForeignPtr, newForeignPtrEnv,
+                           touchForeignPtr, newForeignPtr_)
 import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
 
 import Data.GI.Base.BasicTypes
@@ -244,15 +246,26 @@
   touchManagedPtr boxed
 
 -- | Wrap a pointer, taking ownership of it.
-wrapPtr :: (ForeignPtr a -> a) -> Ptr a -> IO a
+wrapPtr :: WrappedPtr a => (ForeignPtr a -> a) -> Ptr a -> IO a
 wrapPtr constructor ptr = do
-  fPtr <- newForeignPtr ptr_to_g_free ptr
+  fPtr <- case wrappedPtrFree of
+            Nothing -> newForeignPtr_ ptr
+            Just finalizer -> newForeignPtr finalizer ptr
   return $! constructor fPtr
 
--- | Wrap a pointer to n bytes, making a copy of the data.
-newPtr :: Int -> (ForeignPtr a -> a) -> Ptr a -> IO a
-newPtr n constructor ptr = do
-  ptr' <- callocBytes n :: IO (Ptr a)
-  memcpy ptr' ptr n
-  fPtr <- newForeignPtr ptr_to_g_free ptr'
+-- | Wrap a pointer, making a copy of the data.
+newPtr :: WrappedPtr a => (ForeignPtr a -> a) -> Ptr a -> IO a
+newPtr constructor ptr = do
+  ptr' <- wrappedPtrCopy ptr
+  fPtr <- case wrappedPtrFree of
+            Nothing -> newForeignPtr_ ptr
+            Just finalizer -> newForeignPtr finalizer ptr'
   return $! constructor fPtr
+
+-- | Make a copy of a wrapped pointer using @memcpy@ into a freshly
+-- allocated memory region of the given size.
+copyPtr :: WrappedPtr a => Int -> Ptr a -> IO (Ptr a)
+copyPtr size ptr = do
+  ptr' <- wrappedPtrCalloc
+  memcpy ptr' ptr size
+  return ptr'
