One important feature of Windows is the ability it gives you to put code in a dynamic link library, or DLL. DLLs let you compartmentalize your code, providing flexibility when you're performing updates. For example, if you place different aspects of your application in DLLs, then updating an aspect of your program is as easy as shipping a new DLL. In this article, we'll explain how to create DLLs that contain VCL forms. We'll show you how to write a DLL so that your forms can be used by C++Builder programs or even by programs written with tools such as Borland C++, Visual C++, or Delphi.
int __declspec(dllexport) MyFunction(); __declspec(dllexport) int MyFunction();Notice that you can place __declspec either before or after the function's return type (int in this example). The compiler straightens things out at compile time. To import a function, use either of the following:
int __declspec(dllimport) MyFunction(); __declspec(dllimport) int MyFunction();Again, the placement of __declspec isn't critical, as long as it falls somewhere before the function name. To simplify importing or exporting functions, you'll generally use a symbol that expands to __import or __export depending on whether you're building the DLL or the calling application. Let's look at an example before we explain further:
#ifdef BUILDDLL
#define DLL_MODE
__declspec(dllexport)
#else
#ifdef BUILDAPP
#define DLL_MODE
__declspec(dllimport)
#endif
#endif
extern "C" int
DLL_MODE MyFunction();
Place this code in the header file for your DLL. In the source code for your
DLL, you define BUILDDLL before you include the header:#define BUILDDLL #include "MyDll.h"When you define BUILDLL, the DLL_MODE symbol is defined as __declspec(dllexport), thereby exporting the symbols. Naturally, when you build the calling application you'll write the following lines:
#define BUILDAPP #include "MyDll.h"Now, DLL_MODE will be defined as __declspec(dllimport) and the functions will be declared as imported. This technique greatly simplifies importing and exporting functions from DLLs. All you have to remember to do is define either BUILDDLL or BUILDAPP in the DLL code or the calling application code, respectively.
Table A: Function calling conventions
| Convention | Keyword |
|---|---|
| C | __cdecl |
| Pascal | __pascal |
| Register | __fastcall |
| Standard call | __stdcall |
By default, a DLL built with C++Builder will use the C calling convention. This means function names will be case sensitive and will have an underscore prefix. For example, the function in the previous example will show up in the DLL's export section as
_MyFunctionIf you're creating DLLs for use only with C++Builder applications, you don't need to worry about this convention. If, however, you're creating DLLs for use with other development environments, you may wish to use the Standard Call convention to eliminate the leading underscore. The important thing is to use the same calling convention when you build the DLL and when you import the functions in the calling application. If you decide to use the Standard Call convention, you need to use the __stdcall keyword in both the function declaration and the function definition:
// declaration
extern "C"
int __decldpec(DLL_MODE)
__stdcall
MyFunction();
// function definition
int __stdcall MyFunction()
{
// function body
}
You'll notice two things about this code. First, the __declspec modifier is
used only in the function declaration and not in the function definition. Next,
the code uses the extern "C" modifier, thereby preventing the function name
from being mangled. C++ compiler vendors mangle function names to account for
function overloading--you must use the extern "C" modifier if your DLL will be
used from applications built with development tools other than C++Builder.
Without this modifier, other applications won't be able to "see" the functions
in your DLL. One extra note concerning extern "C": If you do use this modifier,
you won't be able to use overloaded functions in your DLL.
| Create a new DLL project. | |
| Create a header file for the DLL. | |
| Create a form. | |
| Write an exported function that will show the form. | |
| Run IMPLIB.EXE on the DLL to create an import library file. | |
| Add the resulting LIB file to the calling application's project. | |
| Call the DLL function to show the form. |
A sentry is a mechanism that prevents your header from being included more than once in a project. A sentry looks like this:
#ifndef __MYDLL_H #define __MYDLL_H // header code #endifNormally, C++Builder does this for you when you create a new unit. But when you create a header from scratch, you must add the sentry code yourself. Be sure to add a line near the top of your DLL's main unit to include your header, and remember to define the BUILDDLL symbol as we discussed earlier.
#include <vcl\vcl.h> #pragma hdrstop #define BUILDDLL #include "MyDll.h" #include "MyForm.h"In this example, C++Builder creates the first two lines, leaving you to type the remaining three lines. Remember to manually include the headers for any forms you create for the DLL. If you forget this step, the compiler will remind you by complaining about undefined symbols.
// declaration in header
extern "C"
int DLL_MODE ShowDll
FormModal(TForm* parent);
// definition in CPP file
int ShowDllFormModal
(TForm* parent)
{
TMyForm* form = new
TMyForm(parent);
int result = form->
ShowModal();
delete form;
return result;
}
This code creates a new TMyForm object with the given parent, shows the form
modally, and then deletes the form. The modal result of the ShowModal() call is
returned to the calling application.
implib mydll.lib mydll.dllto create MYDLL.LIB in the current directory. The IMPLIB utility is located in your CBUILDER\BIN directory. That directory is already on your system's path, so you don't need to type the entire path to IMPLIB.EXE.
if (ShowDllFormModal(this)) // do something here else // do something elseYou could also write a separate function in the DLL to call form->Show() rather than form->ShowModal(). Doing so would allow you to show the form as a modeless form. That's all there is to it! You have a form in a DLL that you can call from any C++Builder application.
| Note: Build without the incremental linker. |
|---|
| Be sure to turn off the incremental linker and debug information before you do a final build of your DLL. If you don't do a build without the incremental linker, your DLL will contain hard-coded path information that will make it nearly impossible to utilize on your users' machines. |
extern "C" int DLL_MODE ShowDll FormModal(TForm* parent);The extern "C" wasn't strictly necessary because the function was being called from a C++Builder application. But using extern "C" means you're ready to go if you want to use your DLL with non-C++Builder applications. Also, notice the function parameter. Passing a TForm pointer makes no sense for a non-VCL calling application (a Visual Basic application, for example, has no understanding of TForm). If your application will be used only from non-VCL applications, you should write the function with no parameters. In the function, you can pass 0 for the Owner parameter when you create the TForm object. For example:
int ShowVCLForm()
{
TDLLForm* form = new TDLLForm(0);
int result = form->ShowModal();
delete form;
return result;
}
Specifying an Owner of 0 causes VCL to create the form without a parent object--in this case, VCL automatically parents the form to the process that created it. This feature is good news because you can call a form from a non-VCL application and the form will behave modally--but it's bad news because you can't show the form modelessly. You can try calling Show() rather than ShowModal(), but as long as the parent of the form is 0 the form will always behave modally.
The only other consideration, when creating a DLL that will be called from non-VCL applications, is the calling convention, as we described earlier. You may want to consider using the Standard Call convention to eliminate the problem with leading underscores in the function name. Doing so will make calling functions in your DLL more straightforward for applications written with tools other than C++Builder.
Listing A: MYFORMS.H
#ifndef __MYFORMS_H
#define __MYFORMS_H
#ifdef BUILDDLL
#define DLL_MODE __declspec(dllexport)
#else
#ifdef BUILDAPP
#define DLL_MODE __declspec(dllimport)
#endif
#endif
// These functions are exported only when
// building a BCB application.
#ifdef BCB
int DLL_MODE ShowDllFormModal(TForm* parent);
void DLL_MODE ShowDllForm(TForm* parent);
void DLL_MODE ShowMDIChildForm (TApplication* app);
void DLL_MODE ResetDllApplication();
#endif
// This function is exported regardless.
extern "C" int DLL_MODE ShowVCLForm();
#endif
Listing B: MYFORMS.CPP#include <vcl\vcl.h>
#pragma hdrstop
TApplication* DllApp = 0;
//---------------------------------------------
#define BCB
#define BUILDDLL
#include "MyForms.h"
#include "FormOne.h"
#include "MDIChild.h"
//---------------------------------------------
USEFORM("FormOne.cpp", DLLForm);
USEFORM("MDIChild.cpp", ChildForm);
//---------------------------------------------
int WINAPI DllEntryPoint(
HINSTANCE hinst, unsigned
long reason, void*)
{
return 1;
}
//---------------------------------------------
int ShowDllFormModal(TForm* parent)
{
// Create the form and show it modally.
TDLLForm* form = new TDLLForm(parent);
int result = form->ShowModal();
delete form;
return result;
}
void ShowDllForm(TForm* parent)
{
// Create the form and show it modelessly.
// The calling application will free the
// memory used by the modeless form. Don't
// call this function from a non-VCL app
// or it will leak memory.
TDLLForm* form = new TDLLForm(parent);
form->Show();
}
int ShowVCLForm()
{
// Create the form and show it modally.
return ShowDllFormModal(0);
}
void ShowMDIChildForm
(TApplication* mainApp)
{
// If the Application object for the DLL has
// not yet been saved then save it now and
// assign the Application object for the
// calling application to the DLL's
// Application object.
if (!DllApp) {
DllApp = Application;
Application = mainApp;
} // Create and show the MDI child form.
TChildForm* child = new TChildForm (Application->MainForm);
child->Show();
}
void ResetDllApplication()
{
// Reset the saved DLL's Application object.
// You must do this before the calling
// application closes or you will get an
// access violation.
if (DllApp) {
Application = DllApp;
}
}
The DLL project creates a DLL called MYFORMS.DLL. If you enter the code by hand, remember to run IMPLIB on the final DLL to create an import library file that the calling application will need.
Code included in the header in Listing A lets either a C++Builder application or a non-C++Builder application use the header. The extra code makes use of a symbol we created called BCB. If BCB isn't defined, the functions requiring VCL objects (like TForm and TApplication) aren't exported.
Listings C and D contain the code for the calling application. The main form of the calling application has the FormStyle property value fsMDIForm. The form contains three buttons on a panel: One button creates a MDI child window, the second button creates a modal form, and the third creates a modeless form. Though you can't tell it from the code listings, the calling application's project includes the file MYFORMS.LIB. Doing so ensures that C++Builder loads the DLL when the application starts and resolves the functions referenced in the calling application at link time.
Listing C: DLLAPP.H
#ifndef DLLAppUH #define DLLAppUH //--------------------------------------------- #include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> #include <vcl\ExtCtrls.hpp> //---------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TPanel *Panel1;
TButton *Button1;
TButton *Button2;
TButton *Button3;
void __fastcall Button1Click(TObject *Sender);
void __fastcall FormCloseQuery(TObject *Sender, bool &CanClose);
void __fastcall Button2Click(TObject *Sender);
void __fastcall Button3Click(TObject *Sender);
private: // User declarations
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------
extern TForm1 *Form1;
//---------------------------------------------#endifListing D: DLLAPP.CPP
#include <vcl\vcl.h>
#pragma hdrstop
#define BUILDAPP
#include "Forms.h"
#include "DLLAppU.h"
//---------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
}
//---------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ShowMDIChildForm(Application);
}
//---------------------------------------------
void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
{
ResetDllApplication();
}
//---------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
ShowDllFormModal(this);
}
//---------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
ShowDllForm(this);
}
Kent Reisdorph is a editor of the C++Builder Developer's Journal as well as director of systems and services at TurboPower Software Company, and a member of TeamB, Borland's volunteer online support group. He's the author of Teach Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days. You can contact Kent at editor@bridgespublishing.com.