Storage Class Attributes

This help file describes extended attribute syntax, which simplifies and standardizes the Microsoft-specific extensions to the Microsoft C and C++ languages. The storage class attributes that use extended attribute syntax include thread, naked, dllimport, and dllexport. The following topics are covered:

  Extended Attribute Syntax

  The thread Attribute

  The naked Attribute

  Rules and Limitations

  Considerations for Writing Prolog/Epilog Code

  The dllexport and dllimport Attributes

  Definitions and Declarations

  Defining Inline Functions with dllexport and dllimport

  General Rules and Limitations

  C++ Specific Rules and Limitations


Extended Attribute Syntax (Microsoft Specific)

The extended attribute syntax for specifying storage class information uses the __declspec keyword, which specifies that an instance of a given type is to be stored with a Microsoft-specific storage class attribute (thread, naked, dllimport, or dllexport). Some examples of other storage class modifiers include the static and extern keywords. However, these keywords are part of the ANSI specification of the C and C++ languages, and as such are not covered by extended attribute syntax.

This is the extended attribute syntax for C:

Syntax

storage-class-specifier :
typedef
extern
static
auto
register
__declspec ( extended-decl-modifier-list )

For C++, the syntax looks like this:

Syntax

decl-specifier :
storage-class-specifier
type-specifier
fct-specifier
friend
typedef
__declspec ( extended-decl-modifier-list )

For C and C++, the extended-decl-modifier-list syntax looks like this:

Syntax

extended-decl-modifier-list :
extended-decl-modifier
extended-decl-modifier extended-decl-modifier-list

extended-decl-modifier :
thread
naked
dllimport
dllexport

White space separates the declaration modifier lists. Examples of the syntax appear in later sections.

The thread, naked, dllimport, and dllexport storage class attributes are a property only of the declaration of the object or function to which they are applied. Unlike the __near and __far keywords, which actually affect the type of object or function (in this case, 2- and 4-byte addresses), these storage class attributes do not redefine the type attributes of the object itself. The thread attribute affects data and objects only. The naked attribute affects functions only. The dllimport and dllexport attributes affect functions, data, and objects.

The following statement declares an integer variable, tls_i, which is used to store thread-specific data.

__declspec( thread ) int tls_i = 1;

To make your code more readable, you can control such declarations using macro definitions:

#define Thread  __declspec( thread )
Thread int tls_i = 1;


The thread Attribute (32-bit Specific)

Thread Local Storage (TLS) is the mechanism by which each thread in a given multi-threaded process allocates storage for thread-specific data. In standard multi-threaded programs, data is shared among all threads of a given process, whereas thread-local storage is the mechanism for allocating per-thread data. For a complete discussion of threads, see The API Programmer's Reference, a document that is part of the Microsoft Win32 Software Development Kit.

The C and C++ languages include a new extended storage class attribute, thread. The thread attribute must be used with the __declspec keyword to declare a thread variable. For example, the following code declares an integer thread local variable and initializes it with a value:

__declspec( thread ) int tls_i = 1;

These guidelines must be observed when you are declaring statically bound thread local objects and variables:

1.	You can apply the thread attribute only to data declarations and definitions. It cannot be used on function declarations or definitions. For example, the following code generates a compiler error:

#define Thread	__declspec( thread )
Thread void func();	// Error

2.	You can specify the thread attribute only on data items with static storage duration. This includes global data objects (both static and extern), local static objects, and static data members of C++ classes. You cannot declare automatic data objects with the thread attribute. For example, the following code generates compiler errors:

#define Thread	__declspec( thread )

void func1()

{

Thread int tls_i;	// Error

}

int func2( Thread int tls_i )	// Error

{

return tls_i;

}

3.	You must use the thread attribute for the declaration and the definition of a thread local object, regardless of whether the declaration and definition occur in the same file or separate files. For example, the following code generates an error:

#define Thread	__declspec( thread )

extern int tls_i;	// This generates an error, because the

int Thread tls_i;	// declaration and the definition differ.

4.	You cannot use the thread attribute as a type modifier. For example, the following code generates a compiler error:

char __declspec( thread ) *ch;	// Error

5.	C++ classes cannot use the thread attribute. However, you can instantiate C++ class objects with the thread attribute. For example, the following code generates a compiler error:

#define Thread __declspec( thread )

class Thread C	// Error: classes can't be declared Thread.

{

// Code

};

C CObject;

Because the declaration of C++ objects that use the thread attribute is permitted, these two examples are semantically equivalent:

#define Thread	__declspec( thread )

Thread class B

{

// Code

} BObject;	// Okay--BObject declared thread local.

class B

{

// Code

}

Thread B BObject;	// Okay--BObject declared thread local.

6.	Because C++ objects with constructors and destructors, as well as objects that use initialization semantics, can be allocated as thread local, an associated initialization routine must be called to initialize the object. In this example, the constructor initializes the thread local object:

class tlsClass

{

private:

int x;

public:

tlsClass() { x = 1; } ;

~tlsClass();

}

__declspec( thread ) tlsClass tlsObject;

extern int func();

__declspec( thread ) int y = func();

7.	The address of a thread local object is not considered constant, and any expression involving such an address is not considered a constant expression. In C and C++, this means that you cannot use the address of a thread local variable as an initializer for an object or pointer. For example, the compiler flags the following code as an error:

#define Thread	__declspec( thread )

Thread int tls_i;

int *p = &tls_i;	//Error

8.	Standard C permits initialization of an object or variable with an expression involving a reference to itself, but only for objects of non-static extent. Although C++ normally permits such dynamic initialization of an object with an expression involving a reference to itself, this type of initialization is not permitted with thread local objects. For example:

#define Thread	__declspec( thread )

Thread int tls_i = tls_i;	// C and C++ error

int j = j;	// Okay in C++; C error

Thread int tls_i = sizeof( tls_i )	// Okay in C and C++

Note that a sizeof expression that includes the object being initialized does not constitute a reference to itself, and is allowed in C and C++.


The naked Attribute (32-bit Specific)

For functions declared with the naked attribute, the compiler generates code without prolog and epilog code. You can use this feature to write your own prolog/epilog code sequences using inline assembly code. Naked functions are particularly useful in writing virtual device drivers and interrupt handlers.

Because the naked attribute is only relevant to the definition of a function and is not a type modifier, naked functions use the extended attribute syntax, described previously. For example, this code defines a function with the naked attribute:

__declspec( naked ) int func( formal_parameters )

{

// Function body

}

Or, alternatively:

#define Naked	__declspec( naked )

Naked int func( formal_parameters )

{

// Function body

}

The naked modifier affects only the nature of the compiler's code generation for the function's prolog and epilog sequences. It does not affect the code that is generated for calling such functions. Thus, the naked attribute is not considered part of the function's type, and function pointers cannot have the naked attribute. Furthermore, the naked attribute cannot be applied to a data definition. For example, the following code samples generate errors:

__declspec( naked ) int i;	// Error--naked attribute not 	// permitted on data 	// declarations.

The naked attribute is relevant only to the definition of the function and cannot be specified in the function's prototype. The following declaration generates a compiler error:

__declspec( naked ) int func();	// Error--naked attribute
// not permitted on function
// declarations.


Rules and Limitations

1.	The return statement is not permitted in a naked function. However, you can return an int by moving the return value into the eax register before the ret instruction.

2.	Structured exception handling constructs are not permitted in a naked function because the constructs must unwind across the stack frame.

3.	Any use of the setjmp run-time function is not permitted in a naked function because it too must unwind across the stack frame. However, use of the longjmp run-time function is permitted.

4.	Use of the _alloca function is not permitted in a naked function.

5.	To ensure that no initialization code for local variables appears before the prolog sequence, initialized local variables are not permitted at function scope. In particular, the declaration of C++ objects is not permitted at function scope. There can, however, be initialized data in a nested scope.

6.	Frame pointer optimization (the /Oy compiler option) is not recommended, but it is automatically suppressed for a naked function.


Considerations for Writing Prolog/Epilog Code

Before writing your own prolog and epilog code sequences, it is important to understand how the stack frame is laid out, and some differences in stack frame layout between 16-bit and 32-bit targets. It is also useful to know how to use the _LOCAL_SIZE symbol. These topics are discussed in the following sections.

Stack Frame Layout

A few minor differences exist between 16- and 32-bit stack frame layout. This example shows the standard prolog code that might appear in a 32-bit function:

push	ebp	; Save ebp

mov	ebp, esp	; Set stack frame pointer

sub	esp, localbytes	; Allocate space for locals

push	registers	; Save registers

The localbytes variable represents the number of bytes needed on the stack for local variables, and the registers variable is a placeholder that represents the list of registers to be saved on the stack. After pushing the registers, you can place any other appropriate data on the stack. The following is the corresponding epilog code:

pop	registers	; Restore registers

mov	esp, ebp	; Restore stack pointer

pop	ebp	; Restore ebp

ret	; Return from function

The stack always grows down (from high to low memory addresses). The base pointer (ebp) points to the pushed value of ebp. The locals area begins at ebp-2. To access local variables, calculate an offset from ebp by subtracting the appropriate value from ebp.

The only change in these code sequences for 16-bit targets is that the register names do not begin with an e. For 16-bit targets, the register names in the examples above would be bp and sp.

__LOCAL_SIZE

The compiler provides a symbol, __LOCAL_SIZE, for use in the inline assembly block of function prolog code. This symbol is used to allocate space for local variables on the stack frame in custom prolog code.

The compiler determines the value of __LOCAL_SIZE. Its value is the total number of bytes of all user-defined local variables and compiler-generated temporary variables. __LOCAL_SIZE can be used only as an immediate operand; it cannot be used in an expression. You must not change or redefine the value of this symbol. For example:

mov	eax, __LOCAL_SIZE	;Immediate operand--Okay

mov	eax, __LOCAL_SIZE + 4	;Error

mov	eax, [ebp - __LOCAL_SIZE]	;Error

The following example of a naked function containing custom prolog and epilog sequences uses __LOCAL_SIZE symbol in the prolog sequence:

__declspec ( naked ) func()

{

int i;

int j;

__asm	/* prolog */

{

push	ebp

mov	ebp, esp

sub	esp, __LOCAL_SIZE

}

/* Function body */

__asm	/* epilog */

{

mov	esp, ebp

pop	ebp

ret

}

}


The dllexport and dllimport Attributes (32-bit Specific)

The dllexport and dllimport storage class modifiers export and import functions, data, and objects to and from a DLL. These modifiers, or attributes, explicitly define the DLL's interface to its client, which can be the executable file or another DLL. Declaring functions as dllexport eliminates the need for a module-definition (.DEF) file, at least with respect to the specification of exported functions. Note that dllexport replaces the __export keyword.

The declaration of dllexport and dllimport uses extended attribute syntax:

#define DllImport	__declspec( dllimport )

#define DllExport	__declspec( dllexport )

DllExport void func();

DllExport int i = 10;

DllImport int j;

DllExport int n;


Definitions and Declarations

The DLL interface refers to all items (functions and data) that are known to be exported by some program in the system, that is, all items that are declared as dllimport or dllexport. All declarations included in the DLL interface must specify either the dllimport or dllexport attribute. However, the definition can specify only the dllexport attribute. For example, the following function definition generates a compiler error:

#define DllImport	__declspec( dllimport )

#define DllExport	__declspec( dllexport )

DllImport int func()	// Error; dllimport prohibited on definition.

{

return 1;

}

This code also generates an error:

DllImport int i = 10;	// Error; this is a definition.

However, this is correct syntax:

DllExport int i = 10;	// Okay; this is an export definition.

The use of dllexport implies a definition, while dllimport implies a declaration. You must use the extern keyword with dllexport to force a declaration; otherwise, a definition is implied. Thus, the following examples are correct:

extern DllImport int k;	// These are both correct and imply a
DllImport int j;	// declaration.

The following examples help to further clarify the above:

static DllImport int l;	// Error; not declared extern.

void func()

{

static DllImport int s;	// Error; not declared extern.

DllImport int m;	// Okay; this is a declaration.

DllExport int n;	// Error; implies external
// definition in local scope.

extern DllImport int i;	// Okay; this is a declaration.

extern DllExport int k;	// Okay; extern implies
// declaration.

DllExport int x = 5;	// Error; implies external
// definition in local scope.

}


Defining Inline Functions with dllexport and dllimport

You can define as inline a function with the dllexport attribute. In this case, the function is always instantiated and exported, whether or not any module in the program references the function. The function is presumed to be imported by another program.

You can also define as inline a function declared with the dllimport attribute. In this case, the function can be expanded (subject to /Ob compiler switch specifications), but never instantiated. In particular, if the address of an inline imported function is taken, the address of the function residing in the DLL is returned. This behavior is the same as taking the address of a non-inline imported function.

These rules apply to inline functions whose definitions appear within a class definition. In addition, static local data and strings in inline functions maintain the same identities between the DLL and client as they would in a single program (that is, an executable file without a DLL interface).

Exercise care when providing imported inline functions. For example, if you update the DLL, don't assume that the client will use the changed version of the DLL. To ensure that you are loading the proper version of the DLL, rebuild the DLL's client as well.


General Rules and Limitations

1.	If you declare a function or object without the dllimport or dllexport attribute, the function or object is not considered part of the DLL interface. Therefore, the definition of the function or object must be present in that module or in another module of the same program. To make the function or object part of the DLL interface, you must declare the definition of the function or object in the other module as dllexport. Otherwise, a linker error is generated.

If you declare a function or object with the dllexport attribute, its definition must appear in some module of the same program. Otherwise, a linker error is generated.

2.	If a single module in your program contains both dllimport and dllexport declarations for the same function or object, the dllexport attribute takes precedence over the dllimport attribute. However, a compiler warning is generated. For example:

DllImport int i;

DllExport int i;	// Warning; inconsistent, but
// dllexport takes precedence.

3.	In C, if you initialize a globally declared pointer with the address of a data object declared with the dllimport attribute, a compiler error is generated. Similarly, you cannot initialize a static local function pointer with the address of a function declared with the dllimport attribute, or initialize a static local data pointer with the address of a data object declared with the dllimport attribute. The C++ compiler does not enforce this restriction, because C++ supports dynamic initialization of local and global static objects. For example, the following code generates errors when compiled with the C compiler, but not with the C++ compiler:

DllImport void func1( void );

DllImport int i;

int *pi = &i;	// Error in C

static void ( *pf )( void ) = &func1;	// Error in C

void func2()

{

static int *pi = &i;	// Error in C

static void ( *pf )( void ) = &func1;	// Error in C

}

However, because a program that includes the dllexport attribute in the declaration of an object must provide the definition for that object somewhere in the program, you can initialize a global or local static function pointer with the address of a dllexport function. Similarly, you can initialize a global or local static data pointer with the address of a dllexport data object. For example, the following code does not generate errors in C or C++:

DllExport void func1( void );

DllExport int i;

int *pi = &i;	// Okay

static void ( *pf )( void ) = &func1;	// Okay

void func2()

{

static int *pi = &i;	// Okay

static void ( *pf )( void ) = &func1;	// Okay

}


C++ Specific Rules and Limitations

You can declare C++ classes with the dllimport or dllexport attribute. These forms imply that the entire class is imported or exported. Classes exported in this manner are referred to as exportable classes.

The following example defines an exportable class. All its member functions and static data are exported:

class DllExport C

{

int i;

virtual int func( void )

{ return 1; }

};

Note that explicit use of the dllimport and dllexport attributes on members of an exportable class is prohibited.

dllexport Classes

When you declare a class dllexport, all its member functions and static data members are exported. You must provide the definitions of all such members in the same program. Otherwise, a linker error is generated. The one exception to this rule applies to pure virtual functions, for which you need not provide explicit definitions. However, because a destructor for an abstract class is always called by the destructor for the base class, pure virtual destructors must always provide a definition. Note that these rules are the same for non-exportable classes.

If you export data of class type or functions that return classes, be sure to export the class.

dllimport Classes

When you declare a class dllimport, all its member functions and static data members are imported. Unlike the behavior of dllimport and dllexport on non-class types, static data members cannot specify a definition in the same program in which a dllimport class is defined.

Inheritance and Exportable Classes

All base classes of an exportable class must be exportable. If not, a compiler warning is generated. Moreover, all accessible members that are also classes must be exportable. This rule permits a dllexport class to inherit from a dllimport class, and a dllimport class to inherit from a dllexport class (though the latter is not recommended). As a rule, everything that is accessible to the DLL's client (according to C++ access rules) should be part of the exportable interface. This includes private data members referenced in inline functions.

Selective Member Import/Export

Because member functions and static data within a class implicitly have external linkage, you can declare them with the dllimport or dllexport attributes, unless the entire class is exported. If the entire class is imported or exported, the explicit declaration of member functions and data as dllimport or dllexport is prohibited. If you declare a static data member within a class definition as dllexport, a definition must occur somewhere within the same program (as with non-class external linkage).

Similarly, you can declare member functions with the dllimport or dllexport attributes. In this case, you must provide a dllexport definition somewhere within the same program.

It is worthwhile to note several important points regarding selective member import and export:

1.	Selective member import/export is best used for providing a version of the exported class interface that is more restrictive; that is, one for which you can design a DLL that exposes fewer public and private features than the language would otherwise allow. It is also useful for fine tuning the exportable interface: when you know that the client, by definition, is unable to access some private data, you need not export the entire class.

2.	If you export one virtual function in a class, you must export all of them, or at least provide versions that the client can use directly.

If you have a class in which you are using selective member import/export with virtual functions, the functions must be in the exportable interface or defined inline (visible to the client).

3.	If you define a member as dllexport but do not include it in the class definition, a compiler error is generated. You must define the member in the class header.

4.	Although the definition of class members as dllimport or dllexport is permitted, you cannot override the interface specified in the class definition.

5.	If you define a member function in a place other than the body of the class definition in which you declared it, a warning is generated if the function is defined as dllexport or dllimport (if this definition differs from that specified in the class declaration).

Shared Header Files

A good way to ensure that the DLL and its client agree on the interface to a DLL is to use the same header files for building the DLL and the client. In this case, all header files should use dllimport since dllexport overrides dllimport in the case of definition.