Chapter 20
In the previous chapter, we discussed the basics of DLL linking and concentrated
In order for a thread to call a function in a DLL module, the DLL's file image must be mapped into the address space of the calling thread's process. You can accomplish this in two ways. The first way is to have your application's source code simply reference symbols contained in the DLL. This causes the loader to implicitly load (and link) the required DLL when the application is invoked.
The second way is for the application to explicitly load the required DLL and explicitly link to the desired exported symbol while the application is running. In other words, while the application is running, a thread within it can decide that it wants to call a function within a DLL. That thread can explicitly load the DLL into the process's address space, get the virtual memory address of a function contained within the DLL, and then call the function using this memory address. The beauty of this technique is that everything is done while the application is running.
Figure 20-1 shows how an application explicitly loads a DLL and links to a symbol within it.
Figure 20-1. How a DLL is created and explicitly linked by an application
At any time, a thread in the process can decide to map a DLL into the process's address space by calling one of these two functions:
HINSTANCELoadLibrary(PCTSTRpszDLLPathName); HINSTANCELoadLibraryEx(PCTSTRpszDLLPathName, HANDLEhFile, DWORDdwFlags); |
Both of these functions locate a file image on the
You'll notice that the
LoadLibraryEx
function has two additional parameters:
hFile
and
dwFlags
. The
hFile
parameter is reserved for future use and must be NULL for now. For the
dwFlags
parameter, you must specify 0 or a combination of the DONT_RESOLVE_DLL_REFERENCES, LOAD_LIBRARY_AS_DATAFILE, and LOAD_WITH_ALTERED_SEARCH_
The DONT_RESOLVE_DLL_REFERENCES flag
In addition, a DLL might import functions contained in another DLL. When the system maps a DLL into a process's address space, it also checks to see whether the DLL requires any additional DLLs and automatically loads these as well. When the DONT_RESOLVE_DLL_REFERENCES flag is specified, the system does not automatically load any of these additional DLLs into the process's address space.
The LOAD_LIBRARY_AS_DATAFILE flag is similar to the DONT_RESOLVE_DLL_REFERENCES flag in that the system simply maps the DLL into the process's address space as if it were a data file. The system spends no additional time preparing to execute any code in the file. For example, when a DLL is mapped into a process's address space, the system examines some information in the DLL to determine which page protection attributes should be assigned to different sections of the file. If you don't specify the LOAD_LIBRARY_AS_DATAFILE flag, the system sets the page protection attributes in the same way that it would if it were expecting to execute code in the file.
This flag is useful for several reasons. First, if you have a DLL that contains only resources and no functions, you can specify this flag so that the DLL's file image is mapped into the process's address space. You can then use the HINSTANCE value returned from LoadLibraryEx in calls to functions that load resources. Also, you can use the LOAD_LIBRARY_AS_DATAFILE flag if you want to use resources that are contained in an .exe file. Normally, loading an .exe file starts a new process, but you can also use the LoadLibraryEx function to map an .exe file's image into a process's address space. With the mapped .exe file's HINSTANCE value, you can access resources within it. Because an .exe file doesn't have the DllMain function, you must specify the LOAD_LIBRARY_AS_DATAFILE flag when you call LoadLibraryEx to load an .exe file.
The LOAD_WITH_ALTERED_SEARCH_PATH flag changes the search algorithm that LoadLibraryEx uses to locate the specified DLL file. Normally, LoadLibraryEx searches for files in the order shown in Chapter 19. However, if the LOAD_WITH_ALTERED_SEARCH_PATH flag is specified, LoadLibraryEx searches for the file using the following algorithm:
When the threads in the process no longer want to reference symbols in a DLL, you can explicitly unload the DLL from the process's address space by calling this function:
BOOLFreeLibrary(HINSTANCEhinstDll); |
You must pass the HINSTANCE value that identifies the DLL you want to unload. This value was returned by an earlier call to LoadLibrary ( Ex ) .
You can also unload a DLL module from a process's address space by calling this function:
VOIDFreeLibraryAndExitThread(HINSTANCEhinstDll, DWORDdwExitCode); |
This function is implemented in Kernel32.dll as
VOIDFreeLibraryAndExitThread(HINSTANCEhinstDll,DWORDdwExitCode){
FreeLibrary(hinstDll);
ExitThread(dwExitCode);
}
|
At first glance, this doesn't look like a big deal, and you might
But if the thread calls
FreeLibrary
and
ExitThread
individually, a serious problem occurs. The problem, of course, is that the call to
FreeLibrary
unmaps the DLL from the process's address space immediately. By the time the call to
FreeLibrary
returns, the code that contains the call to
ExitThread
is no longer available and the thread will attempt to execute nothing. This causes an access violation, and the entire process is
However, if the thread calls
FreeLibraryAndExitThread
, this function calls
FreeLibrary
,
Granted, you probably won't have much need for the FreeLibraryAndExitThread function. I've needed it only once, and I was performing a very specialized task. Also, I was writing code for Microsoft Windows NT 3.1, which did not offer this function. So I was glad to see that Microsoft had added it to more recent versions of Windows.
In reality, the
LoadLibrary
and
LoadLibraryEx
functions increment a
In order for the DLL file image to be unmapped from the process's address space, threads in the process must call FreeLibrary twice—the first call simply decrements the DLL's usage count to 1, and the second call decrements the DLL's usage count to 0. When the system sees that a DLL's usage count has reached 0, it unmaps the DLL's file image from this process's address space. Any thread that attempts to call a function in the DLL raises an access violation because the code at the specified address is no longer mapped into the process's address space.
The system maintains a DLL's usage count on a per-process basis; that is, if a thread in Process A makes the following call and then a thread in Process B makes the same call, MyLib.dll is mapped into both processes' address spaces—the DLL's usage count for Process A and for Process B are both 1.
HINSTANCEhinstDll=LoadLibrary("MyLib.dll");
|
If a thread in Process B later calls the following function, the DLL's usage count for Process B becomes 0, and the DLL is unmapped from Process B's address space. However, the mapping of the DLL in Process A's address space is unaffected, and the DLL's usage count for Process A remains 1.
FreeLibrary(hinstDll); |
A thread can determine whether a DLL is already mapped into its process's address space by calling the GetModuleHandle function:
HINSTANCEGetModuleHandle(PCTSTRpszModuleName); |
For example, the following code loads MyLib.dll only if it is not already mapped into the process's address space:
HINSTANCEhinstDll=GetModuleHandle("MyLib");//DLLextensionassumed
if(hinstDll==NULL){
hinstDll=LoadLibrary("MyLib");//DLLextensionassumed
}
|
You can also determine the full pathname of a DLL (or an .exe) if you have only the DLL's HINSTANCE value by using the GetModuleFileName function:
DWORDGetModuleFileName(HINSTANCEhinstModule, PTSTRpszPathName, DWORDcchPath); |
The first parameter is the DLL's (or .exe's) HINSTANCE. The second parameter,
pszPathName
, is the address of the buffer where the function puts the file image's full pathname. The third parameter,
cchPath
, specifies the
Once a DLL module has been explicitly loaded, the thread must get the address of the symbol that it wants to reference by calling this function:
FARPROCGetProcAddress(HINSTANCEhinstDll, PCSTRpszSymbolName); |
The
hinstDll
parameter, returned from a call to
LoadLibrary
(
Ex
) or
GetModuleHandle
, specifies the handle of the DLL containing the symbol. The
pszSymbolName
parameter can take one of two forms. The first form is the address of a zero-terminated string containing the
FARPROCpfn=GetProcAddress(hinstDll,"SomeFuncInDll"); |
Notice that the
pszSymbolName
parameter is prototyped as a PCSTR, as opposed to a PCTSTR. This means that the
GetProcAddress
function accepts only ANSI strings—you never pass Unicode strings to this function because the compiler/linker always stores symbol
The second form of the pszSymbolName parameter indicates the ordinal number of the symbol whose address you want:
FARPROCpfn=GetProcAddress(hinstDll,MAKEINTRESOURCE(2)); |
This usage assumes that you know that the desired symbol name was assigned the ordinal value of 2 by the creator of the DLL. Again, let me reiterate that Microsoft strongly discourages the use of ordinals, so you won't often see this second usage of GetProcAddress .
Either method provides the address to the desired symbol contained in the DLL. If the
You should be aware that the first method of calling
GetProcAddress
is slower than the second because the system must perform string comparisons and searches on the symbol name string that was passed. With the second method, if you pass an ordinal number that hasn't been assigned to any of the exported functions,
GetProcAddress
might return a non-NULL value. This return value will trick your application into thinking that you have a valid address when you don't. Attempting to call this address will almost