[Image] [Image] [Image] [Image] [Image] [Image] [Image] [Return] Dynamic Linking Techniques [Return] --------------------------------------------------------------------------- To understand dynamic linking, an understanding of modules and "normal" linking is required. Most programs make calls to the Windows API and most functions are part of a system module. The code for these functions reside in the module itself, not the link library, and the link library is needed only to shut the linker up and create an executable. When the program is loaded, the system loader finds references to the functions and replaces the stubs in the executable with pointers to the actual function. If a function or module used by the program cannot be found by the system loader, a message is displayed, the process is stopped and the program is not loaded. Usually, this is a pretty good idea, but if the missing module or function is not required for operation, you may want the program to run anyway. The CTL3D module falls into this catagory. In addition, you may call functions which are not optional, but your link libraries do not have a reference to the function. The new SetWindowRgn() function falls into this catagory. Although CTL3D is generally not required while SetWindowRgn() generally is, both cases are easily solved using dynamic linking. Dynamic linking is where the program itself finds the modules of interest and functions within those modules. Unlike the static linking scenerio described above, the system has no control over this and does not stop an application if a module or function cannot be found. How this situation is handled is entirely up to the programmer. As mentioned, CTL3D is not required for any real functionality and therefore a programmer may choose to allow the program to run if the module is not present. SetWindowRgn(), however, is standard in Windows NT 3.5 (or better) and Windows 95 and is probably a more integral part of the program. Therefore, the programmer may choose to end the program if it cannot be found, such as if the program is run under Windows NT 3.1. Regardless of this situation, if a module cannot be found, the system will whine using a message box. This message may be eliminated using the SetErrorMode() function before attempting to load any optional components. In order to dynamically link to an optional component, you need to know the module and function names. An educated guess is needed for a starting point, but "link -dump -exports modulename.dll" may be used to display the exported functions of any executable module. For example, if you suspect SetWindowRgn() resides in the USER module, run it through the linker as described and look for the function in the export list. If it's there, the task is complete. Otherwise, you need to look elsewhere. Note that functions which take string parameters have a Unicode and an ANSI version. This means you will never find the "straight" function but instead, you will find two slightly different functions. For example, SetWindowText() doesn't actually exist but is a macro in a header file which, depending on if the program is Unicode or not, points to SetWindowTextA() or SetWindowTextW(). A function ending in "W" is the Unicode version. Finding the functions and modules is the hard part. With that information in hand, the rest is fairly easy. Use SetErrorMode() to prevent the system from complaining, then call LoadModule() to load a module. Use GetProcAddress() to get the address of a function within that module. Error checking is absolutely required at some point so the program doesn't jump into never-never land, but how that is handled is up to the programmer. For example, if a module fails to load, there's no point in calling GetProcAddress() at all if it references that module. If the module does load but a function cannot be found, other functions may still work from within that module. In this case, you may want to check the function pointer right before calling the actual function, providing as much functionality as you possibly can. Again, it depends on the application, the required functionality and how much the program suffers if that functionality is missing. A pointer to each function is needed in order to actually call the function. The same pointer may be used for all functions since it's just a pointer, but to make the code more readable, this is not the mothod I've chosen to demonstrate here. Instead, each function has it's own pointer which is laid out to also act as a prototype. How you want to do this is entirely up to you, I chose the more readable way. Describing dynamic linking is more difficult than actually doing it and this isn't a book (although it's getting close to one), so with this brief description out of the way, let's look at some code. Using the code below as a reference, here is a quick description of what happens. First, notice the variables Ctl3dRegister, Ctl3dUnregister and Ctl3dAutoSubclass. They look like functions, but they are actually function pointers. In the code, the error mode is set so the system doesn't complain if the module is not present. An attempt is then made to load the module and the error mode is returned to normal. At this point, you may chose to end the program if LoadModule() fails but in the below code, if the load fails, optional code is simply skipped over. If the module is loaded, however, pointers to the three functions of interest are obtained. Since the module operation hinges on being able to register the program with the module, if that function is missing, the rest are also skipped. Note how the code using the function pointers looks like normal code and is not convoluted with a gazillion casts. The program then proceeds normally until closed, at which time the process is reversed, unloading the module just before the program exits completely. // Entry point into some application or another... int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrev, LPSTR lpCmd, int nShow ) { WNDCLASS wc; MSG msg; BOOL ( CALLBACK *Ctl3dRegister )( HINSTANCE ); // Function pointers BOOL ( CALLBACK *Ctl3dUnregister )( HINSTANCE ); // to some CTL3D calls BOOL ( CALLBACK *Ctl3dAutoSubclass )( HINSTANCE ); UINT uErrorMode; // Holds the original Windows error mode // First, prevent the Windows message box from appearing // if LoadModule() fails uErrorMode = SetErrorMode( SEM_NOOPENFILEERRORBOX ); hCtl3d = LoadLibrary( "CTL3D32.DLL" ); // Load the module of interest SetErrorMode( uErrorMode ); // Put back the original error mode if( hCtl3d ) // If the module loaded... { // Get pointers to the desired functions (FARPROC)Ctl3dRegister = // (cast everything out the butt) (FARPROC)GetProcAddress( hCtl3d, "Ctl3dRegister" ); (FARPROC)Ctl3dUnregister = (FARPROC)GetProcAddress( hCtl3d, "Ctl3dUnregister" ); (FARPROC)Ctl3dAutoSubclass = (FARPROC)GetProcAddress( hCtl3d, "Ctl3dAutoSubclass" ); if( Ctl3dRegister ) // Call the functions normally if { Ctl3dRegister( hInstance ); // the pointers are valid if( Ctl3dAutoSubclass ) // Since this can't be called without Ctl3dAutoSubclass( hInstance ); // registering, it's doubly optional } } wc.style = 0; // Proceed with "normal" code wc.lpfnWndProc = MainWndProc; wc.hInstance = hInstance; wc.lpszClassName = "ETC"; wc.lpszMenuName = "AND_SO_ON"; wc.hIcon = LoadIcon( ... ); wc.hCursor = LoadCursor( ... ); wc.hbrBackground = (HBRUSH)( COLOR_WINDOW + 1 ); wc.cbClsExtra = wc.cbWndExtra = 0; if( ! RegisterClass( &wc ) ) return( FALSE ); hInst = hInstance; hMainWnd = CreateWindow( "BLAH", "BLAH", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); if( ! hMainWnd ) return( FALSE ); ShowWindow( hMainWnd, nShow ); UpdateWindow( hMainWnd ); while( GetMessage( &msg, NULL, 0, 0 ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } if( hCtl3d && Ctl3dRegister ) // Clean up before leaving program { // Yes, Windows can do this but why be sloppy? if( Ctl3dUnregister ) // If the module and pointer are valid Ctl3dUnregister( hInstance ); // The unregister the library and FreeLibrary( hCtl3d ); // free it (opposite of LoadModule) } return( FALSE ); }