The biggest reason to avoid using an in-process module of any kind can be summed up in one word: interoperability. A handler or server can be loaded only into a container process of the same bitness: 16-16 or 32-32. In addition, there is no interoperability between OLE 2 in-process modules and OLE 1 containers, period. The only solution is to create different versions for each possible case you want to support. This is a pain, but that's the price of evolving operating systems.
The other issues that might put in-process modules out of your reach are various technical implementation issues. DLLs, because they have no message loop themselves, have a problem handling, for example, keyboard accelerators or MDI interfaces that would typically require changes to a message loop. Overall, there are a number of things that you cannot do from a DLL. If you have to do one of them, a DLL is not for you. In implementing the Polyline example for this chapter, I ran into such problems. I originally planned to make the Polyline in-process server look much like Cosmo when it opened an object for editing, including menus and so forth. The lack of accelerators, however, meant menus were only marginally useful. I was forced as a result to come up with a different user interface based on a dialog box. Although this works well, it is different, and that difference might be reason enough for you to avoid an in-process server yourself.
The other technical issue is that as a DLL, an in-process server must be loaded into an existing process to be usable: you can't run a DLL by itself. For this reason, in-process servers generally do not support linked objects, only embeddings. What you can do, however, is create a shell EXE wrapper that loads the DLL and provides a stand-alone user interface in which you can create and save files to make suitable link sources.