Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

20/20


SP 96: 20/20

Al, a consultant specializing in software development, training, and documentation, is the author of Steal This Code! (Addison-Wesley, 1995). You can find Al on the Web at http://ourworld.compuserve.com/homepages/Al_Williams.


What do computer programming and religion have in common? (Falling to your knees and praying to meet a deadline doesn't count.) It always strikes me that programming tools and languages develop almost religious followings. If you want to start a jihad, tell a group of programmers what text editor you use! Language choice is at least as bad. I once worked with a very bright engineer who wanted everything written in APL and WordPerfect macro language (that's all he knew, so there's no danger of him reading this). Everyone knows that Visual Basic is the answer to all programming problems. No, C++ is. Wait, Delphi works for everything. A true Tower of Babel.

Since I've survived many languages (all the way back to Fortran IV), I find the language crusades amusing. Imagine going to a hardware store and saying, "I need to drive screws, nail up a fence, and cut my grass. Should I buy a screwdriver or a hammer?" Programming is much the same. Tools work best when applied to certain problems. I've pounded a few nails with a screwdriver, but it didn't work very well. I'd never try to cut the grass with a screwdriver (if I ever cut my grass, that is).

In this installment of "20/20," I'll show you how to create DLLs using Visual Basic (VB) 4.0 and Borland's Delphi 2.0. I'll use DLLs from Delphi, VB, and (for variety) C++. This will show off several interesting features of the languages:

  • How to construct and use OLE DLLs from VB.
  • How to use OLE DLLs from Delphi and C++.
  • How to construct and use non-OLE DLLs from Delphi or use them with VB or C++.
You'll also see how to use a simple Telephony API (TAPI) call to add phone dialing to any Windows application. If you program alone, you might not care to learn multiple languages. Certainly, most languages can do many common tasks. However, for most programmers, there are several reasons you might consider mixing languages:

  • Another group in your company uses VB, but your group uses Delphi.
  • You've written code for a client in Delphi and would like to resell it to a client that uses VB.
  • You find it easier to solve a particular problem using one language.
  • You need to interface to third-party programs that support one language, but not the one you are using.
Even if you don't need to mix languages, it is interesting to see the differences between Delphi DLLs and their VB counterparts.

Lingua Franca

If you look closely at the Windows programming environment, you'll notice that DLLs are truly the universal code. Any language that can make calls to the API certainly has some facility for calling DLLs, since the Windows API is just a collection of DLLs. If you are careful, you can write DLLs that almost any program can use. The key is to use standard calling conventions and data types.

An alternative to standard DLLs is OLE. Although you might think of OLE as an end-user tool, the underlying technology provides a good way to make objects for software. You can make simple objects or full-blown controls (OCX controls).

Delphi can create and use both OLE and non-OLE DLLs. Visual Basic can use either type, but can only create OLE DLLs (unless you use third-party tools; see the text box "Conventional DLLs with VB" for more details). Which type is better? That depends. If you use Visual Basic, OLE DLLs are very easy to use. They aren't much more difficult to use in Delphi, but they are more complex than ordinary DLLs. Using ordinary DLLs in C or C++ is trivial. Using OLE DLLs in C++ is a bit daunting and requires heroic efforts in C.

The Problem

Suppose you are working with other programmers on a suite of related programs. All of the programs need access to a common phone book and they should all be able to place a voice call as part of their features. However, some of the applications are in C++, some are in VB, and still others are in Delphi. You are the only member of the team that understands TAPI (for more information on TAPI, see the accompanying text box entitled "About Assisted TAPI") and you've already written some code in Delphi to dial a number. Too bad the phone book programmer used Visual Basic.

Sure, in this case you could punt and make the dialer and phone book separate programs, but in other cases that might not be an option. Besides, there are many good reasons for integrating the programs into a single unit.

In a perfect world, everyone would work with one language, but this is our world. You'll have to glue Delphi and VB code into different programs in different languages. Obviously, you'll want to use some DLLs.

One Solution

If you want a DLL in VB, you have to go the OLE route--you don't have much choice. That means the phone book will become an OLE object in a DLL (an inproc OLE Server for you OLE people). Delphi can create either type of DLL. To keep things simple, I decided to put the phone dialing code in an ordinary DLL. Figure 1 shows the system design.

Certainly, there are other approaches. In this case, the Delphi code is very simple. You could just rewrite it. But what if it was more complex? You could also turn the Delphi code into an OLE DLL similar to the phone book. Since the programs that access the phone book already need to deal with OLE objects, that wouldn't be a very big problem. Still, ordinary DLLs are usually simpler, so I decided to stick with them where possible.

Creating the VB DLL

When you create a VB DLL, you are really writing an OLE server. The main interface to the server is a VB class object. That object may display forms and do almost anything else you need. Here are a few things you can't do:

  • Use modeless forms. If you show a form, make sure to pass vbModal as an argument to Show.
  • Use the End statement.
  • Pass or return objects that are private.
  • Read a command string via Command$.
To start the DLL project, select New Project from the File menu.
Figure 2 shows the dialog that results. On the Project tab, you'll select Sub Main as the startup form. It isn't really a form, of course, but all DLLs must begin with Sub Main. They can't have a startup form because those are modeless. You'll also want to pick OLE Server in the StartMode options. This doesn't affect your project, but it does affect the way VB starts your DLL for debugging. The purpose of a server is to supply objects to other programs. If you start the server normally, it will notice that no other programs are using its objects and will exit. When StartMode is set to OLE Server, VB will keep your DLL open so you can start the other program and begin debugging. The project name you select (PhoneBk, in the example) is part of how other applications will identify your objects.

If you like, VB can also raise a run-time error if you try to do anything that OLE DLLs don't allow. Go to the Advanced tab and select OLE DLL Restrictions. Since you selected Sub Main as the DLL's entry point, it makes sense that you'll need to create this subroutine. Insert a standard module, and create a Main subroutine (just type Sub Main() in the code window and press Enter). If you don't need any startup code, you can just leave the subroutine empty. For debugging purposes, I usually put a temporary line here to show the form I'm writing. However, you must remove that line before releasing your DLL to others.

The next step is to create a class module. It is simple to insert a class module using the Insert menu. Look at the properties for the module in Figure 3. The Name property is what other programs will use to create the object. In this case, the complete object name is PhoneBk.Phonebook. The Public property is True--without this, the object is not accessible to other programs (and therefore, not an OLE object). Finally, the Instancing property must be Create Multiuse. This allows other programs to successfully create multiple copies of the object without starting multiple instances of your DLL. You must use this option to create OLE DLLs.

Method and Madness

As with any other class module, you'll need to fill this class out with methods and properties. You can find out more about VB's class modules in my previous "20/20" column; see Dr. Dobb's Sourcebook, March/April 1996. The phone book only needs one method: GetEntry (see Listing One). This function fills in a name and phone number string for the caller. It returns True if it succeeds, and False if the information it returns in the strings is invalid. The code simply shows a modal form to collect the information and returns. The form itself (available electronically; see "Availability," page 3) is completely unremarkable. Although I could have used VB's extensive database support, I wanted to keep the example simple, so the phone book resides in a simple flat file. The name of the file is C:\PHONE.DAT by default. You can alter the file name by changing the \HKEY_CURRENT_USER\Software\VB and VBA Program Settings\PhoneBk\Database entry in the registry. Set that key's File value to the full path of the data file you want to use.

If you added a line to show the form in Sub Main you can test the DLL very easily. Just run it. When the program works to your satisfaction, remove the testing line from Sub Main, and select Create OLE DLL from the File menu. That's it. Unless, of course, you would like to distribute your DLL to another machine.

Installation Woes

If you are content to use your DLLs on your machine only, VB does all the work required to register your DLL. Without the crucial entries in the system registry, other programs can't use your DLL. There are many possible ways to register your DLL, but the simplest is to use the REGSVR32 program (found in the \TOOLS\PSS directory of your VB CD-ROM). Just run REGSVR32 and pass the name of your DLL as an argument. REGSVR32 does all the work.

Without REGSVR32, you would have to find all the registry entries related to your server (there are many) and install the same entries in the target machine's registry.

Creating the Delphi DLL

Although you can create OLE DLLs in Delphi, it is much easier to use ordinary ones. The steps are simple:

  1. Create a DLL project using the New menu item.
  2. Modify the provided code to use any units you need.
  3. Add any necessary initialization code in the unit's main section.
  4. Write any functions you want the DLL to expose. Use the export modifier. Usually, you'll want to use the stdcall modifier, too.
  5. Add an exports section and name any exported functions you wrote in step 4 in that section.

You can find an example of this in Listing Two. Since this is a DLL, Delphi doesn't automatically create your main form and associated objects. When you write ordinary programs, all of these details are generated automatically. The code in Listing Two uses Example 1 to create a form (from the dialer unit). The only other interesting feature of the dialer is that it uses TAPI to dial the phone. See "About Assisted TAPI" for a quick overview of using TAPI to manage a telephone connection.

Using the DLLs

Since the dialing DLL does not use OLE, it is very simple to use it from all three target languages. Example 2 shows the declarations required for Delphi, Visual Basic, and C++. Once you have the proper declaration, you can use the DLL call as you would any native call.

The real trick to using DLLs is matching the calling conventions and parameters. VB can only call DLLs that use the stdcall calling convention. That's why the Delphi DLL uses the stdcall modifier. Pascal and Basic use similar string types, and most languages use the same representation for integers. Be prepared to experiment if you pass other data types around.

The OLE DLL takes a little more work. How much work depends on which language you're using. In VB, using the OLE DLL doesn't take much more effort; see Example 3(a). You declare a variable of type PhoneBk.Phonebook and specify the new keyword. This creates an object that looks like an ordinary class object, but is actually an OLE DLL.

In Delphi, the procedure is nearly as straightforward. In Example 3(b), the program creates a Variant variable. Then, a call to CreateOleObject binds the OLE DLL to that variable. Armed with the variable, you can make calls to the DLL as you would any object.

The C++ program, of course, has to do the most work; see Example 3(c). This is an MFC program, so it first has to call AfxOleInit(). This sets up the OLE libraries. Later, when the program needs to load the object, it has to convert the name PhoneBk.Phonebook into a class ID (CLSID). The CLSIDFromProgID call handles that. Armed with the CLSID, the program can bind the object to a _PhoneBook variable (created with Class Wizard) using the CreateDispatch member function. Finally, the string format is different for C++, so the program calls AfxBSTR2CString to make the appropriate conversions. Whew!

Another problem with C++ is that it insists on renaming functions without your knowledge. A stdcall function has an underscore affixed to it, an "@" sign, and the number of stack bytes it needs. However, this has nothing to do with the stdcall convention (none of the API calls have this format). The only way around this is to build a dummy DEF file and use it to construct an import library that matches the DLL's entry points to the oddball C++ names. More details on the DUMMY.DEF file are available electronically. You will also find simple example programs that use the phone book and dialer DLLs. Although none of them do anything else, there is no reason they couldn't be three full-fledged programs, each written in a different language.

Other Considerations

Of course, managing a project that uses multiple languages can be a nightmare. Over the long haul, it is best to consider consolidating your code into one language. However, if you don't have a choice, it is certainly possible to glue together code from a variety of sources.

I'm certain the jihad concerning programming languages will continue for as long as there are programmers. What's my favorite language? I'll keep that to myself for now, but you can always ask me next time you see me.

Conventional DLLs with VB

Imentioned in the accompanying article that Visual Basic can only create OLE DLLs. That isn't strictly true. With Visual DLL from Simply Solutions (Santa Ana, CA, [email protected]), you can create ordinary DLLs using VB. You can use Visual DLL to write callbacks or any other ordinary DLL in VB. If you wanted to create a control-panel applet, for example, this is the ticket. Visual DLL creates a DLL shell for your VB code and automatically generates declarations for VB and a C-compatible header file.

With the ability to create ordinary DLLs with VB, you can do practically anything. Of course, performance may suffer compared to a true compiled C or Pascal program, but for many applications this isn't a major problem. There are a few quirks. For example, functions in your DLL appear as subs in VB. Each function has a parameter named Return_Value that you assign the function's return value. Of course, Visual DLL is twisting VB to do something it won't ordinarily do, so you should expect a quirk or two, but there is nothing that you couldn't become accustomed to. You can find a slide show about Visual DLL (VDLLIN.ZIP) in Simply Solutions' CompuServe forum (type GO SIMSOL).

--A.W.

About Assisted TAPI

The main point of this month's programs was to illustrate how to integrate code from different languages. The telephone example just happened to be handy. However, you may have found it surprising that dialing the phone would be so simple. Well, it wasn't always like that.

In the not-so-distant past, PC vendors noticed that hooking up a phone to a PC made it more valuable. Sure, there have been data modems for a long time, but actually using the PC to take and place calls is a relatively new idea. If you don't believe this has caught on, take a look in the Sunday paper. Try to find a major retailer selling PCs without a speakerphone/voice mail card in it. As usual, every vendor used their own unique hardware with special commands. This makes it difficult to write telephone software that works with a variety of hardware.

To help encourage this market, Microsoft introduced the Telephony API (TAPI), which serves as an abstraction layer between your program and the telephone hardware in the same way printer drivers prevent you from needing to know the specifics about every printer you might encounter. In truth, TAPI serves as several abstractions, depending on what you want to accomplish. TAPI can handle small, single-line voice modems or control sophisticated multiline PBX phone switches.

TAPI can provide your program several views of the hardware. The phone dialer uses the simplified (or assisted) TAPI services. As the name implies, these services are simple, and don't do very much. The simplest request (used by the phone dialer) is tapiRequestMakeCall. This function does exactly what we want: It places a call on the telephone.

Without assisted TAPI, you would have to do much more work: You'd need to set up a TAPI callback, obtain a telephone line, place the call, and process TAPI messages. This would still be an improvement over manually controlling the hardware.

--A.W.

Figure 1: System overview.

Figure 2: Starting an OLE DLL.

Figure 3: OLE DLL class module properties.

Example 1: Creating a form using the DialPhone procedure.

TDlg:=TDialDlg.Create(nil); { no parent window }
 .
 .
 .
if (TDlg.ShowModal=mrOK) ... { do it}

Example 2: Using an ordinary DLL. (a) Visual Basic; (b) Delphi; (c) C++.

(a)
Declare Function DialPhone Lib "dialdll.dll" (ByVal nm As String,
  ByVal num As String) As Integer

(b)
function DialPhone(n : PChar; n1 : PChar) : integer; stdcall;
  external 'dialdll.dll';

(c)
// This requires an import lib constructed in an odd way
// See dummy.def for details
extern "C" {
int _stdcall DialPhone(char const *name, char const *num);
}

Example 3: Using an OLE DLL. (a) Visual Basic; (b) Delphi; (c) C++.

(a)
Dim pb As New PhoneBk.Phonebook
If pb.GetEntry(nam, num) Then
  .
  .
  .

(b)
var
  pb:Variant;
   .
   .
   .
begin
  pb:=CreateOleObject('PhoneBk.Phonebook');
  if (pb.GetEntry(nam,num)) then
    .
    .
    .

(c)
  CString nam,num;
        BSTR bnam,bnum;
        CLSID clsid;
        _PhoneBook pbook;  // Class generated by Class Wizard
        // Init pbook
       COleException e;
       if (CLSIDFromProgID(OLESTR("phonebk.phonebook"), &clsid)
          != NOERROR)
          {
          MessageBox("Error");
          EndDialog(IDABORT);
          }
// Load DLL and attach to pbook variable
       if (!pbook.CreateDispatch(clsid, &e))
          {
          MessageBox("Error");
          EndDialog(IDABORT);
          }
        pbook.GetEntry(&bnam,&bnum);
        AfxBSTR2CString(&nam,bnam);
        AfxBSTR2CString(&num,bnum);

Listing One

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "PhoneBook"
Attribute VB_Creatable = True
Attribute VB_Exposed = True
Public Function GetEntry(ByRef nam As String, ByRef num As String) As Boolean
  Form1.Show 1
  If Form1.Result Then
    nam = Form1.Result_Name
    num = Form1.Result_Num
  End If
  GetEntry = Form1.Result
End Function

Listing Two

library dialdll;
{ Important note about DLL memory management: ShareMem must be the
  first unit in your library's USES clause AND your project's (select
  View-Project Source) USES clause if your DLL exports any procedures or
  functions that pass strings as parameters or function results. This
  applies to all strings passed to and from your DLL--even those that
  are nested in records and classes. ShareMem is the interface unit to
  the DELPHIMM.DLL shared memory manager, which must be deployed along
  with your DLL. To avoid using DELPHIMM.DLL, pass string information
  using PChar or ShortString parameters. }
uses
  SysUtils,
  Classes,
  dialer in 'dialer.pas' {DialDlg};
function tapiRequestMakeCall(num : PChar; app : PChar; name : PChar;
   comment : PChar): Integer; stdcall; external 'TAPI32' name 'tapiRequestMakeCall';
function DialPhone(Name : PChar;Num : PChar) : integer; stdcall; export; { might add auto}
var TDlg : TDialDlg;
begin
  TDlg:=TDialDlg.Create(nil);
  TDlg.Telno.Caption:=Num;
  if (TDlg.ShowModal=mrOK) then
     result:=tapiRequestMakeCall(Num,'Delphi Dialer',Name,nil)
  else
     result:=-1;
end;
exports
  DialPhone;
begin
end.
End Listings


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.