diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+2022-07-07
+        * Version bump (3.10). (#356)
+        * Remove unnecessary dependencies from Cabal package. (#323)
+        * Remove duplicated compiler option. (#328)
+        * Pass structs by reference, not value, in handlers. (#305)
+        * Relax version bounds of dependencies. (#335)
+        * Update repo info in cabal file. (#333)
+
 2022-05-06
         * Version bump (3.9). (#320)
         * Compliance with style guide (partial). (#316)
diff --git a/copilot-c99.cabal b/copilot-c99.cabal
--- a/copilot-c99.cabal
+++ b/copilot-c99.cabal
@@ -1,6 +1,6 @@
 cabal-version             : >= 1.10
 name                      : copilot-c99
-version                   : 3.9
+version                   : 3.10
 synopsis                  : A compiler for Copilot targeting C99.
 description               :
   This package is a back-end from Copilot to C.
@@ -31,8 +31,8 @@
 
 source-repository head
     type:       git
-    location:   git://github.com/Copilot-Language/copilot.git
-    subdir:     lib/copilot-c99
+    location:   https://github.com/Copilot-Language/copilot.git
+    subdir:     copilot-c99
 
 library
   default-language        : Haskell2010
@@ -42,17 +42,15 @@
   -- https://github.com/Copilot-Language/copilot/issues/237
   -- It should be removed in a future version, when this library hides
   -- internal modules.
-  ghc-options             : -Wall -fwarn-tabs -Wno-deprecations
+  ghc-options             : -Wall -Wno-deprecations
   build-depends           : base                >= 4.9 && < 5
-                          , containers          >= 0.4 && < 0.7
                           , directory           >= 1.3 && < 1.4
                           , filepath            >= 1.4 && < 1.5
-                          , mtl                 >= 2.2 && < 2.3
+                          , mtl                 >= 2.2 && < 2.4
                           , pretty              >= 1.1 && < 1.2
 
-                          , copilot-core        >= 3.9   && < 3.10
+                          , copilot-core        >= 3.10  && < 3.11
                           , language-c99        >= 0.1.1 && < 0.2
-                          , language-c99-util   >= 0.1.1 && < 0.2
                           , language-c99-simple >= 0.1.1 && < 0.2
 
   exposed-modules         : Copilot.Compile.C99
diff --git a/src/Copilot/Compile/C99/CodeGen.hs b/src/Copilot/Compile/C99/CodeGen.hs
--- a/src/Copilot/Compile/C99/CodeGen.hs
+++ b/src/Copilot/Compile/C99/CodeGen.hs
@@ -79,12 +79,16 @@
 
     void = C.TypeSpec C.Void
     stmts  =  map mkexcopy exts
-           ++ map mktriggercheck triggers
+           ++ triggerStmts
            ++ tmpassigns
            ++ bufferupdates
            ++ indexupdates
-    (declns, tmpassigns, bufferupdates, indexupdates) =
+    declns =  streamDeclns
+           ++ concat triggerDeclns
+    (streamDeclns, tmpassigns, bufferupdates, indexupdates) =
       unzip4 $ map mkupdateglobals streams
+    (triggerDeclns, triggerStmts) =
+      unzip $ map mktriggercheck triggers
 
     -- Write code to update global stream buffers and index.
     mkupdateglobals :: Stream -> (C.Decln, C.Stmt, C.Stmt, C.Stmt)
@@ -129,13 +133,85 @@
 
       _       -> C.Ident cpyname C..= C.Ident name
 
-    -- Make if-statement to check the guard, call the trigger if necessary.
-    mktriggercheck :: Trigger -> C.Stmt
-    mktriggercheck (Trigger name guard args) = C.If guard' firetrigger
+    -- Make if-statement to check the guard, call the handler if necessary.
+    -- This returns two things:
+    --
+    -- * A list of Declns for temporary variables, one for each argument that
+    --   the handler function accepts. For example, if a handler function takes
+    --   three arguments, the list of Declns might look something like this:
+    --
+    --   @
+    --   int8_t   handler_arg_temp0;
+    --   int16_t  handler_arg_temp1;
+    --   struct s handler_arg_temp2;
+    --   @
+    --
+    -- * A Stmt representing the if-statement. Continuing the example above,
+    --   the if-statement would look something like this:
+    --
+    --   @
+    --   if (handler_guard()) {
+    --     handler_arg_temp0 = handler_arg0();
+    --     handler_arg_temp1 = handler_arg1();
+    --     handler_arg_temp2 = handler_arg2();
+    --     handler(handler_arg_temp0, handler_arg_temp1, &handler_arg_temp2);
+    --   }
+    --   @
+    --
+    -- We create temporary variables because:
+    --
+    -- 1. We want to pass structs by reference intead of by value. To this end,
+    --    we use C's & operator to obtain a reference to a temporary variable
+    --    of a struct type and pass that to the handler function.
+    --
+    -- 2. Assigning a struct to a temporary variable defensively ensures that
+    --    any modifications that the handler called makes to the struct argument
+    --    will not affect the internals of the monitoring code.
+    mktriggercheck :: Trigger -> ([C.Decln], C.Stmt)
+    mktriggercheck (Trigger name guard args) =
+        (aTmpDeclns, ifStmt)
       where
-        guard'      = C.Funcall (C.Ident $ guardname name) []
-        firetrigger = [C.Expr $ C.Funcall (C.Ident name) args']
+        aTmpDeclns = zipWith (\tmpVar arg ->
+                               C.VarDecln Nothing (tempType arg) tmpVar Nothing)
+                             aTempNames
+                             args
           where
+            tempType (UExpr { uExprType = ty }) =
+              case ty of
+                -- If a temporary variable is being used to store an array,
+                -- declare the type of the temporary variable as a pointer, not
+                -- an array. The problem with declaring it as an array is that
+                -- the `arg` function will return a pointer, not an array, and
+                -- C doesn't make it easy to cast directly from an array to a
+                -- pointer.
+                Array ty' -> C.Ptr $ transtype ty'
+                _         -> transtype ty
+
+        aTempNames = take (length args) (argTempNames name)
+
+        ifStmt = C.If guard' firetrigger
+
+        guard' = C.Funcall (C.Ident $ guardname name) []
+
+        -- The body of the if-statement. This consists of statements that assign
+        -- the values of the temporary variables, following by a final statement
+        -- that passes the temporary variables to the handler function.
+        firetrigger = map C.Expr argAssigns ++
+                      [C.Expr $ C.Funcall (C.Ident name)
+                                          (zipWith passArg aTempNames args)]
+          where
+            passArg aTempName (UExpr { uExprType = ty }) =
+              case ty of
+                -- Special case for Struct to pass reference to temporary
+                -- struct variable to handler. (See the comments for
+                -- mktriggercheck for details.)
+                Struct _ -> C.UnaryOp C.Ref $ C.Ident aTempName
+                _        -> C.Ident aTempName
+
+            argAssigns = zipWith (\aTempName arg ->
+                                   C.AssignOp C.Assign (C.Ident aTempName) arg)
+                                 aTempNames
+                                 args'
             args'        = take (length args) (map argcall (argnames name))
             argcall name = C.Funcall (C.Ident name) []
 
diff --git a/src/Copilot/Compile/C99/Compile.hs b/src/Copilot/Compile/C99/Compile.hs
--- a/src/Copilot/Compile/C99/Compile.hs
+++ b/src/Copilot/Compile/C99/Compile.hs
@@ -157,7 +157,15 @@
           where
             cty    = C.TypeSpec C.Void
             params = map mkparam $ zip (argnames name) args
-            mkparam (name, UExpr ty _) = C.Param (transtype ty) name
+            mkparam (name, UExpr ty _) = C.Param (mkParamTy ty) name
+
+            -- Special case for Struct, to pass struct arguments by reference.
+            -- Arrays are also passed by reference, but using C's array type
+            -- does that automatically.
+            mkParamTy ty =
+              case ty of
+                Struct _ -> C.Ptr (transtype ty)
+                _        -> transtype ty
 
     -- Declaration for the step function.
     stepdecln :: C.Decln
diff --git a/src/Copilot/Compile/C99/Util.hs b/src/Copilot/Compile/C99/Util.hs
--- a/src/Copilot/Compile/C99/Util.hs
+++ b/src/Copilot/Compile/C99/Util.hs
@@ -55,9 +55,18 @@
 argname :: String -> Int -> String
 argname name n = name ++ "_arg" ++ show n
 
+-- | Turn a handler function name into a name for a temporary variable for a
+-- handler argument.
+argTempName :: String -> Int -> String
+argTempName name n = name ++ "_arg_temp" ++ show n
+
 -- | Enumerate all argument names based on trigger name.
 argnames :: String -> [String]
 argnames base = [aname | n <- [0..], let aname = argname base n]
+
+-- | Enumerate all temporary variable names based on handler function name.
+argTempNames :: String -> [String]
+argTempNames base = map (argTempName base) [0..]
 
 -- | Define a C expression that calls a function with arguments.
 funcall :: C.Ident -> [C.Expr] -> C.Expr
