iczelion tut17
Tutorial 17: Dynamic Link Libraries
In this tutorial, we will learn about DLLs , what are they and how to create them.
You can download the example here.
Theory:
If you program long enough, you'll find that the programs you wrote usually have some code routines in common. It's such a waste of time to rewrite them everytime you start coding new programs. Back in the old days of DOS, programmers store those commonly used routines in one or more libraries. When they want to use the functions, they just link the library to the object file and the linker extracts the functions from the library and inserts them into the final executable file. This process is called static linking. C runtime libraries are good examples. The drawback of this method is that you have identical functions in every program that calls them. Your disk space is wasted storing several identical copies of the functions. But for DOS programs, this method is quite acceptable since there is usually only one program that's active in memory. So there is no waste of precious memory.Under Windows, the situation becomes much more critical because you can have several programs running simultaneously. Memory will be eat up quickly if your program is quite large. Windows has a solution for this type of problem: dynamic link libraries. A dynamic link library is a kind of common pool of functions. Windows will not load several copies of a DLL into memory so even if there are many instances of your program running at the same time, there'll be only one copy of the DLL that program uses in memory. And I should clarify this point a bit. In reality, all processes that use the same dll will have their own copies of that dll. It will look like there are many copies of the DLL in memory. But in reality, Windows does it magic with paging and all processes share the same DLL code.So in physical memory, there is only one copy of DLL code. However, each process will have its own unique data section of the DLL.
The program links to a DLL at runtime unlike the old static library. That's why it's called dynamic link library. You can also unload a DLL at runtime as well when you don't need it. If that program is the only one that uses the DLL, it'll be unloaded from memory immediately. But if the DLL is still used by some other program, the DLL remains in memory until the last program that uses its service unloads it.
However, the linker has a more difficult job when it performs address fixups for the final executable file. Since it cannot "extract" the functions and insert them into the final executable file, somehow it must store enough information about the DLL and functions into the final execuable file for it to be able to locate and load the correct DLL at runtime.
That's where import library comes in. An import library contains the information about the DLL it represents. The linker can extract the info it needs from the import libraries and stuff it into the executable file. When Windows loader loads the program into memory, it sees that the program links to a DLL so it searches for that DLL and maps it into the address space of the process as well and performs the address fixups for the calls to the functions in the DLL.
You may choose to load the DLL yourself without relying on Windows loader. This method has its pros and cons:
- It doesn't need an import library so you can load and use any DLL even if it comes with no import library. However, you still have to know about the functions inside it, how many parameters they take and the likes.
- When you let the loader load the DLL for your program, if the loader cannot find the DLL it will report "A required .DLL file, xxxxx.dll is missing" and poof! your program doesn't have a chance to run even if that DLL is not essential to its operation. If you load the DLL yourself, when the DLL cannot be found and it's not essential to the operation, your program can just tell the user about the fact and go on.
- You can call *undocumented* functions that are not included in the import libraries. Provided that you know enough info about the functions.
- If you use LoadLibrary, you have to call GetProcAddress for every function that you want to call. GetProcAddress retrieves the entrypoint address of a function in a particular DLL. So your code might be a little bit larger and slower but by not much.
The following code is the DLL skeleton.
;--------------------------------------------------------------------------------------
; DLLSkeleton.asm
;--------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include masm32includewindows.inc
include masm32includeuser32.inc
include masm32includekernel32.inc
includelib masm32libuser32.lib
includelib masm32libkernel32.lib
.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
;---------------------------------------------------------------------------------------------------
; This is a dummy function
; It does nothing. I put it here to show where you can insert functions into
; a DLL.
;----------------------------------------------------------------------------------------------------
TestFunction proc
ret
TestFunction endp
End DllEntry
;-------------------------------------------------------------------------------------
; DLLSkeleton.def
;-------------------------------------------------------------------------------------
LIBRARY DLLSkeleton
EXPORTS TestFunction
The above program is the DLL skeleton. Every DLL must have an entrypoint function. Windows will call the entrypoint function everytime that:
- The DLL is first loaded
- The DLL is unloaded
- A thread is created in the same process
- A thread is destroyed in the same process
mov eax,TRUE
ret
DllEntry Endp
You can name the entrypoint function anything you wish so long as you have a matching END <Entrypoint function name>. This function takes three parameters, only the first two of which are important.
hInstDLL is the module handle of the DLL. It's not the same as the instance handle of the process. You should keep this value if you need to use it later. You can't obtain it again easily.
reason can be one of the four values:
- DLL_PROCESS_ATTACH The DLL receives this value when it is first injected into the process address space. You can use this opportunity to do initialization.
- DLL_PROCESS_DETACH The DLL receives this value when it is being unloaded from the process address space. You can use this opportunity to do some cleanup such as deallocate memory and so on.
- DLL_THREAD_ATTACH The DLL receives this value when the process creates a new thread.
- DLL_THREAD_DETACH The DLL receives this value when a thread in the process is destroyed.
You can put your functions in the DLL following the entrypoint function or before it. But if you want them to be callable from other programs, you must put their names in the export list in the module definition file (.def).
A DLL needs a module definition file in its developmental stage. We will take a look at it now.
LIBRARY DLLSkeleton
EXPORTS TestFunction
Normally you must have the first line.The LIBRARY statement defines the internal module name of the DLL. You should match it with the filename of the DLL.
The EXPORTS statement tells the linker which functions in the DLL are exported, that is, callable from other programs. In the example, we want other modules to be able to call TestFunction, so we put its name in the EXPORTS statement.
Another change is in the linker switch. You must put /DLL switch and /DEF:<your def filename> in your linker switches like this:
link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:masm32lib DLLSkeleton.obj
The assembler switches are the same, namely /c /coff /Cp. So after you link the object file, you will get .dll and .lib. The .lib is the import library which you can use to link to other programs that use the functions in the DLL.
Next I'll show you how to use LoadLibrary to load a DLL.
;---------------------------------------------------------------------------------------------
; UseDLL.asm
;----------------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include masm32includewindows.inc
include masm32includeuser32.inc
include masm32includekernel32.inc
includelib masm32libkernel32.lib
includelib masm32libuser32.lib
.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0
.data?
hLib dd ? ; the handle of the library (DLL)
TestHelloAddr dd ? ; the address of the TestHello function
.code
start:
invoke LoadLibrary,addr LibName
;---------------------------------------------------------------------------------------------------------
; Call LoadLibrary with the name of the desired DLL. If the call is successful
; it will return the handle to the library (DLL). If not, it will return NULL
; You can pass the library handle to GetProcAddress or any function that requires
; a library handle as a parameter.
;------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
.else
mov hLib,eax
invoke GetProcAddress,hLib,addr FunctionName
;-------------------------------------------------------------------------------------------------------------
; When you get the library handle, you pass it to GetProcAddress with the address
; of the name of the function in that DLL you want to call. It returns the address
; of the function if successful. Otherwise, it returns NULL
; Addresses of functions don't change unless you unload and reload the library.
; So you can put them in global variables for future use.
;-------------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
.else
mov TestHelloAddr,eax
call [TestHelloAddr]
;-------------------------------------------------------------------------------------------------------------
; Next, you can call the function with a simple call with the variable containing
; the address of the function as the operand.
;-------------------------------------------------------------------------------------------------------------
.endif
invoke FreeLibrary,hLib
;-------------------------------------------------------------------------------------------------------------
; When you don't need the library anymore, unload it with FreeLibrary.
;-------------------------------------------------------------------------------------------------------------
.endif
invoke ExitProcess,NULL
end start
So you can see that using LoadLibrary is a little more involved but it's also more flexible.