A DLL is a shared library. It's an OS level object - any process can load a DLL and call functions defined in it.
An ActiveX control is a COM object that implements specific interfaces that allows hosts to call its methods and embed it in their UI. There are certain minimum requirements on which interfaces an ActiveX control must implement to be embedded successfully in an application's UI. Since COM objects are typically dynamically loaded by a process, they are implemented as a DLL. A single DLL may implement one COM class or more.
Regarding exceptions - I'm not sure what differences you've observed, but a function is no different from a function implemented in your process's main .EXE file. An exception thrown in it should propagate normally according to the rules defined by your programming language's runtime environment.
An ActiveX method is different. Typically, it's called through something called an IDispatch interface. Rather than being a simple subroutine call, it's invoked by calling a method in the IDispatch interface (IDispatch::Invoke), with its arguments marshaled in a specific way (essentially, they're converted to standard types and packaged in a way that hides differences in calling conventions and data types between the implementation language of the main process and that of the ActiveX control.) IDispatch::Invoke interface then determines which method in the ActiveX the caller is trying to access, and routes it directly.
Exceptions are generally not propagated through the IDispatch interface. How your runtime environment deals with the error codes returned by IDispatch::Invokeis up to the implementer to decide, really. So you can reasonably expect not to have your expectations met when dealing with runtime errors and exceptions thrown in an ActiveX control.