As you are I'm sure aware, you shouldn't be doing much of anything in your DllMain function, but you have to watch out for cases where you end up doing them accidentally.

Some time ago, I was investigating a failure which was traced back to loading a DLL inside DLL_PROCESS_DETACH. Wait, what kind of insane person loads a DLL as part of shutting down? Shouldn't you be cleaning up stuff, not creating new stuff?

The following is not the actual code, but it captures the same spirit:

INFO *CachedInfo;

BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, void *pvReserved)
{
  switch (dwReason) {
  ...
  case DLL_PROCESS_DETACH:
    ...
    CoTaskMemFree(CachedInfo);
    ...
  }
  return TRUE;
}

There is some global variable that contains a pointer to memory that was allocated by CoTaskMemAlloc. In this case, I made it a cache, but the details aren't important. When the DLL is detached from the process, we free the cached memory so we don't have a leak. Since it is okay to pass NULL to the CoTaskMemFree function (it simply returns without doing anything), the cleanup code works even if we never called a function that put a value into the cache.

Except that this code ended up loading a DLL. The reason is delay-loading.

The authors of this DLL sped up its load time by marking OLEAUT32.DLL as a delay-loaded DLL, which means that it doesn't get loaded until somebody calls a function in it.

And in fact, nobody called a function from OLEAUT32. Ever.

"Hooray!" you shout. "We avoided loading OLEAUT32 altogether." After all, the fastest code is code that doesn't run.

Except that it does run. Right there. In your DLL_PROCESS_DETACH handler.

Since nobody called a function from OLEAUT32, the call to CoTaskMemFree was the first call to OLEAUT32 and therefore caused it to be loaded. From inside a DLL_PROCESS_DETACH handler.

Delay-loading is one of those features that is very convenient and saves you a lot of typing (namely, writing those stub functions yourself), but you also have to understand what's going on so you don't use it incorrectly. (In this case, a superficially-redundant if (CachedInfo != NULL) test needs to be inserted.)