September 1998

Using a VCL form in a DLL

by Kent Reisdorph

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.

Import, export, and __declspec

Perhaps one of the most frustrating aspects of dealing with DLLs is the concept of importing and exporting functions. When you build a DLL, you need to declare all public functions with the __export keyword. (Public functions are called from your calling application. A DLL may also contain non-public functions used only within the DLL.) Conversely, when you build the application that uses functions in the DLL, you need to declare the functions with the __import keyword. This process sounds simple, but it's easy to get wrong. You must place the __import or __export symbol in exactly the right place in your function declaration, or your application won't compile. Fortunately, the __declspec keyword cures this ill. To export a function from the DLL, use either of the following:
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.

 

Calling conventions and name mangling

Another confusing aspect of imported and exported functions is the calling convention. The calling convention determines how the exported function names appear in DLL and how the parameters are passed on the stack. The different calling conventions and their keyword modifier appear in Table A.

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

_MyFunction
If 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.

VCL forms in DLLs

With that critical bit of DLL groundwork behind us, we can get on with how to use VCL forms in a DLL. The most basic scenario is a VCL application calling a VCL form that resides in a DLL. This scenario is fairly easy to handle. The steps required are as follows:
bullet Create a new DLL project.
bullet Create a header file for the DLL.
bullet Create a form.
bullet Write an exported function that will show the form.
bullet Run IMPLIB.EXE on the DLL to create an import library file.
bullet Add the resulting LIB file to the calling application's project.
bullet Call the DLL function to show the form.
Let's look at these steps on more detail.

 

Create a DLL project and a header

To create a new DLL project, choose File | New from the main menu and double-click on the DLL icon in the Object Repository. Next, you need to create a header for your DLL that will contain the function declarations for all functions exported from your DLL. When you create a new DLL project, C++Builder doesn't automatically create a header file as it does when you create a source code unit. To create a header for your DLL, create a new text file from the Object Repository and save the file with a .H extension. Be sure your header includes a sentry and the import/export macros we presented in the section "Import, export, __declspec, and you."

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

#endif
Normally, 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.

Create a form in the DLL

You create a new form for a DLL the same way you create a new form for an application. There is, however, one difference: The Include Unit Hdr option isn't available from the File menu in a DLL project. This means you'll have to manually type the code to include the units for any forms you create. Let's say you have a form in a unit saved as MYFORM.CPP. Let's further assume that your DLL is called MYDLL.CPP. The code to include the header for the DLL (created in the previous step) and the code to include the new unit would look like this:
#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.

Write a function to show the form

Now you're ready to enter the code that will show the form. The following code shows both the function declaration (contained in the header) and the function definition (in the DLL's source unit):
// 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.

Create an import library file for the DLL and add it to your project

At this point, you can build the DLL. After you've done that, you'll have a DLL file in your DLL project's directory. Before you can do anything with the DLL in the calling application, you need to create an import library file for your DLL. To do so, go to a command prompt, move to your DLL project's directory, and type
implib mydll.lib mydll.dll
to 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.

Create the calling application

Now that you have an import library file, you can create the calling application. First, add the import library file to your application's project using Add To Project. Doing so will cause the DLL to be loaded when the application starts. The only step remaining is to call the function in the DLL that will show the form. That code looks like the following:
if (ShowDllFormModal(this)) 
  // do something here
else 
  // do something else
You 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.

Calling a VCL form from a non-VCL application

You've already done the work to show a VCL form from a non-VCL application. What remains is to describe some of the reasons behind what we've done so far. Remember how you declared the DLL function? It looked like this:
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.

An example is worth a thousand words

As always, an example will help to solidify the concepts presented in this article. Listings A and B display the code for a DLL that contains two forms: a regular form and an MDI child form (see the companion article, "MDI Child Forms in a DLL"). To save space, we didn't include the code for the forms, but any forms will do.

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;
//---------------------------------------------
#endif
Listing 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);  
}

Conclusion

The ability to put forms in a DLL is a powerful feature of C++Builder. As always, use the tools available wisely--don't put forms in DLLs just because you can. But when you must put your forms in a DLL, you can do so with ease, using the techniques we've discussed in this article.

 

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.