You may be asking yourself: …who cares? Well, first of all it’s good to know. I haven’t noticed a public service announcement to the above. It is an implementation detail, however—CLR assemblies are not even guaranteed to be implemented using files, not to mention DLL files in a specific format that are loaded using the LoadLibrary Win32 API.
However, there are several tools and scenarios which have come to rely on the fact the CLR loads assemblies using LoadLibrary. For example, up to CLR 4, if you wanted to know which .NET assemblies were loaded in your process, a fairly reliable heuristic would be to fire up Sysinternals Process Explorer and look at the DLLs view of a given process. This doesn’t work for CLR 4, as you can see here:
Note that although the .NET Assemblies tab shows clearly an assembly called “TheClassLibrary”, it’s not present in the DLLs list.
I wouldn’t be so concerned with not seeing a DLL in the modules list if there weren’t additional utilities relying on an assembly being “loaded” in the Windows loader sense. One of them has to do with debugging symbols.
SOS and similar tools need debugging symbols for managed code to display source file and line level information. For example, if you use the !CLRStack command to display the managed stack of a certain thread, you expect source-and-line information but it requires PDB files with debugging information for the binary whose methods appear on the stack.
What’s the problem? The problem is that if the .NET assemblies (DLLs) are not actually loaded into the process, the debugger does not load symbols for them. Without symbols, debugger extensions like SOS or SOSEX can’t use the debugging information, so here’s the kind of stack you would get:
0:000> !mk
ESP RetAddr
00:U 0043ef1c 75d473ea KERNEL32!ReadConsoleInternal+0x15
01:U 0043ef24 75d47041 KERNEL32!ReadConsoleA+0x40
02:U 0043efac 75ccf489 KERNEL32!ReadFileImplementation+0x75
03:U 0043eff4 0fa8cc7c MSVCR100D!_read_nolock+0x62c [f:\dd\vctools\crt_bld\self_x86\crt\src\read.c @ 230]
04:U 0043f088 0fa8c5c9 MSVCR100D!_read+0x219 [f:\dd\vctools\crt_bld\self_x86\crt\src\read.c @ 92]
05:U 0043f0d8 0f9e1093 MSVCR100D!_filbuf+0x113 [f:\dd\vctools\crt_bld\self_x86\crt\src\_filbuf.c @ 136]
06:U 0043f100 0f9df5ab MSVCR100D!getc+0x20b [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetc.c @ 75]
07:U 0043f15c 0f9df660 MSVCR100D!_fgetchar+0x10 [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetchar.c @ 37]
08:U 0043f168 0f9df67a MSVCR100D!getchar+0xa [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetchar.c @ 47]
09:U 0043f170 0f8f1396 TheUnmanagedDll!fnTheUnmanagedDll+0x26 [c:\temp\theunmanageddll.cpp @ 12]
…snipped…
0b:M 0043f2a8 002900c7 TheClassLibrary.TheClass.TheMethod()(+0x1 IL)(+0x6 Native)
Read more: All Your Base Are Belong To Us
However, there are several tools and scenarios which have come to rely on the fact the CLR loads assemblies using LoadLibrary. For example, up to CLR 4, if you wanted to know which .NET assemblies were loaded in your process, a fairly reliable heuristic would be to fire up Sysinternals Process Explorer and look at the DLLs view of a given process. This doesn’t work for CLR 4, as you can see here:
Note that although the .NET Assemblies tab shows clearly an assembly called “TheClassLibrary”, it’s not present in the DLLs list.
I wouldn’t be so concerned with not seeing a DLL in the modules list if there weren’t additional utilities relying on an assembly being “loaded” in the Windows loader sense. One of them has to do with debugging symbols.
SOS and similar tools need debugging symbols for managed code to display source file and line level information. For example, if you use the !CLRStack command to display the managed stack of a certain thread, you expect source-and-line information but it requires PDB files with debugging information for the binary whose methods appear on the stack.
What’s the problem? The problem is that if the .NET assemblies (DLLs) are not actually loaded into the process, the debugger does not load symbols for them. Without symbols, debugger extensions like SOS or SOSEX can’t use the debugging information, so here’s the kind of stack you would get:
0:000> !mk
ESP RetAddr
00:U 0043ef1c 75d473ea KERNEL32!ReadConsoleInternal+0x15
01:U 0043ef24 75d47041 KERNEL32!ReadConsoleA+0x40
02:U 0043efac 75ccf489 KERNEL32!ReadFileImplementation+0x75
03:U 0043eff4 0fa8cc7c MSVCR100D!_read_nolock+0x62c [f:\dd\vctools\crt_bld\self_x86\crt\src\read.c @ 230]
04:U 0043f088 0fa8c5c9 MSVCR100D!_read+0x219 [f:\dd\vctools\crt_bld\self_x86\crt\src\read.c @ 92]
05:U 0043f0d8 0f9e1093 MSVCR100D!_filbuf+0x113 [f:\dd\vctools\crt_bld\self_x86\crt\src\_filbuf.c @ 136]
06:U 0043f100 0f9df5ab MSVCR100D!getc+0x20b [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetc.c @ 75]
07:U 0043f15c 0f9df660 MSVCR100D!_fgetchar+0x10 [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetchar.c @ 37]
08:U 0043f168 0f9df67a MSVCR100D!getchar+0xa [f:\dd\vctools\crt_bld\self_x86\crt\src\fgetchar.c @ 47]
09:U 0043f170 0f8f1396 TheUnmanagedDll!fnTheUnmanagedDll+0x26 [c:\temp\theunmanageddll.cpp @ 12]
…snipped…
0b:M 0043f2a8 002900c7 TheClassLibrary.TheClass.TheMethod()(+0x1 IL)(+0x6 Native)
Read more: All Your Base Are Belong To Us