How to Handle Data Types Correctly with the Thunk Compiler

ID: Q142564


The information in this article applies to:


SUMMARY

The thunk compiler supports most of the built-in and user-defined types that the C or C++ languages support. It also supports a subset of the calling conventions that the Microsoft Visual C++ compiler supports. To handle situations where you need to pass or return data of an unsupported type, you need to massage the data into a type that the thunk compiler does recognize. This article explains how to do so for commonly used types supported by the C and C++ languages but not by the thunk compiler.


MORE INFORMATION

There are several strategies for handling data types and function calling conventions that the thunk compiler does not support. These strategies generally rely on substitution and pre- or post-processing the data, and require an understanding of how the thunk compiler translates data. The thunk compiler uses the following rules to translate data:

These two translation rules make thunks handle parameters as expected in C or C++. A pointer is a pointer on both sides, and a scalar value is the same on both sides too. These rules come in handy when you need to use the Generic Thunk API functions in combination with flat thunks.

Before delving into how to handle specific data types, you need to consider the general principles for substituting function calling conventions and data types. These are simple:

Function Calling Conventions

The Windows 95 thunk compiler supports a subset of the calling conventions supported by the Microsoft Visual C++ compiler. Only __stdcall is supported on the 32-bit side of a thunk; on the 16-bit side, only __pascal is supported. __cdecl and __fastcall are not supported calling conventions for either the 16-bit or 32-bit sides of the thunk. Although the thunk must use __pascal and __stdcall calling conventions, the thunk may call or be called by other functions that use any calling convention.

To thunk to a function that uses the __cdecl or __fastcall calling convention, you must create your thunk function with the supported calling conventions and have the target side of the thunk call the real function. Here is an example that shows how a Win32 application might call a 16-bit __cdecl function:


/*-------------------------------------------------------------
Code inside Win32 application or DLL
-------------------------------------------------------------*/ 

/*
   Declaration for benefit of the 32-bit side of thunk, note
   that in Win32 programs, PASCAL is defined as __stdcall,
   and FAR is defined as nothing.
*/ 
void FAR PASCAL C_Function16 (int x);

/*
   MyCFunction is a C calling convention function to interface
   with rest of the Win32 application.  It calls the thunk,
   which is __stdcall.
*/ 
void MyCFunction (int x)
{
   C_Function16 (x);
}

/*-------------------------------------------------------------
Code inside Win16 DLL (target of thunk)
-------------------------------------------------------------*/ 

// Declaration for the real C function
void Real_C_Function (int x);

/*
   C_Function16 is the 16-bit side of the thunk.  In a 32->16
   thunk, it is the target.  Here, it calls the real C function
   that the Win32 application wanted to call in the first
   place. The FAR macro and the __export keyword are used to
   make sure the function is exported correctly from the 16-bit
   DLL.
*/ 
void FAR PASCAL __export C_Function16 (int x)
{
   Real_C_Function (x);
}


/*-------------------------------------------------------------
Thunk script
-------------------------------------------------------------*/ 
enablemapdirect3216 = true;

void C_Function16 (int x)
{
} 


If you try to build a thunk and don't use the __stdcall or __pascal calling conventions, you will get "unresolved external" errors from the linker when building because the function names produced by the C/C++ compiler don't match the names in the code produced by the thunk compiler.

Return Values

The thunk compiler does not support all types as return values that the C and C++ languages support. The limitations stem from the problems of translating addresses from 32-bit address spaces to 16-bit address spaces and vice-versa.

The thunk compiler supports returning all integral types. The signed and unsigned varieties of char, short int, and long int are the same size on both sides of a thunk and thus are returned unmodified. The values of the int type will be truncated to 16-bits upon return from a 16->32 thunk, and will be sign-extended to 32-bits on return from a 32->16 thunk.

Structures may not be returned from either 32->16 or 16->32 thunks. However, pointers to structures can be returned from 32->16 thunks if the structure does not need to be repacked and does not contain pointers. A structure does not require repacking if the types of all members of the structure are the same size in 16-bit and 32-bit code and the structure is packed with the same alignment on both sides of the thunk. Use #pragma pack to pack the structure with the same value in the 32-bit and 16-bit source code, and specify the same packing on the thunk compiler command line.

Pointers cannot be returned from 16->32 thunks because the context of the 32-bit address space is not global. The 32-bit address space for the Win32 target DLL is mapped between 4 megabytes and 2 gigabytes, and it is context- switched as are all other Win32 process address spaces. This means that a Win16 application or DLL could not use a pointer returned by a 16->32 thunk without causing a general protection (GP) fault. You can work around this limitation by writing a wrapper function for the 16-bit side of the thunk that allocates memory and passes it to the thunk function as an extra parameter. The 32-bit side of the thunk should also be a wrapper around the real function to handle the extra parameter and copy the data to the memory allocated by the 16-bit wrapper function.

Pointers are supported as return types in 32->16 thunks as long as the base type requires the same number of bytes on both sides of the thunk and does not require repacking. If the size of the base type of a pointer differs between the Win16 and Win32 sides, the thunk compiler generates an error. For example, a char is one byte on both sides of the thunk, and thus a pointer to a char is supported as a return type. A pointer to an int cannot be returned because an int is 16 bits on the 16-bit side of the thunk, but 32 bits on the 32-bit side.

To return a pointer to a data type whose size is different on the 16-bit and 32-bit sides of a 32->16 thunk, you should write a wrapper function that returns the real type, and have the wrapper pass a buffer to the thunk function as an extra parameter. The target side of the thunk should put the return value into this buffer, and then the wrapper on the calling side should return the data from the buffer once the target side of the thunk returns.

The thunk compiler has one special type (bool) that is not found in C or C++ compilers. You should use this type in your thunk scripts for functions that return boolean values when the TRUE result is any non-zero value.

Floating-Point Data Types

The thunk compiler does not support any floating point types. There are three floating point types to consider, and how you approach them depends on which you need to use.

A float is four bytes long on both the 16-bit and 32-bit sides of a thunk. A float can be passed to a thunk function by declaring it as a DWORD (unsigned long) in the thunk script. This makes the thunk compiler pass a four byte value without translating its value.

A double is eight bytes long on both the 16-bit and 32-bit sides of the thunk. To pass a double to a thunk function, declare a struct containing two DWORDs (unsigned longs) in the thunk script, and pass the struct into the function in the thunk script. Here is an example 32->16 thunk:


/*-------------------------------------------------------------
Code on Win32 side of thunk
-------------------------------------------------------------*/ 

/*
   Declaration for the benefit of the 32-bit side of thunk,
   note that in Win32 programs, PASCAL is defined as __stdcall,
   and FAR is defined as nothing.
*/ 
double FAR PASCAL sqr16 (double x);

double sqr32 (double x)
{
   return(sqr16 (x));
}


/*-------------------------------------------------------------
Code inside Win16 DLL (target of thunk)
-------------------------------------------------------------*/ 

/*
   sqr16() is the 16-bit side of the thunk. In a 32->16 thunk,
   it is the target. The FAR macro and the __export keyword
   are used to make sure it is exported correctly from the 16-bit
   DLL.
*/ 
double FAR PASCAL __export sqr16 (double x)
{
   return (x * x);
}


/*-------------------------------------------------------------
Thunk script
-------------------------------------------------------------*/ 
enablemapdirect3216 = true;
typedef unsigned long DWORD;
typedef struct _MYDOUBLE
{
   DWORD dwLow;
   DWORD dwHigh;
} MYDOUBLE;

MYDOUBLE sqr16 (MYDOUBLE x)
{
} 


In 16-bit code, a long double is a native 80-bit type of the floating point processors in the Intel x86 microprocessor family. Because RISC processors do not have a native 80-bit floating point type, C and C++ compilers for Win32 platforms such as Microsoft Visual C++ implement this type as a 64-bit double. If you cannot tolerate the loss of precision of converting 80-bit long doubles to 64-bit doubles, then you should pass the 80-bit value as a structure consisting of two DWORDs (unsigned longs) and a WORD (unsigned short), and you will have to handle the value manually on the 32-bit side of the thunk.

Pointers

The thunk compiler automatically translates pointers to most types. It properly handles pointers to the following data types:
Pointers to pointers are translated partially. The "outside" pointer is translated, but the pointer it points to -- the "inside" pointer-- is not translated.

The thunk compiler does not handle all cases of pointers used in aggregate types, such as arrays of pointers.

Finally, the thunk compiler places limitations on pointers as return values as described in the "Return Values" section earlier in this article.

There may be times when you want to pass an address without having it translated. Because the thunk compiler automatically translates pointers, you should use the DWORD type instead of a pointer type to pass addresses that you don't want translated. Then, you can use the untranslated address on the target side of the thunk, possibly in calls to the Generic Thunk APIs. However, keep in mind that addresses that have not been translated will cause an access violation if dereferenced on the target side of the thunk.

Unions and C++ Classes

Unions are not supported by the thunk compiler because it cannot determine which type the union will actually hold at run time. For example, the following union must be treated differently by the thunk compiler depending on which member the application was actually using when it made the thunk call:


   union
   {
      DWORD dwIntegerValue;
      LPSTR szFileName;
   } 


The problem the thunk compiler has with this union is whether it should generate code to pass the value unchanged, assuming that the dwIntegerValue member will be used, or to translate the value as a pointer, assuming that szFileName will be used. Because the thunk compiler cannot make this determination, it cannot generate the correct code to handle this union. You should handle union types in thunks by declaring a structure in the thunk script large enough to hold the union, and then handle the union's data manually on both sides of the thunk.

Do not thunk C++ objects. Objects are not supported by the thunk compiler because there isn't any way to reliably pass them as parameters to thunks. In addition to specifying the object's physical layout, a class may also define a vtable which contains addresses of all the class's virtual functions. Because all objects of a class share a single vtable, there is no way to translate the vtable so that it can be used by some objects that have been thunked and others that haven't.

Linked Lists and other Dynamic Data Structures

Although the thunk compiler supports structures with pointers, it does not provide a way to handle linked lists and other dynamic structures. This is because the thunk compiler must know the size of the data it translates at compile time, but the number of elements that dynamically-allocated structures contain is indeterminate at compile time.


REFERENCES

Thunk Compiler reference in the Microsoft Win32 SDK Documentation Generic Thunk API reference in the Microsoft Win32 SDK Documentation

Additional query words: 4.00 GPF


Keywords          : kbcode kbnokeyword kbKernBase kbGrpKernBase 
Version           : 4.00
Platform          : WINDOWS 
Issue type        : 

Last Reviewed: March 7, 1999