Programming with Microsoft Visual C++.NET 6ed [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming with Microsoft Visual C++.NET 6ed [Electronic resources] - نسخه متنی

George Shepherd, David Kruglinski

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید








MFC DLLs: Extension vs. Regular


We've been looking at Win32 DLLs that have a DllMain function and some exported functions. Now we'll move on to the MFC application framework, which adds its own support layer on top of the Win32 basics. The MFC Application Wizard lets you build two kinds of DLLs with MFC library support: extension DLLs and regular DLLs. You must understand the differences between these two types so you can decide which one is best for your needs.





Note

Of course, Visual C++ .NET lets you build a pure Win32 DLL without the MFC library, just as it lets you build a Windows-based program without the MFC library.


An extension DLL supports a C++ interface. In other words, the DLL can export whole classes and the client can construct objects of those classes or derive classes from them. An extension DLL dynamically links to the code in the DLL version of the MFC library. Therefore, an extension DLL requires that your client program be dynamically linked to the MFC library (the MFC Application Wizard default) and that both the client program and the extension DLL be synchronized to the same version of the MFC DLLs (mfc70.dll, mfc70d.dll, and so on). Extension DLLs are quite small; you can build a simple extension DLL with a size of 10 KB, which will load quickly.

If you need a DLL that can be loaded by any Win32 programming environment, you should use a regular DLL. A big restriction here is that the regular DLL can export only C-style functions. It can't export C++ classes, member functions, or overloaded functions because every C++ compiler has its own method of decorating names. You can, however, use C++ classes (and MFC library classes, in particular) inside your regular DLL. Implementing a COM interface for your DLL also solves the issue of integrating with Visual Basic.

When you build an MFC regular DLL, you can choose to statically link or dynamically link to the MFC library. If you choose static linking, your DLL will include a copy of all the MFC library code it needs and will thus be self-contained. A typical release-build statically linked regular DLL is about 144 KB. If you choose dynamic linking, the size will drop to about 17 KB but you'll have to ensure that the proper MFC DLLs are present on the target machine. That's no problem if the client program is already dynamically linked to the same version of the MFC library.

When you tell the MFC wizards what kind of DLL or EXE you want, compiler #define constants are set as shown in the following table.

















Dynamically Linked to Shared MFC Library


Statically Linked to MFC Library


Regular DLL


_AFXDLL, _USRDLL


_USRDLL


Extension DLL


_AFXEXT, _AFXDLL


Unsupported option


Client EXE


_AFXDLL


No constants defined


If you look inside the MFC source code and header files, you'll see a lot of #ifdef statements for these constants. This means that the library code is compiled quite differently depending on the kind of project you're producing.


MFC Extension DLLs: Exporting Classes


If your extension DLL contains only exported C++ classes, you'll have an easy time building and using it. The steps shown later for building the Ex20a example show you how to tell the MFC DLL Wizard that you're building an extension DLL skeleton. That skeleton has only the DllMain function. You simply add your own C++ classes to the project. There's only one special thing you must do: You must add the macro AFX_EXT_CLASS to the class declaration, as shown here:

class AFX_EXT_CLASS CStudent : public CObject

This modification goes into the H file that's part of the DLL project, and it also goes into the H file that client programs use. In other words, the H files are exactly the same for both client and DLL. The macro generates different code depending on the situation—it exports the class in the DLL and imports the class in the client.



The MFC Extension DLL Resource Search Sequence


If you build a dynamically linked MFC client application, many of the MFC library's standard resources (error message strings, print preview dialog templates, and so on) will be stored in the MFC DLLs, but your application will have its own resources, too. When you call an MFC function such as CString::LoadString or CBitmap::LoadBitmap, the framework will step in and search first the EXE file's resources and then the MFC DLL's resources.

If your program includes an extension DLL and your EXE needs a resource, the search sequence will be first the EXE file, then the extension DLL, and then the MFC DLLs. If you have a string resource ID, for example, that is unique among all resources, the MFC library will find it. If you have duplicate string IDs in your EXE file and your extension DLL file, the MFC library will load the string in the EXE file.

If the extension DLL loads a resource, the sequence will be first the extension DLL, then the MFC DLLs, and then the EXE.

You can change the search sequence if you need to. Suppose you want your EXE code to search the extension DLL's resources first. You can use code such as this:

HINSTANCE hInstResourceClient = AfxGetResourceHandle();
// Use DLL's instance handle
AfxSetResourceHandle(::GetModuleHandle("mydllname.dll"));
CString strRes;
strRes.LoadString(IDS_MYSTRING);
// Restore client's instance handle
AfxSetResourceHandle(hInstResourceClient);

You can't use AfxGetInstanceHandle instead of ::GetModuleHandle. In an extension DLL, AfxGetInstanceHandle returns the EXE's instance handle, not the DLL's handle.



The Ex20a Example: An MFC Extension DLL


This example makes an extension DLL out of the CPersistentFrame class you saw in Chapter 14. First you'll build the

Ex20a.dll file, and then you'll use it in a test client program, Ex20b.

Here are the steps for building the Ex20a example:



    Run the MFC DLL Wizard to produce the Ex20a project. Choose New Project from the Visual Studio .NET File menu. Select Visual C++ Projects, and then select MFC DLL from the list of templates. On the Application Settings page, select the MFC Extension DLL, as shown here:





    Examine the

    Ex20a.cpp file. The MFC DLL Wizard generates the following code, which includes the DllMain function:

    // Ex20a.cpp : Defines the initialization routines for the DLL.
    //
    #include "stdafx.h"
    #include <afxdllx.h>
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
    static AFX_EXTENSION_MODULE Ex20aDLL = { NULL, NULL };
    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("Ex20a.DLL Initializing!\n");
    // Extension DLL one-time initialization
    if (!AfxInitExtensionModule(Ex20aDLL, 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(Ex20aDLL);
    }
    else if (dwReason == DLL_PROCESS_DETACH)
    {
    TRACE0("Ex20a.DLL Terminating!\n");
    // Terminate the library before destructors are called
    AfxTermExtensionModule(Ex20aDLL);
    }
    return 1; // ok
    }



    Insert the CPersistentFrame class into the project. Choose Add Existing Item from the Project menu and locate the files

    Persist.h and

    Persist.cpp in the Ex14a folder on the companion CD. Add the class to the current project.



    Edit the

    Persist.h file. Modify the line

    class CPersistentFrame : public CFrameWnd

    to read

    class AFX_EXT_CLASS CPersistentFrame : public CFrameWnd



    Build the project and copy the DLL file. Copy the file

    Ex20a.dll from the \vcppnet\Ex20a\Debug directory to your system directory.





The Ex20b Example: A DLL Test Client Program


This example starts off as a client for

Ex20a.dll . It imports the CPersistentFrame class from the DLL and uses it as a base class for the SDI frame window. Later, we'll add code to load and test the other sample DLLs in this chapter.

Here are the steps for building the Ex20b example:



    Run the MFC Application Wizard to produce the Ex20b project. This is an ordinary MFC EXE program. Select Single Document. Otherwise, accept the default settings. Be absolutely sure that you accept the Use MFC In A Shared DLL option on the Application Type page.



    Copy the file

    persist.h from the \vcppnet\Ex20a directory. Note that you're copying the header file, not the CPP file.



    Change the CFrameWnd base class to CPersistentFrame, as you did in Ex14a. Replace all occurrences of CFrameWnd with CPersistentFrame in both

    MainFrm.h and

    MainFrm.cpp . Also insert the following line into

    MainFrm.h :


    #include "persist.h"



    Add the Ex20a import library to the linker's input library list. Choose Add Existing Item from the Visual Studio .NET Project menu.



    Locate the

    Ex20a.lib file in the \vcppnet\Ex20a\Debug directory on the companion CD.



    Build and test the Ex20b program. If you run the program from the debugger and Windows can't find the Ex20a DLL, Windows will display a message box when Ex20b starts. If all goes well, you should have a persistent frame application that works exactly like the one in Ex14a. The only difference is that the CPersistentFrame code will be in an extension DLL.





MFC Regular DLLs: The AFX_EXTENSION_MODULE Structure


When the MFC DLL Wizard generates a regular DLL, the DllMain function will be inside the framework and you'll end up with a structure of type AFX_EXTENSION_MODULE (and a global instance of the structure). AFX_EXTENSION_MODULE is used during initialization of MFC extension DLLs to hold the state of extension DLL module.

You usually don't need to do anything with this structure. You normally just write C functions and then export them using the __declspec(dllexport) modifier (or using entries in the project's DEF file).



Using the AFX_MANAGE_STATE Macro


When mfc70.dll is loaded as part of a process, it stores data in some truly global variables. If you call MFC functions from an MFC program or extension DLL, mfc70.dll will know how to set these global variables on behalf of the calling process. If you call into mfc70.dll from a regular MFC DLL, however, the global variables will not be synchronized and the effects will be unpredictable. To solve this problem, insert the following line at the start of all exported functions in your regular DLL:

AFX_MANAGE_STATE(AfxGetStaticModuleState());

If the MFC code is statically linked, the macro will have no effect.



The MFC Regular DLL Resource Search Sequence


When an EXE links to a regular DLL, resource loading functions inside the EXE will load the EXE's own resources. Resource loading functions inside the regular DLL will load the DLL's own resources.

If you want your EXE code to load resources from the DLL, you can use AfxSetResourceHandle to temporarily change the resource handle. If you're writing an application that needs to be localized, you can put language-specific strings, dialog boxes, menus, and so forth in an MFC regular DLL. You might, for example, include the modules English.dll, German.dll, and French.dll. Your client program will explicitly load the correct DLL and load the resources using regular resource-management function calls, which will have the same IDs in all the DLLs.



The Ex20c Example: An MFC Regular DLL


This example creates a regular DLL that exports a single square root function. First we'll build the

Ex20c.dll file, and then we'll modify the test client program, Ex20b, to test the new DLL.

Here are the steps for building the Ex20c example:



    Run the MFC DLL Wizard to produce the project Ex20c. Proceed as you did for Ex20a, but accept Regular DLL Using Shared MFC DLL (instead of selecting MFC Extension DLL) on the Application Settings page.



    Examine the

    Ex20c.cpp file. The MFC DLL Wizard generates the following code:

    // Ex20c.cpp : Defines the initialization routines for the DLL.
    //
    #include "stdafx.h"
    #include "Ex20c.h"
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
    //
    // Note!
    // If this DLL is dynamically linked against the MFC
    // DLLs, any functions exported from this DLL which
    // call into MFC must have the AFX_MANAGE_STATE macro
    // added at the very beginning of the function.
    //
    // For example:
    //
    // extern "C" BOOL PASCAL EXPORT ExportedFunction()
    // {
    // AFX_MANAGE_STATE(AfxGetStaticModuleState());
    // // normal function body here
    // }
    //
    // It is very important that this macro appear in each
    // function, prior to any calls into MFC. This means that
    // it must appear as the first statement within the
    // function, even before any object variable declarations
    // as their constructors may generate calls into the MFC
    // DLL.
    //
    // Please see MFC Technical Notes 33 and 58 for additional
    // details.
    //
    // CEx20cApp
    BEGIN_MESSAGE_MAP(CEx20cApp, CWinApp)
    END_MESSAGE_MAP()
    // CEx20cApp construction
    CEx20cApp::CEx20cApp()
    {
    // TODO: add construction code here,
    // Place all significant initialization in InitInstance
    }
    // The one and only CEx20cApp object
    CEx20cApp theApp;
    // CEx20cApp initialization
    BOOL CEx20cApp::InitInstance()
    {
    CWinApp::InitInstance();
    return TRUE;
    }



    Add the code for the exported Ex20cSquareRoot function. It's okay to add this code in the

    Ex20c.cpp file, although you can use a new file if you want to:


    extern "C" __declspec(dllexport) double Ex20cSquareRoot(double d)
    {
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    TRACE("Entering Ex20cSquareRoot\n");
    if (d >= 0.0) {
    return sqrt(d);
    }
    AfxMessageBox("Can't take square root of a negative number.");
    return 0.0;
    }

    You can see that there's no problem with the DLL displaying a message box or another modal dialog box. You'll need to include math.h in the file that contains this code.

    Be sure to prototype the Ex20cSquareRoot function in the

    Ex20c.h file so external clients can see it.



    Build the project and copy the DLL file. Copy the file

    Ex20c.dll from the \vcppnet\Ex20c\Debug directory to your system directory.





Updating the Ex20b Example: Adding Code to Test Ex20c.dll


When we built the Ex20b program, it linked dynamically to the Ex20a MFC extension DLL. Now we'll update the project to implicitly link to the Ex20c MFC regular DLL and to call the DLL's square root function.

Here are the steps for updating the Ex20b example:



    Add a new dialog resource and class to the Ex20b project. Use the dialog editor to create the IDD_EX20C template, as shown here:

    Use the Add Class Wizard to add a class CTest20cDialog that is derived from CDialog. The controls, data members, and message map function are shown in the following table.



















    Control ID


    Type


    Data Member


    Message Map Function


    IDC_INPUT


    Edit control


    m_dInput

    (double)


    IDC_OUTPUT


    Edit control


    m_dOutput

    (double)


    IDC_COMPUTE


    Button


    OnBnClickedCompute




    Code the OnBnClickedCompute function to call the DLL's exported function. Edit the generated function in

    Test20cDialog.cpp as shown here:

    void CTest20cDialog::OnBnClickedCompute()
    {
    UpdateData(TRUE);
    m_dOutput = Ex20cSquareRoot(m_dInput);
    UpdateData(FALSE);
    }

    You must declare the Ex20cSquareRoot function as an imported function. Add the following line to the

    Test20cDialog.h file:


    extern "C" __declspec(dllimport) double Ex20cSquareRoot(double d);



    Integrate the CTest20cDialog class into the Ex20b application. You must add a top-level menu, Test, and an Ex20c DLL option with the ID ID_TEST_EX20CDLL. Use Class View's Properties window to map this option to a member function in the CEx20bView class, and then code the handler in

    Ex20bView.cpp as follows:

    void CEx20bView::OnTestEx20cdll()
    {
    CTest20cDialog dlg;
    dlg.DoModal();
    }

    Of course, you must add the following line to the

    Ex20bView.cpp file:


    #include "Test20cDialog.h"



    Add the Ex20c import library to the linker's input library list. Choose Add Existing Item from the Visual Studio .NET Project menu, and then add \vcppnet\Ex20c\Debug\

    Ex20c.lib to the project. Now the program should implicitly link to both the Ex20a DLL and the Ex20c DLL. As you can see, the client doesn't care whether the DLL is a regular DLL or an extension DLL. You just specify the LIB name to the linker.



    Build and test the updated Ex20b application. Choose Ex20c DLL from the Test menu. Type a number in the Input edit control, and then click the Compute Sqrt button. The result should appear in the Output control.





/ 319