diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,15 @@
 # Change log for unwitch project
 
+## Version 2.2.0
++ New module Unwitch.Convert.CInt — conversions from CInt (Foreign.C.Types)
+  to all supported numeric types (Int, Int8–64, Word, Word8–64, Integer,
+  Natural, Float, Double)
++ Add toCInt to all 14 existing Convert modules (Int, Int8, Int16, Int32,
+  Int64, Integer, Word, Word8, Word16, Word32, Word64, Natural, Float,
+  Double)
++ Add CIntSpec test module with 12 tests covering total and fallible
+  CInt conversions
+
 ## Version 2.1.0
 + Add Haddock documentation to partial conversion functions across
   all Convert modules — docs focus on non-obvious information only:
diff --git a/src/Unwitch/Convert/CInt.hs b/src/Unwitch/Convert/CInt.hs
new file mode 100644
--- /dev/null
+++ b/src/Unwitch/Convert/CInt.hs
@@ -0,0 +1,83 @@
+-- | Conversions from 'CInt'.
+module Unwitch.Convert.CInt
+  ( -- * Conversions
+    toInt8
+  , toInt16
+  , toInt32
+  , toInt64
+  , toInt
+  , toInteger
+  , toWord8
+  , toWord16
+  , toWord32
+  , toWord64
+  , toWord
+  , toNatural
+  , toFloat
+  , toDouble
+  )
+where
+
+import           Unwitch.Errors
+import qualified Unwitch.Convert.Int32 as Int32
+import           Data.Word
+import           Data.Int
+import           Numeric.Natural (Natural)
+import           Prelude hiding (toInteger)
+import           Foreign.C.Types (CInt(CInt))
+
+-- | Narrowing conversion, fails if outside Int8 range.
+toInt8 :: CInt -> Maybe Int8
+toInt8 (CInt x) = Int32.toInt8 x
+
+-- | Narrowing conversion, fails if outside Int16 range.
+toInt16 :: CInt -> Maybe Int16
+toInt16 (CInt x) = Int32.toInt16 x
+
+-- | Unwrap the underlying Int32.
+toInt32 :: CInt -> Int32
+toInt32 (CInt x) = x
+
+-- | Widening conversion, always succeeds.
+toInt64 :: CInt -> Int64
+toInt64 (CInt x) = Int32.toInt64 x
+
+-- | Total conversion, Int is at least 32 bits wide.
+toInt :: CInt -> Int
+toInt (CInt x) = fromIntegral x
+
+-- | Total conversion to Integer.
+toInteger :: CInt -> Integer
+toInteger (CInt x) = Int32.toInteger x
+
+-- | Signed-to-unsigned narrowing, fails if negative or out of range.
+toWord8 :: CInt -> Maybe Word8
+toWord8 (CInt x) = Int32.toWord8 x
+
+-- | Signed-to-unsigned narrowing, fails if negative or out of range.
+toWord16 :: CInt -> Maybe Word16
+toWord16 (CInt x) = Int32.toWord16 x
+
+-- | Signed-to-unsigned, fails if negative.
+toWord32 :: CInt -> Maybe Word32
+toWord32 (CInt x) = Int32.toWord32 x
+
+-- | Signed-to-unsigned, fails if negative.
+toWord64 :: CInt -> Maybe Word64
+toWord64 (CInt x) = Int32.toWord64 x
+
+-- | Signed-to-unsigned, fails if negative.
+toWord :: CInt -> Maybe Word
+toWord (CInt x) = Int32.toWord x
+
+-- | Signed-to-unsigned, returns 'Left' 'Underflow' for negative values.
+toNatural :: CInt -> Either Overflows Natural
+toNatural (CInt x) = Int32.toNatural x
+
+-- | Checked conversion, fails if outside exact float integer range (+-16777215).
+toFloat :: CInt -> Either Overflows Float
+toFloat (CInt x) = Int32.toFloat x
+
+-- | Total conversion, all Int32 values are exactly representable as Double.
+toDouble :: CInt -> Double
+toDouble (CInt x) = Int32.toDouble x
diff --git a/src/Unwitch/Convert/Double.hs b/src/Unwitch/Convert/Double.hs
--- a/src/Unwitch/Convert/Double.hs
+++ b/src/Unwitch/Convert/Double.hs
@@ -15,6 +15,7 @@
   , toWord64
   , toWord
   , toNatural
+  , toCInt
   , ViaIntegerErrors(..)
   , IntegerErrors(..)
   , RationalErrors(..)
@@ -33,6 +34,7 @@
 import Data.Word
 import Data.Int
 import Numeric.Natural (Natural)
+import Foreign.C.Types (CInt(CInt))
 
 -- | Lossy narrowing conversion, may lose precision.
 toFloat :: Double -> Float
@@ -100,6 +102,10 @@
   case Integer.toNatural integer of
     Left err -> Left $ MkInteger $ IntegerFlow integer err
     Right n -> Right n
+
+-- | Converts via 'Integer', fails if not a whole number or out of range.
+toCInt :: Double -> Either ViaIntegerErrors CInt
+toCInt x = CInt <$> toInt32 x
 
 -- | Convert via 'Integer' then narrow, combining errors.
 toViaInteger :: (Integer -> Maybe a) -> Double -> Either ViaIntegerErrors a
diff --git a/src/Unwitch/Convert/Float.hs b/src/Unwitch/Convert/Float.hs
--- a/src/Unwitch/Convert/Float.hs
+++ b/src/Unwitch/Convert/Float.hs
@@ -14,6 +14,7 @@
   , toWord64
   , toWord
   , toNatural
+  , toCInt
   , ViaIntegerErrors(..)
   , IntegerErrors(..)
   , RationalErrors(..)
@@ -31,6 +32,7 @@
 import Data.Word
 import Data.Int
 import Numeric.Natural (Natural)
+import Foreign.C.Types (CInt(CInt))
 
 toDouble :: Float -> Double
 toDouble = F.float2Double
@@ -97,6 +99,10 @@
   case Integer.toNatural integer of
     Left err -> Left $ MkInteger $ IntegerFlow integer err
     Right n -> Right n
+
+-- | Converts via 'Integer', fails if not a whole number or out of range.
+toCInt :: Float -> Either ViaIntegerErrors CInt
+toCInt x = CInt <$> toInt32 x
 
 -- | Convert via 'Integer' then narrow, combining errors.
 toViaInteger :: (Integer -> Maybe a) -> Float -> Either ViaIntegerErrors a
diff --git a/src/Unwitch/Convert/Int.hs b/src/Unwitch/Convert/Int.hs
--- a/src/Unwitch/Convert/Int.hs
+++ b/src/Unwitch/Convert/Int.hs
@@ -14,6 +14,7 @@
   , toNatural
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toInt8#
@@ -36,6 +37,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (Int(..), Word(..), Float(..), Double(..),
                            intToInt8#, int8ToInt#, intToInt16#, int16ToInt#,
@@ -99,6 +101,10 @@
   | x < -maxIntegralRepFloat -> Left Underflow
   | x > maxIntegralRepFloat  -> Left Overflow
   | otherwise                -> Right $ fromIntegral x
+
+-- | Narrowing conversion via Int32, fails if outside Int32 range.
+toCInt :: Int -> Maybe CInt
+toCInt x = CInt <$> toInt32 x
 
 -- | Checked conversion, fails if outside exact double integer range (+/-9007199254740991).
 toDouble :: Int -> Either Overflows Double
diff --git a/src/Unwitch/Convert/Int16.hs b/src/Unwitch/Convert/Int16.hs
--- a/src/Unwitch/Convert/Int16.hs
+++ b/src/Unwitch/Convert/Int16.hs
@@ -14,6 +14,7 @@
   , toNatural
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toInt8#
@@ -31,6 +32,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (Word(..), int16ToInt#, intToInt8#, int8ToInt#,
                            int2Word#, word2Int#,
@@ -82,6 +84,10 @@
 toNatural x = if
   | x < 0     -> Left Underflow
   | otherwise  -> Right $ fromIntegral x
+
+-- | Widening conversion via Int32, always succeeds.
+toCInt :: Int16 -> CInt
+toCInt x = CInt $ toInt32 x
 
 toFloat :: Int16 -> Float
 toFloat = fromIntegral
diff --git a/src/Unwitch/Convert/Int32.hs b/src/Unwitch/Convert/Int32.hs
--- a/src/Unwitch/Convert/Int32.hs
+++ b/src/Unwitch/Convert/Int32.hs
@@ -14,6 +14,7 @@
   , toNatural
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toInt8#
@@ -35,6 +36,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (Int(..), Word(..), Float(..),
                            int32ToInt#, intToInt8#, int8ToInt#,
@@ -97,6 +99,10 @@
   | x < -maxIntegralRepFloat -> Left Underflow
   | x > maxIntegralRepFloat  -> Left Overflow
   | otherwise                -> Right $ fromIntegral x
+
+-- | Direct wrapping, CInt is a newtype over Int32.
+toCInt :: Int32 -> CInt
+toCInt = CInt
 
 toDouble :: Int32 -> Double
 toDouble = fromIntegral
diff --git a/src/Unwitch/Convert/Int64.hs b/src/Unwitch/Convert/Int64.hs
--- a/src/Unwitch/Convert/Int64.hs
+++ b/src/Unwitch/Convert/Int64.hs
@@ -14,6 +14,7 @@
   , toNatural
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toInt8#
@@ -37,6 +38,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (Int(..), Word(..), Float(..), Double(..),
                            int64ToInt#, intToInt64#,
@@ -72,6 +74,10 @@
 
 toInt :: Int64 -> Maybe Int
 toInt = Bits.toIntegralSized
+
+-- | Narrowing conversion via Int32, fails if outside Int32 range.
+toCInt :: Int64 -> Maybe CInt
+toCInt x = CInt <$> toInt32 x
 
 toInteger :: Int64 -> Integer
 toInteger = fromIntegral
diff --git a/src/Unwitch/Convert/Int8.hs b/src/Unwitch/Convert/Int8.hs
--- a/src/Unwitch/Convert/Int8.hs
+++ b/src/Unwitch/Convert/Int8.hs
@@ -14,6 +14,7 @@
   , toNatural
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toWord8#
@@ -30,6 +31,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (Word(..), int8ToInt#, int2Word#,
                            wordToWord8#, wordToWord16#, wordToWord32#,
@@ -79,6 +81,10 @@
 toNatural x = if
   | x < 0     -> Left Underflow
   | otherwise  -> Right $ fromIntegral x
+
+-- | Widening conversion via Int32, always succeeds.
+toCInt :: Int8 -> CInt
+toCInt x = CInt $ toInt32 x
 
 toFloat :: Int8 -> Float
 toFloat = fromIntegral
diff --git a/src/Unwitch/Convert/Integer.hs b/src/Unwitch/Convert/Integer.hs
--- a/src/Unwitch/Convert/Integer.hs
+++ b/src/Unwitch/Convert/Integer.hs
@@ -14,6 +14,7 @@
   , toWord32
   , toWord64
   , toWord
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toDouble#
@@ -38,6 +39,7 @@
 import Data.Word
 import Data.Int
 import Numeric.Natural (Natural)
+import Foreign.C.Types (CInt(CInt))
 import           GHC.Exts (Int(..), Word(..), Float(..), Double(..),
                            intToInt8#, int8ToInt#,
                            intToInt16#, int16ToInt#,
@@ -111,6 +113,10 @@
 
 toWord :: Integer -> Maybe Word
 toWord = Bits.toIntegralSized
+
+-- | Narrowing conversion via Int32, fails if outside Int32 range.
+toCInt :: Integer -> Maybe CInt
+toCInt x = CInt <$> toInt32 x
 
 -- | Bounds-checked double conversion via IS/IP/IN
 toDouble# :: Integer -> (# Overflows | Double #)
diff --git a/src/Unwitch/Convert/Natural.hs b/src/Unwitch/Convert/Natural.hs
--- a/src/Unwitch/Convert/Natural.hs
+++ b/src/Unwitch/Convert/Natural.hs
@@ -14,6 +14,7 @@
   , toInteger
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toWord8#
@@ -37,6 +38,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (Int(..), Word(..), Float(..), Double(..),
                            word2Int#,
@@ -92,6 +94,10 @@
 
 toInteger :: Natural -> Integer
 toInteger = fromIntegral
+
+-- | Narrowing conversion via Int32, fails if outside Int32 range.
+toCInt :: Natural -> Maybe CInt
+toCInt x = CInt <$> toInt32 x
 
 -- | Checked conversion, fails if outside exact float integer range (\u00b116777215).
 toFloat :: Natural -> Either Overflows Float
diff --git a/src/Unwitch/Convert/Word.hs b/src/Unwitch/Convert/Word.hs
--- a/src/Unwitch/Convert/Word.hs
+++ b/src/Unwitch/Convert/Word.hs
@@ -14,6 +14,7 @@
   , toInteger
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toWord8#
@@ -35,6 +36,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (Int(..), Word(..), Float(..), Double(..),
                            wordToWord8#, word8ToWord#,
@@ -86,6 +88,10 @@
 
 toInteger :: Word -> Integer
 toInteger = fromIntegral
+
+-- | Narrowing conversion via Int32, fails if outside Int32 range.
+toCInt :: Word -> Maybe CInt
+toCInt x = CInt <$> toInt32 x
 
 -- | Checked conversion, fails if outside exact float integer range (+-16777215).
 toFloat :: Word -> Either Overflows Float
diff --git a/src/Unwitch/Convert/Word16.hs b/src/Unwitch/Convert/Word16.hs
--- a/src/Unwitch/Convert/Word16.hs
+++ b/src/Unwitch/Convert/Word16.hs
@@ -14,6 +14,7 @@
   , toInteger
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toWord8#
@@ -26,6 +27,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (word16ToWord#, word2Int#,
                            wordToWord8#, word8ToWord#,
@@ -79,6 +81,10 @@
 
 toDouble :: Word16 -> Double
 toDouble = fromIntegral
+
+-- | Widening conversion via Int32, always succeeds.
+toCInt :: Word16 -> CInt
+toCInt x = CInt $ toInt32 x
 
 -- | Unsigned narrowing, roundtrip at Word#
 toWord8# :: Word16 -> (# Word8 | (# #) #)
diff --git a/src/Unwitch/Convert/Word32.hs b/src/Unwitch/Convert/Word32.hs
--- a/src/Unwitch/Convert/Word32.hs
+++ b/src/Unwitch/Convert/Word32.hs
@@ -14,6 +14,7 @@
   , toInteger
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toWord8#
@@ -33,6 +34,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (Int(..), Word(..), Float(..),
                            word32ToWord#, word2Int#,
@@ -93,6 +95,10 @@
 
 toDouble :: Word32 -> Double
 toDouble = fromIntegral
+
+-- | Narrowing conversion via Int32, fails if outside Int32 range.
+toCInt :: Word32 -> Maybe CInt
+toCInt x = CInt <$> toInt32 x
 
 -- | Unsigned narrowing, roundtrip at Word#
 toWord8# :: Word32 -> (# Word8 | (# #) #)
diff --git a/src/Unwitch/Convert/Word64.hs b/src/Unwitch/Convert/Word64.hs
--- a/src/Unwitch/Convert/Word64.hs
+++ b/src/Unwitch/Convert/Word64.hs
@@ -14,6 +14,7 @@
   , toInteger
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toWord8#
@@ -36,6 +37,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (Int(..), Word(..), Float(..), Double(..),
                            word64ToWord#, wordToWord64#,
@@ -89,6 +91,10 @@
 
 toInteger :: Word64 -> Integer
 toInteger = fromIntegral
+
+-- | Narrowing conversion via Int32, fails if outside Int32 range.
+toCInt :: Word64 -> Maybe CInt
+toCInt x = CInt <$> toInt32 x
 
 -- | Checked conversion, fails with 'Overflow' if outside exact float integer range.
 toFloat :: Word64 -> Either Overflows Float
diff --git a/src/Unwitch/Convert/Word8.hs b/src/Unwitch/Convert/Word8.hs
--- a/src/Unwitch/Convert/Word8.hs
+++ b/src/Unwitch/Convert/Word8.hs
@@ -14,6 +14,7 @@
   , toInteger
   , toFloat
   , toDouble
+  , toCInt
   -- * Unboxed conversions
   -- $unboxed
   , toInt8#
@@ -24,6 +25,7 @@
 import           Data.Word
 import           Data.Int
 import           Numeric.Natural (Natural)
+import           Foreign.C.Types (CInt(CInt))
 import           Prelude hiding (toInteger)
 import           GHC.Exts (word8ToWord#, word2Int#, intToInt8#, int8ToInt#,
                            (==#))
@@ -74,6 +76,10 @@
 
 toDouble :: Word8 -> Double
 toDouble = fromIntegral
+
+-- | Widening conversion via Int32, always succeeds.
+toCInt :: Word8 -> CInt
+toCInt x = CInt $ toInt32 x
 
 -- | Unsigned->signed, source fits in Int#, roundtrip at Int#
 toInt8# :: Word8 -> (# Int8 | (# #) #)
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,6 +1,7 @@
 module Main where
 
 import Test.Hspec
+import qualified Test.Convert.CIntSpec
 import qualified Test.Convert.Int8Spec
 import qualified Test.Convert.Int16Spec
 import qualified Test.Convert.Int32Spec
@@ -28,6 +29,7 @@
 
 main :: IO ()
 main = hspec $ do
+  Test.Convert.CIntSpec.spec
   Test.Convert.Int8Spec.spec
   Test.Convert.Int16Spec.spec
   Test.Convert.Int32Spec.spec
diff --git a/test/Test/Convert/CIntSpec.hs b/test/Test/Convert/CIntSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Convert/CIntSpec.hs
@@ -0,0 +1,46 @@
+module Test.Convert.CIntSpec (spec) where
+
+import Test.Hspec
+import Data.Int
+import Data.Word
+import Foreign.C.Types (CInt)
+import qualified Unwitch.Convert.CInt as CInt
+
+spec :: Spec
+spec = describe "Unwitch.Convert.CInt" $ do
+
+  describe "toInt8 (fallible)" $ do
+    it "narrows in-range" $
+      CInt.toInt8 (100 :: CInt) `shouldBe` Just (100 :: Int8)
+    it "rejects out-of-range" $
+      CInt.toInt8 (200 :: CInt) `shouldBe` Nothing
+
+  describe "toInt16 (fallible)" $ do
+    it "converts in-range" $
+      CInt.toInt16 (1000 :: CInt) `shouldBe` Just (1000 :: Int16)
+    it "rejects out-of-range" $
+      CInt.toInt16 (40000 :: CInt) `shouldBe` Nothing
+
+  describe "toInt32 (total)" $
+    it "unwraps CInt" $
+      CInt.toInt32 (42 :: CInt) `shouldBe` (42 :: Int32)
+
+  describe "toInt (total)" $ do
+    it "converts positive" $
+      CInt.toInt (42 :: CInt) `shouldBe` (42 :: Int)
+    it "converts negative" $
+      CInt.toInt (-1 :: CInt) `shouldBe` (-1 :: Int)
+
+  describe "toWord8 (fallible)" $ do
+    it "rejects negative" $
+      CInt.toWord8 (-1 :: CInt) `shouldBe` Nothing
+    it "converts in-range" $
+      CInt.toWord8 (255 :: CInt) `shouldBe` Just (255 :: Word8)
+    it "rejects too large" $
+      CInt.toWord8 (256 :: CInt) `shouldBe` Nothing
+
+  describe "toDouble (total)" $ do
+    it "converts positive" $
+      CInt.toDouble (42 :: CInt) `shouldBe` (42.0 :: Double)
+    it "converts negative" $
+      CInt.toDouble (-100 :: CInt) `shouldBe` (-100.0 :: Double)
diff --git a/unwitch.cabal b/unwitch.cabal
--- a/unwitch.cabal
+++ b/unwitch.cabal
@@ -1,7 +1,7 @@
 cabal-version:      3.0
 
 name:           unwitch
-version:        2.1.0
+version:        2.2.0
 homepage:       https://github.com/jappeace/unwitch#readme
 synopsis:  converts between primitives
 description: 
@@ -82,6 +82,7 @@
   import: common-options
   exposed-modules:
       Unwitch.Convert.ByteString
+      Unwitch.Convert.CInt
       Unwitch.Convert.Char
       Unwitch.Convert.Double
       Unwitch.Convert.Fixed
@@ -118,6 +119,7 @@
   ghc-options: -threaded -rtsopts "-with-rtsopts=-N" -Wno-unused-packages
   other-modules:
       Test.Convert.ByteStringSpec
+      Test.Convert.CIntSpec
       Test.Convert.Int8Spec
       Test.Convert.Int16Spec
       Test.Convert.Int32Spec
