Generic Thunks: Calling a Win32 DLL from a Win16 Application

ID: Q104009

The information in this article applies to:

SUMMARY

Neither Windows NT nor Windows 95 allow direct mixing of 16-bit code and 32- bit code in the same process. Both platforms support IPC mechanisms, such as DDE, RPC, OLE, named pipes, and WM_COPYDATA, which you can use for communication between 16-bit code and 32-bit code. However, there are occasions when it is necessary to call a function in a Win32-based DLL (including functions in the system DLLs) from a 16-bit application under WOW.

Under Windows NT and Windows 95, it is possible to call routines in a Win32 dynamic-link library (DLL) from a 16-bit Windows application using an interface called Generic Thunking. This is not to be confused with the Win32s Universal Thunks interface, which provides the same functionality under Windows 3.1.

Also, Generic Thunks is not to be confused with the Windows 95 Flat Thunks interface, which provides the same functionality under Windows 95. For more information on the different types of thunking interfaces on each Win32 platform, please refer to the following Microsoft Knowledge Base article:

  ARTICLE-ID: Q125710
  TITLE     : PRB: Types of Thunking Available in Win32 Platforms

The Generic Thunking interface consists of functions that allow a 16-bit application to load the Win32 DLL, get the address of an exported DLL routine, call the routine (passing it up to thirty-two 32-bit arguments), convert 16:16 (WOW) addresses to 0:32 addresses (useful if you need to build up a 32-bit structure that contains pointers and pass a pointer to it), call back into the 16-bit application from the Win32 DLL, and free the Win32 DLL.

Generic Thunks are documented in the Win32 SDK documentation, under "Programming and Tools Guides/Programming Techniques."

NOTE: It is a good idea to test the Win32 DLL by calling it from a Win32- based application before attempting to call it from a 16-bit Windows-based application, because the debugging support is superior in the 32-bit environment.

MORE INFORMATION

The basic steps for calling a function through generic thunking are:

CallProc32W() is a Pascal function which was designed to take a variable number of arguments, a Proc address, a mask, and the number of parameters. The mask is used to specify which arguments should be treated as being passed by value and which parameters should be translated from 16:16 pointers to flat pointers. Note that the low-order bit of the mask represents the last parameter, the next lowest bit represents the next to the last parameter, and so forth.

The problem with CallProc32W() is that you cannot create a prototype for it unless you restrict each file so that it only uses calls to functions that contain the same number of parameters. This is a limitation of the Pascal calling convention. Windows NT 3.5 and later supports CallProcEx32W(), which uses the C calling convention to support variable arguments. However, under Windows 95 there are certain caveats in using the CallProc32W() and CallProcEx32W() functions. For more information, see the documentation for these functions.

Sample Code

The following code fragments can be used as a basis for Generic Thunks.

Assume that the 16-bit Windows-based application is named app16, the Win32 DLL is named dll32, and the following are declared:

   typedef void (FAR PASCAL *MYPROC)(LPSTR, HANDLE);

   DWORD ghLib;
   MYPROC hProc;
   char FAR *TestString = "Hello there";

The DLL routine is defined in dll32.c as follows:

   void WINAPI MyPrint( LPTSTR lpString, HANDLE hWnd )
   {
      ...
   }

Attempt to load the library in the app16 WinMain():

   if( NULL == (ghLib = LoadLibraryEx32W( "dll32.dll", NULL, 0 )) ) {
      MessageBox( NULL, "Cannot load DLL32", "App16", MB_OK );
      return 0;
   }

Attempt to get the address of MyPrint():

   if( NULL == (hProc = (MYPROC)GetProcAddress32W( ghLib, "MyPrint" ))) {
      MessageBox( hWnd, "Cannot call DLL function", "App16", MB_OK );
      ...
   }

Although some of the Generic Thunking functions are called in 16-bit code, they need to be provided with 32-bit handles, and they return 32-bit handles. Therefore, before calling CallProcEx32W() and passing it a handle, you must convert the window handle, hWnd, to a 32-bit window handle, hWnd32:

   hWnd32 = WOWHandle32( hWnd, WOW_TYPE_HWND );

Call MyPrint() and pass it TestString and hWnd32 as arguments:

   CallProcEx32W( 2, 2, hProc, (DWORD) TestString, (DWORD) hWnd32 );

Alternatively, you can use CallProc32W() as follows:

   CallProc32W( (DWORD) TestString, (DWORD) hWnd32, hProc, 2, 2 );

A mask of 2 (0x10) is given because we want to pass TestString by reference (WOW translates the pointer) and we want to pass the handle by value.

Free the library right before exiting WinMain():

   FreeLibrary32W( ghLib );

NOTE: When linking the Windows-based application, you need to put the following statements in the .DEF file, indicating that the functions will be imported from the WOW kernel:

   IMPORTS
      kernel.LoadLibraryEx32W
      kernel.FreeLibrary32W
      kernel.GetProcAddress32W
      kernel.GetVDMPointer32W
      kernel.CallProcEx32W
      kernel.CallProc32W

The complete sample is available for download from the Microsoft Software Library:

 ~ GThunks.exe (size: 18926 bytes) 

For more information about downloading files from the Microsoft Software Library, please see the following article in the Microsoft Knowledge Base:

   ARTICLE-ID: Q119591
   TITLE     : How to Obtain Microsoft Support Files from Online Services

Faults

On MIPS systems, an alignment fault will occur when a Win32-based application de-references a pointer to unaligned data that was passed by a 16- bit Windows application. As a workaround, declare the parameter with the UNALIGNED keyword. For example,

   void func( DWORD *var );

becomes

   void func( DWORD unaligned *var);

An application can use SetErrorMode() to specify SEM_NOALIGMENTFAULTEXCEPT flag. If this is done, the system will automatically fix up alignment faults and make them invisible to the application.

The default value of this error mode is OFF for MIPS, and ON for ALPHA. So on MIPS platforms, an application MUST call SetErrorMode() and specify SEM_NOALIGMENTFAULTEXCEPT if it wants the system to automatically fix alignment faults. This call does not have to be made on ALPHA platforms. This flag has no effect on x86 systems. Note that the fix above is preferable.

Additional reference words: kbKernBase kbThunks kbSDKWin32 kbDSupport

Keywords          : kbcode kbfile kbsample
Version           : 4.00    | 3.10 3.50 3.51 4.00
Platform          : NT WINDOWS
Issue type        : kbhowto

Last Reviewed: June 24, 1998