MultiType Class

0 Likes

Problem:

Between the current DDK and the prior DDK (for example NT4DDK and W2KDDK) Microsoft changed the definitions of a function type I am using (for example in InterlockedCompareExchange()). How am I supposed to write code that will compile under both DDKs?

Resolution:

MultiType Class for Prototype Woes

A Class for Dealing with Function Prototype Changes for InterlockedCompareExchange() between DDKs

Sometimes change is for the better. When an API function changes prototypes between two DDKs, we certainly hope that the change was made for the better, because it certainly makes life more difficult for the developer. Such is the case with the kernel API call InterlockedCompareExchange, which has the following prototypes:

PVOID

FASTCALL

InterlockedCompareExchange(

IN OUT PVOID *Destination,

IN PVOID ExChange,

IN PVOID Comperand

);

In the Windows 2000 DDK, the prototype is

NTKERNELAPI

LONG

FASTCALL

InterlockedCompareExchange(

IN OUT PLONG Destination,

IN LONG ExChange,

IN LONG Comperand

);

There is a new function in the Windows 2000 DDK called InterlockedCompareExchangePointer, which has a prototype similar to that of InterlockedCompareExchange in the NT4 DDK. This function doesn't exist in the NT4 DDK, and from the prototype, it is clear that InterlockedCompareExchange was used on pointer values. The reason behind the change is most likely linked with cleaning up the kernel API for future compatibility with 64-bit addresses. The older NT4 definition was probably used interchangeably for different 32 bit data types by use of cast operators. The fact that it was originally specified using PVOID instead of LONG probably heralds from its most common usage.

Whatever the case, the end result is a mess for the developer trying to write code that compiles cleanly using either DDK. The problem arises from the fact that even if the developer knows they are performing a safe operation, for instance using the function to compare and exchange a LONG, the compiler will complain for one DDK or the other depending on how the variables are declared or cast because of type checking. This is a more serious problem for drivers written in C (as opposed to C), because C is much stricter about type checking.

Here is a code snippet that will compile cleanly with NT 2000 DDK, but not NT4 DDK:

{

LONG * pDestination;

LONG Exchange;

LONG Comperand;

LONG Return;

Return = InterlockedCompareExchange ( pDestination, Exchange,

Comperand);

}

There are a few possible solutions to this problem, some uglier than others. The primary aim of this solution was to not introduce DDK dependent code.

To get past the type checking of the C compiler, a template class named MultiType was designed. The class definition follows:

template <CLASS class="" T2 T1,>class MultiType

{

public:

union

{

T1 m_t1;

T2 m_t2;

} u;

MultiType(const T1 t1) { u.m_t1 = t1; }

MultiType(const T2 t2) { u.m_t2 = t2; }

operator T1 () const { return u.m_t1; }

operator T2 () const { return u.m_t2; }

};

The class is very simple, consisting of two constructors and two cast operators. Each constructor accepts one of the two data types and initializes the union data member. Each of the two cast operators returns the union data member appropriate to the data type being cast. The purpose of the class is to allow inline construction of a class instance using one data type that is capable of being implicitly cast by the compiler to either of the two data types specified in the template parameters.

Essentially, we've created an object of neutral type, allowing the compiler to implicitly cast it to agree with its declaration in the DDK we are using.

To handle the problem associated with InterlockedCompareExchange, two data types can be defined using the template class definition.

typedef MultiType<VOID**,LONG*> PpvoidPlong;

typedef MultiType<VOID*, long> PvoidLong;

Armed with these new data types, the code snippet from above can be modified to:

{

LONG * pDestination;

LONG Exchange;

LONG Comperand;

LONG Return;

Return = PvoidLong( InterlockedCompareExchange(

PpvoidPlong(pDestination), PvoidLong(Exchange),

PvoidLong(Comperand)) );

}

We have constructed class instances inline using the appropriate MultiType types defined above. The compiler implicitly casts the instance to the desired type depending on which DDK we are compiling with, and no data type errors are encountered. Code performance is maintained, since all of the inline functions will be optimized out in a Release build by the compiler. To improve the readability of the code, a macro can be defined to hide the inline constructors:

#define INTERLOCKED_COMPARE_EXCHANGE(Dest, Exch, Comp)

PvoidLong( InterlockedCompareExchange( PpvoidPlong(Dest),

PvoidLong(Exch), PvoidLong(Comp) ) )

The code snippet would then look like this

{

LONG * pDestination;

LONG Exchange;

LONG Comperand;

LONG Return;

Return = INTERLOCKED_COMPARE_EXCHANGE(

pDestination, Exchange, Comperand );

}

which is very close to the look of the original API call, with the significant difference that it compiles for both DDKs. The MultiType class could easily be used to solve the same problem if it arose for other API functions whose prototypes change. There are of course all of the inherent dangers present associated with casting, and it is of course up to the user to make sure that the type conversions are indeed safe.

Old KB# 11308
Comment List
Anonymous
Related Discussions
Recommended