Creating C DLLs


Creating C++ DLLs

C++ also has a DLL wizard (the Dynamic-link Library item in the C++Builder Projects category) that should be used to create a new DLL. When you select to create a new dynamic link library, the IDE displays the New Dynamic-link Library dialog box (see Figure 21-8) that allows you to select which language you want to use, whether or not you'll be using VCL components, and if you want the IDE to generate the DllMain function (Visual C++ style) or the DllEntryPoint function (Borland C++ style).

image from book
Figure 21-8: The New Dynamic-link Library wizard

After you click OK, the IDE will generate the necessary files, and place the main DLL function in the .cpp file (see Listing 21-12).

Listing 21-12: Basic source code of a C++Builder DLL

image from book
//---------------------------------------------------------------- #include <vcl.h> #include <windows.h> #pragma hdrstop //---------------------------------------------------------------- //     Important note about DLL memory management when your DLL uses //     the static version of the RunTime Library: // //     If your DLL exports any functions that pass String objects //     (or structs/classes containing nested Strings) as parameter //     or function results, you will need to add the library MEMMGR.LIB //     to both the DLL project and any other projects that use the DLL. //     You will also need to use MEMMGR.LIB if any other projects that //     use the DLL will be performing new or delete operations on any //     non-TObject-derived classes that are exported from the DLL. //     Adding MEMMGR.LIB to your project will change the DLL and its //     calling EXEs to use the BORLNDMM.DLL as their memory manager. //     In these cases, the file BORLNDMM.DLL should be deployed along //     with your DLL. // //     To avoid using BORLNDMM.DLL, pass string information using //     "char *" or ShortString parameters. // //     If your DLL uses the dynamic version of the RTL, you do //     not need to explicitly add MEMMGR.LIB as this will be done //     implicitly for you //---------------------------------------------------------------- #pragma argsused int WINAPI DllEntryPoint(HINSTANCE hinst,    unsigned long reason, void* lpReserved) {    return 1; } //----------------------------------------------------------------
image from book

The most notable difference between a Delphi and a C++Builder DLL is the DllEntryPoint function, which is called by the operating system when the library is loaded and unloaded. Unlike other "main" functions, you usually don't write code inside the DllEntryPoint function and you have to return a nonzero value to notify the system that everything is OK.

Exporting Functions

To see how to export functions from a C++ DLL, first add two simple functions that will display "Hello" and "Goodbye" messages using the Windows API MessageBox function (see Listing 21-13). The two functions also call the GetActiveWindow() function, which makes sure that the message dialog box is always displayed on top of the active window.

The default calling convention in C++ is cdecl, which allows functions to accept a variable number of parameters and also results in function names that start with an underscore. Just like the register (__fastcall) calling convention in Delphi, you should forget about cdecl in DLLs and use the default Windows calling convention, stdcall.

Listing 21-13: Two DLL functions (not counting DLLEntryPoint)

image from book
#pragma argsused int WINAPI DllEntryPoint(HINSTANCE hinst,    unsigned long reason, void* lpReserved) {    /* In DLLs, a nonzero value means that everything is OK */    return 1; } //--------------------------------------------------------------------------- void __stdcall SayHello(void) {    MessageBox(GetActiveWindow(), "Hello",       "C++ DLL", MB_OK | MB_ICONINFORMATION); } void __stdcall SayGoodbye(void) {    MessageBox(GetActiveWindow(), "Goodbye",       "C++ DLL", MB_OK | MB_ICONINFORMATION); }
image from book

These two functions can currently be used only inside the DLL. To use them in an application, you have to export them using the __declspec(dllexport) storage class specifier.

Here is how you export the SayHello() function from the DLL:

__declspec(dllexport) void __stdcall SayHello(void);

In a C DLL, the __declspec(dllexport) directive is all you need to successfully export a function from a DLL. In a C++ DLL, the __declspec(dllexport) directive is also enough to successfully export a function, but the results are different because the C++ compiler mangles function names. To retain the original function name, you also need to use the extern "C" directive.

To properly export a function from a C++ DLL, you should use both the extern and __declspec storage class specifiers to export functions from a DLL:

extern "C" __declspec(dllexport) void __stdcall SayHello(void);

If you have to export several functions, you don't have to mark them all with the extern directive. When you have to export several functions, you can open an extern "C" block and then export all of your functions inside it:

extern "C" {    __declspec(dllexport) void __stdcall SayHello(void);    __declspec(dllexport) void __stdcall SayGoodbye(void); };

When exporting functions, you should also make sure you only apply the extern "C" specifier when you compile the DLL as a C++ DLL. To do that, you can use the #ifdef conditional directive to check if the __cplusplus constant is defined. If the __cplusplus constant is defined, it means that the compiler is working in C++ mode:

#ifdef __cplusplus extern "C" { #endif /* __cplusplus */    __declspec(dllexport) void __stdcall SayHello(void);    __declspec(dllexport) void __stdcall SayGoodbye(void); #ifdef __cplusplus }; #endif /* __cplusplus */

Creating a DLL Header File

In order to easily use the DLL in an application, you should move the export directives to a header file. When you move the exports to a header file, don't forget to include it in the main source file of the DLL.

Listings 21-14A and 21-14B show the current versions of the main source and header file.

Listing 21-14A: The DLL's source file (MainDLLUnit.cpp)

image from book
#include <vcl.h> #include <windows.h> #pragma hdrstop #include "MainDLLUnit.h"    /* Include the DLL's header file */ #pragma argsused int WINAPI DllEntryPoint(HINSTANCE hinst,    unsigned long reason, void* lpReserved) {    return 1; } void __stdcall SayHello(void) {    MessageBox(GetActiveWindow(), "Hello",       "C++ DLL", MB_OK | MB_ICONINFORMATION); } void __stdcall SayGoodbye(void) {    MessageBox(GetActiveWindow(), "Goodbye",       "C++ DLL", MB_OK | MB_ICONINFORMATION); }
image from book

Listing 21-14B: The DLL's header file (MainDLLUnit.h)

image from book
#ifndef MAIN_DLL_UNIT_H #define MAIN_DLL_UNIT_H #ifdef __cplusplus extern "C" { #endif    __declspec(dllexport) void __stdcall SayHello(void);    __declspec(dllexport) void __stdcall SayGoodbye(void); #ifdef __cplusplus }; #endif /* __cplusplus */ #endif /* MAIN_DLL_UNIT_H */
image from book

Static Loading (Static Linking)

To test the previously created DLL and to see how to statically link a DLL, use the Project Manager to add a C++Builder VCL Forms application to the project group.

To statically link a DLL in a C++ application, you can't simply include the DLL's header file and compile the application. If you do that, you'll receive the "Unresolved external…" error because the compiler won't be able to find the functions listed in the header file.

To properly link a DLL, you need to include the DLL's header so you can call the functions it contains and you need to add the DLL's .lib file to the project. The .lib file is generated by the compiler when you compile the DLL, and you have to add it to client projects because it contains data the compiler needs to link the client application with the DLL.

You can add the .lib file to the client application by right-clicking it in the Project Manager and selecting the Add item on the context menu.

image from book
Figure 21-9: DLL and client application project files

When you include the necessary header files and add the .lib file to the project, you can use the functions exported from the DLL as easily as the ones that are in the same unit:

void __fastcall TMainForm::SayHelloButtonClick(TObject *Sender) {    SayHello(); // call the DLL SayHello() function }

The result of the SayHello() call is displayed in Figure 21-10.

image from book
Figure 21-10: Testing the DLL

Functions exported from a DLL can also be used in a client application without including the header file. To do so, add the DLL's .lib file to the project and import the functions using the __declspec(dllimport) directive:

extern "C" __declspec(dllimport) void __stdcall SayHello(void);  extern "C" __declspec(dllimport) void __stdcall SayGoodbye(void);

The DllEntryPoint (DllMain) Function

The DllEntryPoint function is the main function in a DLL, called by the system on four occasions: when an application loads and unloads the DLL or when a thread loads and unloads the DLL.

You can easily determine why the DLL is used by checking the reason parameter of the DllEntryPoint function (see Listing 21-15). The reason parameter can be DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, or DLL_THREAD_DETACH.

Listing 21-15: Executing code when the DLL is loaded and unloaded

image from book
#pragma argsused int WINAPI DllEntryPoint(HINSTANCE hinst,    unsigned long reason, void* lpReserved) {    if(reason == DLL_PROCESS_ATTACH)       SayHello();    else if(reason == DLL_PROCESS_DETACH)       SayGoodbye();    return 1; }
image from book

The DllEntryPoint Function in a Delphi DLL

By default, the IDE doesn't generate the DllEntryPoint function for a DLL, but that doesn't mean a Delphi DLL can't execute code when a process loads or unloads the DLL.

To create a DllEntryPoint function in a Delphi DLL, you need to do the following three things:

  1. Declare a procedure that only accepts the Reason parameter.

  2. Pass this procedure, inside the main begin-end block, to the global DllProc variable to let Delphi know which procedure to call.

  3. Call your procedure with the DLL_PROCESS_ATTACH reason when the DLL is loaded.

Listing 21-16 shows the Delphi version of the SayHello/SayGoodbye DLL we created in C++ in Listing 21-13.

Listing 21-16: DllEntryPoint in a Delphi DLL

image from book
library EntryDLL; uses Windows; {$R *.res} procedure SayHello; stdcall; begin   MessageBox(GetActiveWindow, 'Hello',     'Delphi DLL', MB_OK or MB_ICONINFORMATION); end; procedure SayGoodbye; stdcall; begin   MessageBox(GetActiveWindow, 'Goodbye',     'Delphi DLL', MB_OK or MB_ICONINFORMATION); end; procedure DLLEntryPoint(Reason: Integer); begin   if Reason = DLL_PROCESS_ATTACH then     SayHello   else if Reason = DLL_PROCESS_DETACH then     SayGoodbye; end; exports   SayHello,   SayGoodbye; begin   { Assign your DllEntryPoint function to the DllProc pointer }   DllProc := DllEntryPoint;   { Call DllProc with DLL_PROCESS_ATTACH when the DLL is loaded. }   DllProc(DLL_PROCESS_ATTACH); end.
image from book

Dynamic Loading in C++

To dynamically load a DLL in C++, you have to follow the same steps as you do in Delphi:

  1. Create a new procedural type (a function pointer) that describes the routine you want to call (the return type, calling convention, parameters).

  2. Load the DLL using the LoadLibrary function.

  3. Call the GetProcAddress function to acquire a pointer to the routine in the DLL.

  4. Call one or more routines from the DLL.

  5. Remove the DLL from memory by calling FreeLibrary.

But before you see how to load functions from a DLL, you should create a DLL with a function or two. The following listing contains just that — a DLL with a simple function that displays an empty VCL form.

Listing 21-17: C++ DLL with VCL forms (MyDLL.dll)

image from book
#include <vcl.h> #include <windows.h> #pragma hdrstop extern "C" {    __declspec(dllexport) void __stdcall VCL_Form(void); }; #pragma argsused int WINAPI DllEntryPoint(HINSTANCE hinst,    unsigned long reason, void* lpReserved) {    return 1; } //--------------------------------------------------------------------------- void __stdcall VCL_Form(void) {    TForm* f = new TForm(Application);    try    {       f->Caption = "VCL Form in a C++ DLL";       f->ShowModal();    }    __finally    {       delete f;    } }
image from book

To call the VCL_Form function from MyDLL.dll, you need to create a function pointer type by using the following syntax:

typedef return_value (*function_name)(param_list);

or

typedef return_value (calling_convention *function_name)(param_list);

In this particular case, you have to declare the following type:

/* function pointer */ typedef void(__stdcall *PROC)(void); /* you also need a variable of type PROC */ PROC proc;

Once you've declared the function pointer type, you're done with the hardest part. The only thing left to do now is to load the DLL and acquire the pointer to the VCL_Form function. To use the pointer returned by the GetProc- Address function, you need to typecast it to your pointer type. Listing 21-18 shows how to use the LoadLibrary, GetProcAddress, and FreeLibrary functions to dynamically load a DLL in C++.

Listing 21-18: Dynamically loading a DLL in C++

image from book
void __fastcall TMainForm::ShowFormButtonClick(TObject *Sender) {    /* function pointer */    typedef void(__stdcall *PROC)(void);    /* you also need a variable of type PROC */    PROC proc;    HMODULE lib = LoadLibrary("MyDLL.dll");    if(lib != 0)    {       proc = (PROC)GetProcAddress(lib, "VCL_Form");       if(proc != NULL) {         proc(); /* call the function */       } else {         ShowMessage("VCL_Host() cannot be found!");       }    FreeLibrary(lib);    }    else    {       ShowMessage("LoadLibrary failed!");    } }
image from book



Inside Delphi 2006
Inside Delphi 2006 (Wordware Delphi Developers Library)
ISBN: 1598220039
EAN: 2147483647
Year: 2004
Pages: 212
Authors: Ivan Hladni

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net