Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]-2

.pdf
Скачиваний:
74
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Writing Your Own DLLs

Figure 18-4

Figure 18-5

909

Chapter 18

Now that the MFC DLL wizard has done its stuff, you can look into the code that has been generated on your behalf. If you look at the contents of the project in the Solution Explorer pane, you’ll see that the MFC DLL wizard has generated several files, including a .txt file that contains a description of the other files. You can read what they’re all for in the .txt file, but the following two are the ones of immediate interest in implementing our DLL:

Filename

Contents

 

 

ExtDLLExample.cpp

This contains the function DllMain() and is the primary source file

 

for the DLL.

ExtDLLExample.def

The information in this file is used during compilation. It contains

 

the name of the DLL, and you can also add to it the definitions of

 

those items in the DLL that are to be accessible to a program using

 

the DLL. You’ll use an alternative and somewhat easier way of iden-

 

tifying such items in the example.

 

 

When your DLL is loaded, the first thing that happens is that DllMain() is executed, so perhaps you should take a look at that first.

Understanding DllMain()

If you look at the contents of ExtDLLExample.cpp, you will see that the MFC DLL wizard has generated a version of DllMain() for you, as shown here:

extern “C” int APIENTRY

DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)

{

// Remove this if you use lpReserved UNREFERENCED_PARAMETER(lpReserved);

if (dwReason == DLL_PROCESS_ATTACH)

{

TRACE0(“EXTDLLEXAMPLE.DLL Initializing!\n”);

// Extension DLL one-time initialization

if (!AfxInitExtensionModule(ExtDLLExampleDLL, hInstance)) return 0;

//Insert this DLL into the resource chain

//NOTE: If this Extension DLL is being implicitly linked to by

//an MFC Regular DLL (such as an ActiveX Control)

//instead of an MFC application, then you will want to

//remove this line from DllMain and put it in a separate

//function exported from this Extension DLL. The Regular DLL

//that uses this Extension DLL should then explicitly call that

//function to initialize this Extension DLL. Otherwise,

//the CDynLinkLibrary object will not be attached to the

//Regular DLL’s resource chain, and serious problems will

//result.

new CDynLinkLibrary(ExtDLLExampleDLL);

910

Writing Your Own DLLs

}

else if (dwReason == DLL_PROCESS_DETACH)

{

TRACE0(“EXTDLLEXAMPLE.DLL Terminating!\n”);

// Terminate the library before destructors are called AfxTermExtensionModule(ExtDLLExampleDLL);

}

return 1; // ok

}

There are three arguments passed to DllMain() when it is called. The first argument, hInstance, is a handle that has been created by Windows to identify the DLL. Every task under Windows has an

instance handle which identifies it uniquely. The second argument, dwReason, indicates the reason why DllMain() is being called. You can see this argument being tested in the if statements in DllMain(). The first if tests for the value DLL_PROCESS_ATTACH, which indicates that a program is about to use the DLL, and the second if tests for the value DLL_PROCESS_DETACH, which indicates that a program has finished using the DLL. The third argument is a pointer that’s reserved for use by Windows, so you can ignore it.

When the DLL is first used by a program, it’s loaded into memory, and the DllMain() function is executed with the argument dwReason set to DLL_PROCESS_ATTACH. This results in the Windows API function AfxInitExtensionModule() being called to initialize the DLL and an object of the class CDynLinkLibrary created on the heap. Windows uses objects of this class to manage extension DLLs. If you need to add initialization of your own, you can add it to the end of this block. Any cleanup you require for your DLL can be added to the block for the second if statement.

Adding Classes to the Extension DLL

You’ll use the DLL to contain the implementation of the Sketcher shape classes, so move the files Elements.h and Elements.cpp from the folder containing the source for Sketcher to the folder containing the DLL. Be sure that you move rather than copy the files. Because the DLL is going to supply the shape classes for Sketcher, you don’t want to leave them in the source code for Sketcher.

You’ll also need to remove Elements.cpp from the Sketcher project. To do this, open the Sketcher project, highlight Elements.cpp in the Solution Explorer pane by clicking the file, and then press Delete. If you don’t do this, the compiler complains that it can’t find the file when you try to compile the project.

Follow the same procedure to get rid of Elements.h from the Header Files folder in the Solution Explorer pane.

The shape classes use the constants that you have defined in the file OurConstants.h, so copy this file from the Sketcher project folder to the folder containing the DLL. Note that the variable VERSION_NUMBER is used exclusively by the IMPLEMENT_SERIAL() macros in the shape classes, so you could delete it from the OurConstants.h file used in the Sketcher program.

You now need to add Elements.cpp containing the implementation of our shape classes to the extension DLL project, so open the ExtDLLExample project, select the menu option Project > Add Existing Item and choose the file Elements.cpp from the list box in the dialog box, as shown in Figure 18-6.

911

Chapter 18

Figure 18-6

The project should also include the files containing the definitions of the shape classes and your constants, so repeat the process for Elements.h and OurConstants.h to add these to the project. You can add multiple files in a single step by holding down the Ctrl key while you select from the list of files in the Add Existing Item dialog box. You should eventually see all the files in the Solution Explorer pane and all the shape classes displayed in the Class View pane for the project.

Exporting Classes from the Extension DLL

The names of the classes defined in the DLL that are to be accessible in programs that use it must be identified in some way so that the appropriate links can be established between a program and the DLL. As you saw earlier, one way of doing this is by adding information to the .def file for the DLL. This involves adding what are called decorated names to the DLL and associating the decorated name with a unique identifying numeric value called an ordinal. A decorated name for an object is a name generated by the compiler, which adds an additional string to the name you gave to the object. This additional string provides information about the type of the object or, in the case of a function for example, information about the types of the parameters to the function. Among other things, it ensures that everything has a unique identifier and enables the linker to distinguish overloaded functions from each other.

Obtaining decorated names and assigning ordinals to export items from a DLL is a lot of work and isn’t the best or the easiest approach with Windows. A much easier way to identify the classes that you want to export from the DLL is to modify the class definitions in Elements.h to include the keyword AFX_ EXT_CLASS before each class name, as shown in the following for the CLine class:

// Class defining a line object

class AFX_EXT_CLASS CLine: public CElement

{

DECLARE_SERIAL(CLine)

public:

912

Writing Your Own DLLs

virtual

void

Draw(CDC* pDC, CElement* pElement=0); //

Function

to

display

a line

virtual

void

Move(CSize& aSize);

//

Function

to

move an

element

// Constructor for a line object

CLine(CPoint Start, CPoint End, COLORREF aColor, int PenWidth);

virtual void Serialize(CArchive& ar);// Serialize function for CLine

protected:

 

CPoint m_StartPoint;

// Start point of line

CPoint m_EndPoint;

// End point of line

CLine(void);

// Default constructor - should not be used

};

 

The AFX_EXT_CLASS keyword indicates that the class is to be exported from the DLL. This has the effect of making the complete class available to any program using the DLL and automatically allows access to any of the data and functions in the public interface of the class. The collection of things in a DLL that are accessible by a program using it is referred to as the interface to the DLL. The process of making an object part of the interface to a DLL is referred to as exporting the object.

You need to add the keyword AFX_EXT_CLASS to all of the other shape classes, including the base class CElement. Why is it necessary to export CElement from the DLL? After all, programs create only objects of the classes derived from CElement, and not objects of the class CElement itself. The reason is that you have declared public members of CElement which form part of the interface to the derived shape classes, and which are almost certainly going to be required by programs using the DLL. If you don’t export the CElement class, functions such as GetBoundRect()will not be available.

The final modification needed is to add the directive:

#include <afxtempl.h>

to stdafx.h in the DLL project so that the definition of CList is available.

You have done everything necessary to add the shape classes to the DLL. All that remains is for you compile and link the project to create the DLL.

Building a DLL

You build the DLL in exactly the same way as you build any other project — by using the Build | Build Solution menu option or selecting the corresponding toolbar button. The output produced is somewhat different, though. You can see the files that are produced in the debug subfolder of the project folder for a Debug build, or in the release subfolder for a Release build. The executable code for the DLL is contained in the file ExtDLLExample.dll. This file needs to be available to execute a program that uses the DLL. The file ExtDLLExample.lib is an import library file that contains the definitions of the items that are exported from the DLL, and it must be available to the linker when a program using the DLL is linked.

If you find the DLL build fails because Elements.cpp contains an #include directive for Sketcher.h, just remove it. On my system the Class Wizard added this #include directive when creating the code for the CElement class, but it is not required.

913

Chapter 18

Using the Extension DLL in Sketcher

You now have no information in the Sketcher program on the shape classes because you moved the files containing the class definitions and implementations to the DLL project. However, the compiler still needs to know where the shape classes are coming from in order to compile the code for the program. The Sketcher program needs to include a header file that defines the classes that are to be imported from the DLL. It must also identify the classes as external to the project by using the AFX_EXT_CLASS macro in the class definitions in exactly the same way as for exporting the classes from the DLL. You can therefore just copy the file Elements.h from the DLL project to the folder containing the Sketcher source because it contains exactly what is required to import the classes into Sketcher. It would be a good idea to identify this file as specifying the imports from the DLL in the Sketcher source code. You could do this by changing its name to DllImports.h, in which case you’ll need to change the #include directives that are already in the Sketcher program for Elements.h to refer to the new file name (these occur in

Sketcher.cpp, SketcherDoc.h, and SketcherView.cpp). You should also add the DllImports.h file to the project by right-clicking the Header Files folder in the Solution Explorer pane and selecting Add > Existing Item from the context menu.

When you rebuild the Sketcher application, the linker needs to have the ExtDLLExample.lib file identified as a dependency for the project because this file contains information about the contents of the DLL. Right-click Sketcher in the Solution Explorer pane and select Properties from the pop-up. You can then expand the Linker folder and select Input in the left pane of the Properties window. You can then enter the name of the .lib file as an additional dependency as shown in Figure 18-7.

Figure 18-7

Figure 18-7 shows the entry for the debug version of Sketcher. The .lib file for the DLL is in the Debug folder within the DLL project folder. If you create a release version of Sketcher, you’ll also need the release version of the DLL available to the linker and its .lib file, so you’ll have to enter the fully qualified name of the .lib file for the release version of the DLL, corresponding to the release version of

914

Writing Your Own DLLs

Sketcher. The file to which the properties apply is selected in the Configuration drop-down list box in the Properties window. You have only one external dependency, but you can enter several when this is necessary by clicking the button to the right of the text box for input. Because the full path to the .lib file has been entered here, the linker will know not only that ExtDLLExample.lib is an external dependency but also where it is.

Be aware that if the complete path to the .lib file contains spaces (as in the example here), you’ll need to enclose it within quotation marks for the linker to recognize it correctly.

You can now build the Sketcher application once more, and everything should compile and link as usual. However, if you try to execute the program, you see the message box shown in Figure 18-8.

Figure 18-8

This is one of the less cryptic error messages — it’s fairly clear what’s gone wrong. To enable Windows to load a DLL for a program, it’s usual to place the DLL in your \WINNT\System folder. If it’s not in this folder Windows searches the folder containing the executable Sketcher.exe. If it isn’t there you get the error message. Because you probably don’t want to clutter up your \WINNT\System folder unnecessarily, you can copy ExtDllExample.dll from the debug folder of the DLL project to the debug folder for Sketcher. Sketcher should execute exactly as before, except that now it uses the shape classes in the DLL you have created.

Files Required to Use a DLL

From what you have just seen in the context of using the DLL you created in the Sketcher program, you can conclude that three files must be available to use a DLL in a program:

Extension

Contents

 

 

.h

Defines those items that are exported from a DLL and enables the compiler to

 

deal properly with references to such items in the source code of a program

 

using the DLL. The .h file needs to be added to the source code for the pro-

 

gram using the DLL.

.lib

Defines the items exported by a DLL in a form, which enables the linker to

 

deal with references to exported items when linking a program that uses

 

a DLL.

.dll

Contains the executable code for the DLL, which is loaded by Windows when

 

a program using the DLL is executed.

 

 

915

Chapter 18

If you plan to distribute program code in the form of a DLL for use by other programmers, you need to distribute all three files in the package. For applications that already use the DLL, just the .dll is required along with the .exe file.

Exporting Variables and Functions from a DLL

You’ve seen how you can export classes from an extension DLL using the AFX_EXT_CLASS keyword. You can also export objects of classes that are defined in a DLL, as well as ordinary variables and functions. These can be exported from any kind of DLL by using the attribute dllexport to identify them. By using dllexport to identify class objects, variables or functions that are to be exported from a DLL, you avoid getting involved in the complications of modifying the .def file and, as a consequence, you make defining the interface to the DLL a straightforward matter.

Don’t be misled into thinking that the approach you’re taking to exporting things from your DLL makes the .def file method redundant. The .def file approach is more complicated — which is why you’re taking the easy way out — but it offers distinct advantages in many situations over the approach you’re taking. This is particularly true in the context of products that are distributed widely, and are likely to be developed over time. One major plus is that a .def file enables you to define the ordinals that correspond to your exported functions. This allows you to add more exported functions later and assign new ordinals to them, so the ordinals for the original set of functions remain the same. This means that someone using a new version of the DLL with a program built to use the old version doesn’t have to relink their application.

You must use the dllexport attribute in conjunction with the keyword _declspec when you identify an item to be exported. For example, the statement

_declspec(dllexport) double aValue = 1.5;

defines the variable aValue of type double with an initial value of 1.5 and identifies it as a variable that is to be available to programs using the DLL. To export a function from a DLL, you use the dllexport attribute in a similar manner. For example:

_declspec(dllexport) CString FindWinner(CString* Teams);

This statement exports the function FindWinner() from the DLL.

To avoid the slightly cumbersome notation for specifying the dllexport attribute, you can simplify it by using a preprocessor directive:

#define DllExport _declspec(dllexport)

With this definition, you can rewrite the two previous examples as:

DllExport double aValue = 1.5;

DllExport CString FindWinner(CString* Teams);

This notation is much more economical, as well as easier to read, so you may want to adopt this approach when coding your DLLs.

916

Writing Your Own DLLs

Obviously, only symbols that represent objects with global scope can be exported from a DLL. Variables and class objects that are local to a function in a DLL cease to exist when execution of a function is completed, in just the same way as in a function in a normal program. Attempting to export such symbols results in a compile-time error.

Importing Symbols into a Program

The dllexport attribute identifies the symbols in a DLL that form part of the interface. If you want to use these in a program, you must make sure that they are correspondingly identified as being imported from the DLL. This is done by using the dllimport keyword in declarations for the symbols to be imported in a .h file. You can simplify the notation by using the same technique you applied to the dllexport attribute. Define DllImport with the directive:

#define DllImport _declspec(dllimport)

You can now import the aValue variable and the FindWinner() function into a program with the declarations:

DllImport double aValue;

DllImport CString FindWinner(CString* Teams);

These statements would appear in a .h file that would be included into the .cpp files in the program that referenced these symbols.

Implementing the Export of Symbols from a DLL

You could extend the extension DLL for Sketcher to make the symbols defining shape types and colors available in the interface to it. You can then remove the definitions that you have in the Sketcher program and import the definitions of these symbols from the extension DLL.

You can modify the source code for the DLL first to add the symbols for shape element types and colors to its interface. To export the element types and colors, they must be global variables. As global variables, it would be better if they appeared in a .cpp file, rather than a .h file, so move the definitions of these out of the OurConstants.h file to the beginning of Elements.cpp in the DLL source. You can then apply the dllexport attribute to their definitions in the Elements.cpp file, as follows:

//Definitions of constants and identification of symbols to be exported

#define DllExport __declspec(dllexport)

//Element type definitions

//Each type value must be unique

DllExport extern const unsigned int LINE = 101U;

DllExport extern const unsigned int RECTANGLE = 102U; DllExport extern const unsigned int CIRCLE = 103U; DllExport extern const unsigned int CURVE = 104U; DllExport extern const unsigned int TEXT = 105U;

///////////////////////////////////

// Color values for drawing

917

Chapter 18

DllExport extern const COLORREF BLACK = RGB(0,0,0);

DllExport extern const COLORREF RED = RGB(255,0,0);

DllExport extern const COLORREF GREEN = RGB(0,255,0);

DllExport extern const COLORREF BLUE = RGB(0,0,255); DllExport extern const COLORREF SELECT_COLOR = RGB(255,0,180);

///////////////////////////////////

Add these to the beginning of Elements.cpp, after the #include directives. You first define the symbol DllExport to simplify the specification of the variables to be exported, as you saw earlier. You then assign the attribute dllexport to each of the element types and colors.

Notice that the extern specifier has also been added to the definitions of these variables. The reason for this is the effect of the const modifier, which indicates to the compiler that the values are constants and shouldn’t be modified in the program, which was what you wanted. However, by default the const keyword also specifies the variables as having internal linkage, so they are local to the file in which they appear. You want to export these variables to another program so you have to add the extern modifier to override the default linkage specification due to the const modifier and ensure that they have external linkage. Symbols that are assigned external linkage are global and so can be exported. Of course,

if the variables didn’t have the const modifier applied to them, you wouldn’t need to add extern because they would be global automatically as long as they appeared at global scope.

The OurConstants.h file now contains only one definition:

// Definitions of constants #pragma once

// Define the program version number for use in serialization UINT VERSION_NUMBER = 1;

Of course, this is still required because it is used in the IMPLEMENT_SERIAL() macros in Elements.cpp. You can now build the DLL once again, so it’s ready to use in the Sketcher program. Don’t forget to copy the latest version of the .dll file to the Sketcher project Debug folder.

Using Exported Symbols

To make the symbols exported from the DLL available in the Sketcher program, you need to specify them as imported from the DLL. You can do this by adding the identification of the imported symbols to the file DllImports.h, which contains the definitions for the imported classes. In this way, you’ll have one file specifying all the items imported from the DLL. The statements that appear in this file are as follows:

// Variables defined in the shape DLL ExtDLLExample.dll #pragma once

#define DllImport __declspec( dllimport )

//Import element type declarations

//Each type value must be unique DllImport extern const unsigned int LINE;

DllImport extern const unsigned int RECTANGLE; DllImport extern const unsigned int CIRCLE;

918