Here is some C code for a 100% barebones, do-nothing DLL. The code is well commented, so check it out. The entire source code is available in text (compilable) and html. In this tutorial, however, we will only be going over the DLL source and a snippet of the DLLUser application. The code to the DLLUser application is well commented, and most likely very interesting if you want to see what a Windows program looks like, but is out of the scope of this tutorial.
First we will look at the DLL's entrypoint function, DllEntryPoint(). Windows calls this function when the DLL is first loaded, when another process attaches to/detaches from the DLL (says it will use the DLL), and when an attached process creates/destroys a thread.
Windows lets the DLL know why DllEntryPoint() is being called via the fdwReason parameter. In our DllEntryPoint(), we check to see what the dwReason parameter is and take action accordingly.
/*
DllEntryPoint
Preconditions: none - it is called at various times during the DLL's
life (when first loaded, when processes attach/detach,
etc).
PostConditions: none - this implementation of DllEntryPoint doesn't
really do anything. Normally, this function would
look at the fdwReason parameter and allocate/free
memory depending on what fdwReason is.
Parameters:
HINSTANCE hDLL: The HINSTANCE of this dll. You save this variable
and pass it to Windows API functions that require
an HINSTANCE parameter.
DWORD fdwReason: This is a numeric code that tells us why Windows
is calling DllEntryPoint()
LPVOID lpvReserved: This parameter is not used - it is reserved
by Windows for a possible future use. Microsoft
likes to do this stuff sometimes...
Return value: Returns TRUE if initialization completes succesfully,
It returns FALSE if something goes wrong and the DLL
is unable to initialize.
*/
BOOL WINAPI DllEntryPoint( HINSTANCE hDll,
DWORD fdwReason,
LPVOID lpvReserved )
{
/*
Let's find out exactly why Windows called us...
*/
switch ( fdwReason )
{
case DLL_PROCESS_ATTACH:
/*
Here we would normally do some startup stuff, like saving the
hDll parameter to a global variable, allocating memory for
local structures and stuff, etc...
*/
break ;
case DLL_PROCESS_DETACH:
/*
Here we would free the memory and stuff we allocated in
response to DLL_PROCESS_ATTACH.
*/
break ;
default :
/*
If fdwReason isn't the above two cases, we'll just ignore it
*/
break ;
};
There is only one other function in basicdll.dll, and that is our one exported function. It is aptly named ExportedFunction(). Exported function doesn't do anything really, it just displays a message box that informs the user that it has been called properly.
/*
ExportedFunction
Preconditions: none
Postcontitions: none
Parameters:
none
*/
int WINAPI ExportedFunction( void )
{
/*
Let's call MessageBox() to display a nice friendly notification
that we were called. Another program or DLL can call this function
by either linking to this DLL at compile time, or calling
LoadLibrary() and GetProcAddress().
*/
MessageBox( NULL, //HWINDOW of the window that owns the message box
"Hello from inside basicdll.dll!" , //Text
"ExportedFunction() has been called!" ,//Title
MB_OK | MB_ICONINFORMATION );//Bit mask flags
return 1;
}
Next we are going to take a look at the DllUser.exe code that calls ExportedFunction() from our DLL. I'm only going to go over the code that calls Exported Function, and the .DEF file that is linked with DllUser.exe .
The code that we are looking at is inside DllUser.exe's message loop, which is the function in Windows programs that is notified of user actions. DllUser calls ExportedFunction() in two ways - it calls it like "normal", as it is able to do because it has linked with the DEF file that tells the linker where ExportedFunction() is. It will also explicitly load basicdll.dll into memory and use GetProcAddress() (a WindowsAPI function) to get the address of ExportedFunction() in our DLL. It then uses a function pointer to call ExportedFunction(). As you can see when you download the sourcecode, both methods work.
case CM_USENORMAL:
// We linked to the function at compile time,
// so let's just call it
//
ExportedFunction();
break ;
case CM_USEFP:
// First let's load the DLL
//
hDll = LoadLibrary("basicdll.dll"
);
// On error, we need to return
//
if ( !hDll )
return 0;
// Now let's get a pointer to ExportedFunction
//
funcPtr = (LPFNEXPORTED)
GetProcAddress( hDll, "ExportedFunction" );
// If funcPtr is null, we need to return
//
if ( !funcPtr )
return 0;
// Now let's call ExportedFunction! Notice how you
// use the name of the function pointer just like
// you would use the actual function name.
//
funcPtr();
// Now we're done with basicdll.dll, so let's unload
// it.
//
FreeLibrary( hDll );
break ;
Now let's look at the DEF file that DllUser.exe is linked with. The code is very well commented, as you can see, and should be relatively self explanatory. Notice that by listing ExportedFunction() in our DEF file, we are able to call it without doing any special preparation in our code.
Want more? | for the more adventurous...
Want to see what the code for a Windows program looks like? Want to see what kind of stuff goes into that? Then go download the sourcecode to basicdll.dll and dlluser.exe. The code is well commented and you will prolly be able to learn alot about exactly what it takes to write a Windows program in straight C. Head on over to the compilers sections to see a tutorial on how to compile this code using specific compilers.