devxlogo

IDL For VB Tutorial

IDL For VB Tutorial

The Interface Definition Language, or IDL, is a language for describing collections of data types, constants, methods (functions, procedures, etc.), and groups of methods known as interfaces. This is not all IDL can describe, but its what were primarily concerned with. When you use VBs object browser to look at classes, functions, and types native to VB, or those provided by some other component, you are looking at what is commonly called type information. VB creates type information for you automatically when you create ActiveX components, but you can also create independent type information using IDL and the Microsoft IDL compiler. Using IDL in this way can enhance your programming skills and allow you to solve some pretty complex problems. The best way to get an understanding of how to write IDL, compile it to a type library, and use the type library in VB is see some real examples in a tutorial format. I believe this will give you a better understanding of this technique. To that end, I have written this article with a strong emphasis on good, meaningful, examples.

The first three examples are designed to give you needed background information. The fourth example walks you through a solution to a real problem that can be solved very effectively using this technique. Throughout the examples, I include references to articles and books that will provide you with important background information.

Requirements

To get the most out of this article, not to mention your software development efforts, you need Microsoft Visual Studio Professional. This article is based on version 6.0, but 5.0 will work fine. Here are the programs you will use if try the examples in this article:

  • VB6.EXE Visual Basic
  • MSDEV.EXE Visual C++
  • OLEVIEW.EXE OLE View Tool
  • GUIDGEN.EXE GUID Generation Tool
  • MIDL.EXE Microsoft IDL Compiler

Dont worry, you will not be using MSDEV.EXE to write C++. I think of it as a great tool that also does C++. In addition to these programs, you will probably use the MSDN Library CD to look up references included in this article. If you want to go through the first three examples a little more quickly, I have packed up the sample code into the following zip file: Misc.Zip .

Example 1: Looking Under the Hood of VB Components

Type information is the single most important element in component development, and VB hides almost every technical detail about it from the programmer. This example is designed to get you under the hood with respect to VB generated type libraries.

Example 2: Compiling and Registering a Type Library

In subsequent examples you will need to be able to compile type libraries and reference them from VB projects. In this example you will compile an IDL file using the Microsoft IDL compiler and register the resulting type library file with Windows, thereby allowing you to reference the type library in VB just like an ActiveX component.

Example 3: IDL to VB

This example will begin to show you how to write IDL by showing the effects of an IDL generated type library in VB. You will see which IDL data types translate to VB data types and which IDL method declarations translate to VB method declarations.

Example 4: IDL for a Reason

The previous examples may help you understand how to write IDL and create, register, and reference the resulting compiled type libraries, but they dont give you a reason to care. This example is designed to provide a problem and an IDL/type library solution that will show you the power of this technique.

IDL vs. ODL

Microsofts first incarnation of a type description language was ODL (Object Description Language) which you could compile using MKTYPLIB.EXE. According to MSDN articles, the differences between IDL and ODL are minor. The primary areas of difference are:

  • The syntax of typedef statements
  • Boolean data types
  • The scope of symbols used in enums
  • Support for certain base types

If you want to know more about ODL vs. IDL, the following MSDN topic should provide you more than enough information:

  • MSDN Library(+)Platform SDK(+)COM and ActiveX Object Services(+)MIDL(+)Using The MIDL Compiler(+)Generating a Type Library With MIDL

Example 1: Looking Under the Hood of VB Components

Type information is the single most important element in component development, and VB hides almost every technical detail about it from the programmer. This example is designed to get you under the hood with respect to VB generated type libraries. Here are the steps:

  1. Start by opening VB and creating a new ActiveX DLL.
  2. Change the name of the project from Project1 to EUTH.
  3. Get into the project properties and for the project description enter Example, Under The Hood.
  4. Open up the Class1 class module and change the name from Class1 to Person.
  5. Make sure the Person class instancing is set to 5 MultiUse and insert the following code:
Public Enum PersonTypeEnum
euPersonUnknown = 0
euPersonButcher = 1
euPersonBaker = 2
euPersonCandleStickMaker = 3
End Enum

Public Property Get Name() As String
End Property

Public Property Let Name(ByVal RHS As String)
End Property

Public Property Get PersonType() As PersonTypeEnum
End Property

Public Property Let PersonType(ByVal RHS As PersonTypeEnum)
End Property

Public Sub MakeFriends(ByRef WithPerson As Person)
End Sub

Public Function IsHealthy() As Boolean
End Function

Of course, there is no logic in this code. Thats because we dont care about the actual logic that makes this class work, just the definition of the public members (properties, functions, etc.) of the class.

  1. Now, make the EUTH.DLL and save the project files so you can experiment with them later.

When you compile your ActiveX component, VB creates a binary file of type information and includes this file in the component as a resource. This type library is what you see when you reference a component in another project and view it with the Object Browser.

  1. Next, close VB and open Visual C++. Ill provide a little more guidance here, in case you’re not familiar with VC++.
  2. Chose File.Open on the pull down menus, select the directory where EUTH.dll resides, change the Files of type: selection to Executable Files , and change the Open as: selection to Resources. Your dialog should look like this:

  1. Press Open to view the EUTH.dll resources. You may get a message box warning you that you cannot save resource back to the executable, just click OK. VC++ will open a window for you to view the resources compiled into the DLL.
  2. Expand the TYPELIB folder. Your window should look similar to this:

Notice the icon under the TYPELIB folder. This icon tells you that the resource is a binary file. You can use the VB Resource Editor add-in to include files like icons, cursors, wave file, etc., to your components.

  1. Now right mouse click on the 1 [Neutral] resource and select Export.
  2. Name the file EUTH.TLB and save it in the folder containing the EUTH.dll.

Now youve extracted a copy of the type library from the component. So what? You didnt have to extract the binary file at all. Instead you could have let VB create an independent type library for you by checking Remote Server Files in the Project Properties dialog. Well, now you know two methods of getting an independent type library from a VB component. There is also a third way:

  1. Under Visual Studio tools in the Windows task bar, find and execute OLE View.
  2. Select the File pull down menu and select View TypeLib.
  3. Next you will see the common Windows file dialog. Locate and Open EUTH.dll.

With a few mouse click you can explore what seems to be the contents of the type library. This is really just another representation of the type library. The real content is binary. The right hand pane contains decompiled IDL code representing the type information of the type library resource inside EUTH.dll. This text can be saved to an ASCII file and compiled into a type library using MIDL.EXE.

You will notice, if you spend some time exploring the contents using OLE View, that the information presented contains some similarities to the VB project from which it came. Type libraries supply information about components to the world of COM for two primary reasons: to help developers reuse components and to help compilers create client applications that properly address components. Unfortunately, I have heard too many VB programmers talk about creating COM components yet they dont know what a vtable is, cannot adequately describe COM mechanisms, and dont know what operating system components provide the essential services of COM. The best way to keep from looking like a techno-poser is to study hard and solidify your knowledge with lots of experimentation. Here are some references to start with:

  • MSDN Library(+)Platform SDK(+)COM and ActiveX Object Services(+)COM MSDN
  • Library(+)Platform SDK(+)COM and ActiveX Object Services(+)MIDL
  • Programming Distributed Applications with COM and Microsoft Visual Basic 6.0, Ted Pattison Microsoft Press

Example 2: Compiling and Registering a Type Library

In subsequent examples you will need to be able to compile type libraries and reference them from VB projects. In this example you will compile an IDL file using the Microsoft IDL compiler and register the resulting type library file with Windows, thereby allowing you to reference the type library in VB just like an ActiveX component. But first, you may require some essential background information. The MIDL compiler is a DOS based application. Because of this, you must make sure MIDL is running in a DOS shell with the proper environment variables set. MIDL must be able to locate additional files it requires to compile your IDL code. If your experience with DOS is limited this can be troublesome depending which Windows operating system your using and how you installed Visual Studio. So, the first few steps in this example are the steps I used to get my Windows 98 system ready to compile IDL files. If youre running Windows NT and you have properly installed the C++ portion of Visual Studio you will probably be able to go straight to step 5.

  1. Locate a file with the name VCVARS32.BAT, normally located in C:Program FilesMicrosoft Visual StudioVC98Bin. Visual Studio will have already included this file during installation. The contents of the batch file should look like this:
@echo off
rem
rem Root of Visual Developer Studio Common files.
set VSCommonDir=C:PROGRA~1MICROS~4COMMON

rem
rem Root of Visual Developer Studio installed files.
rem
set MSDevDir=C:PROGRA~1MICROS~4COMMONmsdev98
rem
rem Root of Visual C++ installed files.
rem
set MSVCDir=C:PROGRA~1MICROS~4VC98

rem
rem VcOsDir is used to help create either a Windows 95 or Windows NT specific path.
rem
set VcOsDir=WIN95
if "%OS%" == "Windows_NT" set VcOsDir=WINNT

rem
echo Setting environment for using Microsoft Visual C++ tools.
rem

if "%OS%" == "Windows_NT" set PATH=%MSDevDir%BIN;%MSVCDir%BIN;%VSCommonDir%TOOLS\%VcOsDir%;%VSCommonDir%TOOLS;%PATH%
if "%OS%" == "" set PATH="%MSDevDir%BIN";"%MSVCDir%BIN";"%VSCommonDir%TOOLS\%VcOsDir%";"%VSCommonDir%TOOLS";"%windir%SYSTEM";"%PATH%"
set INCLUDE=%MSVCDir%ATLINCLUDE;%MSVCDir%INCLUDE;%MSVCDir%MFCINCLUDE;%INCLUDE%
set LIB=%MSVCDir%LIB;%MSVCDir%MFCLIB;%LIB%

set VcOsDir=
set VSCommonDir=

  1. Next create a shortcut on your desktop to COMMAND.COM. Name the shortcut Go MIDL.
  2. Right mouse click over the shortcut and select to view the shortcuts properties. Select the Program tab and set the properties as follows: Cmd line: C:WINDOWSCOMMAND.COM Working: “C:Program FilesMicrosoft Visual StudioVC98” Batch file: BINVCVARS32.BAT
  3. Now select the Memory tab and make sure the Initial environment: property is set to at least 2048.

When you click on this shortcut, you will be in a DOS shell that is properly set for compiling IDL files. The shortcut will run the VCVARS32.BAT file for you. This properly initializes the DOS environment variables for the MIDL compiler.

  1. Next, copy the following IDL code, paste it into Notepad, and save the file as EX2.IDL:
// TypeLib : EX2.IDL (EX2)
// TargetFile : EX2.TLB
//
// Version 1.0
// - Author : Philip G. Fucich
// - Completed: 01/01/2000
// - Notes : Example 2 type library.

// UUID Usage:
// {C4408B80-EFC0-11D3-A88C-81F2B32E4671} : library EX2
// {C4408B82-EFC0-11D3-A88C-81F2B32E4671} : typedef exSampleEnum
// {C4408B84-EFC0-11D3-A88C-81F2B32E4671} : interface Box

[
uuid(C4408B80-EFC0-11D3-A88C-81F2B32E4671),
version(1.0),
helpstring("Example 2 Library")
]
library EX2
{
importlib("STDOLE2.TLB");

typedef
[
uuid(C4408B82-EFC0-11D3-A88C-81F2B32E4671),
helpstring("Sample enum declaration."),
v1_enum,
version(1.0)
]
enum exSampleEnum {
[helpstring("Helpstring for exDefault.")]
exDefault = 0,
exOne = 1,
exTwo = 2,
} exSampleEnum ;

[
uuid(C4408B84-EFC0-11D3-A88C-81F2B32E4671),
helpstring("A Box."),
nonextensible,
oleautomation,
]
interface Box : IUnknown {
[helpstring("Box height.")]
[propget] HRESULT Height ([out, retval] long* PropData );
[helpstring("Box width.")]
[propget] HRESULT Width ([out, retval] long* PropData );
[helpstring("Box depth.")]
[propget] HRESULT Depth ([out, retval] long* PropData );
};

}
  1. Copy or move EX2.IDL to the following directory:
C:Program FilesMicrosoft Visual StudioVC98
  1. Now click on your Go MIDL shortcut.
  2. In the DOS Window, type the following command:
binmidl EX2.IDL

If all has gone well up to this point, you will have an EX2.TLB file. This file cannot be referenced in a VB project until it has been registered with Windows. The next steps will cover how to register your type library using some simple VB code.

  1. Move the EX2.IDL and EX2.TLB files to a directory of your choice. For this example, Ill say its in C:Example2.
  2. Open VB and create a new Standard EXE project.
  3. Go the project references dialog and add a reference to TypeLib Information as shown here:

  1. Click OK to return the to Form1 and add a command button to the form.
  2. Double click on the command button and add the following code to the Command1_Click() event procedure:
Dim o_TypeLib As TypeLibInfo

Set o_TypeLib = TLIApplication.TypeLibInfoFromFile("C:Example2EX2.TLB")

o_TypeLib.Register

MsgBox "The TypeLibrary is registered."
  1. Now, run the project and click the Command1 button.

The EX2.TLB will now be registered with Windows and thereby ready to reference in a VB project. To check out the EX2.TLB, open a new Standard EXE project, go to the project references dialog, and add it as a reference. You will find it listed as Example 2 Library. When youve added the reference, open the VB Object Browser and select EX2 in the Project/Library combo box. Your Object Browser will look something like this:

Example 3: IDL to VB

This example will begin to show you how to write IDL by showing the effects of an IDL generated type library in VB. You will see which IDL data types translate to VB data types and which IDL method declarations translate to VB method declarations. This is the meat and potatoes of examples. Therefore, I will give much more information here than in the previous examples. The best place to start is with the IDL itself. Here is the entire example IDL:

// TypeLib : EX3.IDL (EX3)
// TargetFile : EX3.TLB
//
// Version 1.0
// - Author : Philip G. Fucich
// - Completed: 01/01/2000
// - Notes : Types and interfaces for use in VB.

// UUID Usage:
// {3D6C1720-F527-11D3-A88C-ECBD3B289C70} : library EX3
// {3D6C1722-F527-11D3-A88C-ECBD3B289C70} : typedef SampleEnum
// {3D6C1724-F527-11D3-A88C-ECBD3B289C70} : typedef SampleUDT
// {3D6C1726-F527-11D3-A88C-ECBD3B289C70} : interface AllTypes
// {3D6C1728-F527-11D3-A88C-ECBD3B289C70} : interface AllMethods
// {3D6C172A-F527-11D3-A88C-ECBD3B289C70} : module Constants

[
uuid(3D6C1720-F527-11D3-A88C-ECBD3B289C70),
version(1.0),
helpstring("Example 3 Library")
]
library EX3
{
importlib("STDOLE2.TLB");

interface AllTypes;
interface AllMethods;

typedef
[
  uuid(3D6C1722-F527-11D3-A88C-ECBD3B289C70),
  helpstring("Sample enum declaration."),
  v1_enum,
  version(1.0)
]
  enum SampleEnum {
    [helpstring("Helpstring for seDefault.")]
    seDefault = 0,
    seOne = 1,
    seTwo = 2,
    [helpstring("Enum constant using hex notation.")]
    seHex7F000000 = 0x7F000000,
    seHex7F001001 = 0x7F001001
} SampleEnum;

typedef
[
  uuid(3D6C1724-F527-11D3-A88C-ECBD3B289C70),
  version(1.0)
]
struct SampleUDT {
  VARIANT_BOOL   Boolean;
  unsigned char  Byte;
  CURRENCY       Currency;
  SampleEnum     CustEnum;
  AllMethods*    CustObj;
  DATE           Date;
  double         Double;
  short          Integer;
  long           Long;
  IDispatch*     Object;
  float          Single;
  BSTR           String;
  VARIANT        Variant;
  [helpstring("Array of Variants.")]
  SAFEARRAY(VARIANT) Array;
} SampleUDT;

[
  uuid(3D6C1726-F527-11D3-A88C-ECBD3B289C70),
  helpstring("Example interface showing VB compatible data types."),
  nonextensible,
  oleautomation,
]
interface AllTypes : IUnknown {
  HRESULT TheObject ([out, retval] IDispatch** FuncData );
  [helpstring("Helpstring for TheVariant function.")]
  HRESULT TheVariant ([out, retval] VARIANT* FuncData );
  HRESULT TheBoolean ([out, retval] VARIANT_BOOL* FuncData );
  HRESULT TheByte ([out, retval] unsigned char* FuncData );
  HRESULT TheCurrency ([out, retval] CURRENCY* FuncData );
  HRESULT TheDate ([out, retval] DATE* FuncData );
  HRESULT TheDouble ([out, retval] double* FuncData );
  HRESULT TheSingle ([out, retval] float* FuncData );
  HRESULT TheLong ([out, retval] long* FuncData );
  HRESULT TheInteger ([out, retval] short* FuncData );
  HRESULT TheString ([out, retval] BSTR* FuncData );
  HRESULT TheCustomObj ([out, retval] AllMethods** FuncData );
  HRESULT TheCustomEnum([out, retval] SampleEnum* FuncData );
  HRESULT TheCustomUDT ([out, retval] SampleUDT* FuncData );
};

[
  uuid(3D6C1728-F527-11D3-A88C-ECBD3B289C70),
  helpstring("Example interface showing VB equivalent methods and parameter passing."),
  nonextensible,
  oleautomation,
]
  interface AllMethods : IUnknown {
    [propget] HRESULT TheProperty([out, retval] VARIANT* PropData);
    [propput] HRESULT TheProperty([in] VARIANT PropData);
    [propputref] HRESULT TheProperty([in] VARIANT PropData);
    HRESULT TheFunction([out, retval] VARIANT* FuncData);
    HRESULT TheSub();
    HRESULT TheByValParams([in] long ByValLong,
         [in,
          optional] long OptByValLong,
         [in, optional,
          defaultvalue(2)] long OptDef2ByValLong );
    HRESULT TheByRefParams([in, out] long* ByRefLong,
         [in, out,
          optional] long* OptByRefLong,
         [in, out,
          optional,
          defaultvalue(4)] long* OptDef4ByRefLong );
    [vararg] HRESULT TheParamArray
        ([in, out] SAFEARRAY(VARIANT)* VntArgs );
};

[
  uuid(3D6C172A-F527-11D3-A88C-ECBD3B289C70),
  helpstring("Example set of constants that can be used in VB.")
]
  module Constants {
    [helpstring("Equivalent of vbCrLf. Uses cryptic C escape characters.")]
    const LPSTR seCrLf = "
";

    const LPSTR seHello = "Hello World!.";

    [helpstring("Here's a Long integer.")]
    const long seZero = 0;
};

}

This might seem a little overwhelming, so lets go through the code step by step.

  1. The first part is a group of comments I added to help document the IDL. All comments are started with the // characters or surrounded by the /* and */ characters:
// TypeLib : EX3.IDL (EX3)
// TargetFile : EX3.TLB
//
// Version 1.0
// - Author : Philip G. Fucich
// - Completed: 01/01/2000
// - Notes : Types and interfaces for use in VB.

// UUID Usage:
// {3D6C1720-F527-11D3-A88C-ECBD3B289C70} : library EX3
// {3D6C1722-F527-11D3-A88C-ECBD3B289C70} : typedef SampleEnum
// {3D6C1724-F527-11D3-A88C-ECBD3B289C70} : typedef SampleUDT
// {3D6C1726-F527-11D3-A88C-ECBD3B289C70} : interface AllTypes
// {3D6C1728-F527-11D3-A88C-ECBD3B289C70} : interface AllMethods
// {3D6C172A-F527-11D3-A88C-ECBD3B289C70} : module Constants

Notice the UUID Usage: section I included here. The library, typedef, interface, and module type definitions require a UUID (Universally Unique Identifier) a.k.a. GUID (Globally Unique Identifier). This is where the Visual Studio tool, GUIDGEN.EXE, comes to the rescue. All you do is run GUIDGEN.EXE and click the Copy button. Then you paste the UUID right into the IDL. If you need more than one, just copy it and increment first hex number group as I have done here. You must consider each UUID you use as sacred. If you use a set of UUIDs in a type library you must never use them ever again. If you did, they wouldnt be unique. One could write an entire article on the subject of UUID use, extending interface definitions, and other binary compatibility issues, but this information already exists in print. Some of the best technical information about UUIDs and other COM issues can be found in the following references:

  • Programming Distributed Applications with COM and Microsoft Visual Basic 6.0, Ted Pattison, Microsoft Press
  • MSDN Library(+)Platform SDK(+)COM and ActiveX Object Services(+)COM
  • MSDN Library(+)Platform SDK(+)COM and ActiveX Object Services(+)Automation
  1. The next part of the IDL is the library statement which tells the MIDL compiler to create a type library:
[
  uuid(3D6C1720-F527-11D3-A88C-ECBD3B289C70),
  version(1.0),
  helpstring("Example 3 Library")
]
library EX3
{
  // lots of stuff in the middle then..
}

You can see I have used the first UUID and included a helpstring for the library structure. The code in brackets, [], is called an attribute list. When you compile the IDL and view the resulting type library in VBs object browser, youll be able to see the help string, the version, and the EX3 library name. All the IDL from here on out will be inside the library structure (i.e., between the braces, {}).

  1. The next statement is an importlib statement:
importlib("STDOLE2.TLB");

This allows one type library to reference types of another (included here just as an example).

  1. Next come a couple of forward declarations. This is concept foreign to VB programmers. Just remember that the MIDL compiler parses the IDL code from top to bottom so if you reference a type that has not yet been declared youll get an error. Its best to always create forward declarations for all your interfaces and put all typedef definitions in order at the top.
interface AllTypes;
interface AllMethods;

  1. After the forward declarations, I include my typedef definitions. This first one in the sample IDL is a typedef enum which is like a VB enum:
typedef
[
  uuid(3D6C1722-F527-11D3-A88C-ECBD3B289C70),
  helpstring("Sample enum declaration."),
  v1_enum,
  version(1.0)
]
enum SampleEnum {
  [helpstring("Helpstring for seDefault.")]
  seDefault = 0,
  seOne = 1,
  seTwo = 2,
  [helpstring("Enum constant using hex notation.")]
  seHex7F000000 = 0x7F000000,
  seHex7F001001 = 0x7F001001
} SampleEnum;

Use the v1_enum attribute for all enumeration definitions. This makes them equivalent to VB enumeration definitions. Also notice that you can include a help string attribute for each constant in the enumeration.

  1. Next, I included a typedef struct definition. This is like a VB type statement.
typedef
[
  uuid(3D6C1724-F527-11D3-A88C-ECBD3B289C70),
  version(1.0)
]
struct SampleUDT {
  VARIANT_BOOL  Boolean;
  unsigned char Byte;
  CURRENCY      Currency;
  SampleEnum    CustEnum;
  AllMethods*   CustObj;
  DATE          Date;
  double        Double;
  short         Integer;
  long          Long;
  IDispatch*    Object;
  float         Single;
  BSTR          String;
  VARIANT       Variant;
  [helpstring("Array of Variants.")]
  SAFEARRAY(VARIANT) Array;
} SampleUDT;

In IDL the type of a variable is given before the name of the variable. For example, the first member of this structure is named Boolean and its type is VARIANT_BOOL. Of course, this is the opposite of VB declarations. The * character denotes that a variable is a pointer. You cannot use the * anywhere you want and expect your declarations to be usable in VB. The pointers here are for object type elements and will be acceptable to VB. You might be wondering if VB can handle this type definition because the some of the members are named using VB keywords. You can create a VB type definition equivalent to this one except for the Array element. In VB, this would be written, Array() As Variant, and would be illegal. One of the positive aspects of IDL is that you can name methods and type members with VB keywords and they will work just fine.

  1. Now we come to the interfaces. An interface is exactly like a VB class but has no implementation, which is like saying there is no working code for any of the methods. After we create the type library, we will create a VB class to implement all the methods of the interface. Interface definitions can vary widely depending on their intended use. Because we want to use interfaces in VB, all our interfaces will include the oleautomation and nonextensible attributes and will be derived from IUnknown. The references I included above will help you gain an understanding of what this means. Lets take a closer look at the first interface I included in the sample IDL:
[
  uuid(3D6C1726-F527-11D3-A88C-ECBD3B289C70),
  helpstring("Example interface showing VB compatible data types."),
  nonextensible,
  oleautomation,
]
interface AllTypes : IUnknown {
  HRESULT TheObject ([out, retval] IDispatch** FuncData );
  [helpstring("Helpstring for TheVariant function.")]
  HRESULT TheVariant ([out, retval] VARIANT* FuncData );
  HRESULT TheBoolean ([out, retval] VARIANT_BOOL* FuncData );
  HRESULT TheByte ([out, retval] unsigned char* FuncData );
  HRESULT TheCurrency ([out, retval] CURRENCY* FuncData );
  HRESULT TheDate ([out, retval] DATE* FuncData );
  HRESULT TheDouble ([out, retval] double* FuncData );
  HRESULT TheSingle ([out, retval] float* FuncData );
  HRESULT TheLong ([out, retval] long* FuncData );
  HRESULT TheInteger ([out, retval] short* FuncData );
  HRESULT TheString ([out, retval] BSTR* FuncData );
  HRESULT TheCustomObj ([out, retval] AllMethods** FuncData );
  HRESULT TheCustomEnum([out, retval] SampleEnum* FuncData );
  HRESULT TheCustomUDT ([out, retval] SampleUDT* FuncData );
};

The name of this interface is AllTypes and it contains function declarations only. Each function declared here returns two variables. The first is called an HRESULT, which is a specially defined long integer designed to signal the success or failure of any COM method call. The other is the return type parameter marked with the [out, retval] attributes. The return types here are given the name FuncData. With functions and property get procedures, the return type is named in IDL but the name will not be visible anywhere in VB.

  1. The second interface definition in the sample IDL is designed to show different types of method declarations and parameter passing acceptable to VB:
[
  uuid(3D6C1728-F527-11D3-A88C-ECBD3B289C70),
  helpstring("Example interface showing VB equivalent methods and parameter passing."),
  nonextensible,
  oleautomation,
]
interface AllMethods : IUnknown {
  [propget] HRESULT TheProperty([out, retval] VARIANT* PropData);
  [propput] HRESULT TheProperty([in] VARIANT PropData);
  [propputref] HRESULT TheProperty([in] VARIANT PropData);
  HRESULT TheFunction([out, retval] VARIANT* FuncData);
  HRESULT TheSub();
  HRESULT TheByValParams([in] long ByValLong,
          [in,
           optional] long OptByValLong,
          [in, optional,
           defaultvalue(2)] long OptDef2ByValLong );
  HRESULT TheByRefParams([in, out] long* ByRefLong,
          [in, out,
           optional] long* OptByRefLong,
          [in, out,
           optional,
           defaultvalue(4)] long* OptDef4ByRefLong );
  [vararg] HRESULT TheParamArray
         ([in, out] SAFEARRAY(VARIANT)* VntArgs );
};

All the IDL given here will make much more sense when you see what happens in VB when you implement the methods of these interfaces and use the declared typedefs. Notice here that all methods return the HRESULT. This is because all COM methods are essentially functions. Of course, VB hides this from you. In VB, all these methods will take on their familiar forms and thats all we care about. The propget, propput, and propputref attributes are used to mark a method as a property get, property let, or property set method respectively. The vararg attribute marks a method as having a variable number of arguments for the last parameter of the method. Youll notice that parameters are primarily marked as [in], [in, out], or [out, retval]. These correspond to VB in the following way:

[in] = ByVal
[in, out] = ByRef
[out, retval] = (Return type of function or property get)
  1. The last declaration in the sample IDL is the module. If you get really good with IDL, youll find that you can use module declarations to define Windows API function calls. This will help you avoid writing those pesky declare statements in VB. The module declaration included here is just for creating various constants in a type library:
[
  uuid(3D6C172A-F527-11D3-A88C-ECBD3B289C70),
  helpstring("Example set of constants that can be used in VB.")
]
module Constants {
  [helpstring("Equivalent of vbCrLf. Uses cryptic C escape characters.")]
  const LPSTR seCrLf = "
";

  const LPSTR seHello = "Hello World!.";

  [helpstring("Here's a Long integer.")]
  const long seZero = 0;
};

To understand these declarations, look up the keywords in MSDN Library:

  • MSDN Library(+)Platform SDK(+)COM and ActiveX Object Services(+)MIDL

To see how this all translates to VB complete the following steps:

  1. Copy the IDL to Notepad and save the file as EX3.IDL.
  2. Compile the IDL using the steps in Example 2.
  3. Register the resulting type library with Windows using the steps in Example 2.
  4. Create a new ActiveX DLL project in VB, change the name of the project from Project1 to EX3Server, and reference the Example 3 Library in the project.
  5. Create a class called CAllTypes and include the following line at the top of the class module:
Implements EX3.AllTypes
  1. Implement all the methods of the AllTypes interface as you see fit.
  2. Create a class called CAllMethods and include the following line at the top of the class module:
Implements EX3.AllMethods
  1. Implement all the methods of the AllMethods interface as you see fit.
  2. Run the ActiveX DLL project in VB.
  3. Open another instance of VB, create a new Standard EXE project, and reference the Example 3 Library in the project.
  4. On Form1 of the project add a command button.
  5. In the Command1 click event, add the following code to test out the ActiveX DLL classes and their methods:
Dim p_AllTypes As EX3.AllTypes
Dim p_AllMethods As EX3.AllMethods

Set p_AllTypes = CreateObject("EX3Server.CAllTypes")
Set p_AllMethods = CreateObject("EX3Server.CAllMethods")

Debug.Print p_AllTypes.TheBoolean
Debug.Print p_AllTypes.TheByte
Debug.Print p_AllTypes.TheCurrency
  1. Set a break point on the Command1 click event, run the Standard EXE project, and step through the code.

Also, open the Object Browser in VB to see how all the declarations have translated to VB.

Here, again, are all the references Ive mentioned throughout this example. These references will provide you with important background that is too lengthy to include here:

  • Programming Distributed Applications with COM and Microsoft Visual Basic 6.0, Ted Pattison, Microsoft Press
  • MSDN Library(+)Platform SDK(+)COM and ActiveX Object Services(+)COM
  • MSDN Library(+)Platform SDK(+)COM and ActiveX Object Services(+)Automation
  • MSDN Library(+)Platform SDK(+)COM and ActiveX Object Services(+)MIDL

Example 4: IDL for a Reason

The previous examples may help you understand how to write IDL and create, register, and reference the resulting compiled type libraries, but they dont give you a reason to care. This example is designed to provide a problem and an IDL/type library solution that will show you the power of this technique.

The Problem

Our team has been tasked with creating an application where large portions of functional requirements remain undefined. The team has sufficient business information to complete portions of the final system, and we have determined that many parts of the final system can be delivered independent of the others. The team has come to the consensus that we must build a shell application that will allow pieces of the system to plug in as they are completed and installed. This is similar to the concept of VB add-ins. The shell application will consist of a simple toolbar that will provide access to the individual plug-in components. The components (the subsystem DLLs) will hold all the forms and user interface functionality of a single subsystem. Here is a diagram depicting the idea:

This means our development will differ from normal VB application development. Under normal circumstances, we create DLLs to help with common functionality across all subsystems and the final application project contains all the user interface functionality. In addition, the DLLs are referenced by the application project and are included as a part of the final installation of the application. If we forgot to include a DLL in the installation, the system would crash. In our toolbar scenario, we want the DLLs to take on entire subsystem functionality and we want the finished application executable to work even if the DLLs are not there or are installed in the future. In other words, we need to create forward compatibility. This is the real programming problem we must overcome and we will solve this problem using a design pattern called Facade. In addition, we will create a type library to enforce our desired solution across the entire development effort and to ensure forward compatibility.

The Solution

Our solution rests on a simple capability VB possesses. Using the CreateObject() function we can dynamically create an instance of an object from another ActiveX component without actually referencing this ActiveX component in the client project. The code looks like this:

Dim p_objSubsystem As Object

Set p_objSubsystem = CreateObject(progid)

p_objSubsystem.DoSomething

The progid, short for programmatic identifier, is the human readable name for the object class were trying to create (like Word.Document). We want our toolbar application to get programmatic identifiers from a file or a database when it starts up. This will give us the flexibility to change which subsystems the toolbar loads. The drawback to the code above is its use of late binding (i.e., the Object data type). Late binding does not allow us to enforce a specific design. What we really want to do is something similar to this:

Public Function GetAddIn(ByVal strProgID As String) As PIL.PlugIn
   Dim p_objTest As Object

   On Error GoTo CreateError
   Set p_objTest = CreateObject(strProgID) 'attempt to create object

   If TypeOf p_objTest Is PIL.PlugIn Then 'test for correct interface
   Set GetAddIn = p_objTest
   End If

ExitFunc:
   Set p_objTest = Nothing 'clean up object variable
CreateError:
   Resume ExitFunc 'exit gracefully if the progid is bad
End Function 'GetAddIn

The name PIL in this example is be the name of a type library we would create for all the independent developers to reference. Each subsystem DLL would contain a class that implements the PlugIn interface from the PIL library. Here is the IDL for the PlugIn interface:

[
  uuid(9771F9AA-CB8E-11D3-A88C-EFEDC0026670),
  helpstring("Each Plug-In component must contain one class that implements the PlugIn interface."),
  nonextensible,
  oleautomation,
]
interface PlugIn : IUnknown {
  [helpstring("The PlugIn host will call Connect passing an App object. The PlugIn may keep references to the PlugIn provided the PlugIn destroys all App references when Disconnect is called.")]
  HRESULT Connect([in] App* HostApp );
  [helpstring("The PlugIn host will call Disconnect when the application must shut down. The PlugIn must destroy all references to the App object passed in the Connect method.")]
  HRESULT Disconnect();
  [helpstring("The PlugIn host will call Execute when the user has requested the services of the PlugIn object.")]
  HRESULT Execute ();
  [helpstring("Returns either Nothing or the Icon which represents the services of the PlugIn. Icon is displayed, if available, as a menu option to the user along with the Name.")]
  [propget] HRESULT Icon ([out, retval] IDispatch** PropData );
  [helpstring("Returns the display Name of the PlugIn. The PlugIn host creates a menu selection for the PlugIn using Name and Icon.")]
  [propget] HRESULT Name ([out, retval] BSTR* PropData );
};

The PlugIn interface is really a contract that details how future components can plug in to a preexisting toolbar application. In the Connect method youll notice an App object is passed in (App*). Here is the IDL for the App interface:

[
  uuid(9771F9AB-CB8E-11D3-A88C-EFEDC0026670),
  helpstring("The hosting application provides services to any PlugIn through the App interface."),
  nonextensible,
  oleautomation,
]
interface App : IUnknown {
  [helpstring("Returns the file path of the application.")]
  [propget] HRESULT Path([out, retval] BSTR* PropData );
  [helpstring("Displays application errors in an application standard error dialog.")]
  HRESULT ShowError([in] long ErrNumber,
                    [in] BSTR ErrMessage,
                    [in, optional, defaultvalue("")] BSTR ErrSource);
};

The App interface represents a contract detailing the services provided by the application toolbar to the individual plug in subsystems. This means the App interface will be implemented by a class in the toolbar application and an instance of this class will be passed to each subsystem component that connects to the application executable.

Design Patterns

Both the App and PlugIn interfaces are examples of the Facade design pattern. Design patterns are abstract models of objects and interfaces proven to solve specific problems. The Facade design pattern is used to simplify access to subsystems. Some information on design patterns can be found online or in magazine articles, but to get the definitive source on design patterns, I strongly suggest purchasing the following book:

  • Design Patterns: Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides; Addison-Wesley

I consider this book the most important object-oriented programming reference Ive ever read. The book details twenty-three primary design patterns and includes information on the types problems solved by each. The most difficult aspect of learning about design patterns is that the patterns and the problems they solve are both very abstract. This abstract nature, however, means that each pattern is really a solution to a broad category of problems. The Facade pattern is fairly simple and the problem it addresses is not overly complex. Our solution is made more elegant through the use of an independent type library and the VB CreateObject function. Because our various VB projects do not reference each other specifically, we have the flexibility to change any subsystem component, or the toolbar executable, at any time so long as each new part adheres to the appropriate type library interface.

The Final System

The final example system cannot adequately be provided by code samples. So, in order for you to finish, I have packaged the complete example plug in system code for you. Just download in the following file: PIExample.Zip . Youll find these files in PIExample.Zip:

PIL.idlPlug In Library IDL
PIToolbar.vbpPlug In Toolbar VB Project
frmPIToolbar.frmPlug In Toolbar Form
frmPIToolbarErr.frmPlug In Toolbar Error Display Form
CToolbarApp.clsPlug In App Class (implements PIL.App)
modPIToolbar.basPlug In Toolbar Module
PlugIns.lstPlug In List Text File
PIOne.vbpSample Plug In One VB Project
frmPIOne.frmSample Plug In One Form
PlugInOne.clsSample Plug In One Class (implements PIL.PlugIn)
modPIOne.basSample Plug In One Module
PITwo.vbpSample Plug In Two VB Project
frmPITwo.frmSample Plug In Two Form
PlugInTwo.clsSample Plug In Two Class (implements PIL.PlugIn)
modPITwo.basSample Plug In Two Module

Once youve downloaded an unpacked the example files, perform the following steps:

  1. Compile the PIL.idl file to a type library using the steps outlined in Example 2.
  2. Register the PIL.TLB file with Windows using the steps outlined in Example 2.
  3. Compile the PIToolbar.vbp project using VB and run the resulting PIToolbar.exe.
  4. The toolbar application will tell you it was unable to load the PIOne and PITwo plug in components. The toolbar does this because the PlugIns.lst file contains programmatic identifiers for both sample plug in components.
  5. Compile the PIOne.vbp project using VB and run the PIToolbar.exe. The toolbar application will tell you it was unable to load the PITwo plug in component only.
  6. Compile the remaining PITwo.vbp project and run the PIToolbar.exe.
  7. To see the code in action, load all the VB projects in separate instances of VB and set a break point on the Sub Main procedure of the modPIToolbar.bas module.
  8. Start the two plug in components and finally run the toolbar project. Step through the code an watch the toolbar connect to each plug in.

There you have it. This example is really only a minor display of how design patterns can provide elegant and flexible solutions to complex problems.

Conclusions

Here are some advantages to using IDL created type libraries:

  1. It forces you to separate your design from its implementation. This is a subtle but powerful technique.  The enterprise edition allows you to create separate type libraries files for your components, but these type libraries are very limited in what they can express and their purpose is focused on remote access of components.
  2. You can precisely control the specification of your components classes and make major design changes without the headaches associated with VB binary and project compatibility settings. In some cases you can get away with just setting a DLL project to No Compatibility and still have it work with existing EXEs.
  3. Because VB always includes a type library in your DLL, your components are vulnerable to abuse and misuse by VBA hacks.  If you’re creating DLL’s for in-process use only, you don’t need to distribute your type libraries with your application.  This virtually assures the security of your DLL’s.
  4. You can use VB keywords as names for properties or methods.  You can also include helpstrings for members of the type library where VB does not provide support (i.e., helpstrings for enumerations).
  5. You can make applications that are more flexible and independent from the various DLLs you use to put them together.
  6. You can include string and numeric constants in a type library and if you dont use them in a particular project that references the type library, they do not consume resources in the final compiled program.

Here are some disadvantages to using IDL created type libraries:

  1. You have to learn a new language and have a deeper understanding of COM.
  2. You can easily create type information in IDL that is unusable in Visual Basic.
  3. You must be more careful when you make changes to your IDL and generate a new library. You can easily break compatibility and cause existing applications to crash if youre not careful.

This technique alone does not solve any earth shattering problems. However, when you couple the use of IDL created type libraries with design patterns, you will probably find this kind of programming power irresistible. In my experience, I have found the advantages far outweigh the disadvantages.


devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist