Windows 9x and Flat Thunking

Contents
1 Overview of Win32/Win32s thunking mechanisms
2 The flat thunking mechanism
3 A flat thunking example
4 Debugging notes
5 Recommended reading
6 Examples

Intro
This document describes thunking on Windows 9x platform. Document is based on article from flat
thunking example supplied with Turbo Assembler 5.0 so it is applied to Borland programming tools.

1) Overview of Win32/Win32s thunking mechanisms

For the Win32s, Windows NT and Windows 95 platforms, Microsoft has provided
us with three thunking mechanisms: universal thunking, general thunking, and
flat thunking.

Universal thunking is available to Win32 applications running on Win32s, a
32-bit translation layer which allows 32-bit programs to be run on 16-bit
Windows 3.x. Primarily, this mechanism accomodates 32-bit applications which
are running in a hybrid environment of 32-bit modules inherent to the
application itself and 16-bit modules inherent to the 16-bit environment.

General thunking is available on both Windows NT and Windows 95. Howevever,
it is intended for use primarily on Windows NT and Microsoft documentation
advises that applications using General Thunking should not be expected to
have the same behaviour when run under Windows NT and Windows 95 due to
differences in the operating systems. General thunking allows a 16-bit
application to call into a 32-bit DLL, but not the reverse. Its usefulness
seems to be primarily to support incremental porting of a 16-bit application
to 32 bits, where the DLLs of the application would be ported first, and the
executable only in a later revision, after all DLLs have been ported.

Flat thunking is available only under Windows 95. Flat thunking allows
bi-directional calls between 16- and 32-bit modules. Using flat thunking
requires that 16- and 32-bit modules be rebuilt, linking in special thunk
modules created using the Microsoft thunk compiler and an assembler. Like
general thunking, it seems primarily designed to support incremental porting
of applications. An advantage of flat thunking is that you can call in
either direction, and can even have a single call chain that moves back and
forth between a pair of 16- and 32-bit modules. However, a module can only
connect to one other module, so a bit of planning may be wise for larger
projects.

2) The flat thunking mechanism

Flat thunking requires that we create a special assembly module, assemble it
twice to a 16-bit and a 32-bit object, then link those two objects into the
corresponding 16- and 32-bit modules (DLLs or executables). The assembly
file is created by first writing a thunk script, which is very much like a C
header file containing function prototypes and type definitions. The
Microsoft thunk compiler then translates the script into a single assembly
source file containing both 16- and 32-bit code. Two assembler macros, IS_16
and IS_32, allow the file to be assembled to the two distinct objects. Next,
code is added to the 16- and 32-bit modules (DLL's or executables) to allow
a runtime connection to be established between the two modules. Finally, a
few modifications are made to the module definition files for each of the
targets and the targets are brought up to date.

To examine the flat thunking mechanism, we will use an example of a 32-bit
DLL calling a function in a 16-bit DLL, which is what the example "Thunker"
does. In the function names referred to below, the "ThunkObj_" prefix is a
result of names generated by the Microsoft thunk compiler which it derives
from the filename of our thunk script. These are not standard names that you
can look up in a help file.

First, the 32-bit DLL must establish a connection with the 16-bit DLL. This
is done by calling ThunkObj_ThunkConnect32 and is most easily done from the
32-bit DLL's DllEntryPoint. This call causes the 16-bit DLL to be loaded. If
the 16-bit DLL is marked for Windows 4.0, then the system will call its
DllEntryPoint. A quirk is that the 16-bit DLL must have both a LIbMain and a
DllEntryPoint. LibMain is called only when the DLL is loaded, while
DllEntryPoint is called both on load and unload.

In DllEntryPoint, the 16-bit DLL establishes its side of the connection with
the 32-bit DLL by calling ThunkObj_ThunkConnect16. The process of
establishing this connection invokes code in the thunk modules which
initializes jump tables for the exported functions. In the thunker example,
the 16-bit DLL does not call into the 32-bit DLL, so the code generated for
the 16-bit thunk module contains code only to initialize this jump table,
after which, the 16-bit thunk module's job is done.

Calls can now be made between the 32- and 16-bit DLL's. Given that the
16-bit DLL exports a function Fun16, a call from the 32-bit DLL can be
described. First, the call from the 32-bit DLL is actually resolved to a
Fun16 procedure in the ThunkObj assembly module (which is why we never do
anything to import Func16 from the 16-bit DLL). This thunk procedure fixes
up the stack to account for a change from standard call to pascal calling
convention, accomodates changes in data sizes (ie, size of int), and
translates pointers (between 32-bit flat pointers expected in the 32-bit
module and the selector:offset format expected in the 16-bit module). The
thunk then uses the jump table to call directly into the 16-bit DLL.

The connection is broken by calling the same ThunkObj_ThunkConnect32 and
ThunkObj_ThunkConnect16 functions, but passing DLL_PROCESS_DETACH as the
last parameter.

3) A flat thunking example

The example, Thunker, provides a shell demonstration of flat thunking. In
this example, a 32-bit application uses a 32-bit DLL which calls into a
16-bit DLL. Microsoft documentation recommends that we thunk between DLL's
rather than directly from an executable to a DLL as we can later port the
16-bit DLL and then simply replace the 16- and 32-bit thunking DLL's with
one new 32-bit DLL. Since flat thunking has the limitation of a module being
able to connect to only one other module, it is the only way to have a
32-bit executable effectively use multiple 16-bit DLL's, or vice-versa.
Also, the model of interfacing thru a DLL is conducive to a multi-platform
application which will determine at runtime which interface DLL to use
(since no one thunking mechanism will work on all Windows 32-bit platforms).

The example demonstrates all the basic requirements for coding and building
an application using flat thunking. I strongly recommend you read thru the
makefile and all the associated source - including the module definition
files. Omitting a step or requirement can lead to strange runtime failures
that can be difficult to debug.

Further information on flat thunking is available in the Win32 online help
file. Go to index item Thunk Compiler and page thru the "chapter". Further
information on general and universal thunking is available in the Microsoft
Win32 SDK documentation, and on the Microsoft Developer's Network.

This example demonstrates calling from a 32-bit application into a 16-bit
DLL. Per recommendations in Microsoft technical documentation, this is done
by calling thru a 32-bit DLL. The example presumes that all calls into the
16-bit DLL are made only from the 32-bit DLL and describes modifications
from that starting point. The application model looks like this:

Flat Thunk Diagram

At runtime, the executable causes the 32-bit DLL to be loaded and that DLL
establishes a connection by calling a Thunk_Connect function exported from
the 16-bit thunk object. The 16-bit side then initializes a jump table which
is passed back to the 32-bit DLL. When a call is made to a function exported
from the 16-bit DLL, the call from the 32-bit DLL call actually resolves to
a function in the 32-bit thunk object. This function then performs the steps
neccassary to translate the call (ie, allowing for different sizes in data
types) and then calls thru the jump table to the actual function in the
16-bit DLL. On return, control returns to the thunk function in the 32-bit
thunk object, which does some translating of return values and structures
passed by reference and ultimately returns to the original caller.

To implement a thunking application, you must:

1) Create a thunk script providing declarative information about
functions and data structures being thunked.

2) Generate assembly source from the thunk script using the Microsoft
thunk compiler.

3) Assemble the thunk module to a 16-bit object module which will be
linked into the 16-bit DLL exporting the user functions.

4) Assemble the thunk module to a 32-bit object module which will be
linked into the 32-bit DLL that will be importing the user functions.

5) Modify the 16-bit DLL to provide a DllEntryPoint function which will
be called to establish a connection with the 32-bit DLL.

6) Modify the 16-bit DLL's module definition file to:
a) Add SUBSYSTEM 4.0 so the DLL is marked for Windows 4.0;
b) Export DllEntryPoint, the 16-bit thunk data, and those functions
you wish to call from the 32-bit world; and,
c) Import C16THKSL01 and THUNKCONNECT16 from kernel.

7) Modify the 32-bit DLL to establish and break the connection with the
16-bit DLL at runtime.

8) Modify the 32-bit DLL's module definition file to:
a) import ThunkConnect32@24 from kernel32
b) export the 32-bit thunk data.

Special notes:

The 16-bit DLL must be marked for subsystem 4.0, else the added
DllEntryPoint function will not be called to establish the connection. This
can be done with the linker /V switch or with a SUBSYSTEM entry in the
module definition file. Of course, having done this, the DLL will only work
on Windows 95.

When assembling the thunk module, you must use the assembler's /ml option
with /DIS_32 and must not use it with /DIS_16. This is because the thunk
compiler always generates case sensitive symbols. By default, the 32-bit
side of the thunk mechanism uses standard call calling convention, for which
the case sensitivity must be preserved. The 16-bit side of the thunk
mechanism uses pascal calling convention and the symbols there should be
forced to upper case. (Since the thunk compiler generates functions which
manipulate parameters being passed between the 16- and 32-bit modules, do
not attempt to override the default calling conventions.)

You may notice that in the 32-bit thunk module, the generated names for
functions are created by adding to the function name an at sign ('@')
followed by a numeric value representing the width of the parameter list.
This is the naming convention that Microsoft uses for stdcall calling
convention and the names are generated by the Microsoft thunk compiler. To
resolve them, Turbo Assembler, when given the -utthk switch, automatically
generates aliases which match the Borland naming convention. You can examine
both forms of these names using tdump with this syntax:

tdump -oipubdef -oiextdef -oialias thkobj16.obj
tdump -oipubdef -oiextdef -oialias thkobj32.obj

The 32-bit module can be built with debug info, but the 16-bit module cannot
as tasm32 generates only 32-bit debug info which the 16-bit linker does not
expect. Also, the -utthk switch is unique to tasm32 and is rejected by tasm
and tasmx.

4) Debugging notes

Turbo Debugger for Win32 cannot step from 32-bit code into 16-bit code. It
is possible to step thru the 32-bit thunk module in source assembly or in
the debuggers CPU view. While in the CPU view, do not attempt to step into a
call thru the thunk table into the 16-bit DLL as it will cause TD32 to hang.
Attempting to debug with available tool sets at this level will likely be
very challenging and require very good Assembly language skills.

5) Recommended reading

Additional information on thunking under various Microsoft Windows'
environments can be found on the Microsoft Developers Network CD and in the
Microsoft Win32 SDK documentation. Of particular interst are the articles
"Flat Thunking Mechanics" and "How to Debug Flat Thunks" on the MSDN CD.

6) Examples

Borland C++ Example Flat Thunk Example

Visual C++ Example Flat Thunk Example

Hosted by uCoz