GHC knows about quite a few flavours of Large Swathes of Bytes.
First, GHC distinguishes between primitive arrays of (boxed) Haskell objects (type Array# obj) and primitive arrays of bytes (type ByteArray#).
Second, it distinguishes between…
Arrays that do not change (as with “standard” Haskell arrays); you can only read from them. Obviously, they do not need the care and attention of the state-transformer monad.
Arrays that may be changed or “mutated.” All the operations on them live within the state-transformer monad and the updates happen in-place.
A C routine may pass an Addr# pointer back into Haskell land. There are then primitive operations with which you may merrily grab values over in C land, by indexing off the “static” pointer.
If, for some reason, you wish to hand a Haskell pointer (i.e., not an unboxed value) to a C routine, you first make the pointer “stable,” so that the garbage collector won't forget that it exists. That is, GHC provides a safe way to pass Haskell pointers to C.
Please see Section 4.34 in Haskell Libraries for more details.
A “foreign object” is a safe way to pass an external object (a C-allocated pointer, say) to Haskell and have Haskell do the Right Thing when it no longer references the object. So, for example, C could pass a large bitmap over to Haskell and say “please free this memory when you're done with it.”
Please see Section 4.14 in Haskell Libraries for more details.
The libraries documentatation gives more details on all these “primitive array” types and the operations on them.