How to Port a 16-bit DLL to a Win32 DLL

ID: Q125688

4.00    | 3.51 4.00
WINDOWS | WINDOWS NT kbprg

The information in this article applies to:

SUMMARY

There are several significant differences between Win16 DLLs and Win32 DLLs. These differences require more than just a simple recompilation to turn your Win16 DLL into a Win32 DLL. In this article we will show you how to port your Win16 DLL to a Win32 DLL.

MORE INFORMATION

The first major difference is that the Win32 DLL entry point is called with every process attach and detach. Secondly, you must account for the fact that processes can be multithreaded and as such your DLL entry point will be called with thread attach and detach messages. You need to ensure that your DLL is "thread-safe" by using multithreaded libraries and mutual exclusion for functions in your DLL that would otherwise cause data corruption when preempted and reentered. This requires that you use Win32 synchronization methods to guard critical resources. Finally, each Win32 process gets its own copy of the Win32 DLL's data.

Step One for Porting Your DLL

The first step in porting a DLL from 16-bit Windows to 32-bit Windows is moving code from your LibMain (or LibEntry) and _WEP (or WEP) to the new DLL initialization function. The new DLL initialization function is called DllMain. You might code your DllMain like this:

BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) {

   switch (fdwReason)
   {
      case DLL_PROCESS_ATTACH:
         /* Code from LibMain inserted here.  Return TRUE to keep the
            DLL loaded or return FALSE to fail loading the DLL.

            You may have to modify the code in your original LibMain to
            account for the fact that it may be called more than once.
            You will get one DLL_PROCESS_ATTACH for each process that
            loads the DLL. This is different from LibMain which gets
            called only once when the DLL is loaded. The only time this
            is critical is when you are using shared data sections.
            If you are using shared data sections for statically
            allocated data, you will need to be careful to initialize it
            only once. Check your code carefully.

            Certain one-time initializations may now need to be done for
            each process that attaches. You may also not need code from
            your original LibMain because the operating system may now
            be doing it for you.
         */ 
         break;

      case DLL_THREAD_ATTACH:
         /* Called each time a thread is created in a process that has
            already loaded (attached to) this DLL. Does not get called
            for each thread that exists in the process before it loaded
            the DLL.

            Do thread-specific initialization here.
         */ 
         break;

      case DLL_THREAD_DETACH:
         /* Same as above, but called when a thread in the process
            exits.

            Do thread-specific cleanup here.
         */ 
         break;

      case DLL_PROCESS_DETACH:
         /* Code from _WEP inserted here.  This code may (like the
            LibMain) not be necessary.  Check to make certain that the
            operating system is not doing it for you.
         */ 
         break;
   }

   /* The return value is only used for DLL_PROCESS_ATTACH; all other
      conditions are ignored.  */ 
   return TRUE;   // successful DLL_PROCESS_ATTACH
}

DllMain Called with Flags

There are several conditions where DllMain is called with the DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, or DLL_THREAD_DETACH flags.

The DLL_PROCESS_ATTACH flag is sent when a DLL is loaded into the address space of a process. This occurs in both situations where the DLL is loaded with LoadLibrary, or implicitly during application load. When the DLL is implicitly loaded, DllMain is executed with DLL_PROCESS_ATTACH before the processes enter WinMain. When the DLL is explicitly loaded, DllMain is executed with DLL_PROCESS_ATTACH before LoadLibrary returns.

The DLL_PROCESS_DETACH flag is sent when a process cleanly unloads the DLL from its address space. This occurs during a call to FreeLibrary, or if the DLL is implicitly loaded, a clean process exit. When a DLL is detaching from a process, the individual threads of the process do not call the DLL_THREAD_DETACH flag.

The DLL_THREAD_ATTACH flag is sent when a new thread is being created in a process already attached to the DLL. Threads in existence before the process attached to a DLL will not send the DLL_THREAD_ATTACH flag. The first thread to attach to the DLL does not send the DLL_THREAD_ATTACH flag; it sends the DLL_PROCESS_ATTACH flag instead.

The DLL_THREAD_DETACH flag is sent when a thread is exiting cleanly. There is a situation when DllMain may be called when the thread did not first send the DLL_THREAD_ATTACH flag. This can happen if there are other threads still running and the original thread exits cleanly. The thread originally called DllMain with the DLL_PROCESS_ATTACH flag and later calls DllMain with the DLL_THREAD_DETACH flag. You may also have DllMain being called with DLL_THREAD_DETACH if a thread exits but was running in the process before the call to LoadLibrary.

Situations Where DllMain is Not Called or Is Bypassed

DllMain may not be called at all in dire situations where a thread or process was killed by a call to TerminateThread or TerminateProcess. These functions bypass calling DllMain. They are recommended only as a last resort. Data owned by the thread or process is at risk of loss because the process or thread could not shut itself down properly.

DllMain may be bypassed intentionally by a process if it calls DisableThreadLibraryCalls. This function (available with Windows 95 and Windows NT versions 3.5 and later) disables all DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications for a DLL. This enables a process to reduce its code size and working set. For more information on this function, see the SDK documentation on DisableThreadLibraryCalls.

Step Two for Porting Your DLL

The second step of porting your DLL involves changing functions that were declared with __export and included in the module definition (.DEF) file EXPORTS section. The proper export declaration for the Microsoft Visual C++ 32-bit compiler and linker is __declspec(dllexport). This declaration should be used when prototyping and declaring functions. You do not have to explicitly declare an exported function in the DEF file for the proper LIB and EXP files to be created; __declspec(dllexport) will do this for you. Here's an example of an exported function:

// prototype in DLL is necessary
__declspec(dllexport) DWORD WINAPI DLLFunc1(LPSTR);

// function
__declspec(dllexport) DWORD WINAPI DLLFunc1(LPSTR lpszIn) {
   DWORD dwRes;

   /* DLL function logic */ 

   return dwRes;
}

To include the function in an application, prototype the above function in the application with the __declspec(dllimport) modifier. __declspec(dllimport) is not necessary, but does improve the speed of your code that implements the function call. Here's an example:

   __declspec(dllimport) DWORD WINAPI DLLFunc1(LPSTR);

Then, link the DLL's import library (.LIB) file with the application makefile or project.

Some sections of your DEF file will be ignored by the 32-bit linker because of architectural differences between Win16 and Win32. You may still use the EXPORTS section of your DEF if you wish to include ordinals for exported functions, or to rename exported functions. See your linker documentation for more information about what is acceptable in a DEF file for a Win32 DLL. Users of other 32-bit compilers and linkers will have to refer to their documentation for more information on exporting functions.

Applications that link to your DLL may be multithreaded. This possibility means that you should always build your Win32 DLL as multithreaded to support preemption and reentrancy. If you use runtime library functions in your DLL, they may be preempted and reentered. That would cause problems for a normal runtime library. If you use C runtime or some other runtime library, you should use a multithreaded version of the runtime library. Microsoft Visual C++ users should link with the /MT option and include LIBCMT.LIB. This will include the multithreaded C runtime library. Optionally, you can select the Multithreaded Run-time Library option in the project settings in the Visual Workbench. If you are using another runtime library and cannot get a multithreaded version of the library, you must protect calls to the library from reentrancy using a mutex or critical section synchronization object. Information later in this article discusses this issue.

Other Design Issues You Should Consider

One of the most significant changes to DLLs in 32-bit Windows operating systems is that each process executes in its own private address space. This means that a DLL cannot directly share dynamically-allocated memory between processes. Addresses are 32-bit offsets in a process's address space. Passing them between processes is possible, but won't work as in Win16 because each process has its own address space. In another process, this pointer may address unknown data, or invalid memory space.

"DS != SS" issues common to 16-bit DLLs no longer apply. A Win32 process executes in its own private address space and there is no segmentation of this address space. In a Win32 DLL, all functions are called using the calling thread's stack and all pointers are 32-bit linear addresses. In a Win32 process or DLL, a FAR pointer is the same as a near pointer. In other words, a pointer is just a pointer.

If you must share data, you can specify certain global variables to be shared among processes by using a shared data section in the DLL. For more information on shared data sections, please search the Microsoft Knowledge Base using the following words:

   #pragma data_seg

or:

   sections share dll

You can also use a file-mapping object to share memory by sharing the system page file. This will allow two different processes to share dynamic memory. For more information on file mapping objects, please search the Microsoft Knowledge Base using these words:

   shared memory

Another significant change in the behavior of Win32 DLLs from Win16 DLLs is the inclusion of synchronization. The Win32 API provides synchronization objects that allow the programmer to implement correct synchronization. You should be aware that your 32-bit DLL may be preempted and called again from a different thread in the process. For example, if a thread executing a DLL function accesses global data and is preempted and another function modifies the same data, the original thread will resume but will be using modified data. You'll need to use synchronization objects to resolve this situation.

Your Win32 DLL may also be preempted by a thread in a different process. This situation becomes important if the functions use shared data sections or file mappings. You will need to examine the functions in a DLL to determine if this will cause problems. If so, you will have to control access to data or sections of code that are sensitive to this problem. Mutexes and critical sections are well suited for DLL synchronization. For more information on synchronization, please search the Microsoft Knowledge Base using these words:

   synchronization objects

For additional information, please search the Microsoft Knowledge Base using these words:

   Win32 DLL

KBCategory: kbprg KBSubcategory: BseDll Additional reference words: 3.51 4.00 LibEntry LibMain Port WEP _WEP Win32
Keywords          : kbDLL kbKernBase kbGrpKernBase 
Version           : 4.00    | 3.51 4.00
Platform          : NT WINDOWS

Last Reviewed: December 18, 1996