digitalmars.com                        
Last update Sun Mar 4 11:58:08 2018

Building and Using 16 Bit Windows DLLs

Dynamic linked libraries (DLLs) are a powerful part of Windows. They let multiple programs use a single copy of the same library at run-time, thus saving both memory and storage space. DLLs also improve performance since the program is no longer linking on the fly.

This chapter describes how to write and compile DLLs under Windows 3.1, and call a DLL function from another program. For information on compiling Win32 DLLs, see Win32 Programming Guidelines.

What's in This Chapter

Writing a DLL

This section describes the three steps for creating a Windows dynamic linked library (DLL): It also discusses how to avoid problems if you use standard library functions in your DLL.

Initializing the DLL

Your DLL must include an initialization function named LibMain(), which is called when the DLL is first used. You can perform any initialization the DLL needs in this routine.

Note: When you compile a DLL, the compiler automatically includes some start-up code in it. When you use the DLL, Windows calls that start-up code, which, in turn, calls your LibMain() function.

The following example shows a typical skeleton for LibMain():

int FAR PASCAL LibMain(HANDLE hInstance,
        WORD wDataSeg, WORD wHeapSize,
        LPSTR lpsxCmdLine )
{
    /*
     * Perform any initialization here.
     * ...
     */

    /*
     * Unlock the DLL data segment if it's
     * moveable.
     */
    if (wHeapSize > 0)
        UnlockData(0);

    /*
     * Return 1 if LibMain() is successful.
     */
    return 1;
}

LibMain() must be declared FAR PASCAL. It has four arguments, described in Table 19-1.

Table 19-1 Arguments to LibMain()
Argument Description
hinst The DLL's instance handle.
wDataSeg The value of the Data Segment (DS) register.
cbHeapSize The DLL's heap size, as set in its definition file.
lpszCmdLine Command line information. It's rarely used.

The start-up code that the compiler adds to your DLL locks the data segment into memory. If you want the DLL's data segment to be re-locatable, you must unlock it with UnlockData().

If LibMain() successfully initializes your DLL, return 1. Otherwise, return zero, and Windows unloads the DLL from memory.

Using WEP when the DLL is terminated

The run-time library provides a default Windows Exit Procedure (WEP()), which is called when a DLL is terminated or Windows exits. This function calls static destructors, calls atexit(), and performs other typical cleanup services.

Before calling static destructors, WEP calls the function _WEP. The run-time library provides a _WEP that does nothing. If your DLL needs to perform any special cleanup (such things as setting values upon exit, or displaying messages) before Windows unloads it, include a _WEP termination function.

To allow your DLL to use the default WEP, write EXPORT WEP in the DLL's module definition file. For related information, see the section "Writing the definition file" later in this chapter.

Note: In C++ Version 6, users were required to provide their own WEP. A default WEP is now provided for compatibility with Microsoft C++. If you export your own WEP function, it automatically overrides the default WEP. However, your code is then responsible for all cleanup.

Declaring functions to export

Functions that other applications or libraries call must be declared as FAR, because they will not be in the same segment as the code that calls them. You may also want to declare them as PASCAL, because Pascal linkage is more efficient than C linkage. The following function definition is declared implicitly as residing in a far code segment, via the commonly used CALLBACK macro; just as functions shown in all previous examples, it can be called by other applications or libraries:
int CALLBACK AddTwo(int x)
{
    return (x + 2);
}
Read more about declaring functions to export in the section "Writing the definition file" later in this chapter.

The DS and SS registers

Unlike an application, a DLL does not have its own stack. Instead, the DLL uses the stack of the application that called it, so the data segment DS and stack segment SS registers are not the same. As in Large and Compact memory models, DS does not equal SS; this is the default situation for DLLs. (For more information, see Compiling Code). The difference can cause problems if you use code that expects the DS and SS registers to be the same, and you compile your DLL in a memory model that uses near pointers (the Small and Medium memory models). For example, the standard libraries for the Small and Medium memory models expect the DS and SS registers to be the same.

For more information on memory models, see Choosing a Memory Model.

Suppose this function is in a DLL:

void myDLLFunc()
{
    char *from="hello there", to[25];
    strcpy(to, from);
}
If you compile your program with the Small or Medium memory model, the compiler will raise an error. strcpy() expects its arguments to be near pointers, so it expects the pointers to be relative to the data segment. However, the pointers from and to are relative to the calling application's SS.

With most functions, you can avoid this problem two ways. One way is to compile the DLL with the Large memory model, which uses far pointers. The other way is to declare the variable you pass to be static or global; this stores it in the DLL's data segment. For example, the previous function would look like this:

void myDLLFunc()
{
    static char *from="hello there", to[25];
    strcpy(to, from);
}
Some functions in the standard library will work correctly only in DLLs that are compiled with the Large memory model. For example, file I/ O functions such as fprintf() call several internal functions with their own local variables. When you compile your DLL in the Medium or Small memory model, the internal functions that the file I/ O functions call expect their pointer arguments to be relative to the data segment. But the arguments are relative to the calling application's stack segment. The application will cause a general protection fault because it looks for the data in the data segment instead of in the stack segment.

When you write a function that DLL functions may call, be sure your function's pointer arguments are far pointers.

Compiling a DLL

Once you've written a DLL, the next step is to compile it. Several considerations must be kept in mind, beginning with writing the module definition file.

Writing the definition file

You must write a module definition file for your DLL that lists all the functions that other applications or libraries may use. The following shows an example of a module definition file for a DLL that exports AddTwo() and uses the termination routine WEP():
LIBRARY MyDLL
DESCRIPTION 'MyDLL --The DLL I Wrote'
EXETYPE WINDOWS
CODE MOVEABLE DISCARDABLE
DATA SINGLE MOVEABLE
HEAPSIZE 0
EXPORTS
        _AddTwo @1
        _TimesTen @2
        WEP @3 RESIDENTNAME
For more information on writing definition files for DLLs, see Win32 Programming Guidelines.

The LIBRARY statement is mandatory and specifies that this is a dynamic-linked library, not an application. The name of the library, MyDLL, follows the keyword. It is a good idea to use the same name in this statement and in the name of the .dll file.

The DESCRIPTION statement is optional and can contain a maximum of 128 characters for describing the DLL.

The EXETYPE statement is mandatory and specifies what kind of executable this is. For a Windows DLL, you must specify WINDOWS.

The DATA statement is mandatory and specifies the attributes for the DLL's data segment. You must include SINGLE. It specifies that there can be only one instance of a library in memory no matter how many applications access it. You can include other keywords to specify more attributes. For example, including MOVEABLE allows the memory manager to move the segment if necessary.

The HEAPSIZE statement is optional and specifies the minimum amount of space for the DLL's local heap. Use LocalAlloc() to allocate local memory. Since this DLL doesn't use the local heap, it specifies zero as the minimum heap size. Don't use a STACKSIZE statement, since a DLL doesn't have a stack. It uses the calling application's stack.

Functions that your DLL provides must be exported explicitly. There are two ways: the EXPORTS statement and the _exports keyword.

The EXPORTS statement lists the functions that other applications and libraries can call. If your DLL uses WEP(), you must list it here. All exported functions must follow the name mangling scheme as specified in the section "Name Mangling in Digital Mars C++" in Mixing Languages. Following the exported mangled name is an optional cardinal entry value. This cardinal is an index that allows for faster lookup times than searching by name allows. A cardinal is an integer proceeded by an @ and is separated from the function name by a space. Optionally, the exported name can be wrapped in double quotes, eliminating the need for intermediate spaces. The following shows the EXPORTS used both ways:

EXPORTS ?TimesTwo@@ZCHH@Z @2
or
EXPORTS "?TimesTwo@@ZCHH@Z" @2

Alternative export method

The other way to export functions is to use the _export keyword in your C or C++ function declaration statement. This keyword causes the compiler to notify the linker that this is an exported function and is typically used within Windows programs in place of the definition file's EXPORTS statement. With the EXPORTS statement, you had to contend with name mangling; the advantage of using the _export keyword is that you do not.

See "__export and __loadds" in Digital Mars C++ Language Implementation for more information.

Consider the following example:

int _export TimesTwo(int x)
{
    return (x * 2);
}

Exporting C++ functions

If the DLL is written in C++, you must use the mangled name of its functions in the EXPORTS statement of the DLL's module definition file. To find the mangled name, you can do one of two things: link to create a map file for your DLL, and look for your functions in the section "Publics by Name"; or run the utility libunres with the -p switch on the .obj files that comprise your DLL. Both methods give you a listing of mangled names. For more information, see the section "Name Mangling in Digital Mars C++" in Mixing Languages.

For example, suppose you have a C++ DLL named CPPDLL that has one module called cppdll.cpp, and a function called TimesTwo(). Compile the source module as follows:

dmc -c -WD cppdll.cpp
Now run the libunres utility as follows:
libunres -p cppdll.obj
This will list all public symbols in cppdll.obj. One of them will be the mangled version of TimesTwo(), and might look something like this:
?TimesTwo@@ZCHH@Z
You can usually tell at first glance if this is the symbol you are looking for, but if in doubt, run the UNMANGLE utility on the mangled name.

Finally, edit your module definition file, cppdll.def, so that the EXPORTS statement contains the mangled name:

EXPORTS ?TimesTwo@@ZCHH@Z
When you use the DLL with an application, be sure to use the function's mangled name in the IMPORTS statement of the application's module definition file. However, when you call the DLL function in the application, use the unmangled name and call it just as you would any other function. For example, to call TimesTwo() from an application, use this:
a = TimesTwo(b);

Building the DLL

When you compile a DLL in the IDDE, be sure to do the following:

Assuming you have already provided source and a definition file. mydll.dll is the name of the DLL. There are two ways to build and link a DLL:

dmc -c -W -msw mydll.c
link mydll,mydll.dll,,,mydll.def

and

dmc -WD mydll.c

This last method launches the linker for you and assumes the .def file has the same name as the source file.

Prolog and epilog

Different types of prologs and epilogs are generated depending on the setting of the -W compiler option.

For information on choosing prolog and epilog code, see Win32 Programming Guidelines.

Using DLLs in an Application

Three ways that an executable can link and use the functions in a DLL are: This section describes how to use each of these methods.

Explicitly with an IMPORTS statement

To import a function explicitly when you link your application, use an IMPORTS statement in the application's module definition file to list the names of the DLL functions that it uses. This method is useful if you know what DLL functions the application needs when you link the application.

For example, if you have a DLL written in C called mydll.dll that contains AddTwo(), the IMPORTS section might look like this:

IMPORTS MYDLL._AddTwo
Notice the underscore before AddTwo; this results from C-style mangling for functions with C linkage. When specifying the DLL function to import, you must follow the name mangling conventions as specified in "Name Mangling in Digital Mars C++" in Mixing Languages.

If the DLL assigned a cardinal entry value to one of its exported functions, you can use that cardinal in the IMPORTS statement to help Windows find the function more quickly. If MyDLL assigned AddTwo() a cardinal entry value of 1, the IMPORTS statement would look like this:

IMPORTS _AddTwo= MYDLL.1

Implicitly with an import library

If you want to import a function implicitly when you link an application, you can create an import library, then link it in with your application. An import library contains no code; it is a symbol table that the linker uses to define the locations of the DLL functions. This method is useful if when you link the application, you know what DLL functions it needs.

Create an import library with the IMPLIB command line utility. It takes two arguments: the name for the import library and the name of the DLL's module definition file. For example, this command line creates an import library for mydll.dll:

implib mydll.lib mydll.def
IMPLIB finds a .DLL file that has the same name as its first argument and creates an import library for it. When you link your application, list the import library along with the other libraries that it uses.

Here is an example of a function that is linked implicitly:

extern... FAR AddTwo();

int x, y = 3;
x = AddTwo(y);

Dynamically with LoadLibrary()

If you want to dynamically import a function when you run your application, load its DLL with LoadLibrary(). This method is useful if you won't know what functions the application needs until it's running. For example, each option in the Windows Control Panel is a DLL that the Windows Control Panel loads dynamically each time you run it. The Windows Control Panel doesn't know until it's running what options are available.

For example, to load mydll.dll and call the function AddTwo(), use this:

HINSTANCE hLib;
FARPROC func;
int x, y = 3;

hLib = LoadLibrary("MYDLL.DLL");
if (hLib >= 32) {
    func = GetProcAddress(hLib, "_AddTwo");
    if (func != (FARPROC) NULL)
        x = (*func)(y);
}
FreeLibrary(hLib);
In the previous example, when you call GetProcAddress(), Windows creates a Thunk layer and Thunk calls the function. In this case, the DLL function must have Pascal linkage in order to use the load library. See Programming Windows by Charles Petzold for more information on Thunk layers.

LoadLibrary() loads the library and returns a pointer to it. GetProcAddress() looks up the function by name in the library and returns a pointer to the function. The expression (*func)(y) dereferences the function pointer and calls AddTwo() with y as its argument. FreeLibrary() lets Windows know that you are no longer using the DLL and Windows can unload the library if nothing else is using it.

To look up the function by its ordinal entry value instead of by name, you need to change the call to GetProcAddress(). Suppose MyDLL assigned AddTwo() the number 1. Get the function's address with this statement:

func = GetProcAddress(hLib, MAKEINTRESOURCE(1));

Using a C++ DLL with a C program

Avoid using a C++ DLL with a C application. Since C and C++ differ in how they pass parameters and handle function names, it is not easy to call a C++ function from a C program.

However, if you must, you can use a C++ DLL with a C program. The easiest way is to recompile the C application as a C++ source file, and to use the extern "C" construct to give the application's functions C linkage and use the extern "C++" construct to give the DLL's functions C++ linkage. For example, this code gives three application functions C linkage and one DLL function C++ linkage:

extern "C" {
  void old_app_function1();
  void old_app_function2();
  extern "C++" {
    int cplusplus_DLL_func();
  };
  void old_ app_ function3();
};
Home | Runtime Library | IDDE Reference | STL | Search | Download | Forums