Article 2. Libraries Made Too Easy

Bruce McKinney

April 18, 1996

Microsoft® Visual Basic® 4.0 may have come as kind of a shock to those poor unfortunates who wrote large dynamic-link libraries (DLLs) to enhance Visual Basic 3.0. Although Microsoft promised compatibility with old Basic programs (and for the most part came through), it never made any promises about compatibility of DLLs. Rather than risk breaking every control ever written, Microsoft provided a compatibility layer for running VBX controls in 16-bit Visual Basic 4.0. But it provided no compatibility layer for DLLs that weren't controls. And of course, it would have been pretty difficult to make 16-bit DLLs or controls very compatible with their 32-bit counterparts.

So this article (second of five in a series) will start at ground zero. I assume that you have never written a DLL for Visual Basic. If you managed to figure out the instructions in the VB4DLL.TXT file (supplied with version 4.0) and create a DLL (despite the lack of sample code), humor me. Forget everything you learned and start over with me. You can skim if you think you already know it, but I'm going to be introducing some topics that will be very important in later articles, so skip this one at your peril.

The Three Levels of DLL Programming

There are several different ways to add procedures written in C++ (or some other compiled language) to your Visual Basic programs. Following are three ways that I discuss in some depth in this series.

This chapter tells how to write DLLs at level 2. The part of level 1 that is a subset of level 2 is also covered. Most of what you learn here will carry over when you start writing level 3 objects and libraries.

The Mechanics of Building DLLs

We have to wade through some tedious mechanical issues before we can get to the fun of real programming. I'll use my Visual Basic Utilities DLL as an illustration.

The VBUTIL DLL was provided in 16- and 32-bit formats with my book Hardcore Visual Basic. It contained various functions for fiddling bits, making Win32® functions more Basic-friendly, and doing a few other tasks that Basic doesn't handle. This book provides an updated 32-bit version with a few more functions and some enhanced programming tools. The old version was written in C, but this version is in C++ and takes advantage of some C++ types (classes) that will be introduced in this article and explained in detail in subsequent articles.

The Sample Code

The sample code for this article (and for Articles 3, 4, and 5) consists of two libraries—one static, one dynamic. The OleType library is a static library intended for C++  programmers. VBUtil is a dynamic-link library intended for Visual Basic programmers. The VBUtil library uses the OleType library. When you write your own DLLs for Visual Basic, you’ll use VBUtil as a model. You’ll use OleType as a tool.

The OleType library comes in two versions—Release and Debug. There are no separate Unicode™ versions of this library because Unicode conversion is handled at run time through function overloading, as you’ll see in Article 3. VBUTIL32.DLL can be built four different ways—ANSI Debug, ANSI Release, Unicode Debug, and Unicode Release. In fact, there is only one version of the file because registering four different DLLs under Visual Basic would be too much trouble. Changing the build settings overwrites the previous version. The Visual Basic project Cpp4VB.VBP gives the procedures in VBUTIL32.DLL a workout.

The sample files for this series contain a README.TXT file with specific instructions for setting up and building the sample libraries.

Static and Dynamic Library Summary

Let’s look at the parts of the two libraries in more detail. First, the following table shows the files in the OLETYPE project.

File Description
OLETYPE.MDP and OLETYPE.MAK These are the Microsoft Developer Studio project files. If you use a different compiler, you will have to use its project format.
OLETYPE.H This file defines the standard OLE types described later in this chapter. It includes the BSTRING.H, VARIANT.H, and SAFEARRAY.H files described below so that clients see only one include file.
BSTRING.H and BSTRING.CPP These files declare and implement a new String type that encapsulates the OLE BSTR type.
VARIANT.H and VARIANT.CPP These files declare and implement a new Variant type that encapsulates the OLE VARIANT type.
SAFEARRAY.H This file declares and implements a new SafeArray template type that encapsulates the OLE SAFEARRAY type. Where’s the .CPP file? Article 5 will explain.

The result of building this project is the release (OleType.Lib) or debug (OleTypeD.Lib) version of a static library. The OleType.H and OleType.Odl include files should be considered part of the library. Put them in your include directory and include them in the appropriate files of any projects that use the OleType library.

The following table shows files for the VBUTIL project.

File Description
VBUTIL.MDP and VBUTIL.MAK These are the Microsoft Developer Studio project files. If you use a different compiler, you will have to use its project format.
VBUTIL.DEF The .DEF file is required to define exports for new functions.
VBUTIL.RC The only resources here are version resources. You could also embed the type library as a resource. Later I’ll explain why VBUTIL doesn’t.
VBUTIL.ODL Polite DLLs should provide a type library so that clients don’t have to bother with Declare statements. Article 1 describes type library source files. This article adds a few more details.
VBUTIL.CPP and VBUTIL.H These files just provide initialization and termination routines, global variables, error handling, and definitions used by any DLL. The real functionality goes in separate source files. They also serve as the base files for precompiled headers.
TOOLS.CPP and TOOLS.H These files provide some general utility functions for manipulating bits and other chores that can’t be done easily in Basic. This article contains several samples from these files.
WIN32.CPP and WIN32.H These files provide Basic-style implementations of some Win32 functions that are difficult to use in Visual Basic. You’ll see examples in Articles 3 and 4.
OLETYPE.H, OLETYPE.ODL, and OLETYPE.LIB These files provide definitions of standard OLE types. See Articles 3, 4, and 5.
TEST.CPP and TEST.H These files provide test functions that systematically test the String, Variant, and SafeArray classes.  You could put this module in conditionals so that it won’t be built in release builds.

Build Settings

Let me summarize a few of the settings I use in the Microsoft Developer Studio. The same issues will come up with other compilers, though the solutions may be slightly different.

Module Initialization and Standard Definitions

I put all the standard stuff required by any DLL in the VBUTIL.CPP and VBUTIL.H files. Normally, these will rarely change during the life of a project. If you put the standard definitions and include files required by all projects in VBUTIL.H and set this file to become part of your precompiled header, you can get much faster builds for changes in the rest of the project.

Here are the standard initialization routines in VBUTIL.CPP:

#include "vbutil.h"

HINSTANCE hInst;

// This function is the library entry point. It's technically 
// optional for 32-bit programs, but you'll have more options later 
// if you define it here. 

BOOL WINAPI DllMain(HINSTANCE hInstA, DWORD dwReason, LPVOID lpvReserved)
{
    switch (dwReason) {
    case DLL_PROCESS_ATTACH:
        // The DLL is being mapped into the process's address space.
        // Do any additional initialization here.
        hInst = hInstA;
          break;

    case DLL_THREAD_ATTACH:
        // A thread is being created.
        break;

    case DLL_THREAD_DETACH:
        // A thread is exiting cleanly.
        break;

    case DLL_PROCESS_DETACH:
        // The DLL is being unmapped from the process's address space.
        // Do any additional cleanup here.
        hInst = 0;
        break;
    }

    return TRUE;
}

Some of you may be old enough to remember the ancient times when 16-bit DLLs had a LibMain function for initialization and a WEP (Windows Exit Procedure) function for cleanup. Nowadays, there's just one function and it handles not only program initialization and cleanup, but also the same operations for separate threads started by the DLL. This series isn't going to get into separate threads, but you get the idea.

The hInst variable is saved globally, based on the passed value of the hInstA parameter. This may prove handy in your DLL functions if you ever need an instance handle for loading resources.

VBUTIL.H contains mostly standard include files and a few standard definitions.

#include <windows.h> 
#include <io.h>
#include <iostream.h>
#include <strstrea.h>
#include "oletype.h"

// Temporary buffer size
const TEMP_MAX = 512;

#define DLLAPI  WINAPI // Currently evaluates to __stdcall.

// Make ASSERT statement (fails in expressions where it shouldn't be used).
#if defined(DEBUG)
   #define ASSERT(f) \
      if (f)        \
         NULL;     \
      else          \
         assert(f) 
#else
   #define ASSERT(f) NULL
#endif

void ErrorHandler(Long e);
DWORD HResultToErr(Long e);

The contents of all those include files will go into the precompiled header the first time you compile and you won't have to compile them again. You might need to add more include files if you use more run-time functions in your DLL. You'll have to recompile when you add them. We'll be talking about the DLLAPI macro in the next section. The ASSERT macro is a standard one that I use in all my coding. Its purpose is to make asserting within an expression—a dangerous technique—illegal so that it will generate a compile-time error. I borrowed this technique from Steve Maguire's book, Writing Solid Code (Microsoft Press, 1993).

Calling Convention and Linkage

Calling conventions are simple: Use stdcall. OLE supports three different calling conventions: stdcall, pascal, and cdecl. Most compilers support only two conventions: pascal and cdecl for 16-bit compilers or stdcall and cdecl for 32-bit compilers. Because this article focuses solely on 32-bit code, we can eliminate pascal. The only reason to use the cdecl convention is so that you can have a variable number of arguments in the C style. The only kind of host that could use such arguments is a C or C++ client. Because we're building a DLL targeted at Visual Basic, we can eliminate cdecl. That leaves stdcall, which is in fact the only calling convention recognized by 32-bit Visual Basic.

You don't need to understand that the stdcall convention means that the callee cleans the stack or that arguments are pushed right to left. These details generally only matter to compiler writers. But you do need to understand something about the stdcall linking conventions.

Almost everyone (myself included) who starts writing DLLs for Visual Basic makes the same mistake. In the old 16-bit days, you specified that a function should be exported to a DLL like this:

void __export __pascal DoNothing(void);

Neither __pascal nor __export work in 32-bit compilers. Their approximate equivalents are __stdcall and __declspec(dllexport). So the natural thing is to write the function like this:

void __declspec(dllexport) __stdcall DoNothing(void);

In Microsoft Visual C++, this produced the following function name in the DLL:

?DoNothing@@YGXXZ

(You can verify this by building a DLL and checking the exported functions with the DUMPBIN program provided with Visual C++.) By default, C++ mangles names in order to support functions with the same names but different parameter lists. All C++ compilers mangle names, although different compilers use different mangling schemes.

You can turn off C++ name mangling like this:

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

This produces the following name in the DLL:

_DoNothing@0

Here the mangling follows the Win32 stdcall mangling convention. The names always have an underscore prefix and a suffix of the "at" sign (@) followed by the number of bytes of parameters in hexadecimal.

This isn't what you want for Visual Basic. Of course, your clients could get around this limitation by aliasing all their Declare statements:

Declare Sub DoNothing Lib "MyLib.DLL" Alias "_DoNothing@0" ()

However, it would be extremely rude to impose this extra burden on your users. In fact, it's not very polite to force your users to write any Declare statements. You should provide a type library. And if you do, you can get around this problem by aliasing the functions there. The ODL code would look like this:

[
entry("_DoNothing@0"),
helpstring("Do absolutely nothing")
]
void WINAPI DoNothing(); 

This works OK, but you have to figure out stdcall mangling for every function. It's not really difficult—usually just multiply the number of parameters by four and append this number in hexadecimal after the @ sign. But the easier solution is to specify the export name in the .DEF file.

A .DEF file is optional for Win32 DLLs. Most of the options you set in a .DEF file have acceptable defaults. You can get by without one on most C++ projects, but unfortunately you'll probably want to use one when targeting Visual Basic. You can define your functions like this:

void __stdcall DoNothing(void); 

Add a .DEF file entry in the EXPORTS section for each function:

EXPORTS
    DoNothing

Now the function name comes out the way you expect in the DLL: DoNothing. You don't have to use aliases in Declare statements or type library entries. You do have to remember to modify the .DEF file every time you create a new function.

So why does VBUtil use DLLAPI instead of __stdcall in its function prototypes and definitions?

void DLLAPI DoNothing(void); 

I'm not just trying to get rid of underscores, although that's a pleasant side effect. (I'm going to resist the urge to flame about the ANSI C++ requirement for double underscores on compiler-specific keywords.) If you check back through the chain of definitions (the browser in the Microsoft Developer Studio makes this easy), you'll find the following declaration in VBUTIL.H:

#define DLLAPI  WINAPI

Going back one more level to the Win32 include file WINDEF.H, you'll find this:

#define WINAPI  __stdcall

I keep this somewhat confusing system to make it easy to port to new environments (such as Macintosh®) or to different compilers. For example, instead of __declspec(dllexport), your compiler might have a 32-bit attribute that has the same effect as the old 16-bit __export attribute. Let's assume this attribute is called (surprise!) __export. You change the define statement to this:

#define DLLAPI  WINAPI __export

Now you don't need a .DEF file.

Note   Borland representatives assured me that their implementation of extern "C" and __export or __declspec(dllexport) will generate unmangled function names and that you won't need a .DEF file. I didn't have a chance to test this for Borland® C++ or other compilers.

Debugging

I'm going to describe some of the debugging issues for the Microsoft development environment. Other IDEs will no doubt have similar issues.

I make the Visual Basic environment (VB32.EXE) the program to be debugged. I set the working directory to the directory containing my Basic test programs. You'll have to adjust the directories for your own configuration. I set the program arguments to /r with the name of my Basic test program. When I press the Go toolbar button (or use the Start hotkey or menu item), the IDE runs Visual Basic, which runs my Basic test program, which loads my DLL. When I execute Basic code that uses DLL functions, the IDE will stop at any breakpoints in my C++ code. The load time is a little slow, but I can debug normally.

Before any of this happens, you'll see the same obnoxious error message described in Article 1, telling you that VB32.EXE does not contain debugging information. Be calm. Do not curse. As my dad says, "It'll get better, or you'll get used to it." Or you'll get your hands on Visual C++ 4.1, which fixes the problem.

To do serious debugging of the DLLs in this chapter, you'll need to enable Unicode debugging by modifying the AUTOEXP.DAT file. I'll explain this in detail in Article 3.

Another thing I usually do is to enable the startup banner for debug builds in the Customize mode of the C/C++ tab in the Settings dialog. This causes the environment to output a lot more information during compiles, including the complete compile command lines with all the options used. Once you've got a project working properly, you may prefer to turn off this extra noise, but it can be handy for debugging build problems. The Linker and OLE Types tabs have similar setting, although I find these less useful.

The OLE Types

C++ has types and more types—signed types, unsigned types, pointer types, intrinsic types, implementation-specific types, standard typedefed types . . . . And there are more coming when the ANSI C++ committee lays down the law "real soon now."

Basic takes a different approach. It has only a few types, but they're solid ones. No pointer types. No unsigned types. No types that act like types but aren't quite types (such as char* in C++). Because this article is about extending Visual Basic with C++, we're going to trim the C++ type list down to fit the Basic type list. Or to be more exact, we're going to trim C++ types to OLE size.

One goal of OLE automation is to be language-independent, but because the people who wrote the technology also work on Visual Basic for Applications, the OLE types look strangely familiar to Basic programmers. Here is a table of OLE types with comparisons of Basic and C++ types.

OLE Type Basic Type C++ Type
BYTE Byte unsigned char
SHORT Integer short
LONG Long long
FLOAT Single float
double Double double
CURRENCY Currency __int64
HRESULT none long
VARIANT Variant none
IDispatch* Object none
IUnknown* none none
SAFEARRAY array syntax none

The OLETYPE.H include file contains classes or typedefs that create Basic type names out of the corresponding C++ and OLE types. OLETYPE.ODL has similar declarations for Object Description Language. If you add new modules to VBUTIL (and that's what it's for), you should include OLETYPE.H at the top of each new module file. OLETYPE.ODL is already included in VBUTIL.ODL.

Because the type definitions work out differently for each type, we'll have to look at them on a case-by-case basis.

Numeric Types—Byte, Integer, Long, Single, and Double

Basic calls them Byte, Integer, Long, Single, and Double. C++ calls them unsigned char, short, long, float, and double. Windows calls them BYTE, SHORT, LONG, FLOAT, and double (really), but rarely uses the floating point types at all. OLE usually calls them by their Windows names. If you've seen one (and you will), you've seen them all. There is one inconsistency to note: Byte is an unsigned type, whereas Integer and Long are signed. Here are the typedefs to implement our Basic names.

typedef unsigned char   Byte;
typedef short           Integer;
typedef long            Long;
typedef float           Single;
typedef double          Double;

Do you notice a conspicuous absence here? There is no type for the system word size (what C++ calls int). Basic has no such concept—to the chagrin of Visual Basic 4.0 programmers who wanted to write portable Basic code easily. Probably the reason Basic didn't provide this feature is that OLE doesn't provide it. The OLE automation model assumes that all types are size-specific. This annoyance doesn't affect these articles because we're 32-bit only, but the same problem will come up again when the world starts moving from 32-bit to 64-bit.

Boolean

Basic calls it Boolean. OLE calls it VARIANT_BOOL. I call it Boolean in my C++ code. But it's really just a C++ short variable. In other words, C++ can't tell the difference between a Basic Boolean and a Basic Integer. This will create some difficulties (later in Article 4) when we try to overload constructors and other member functions for the C++ Variant type.

You might think that a variable with only two possible values would be the one area where two languages couldn't possibly find anything significant to disagree about. Wrong! Basic and C++ have different ideas on the subject, and you can get caught with annoying bugs if you don't understand the differences.

The Windows BOOL type is an int (32 bits in 32-bit mode), defined like this in WINDEF.H:

typedef int  BOOL;

The Basic Boolean type is a short (always 16 bits). You might argue that it would make more sense to make a Boolean type a byte (for more efficient storage) or an int (for more efficient run-time processing). But for whatever reason, OLE defines VARIANT_BOOL like this in OAIDL.H:

typedef short   VARIANT_BOOL;

There's more to this difference than size. Windows defines the following values for TRUE and FALSE:

#define TRUE  1
#define FALSE 0

OLE defines its own Boolean constants:

#define VARIANT_TRUE  ((VARIANT_BOOL)0xffff)
#define VARIANT_FALSE ((VARIANT_BOOL)0)

Basic uses the OLE versions, although it spells them differently. For your convenience, I provide versions with Basic spelling in VARIANT.H:

typedef VARIANT_BOOL   Boolean;
const Boolean True = VARIANT_TRUE;
const Boolean False = VARIANT_FALSE;

I haven't checked every possible language, but C-based languages are the only ones I know of that use 1 as the constant value of truth. Most languages use -1. This doesn't make much difference on the C++ side, and most Basic programmers already know how to handle the differences on the Basic side.

For now, Boolean, True, and False are more language-independent than BOOL, TRUE, and FALSE. C++ is supposed to add an intrinsic Boolean type called "bool," but it hasn't appeared in the compiler I use, and when it does, I'm not sure it will be compatible with OLE's Boolean type anyway. Most C and C++ representations of Boolean map to the int or to unsigned char rather than to short.

Note   The MIDL compiler that will eventually replace MKTYPLIB (as described in Article 1) has an intrinsic Boolean type that is an unsigned char. WINTYPE.ODL conditionally defines Boolean (note the initial capital) to VARIANT_BOOL so that when MIDL becomes available, Boolean will have the size used by OLE and Visual Basic.

Currency

Basic calls it Currency. OLE calls it CURRENCY. The Microsoft Foundation Classes call it COleCurrency. But what it really is is a 64-bit integer. OLE represents it something like this (with extra typedefs and macros cleaned out):

union CURRENCY {
    struct {
        unsigned long Lo;
        long Hi;
    };
    LONGLONG int64;   // __int64 in Microsoft Visual C++
};

This allows you to address the data all in one chunk with the int64 field, or in 32-bit pieces with the Lo and Hi fields. In Microsoft Visual C++, LONGLONG is a typedef to the nonstandard intrinsic type __int64. Other compiler vendors may not provide this type or may call it something different. A Borland representative told me their compiler doesn't support a 64-bit integer type. I don't know about other vendors. If your compiler has the same type with a different name, you can typedef LONGLONG to your compiler's intrinsic type and everything will be fine.

Visual Basic already provides good support for the currency type. I doubt that many people will want to write C++ code to enhance it, so this series isn't going to say much more about Currency. I could be wrong. If you really want to work with Currency in C++, take a look at MFC's COleVariant class. Unlike my Variant type (see Article 4 in this series), theirs encapsulates currency through the COleCurrency class and provides a lot of support for handling currency variables inside or outside of variants.

Date

Visual Basic calls it Date (introduced in Version 4). OLE calls it DATE. MFC calls it COleDateTime. But it's really just a double. As with other typedefed types, C++ can't tell the difference between a DATE and double. I don't have much to say about this type for the same reasons I don't say much about Currency. I think relatively few programmers want to enhance Visual Basic by writing new Date functions. If I'm wrong, go ahead and enhance my Variant type. This is another area where the MFC COleVariant class does a lot more than my Variant type.

By the way, OLE provides the DosDateTimeToVariantTime and VariantTimeToDosDateTime functions to convert dates. Unfortunately, the MS-DOS® date format these functions work with is accurate to two-second intervals—far less precision than provided by the Date format. If I were going to add full Date functionality, I'd skip the OLE conversion functions and write my own. That's what the COleDateTime class does.

Errors

OLE calls this type HRESULT. To C++, it's a long. Basic doesn't have an intrinsic type that maps directly to an HRESULT, but it does have an Err object that represents the information contained in an HRESULT and more. Again, you'll have some difficulties with this type because C++ can't tell the difference between an HRESULT and a long. Fortunately, you'll rarely need to use this type.

You can think of an HRESULT as an error code, but it actually goes beyond that. An HRESULT indicates the results of a function. Generally we think of a function result in terms of success or failure. Some functions return 0 to indicate failure and non-zero to indicate success. This corresponds with how people think of Booleans, but it is limiting because functions often have only one kind of success, but many kinds of failure. Therefore, some functions return 0 to indicate success and an error code to indicate failure. An HRESULT carries this further, allowing you to return different kinds of failure and different kinds of success. Negative numbers indicate failure, and they contain several specific bits indicating the kind and severity of the error. Positive numbers (including zero) indicate success. Usually functions return the S_OK macro, which represents zero, but if a function can succeed in various ways, it might return some other positive success code.

String

Basic calls it String. OLE calls it BSTR. C++ doesn't know anything about this concept, but the String class that we'll introduce in Article 3 of this series will make it easy. OleType.H includes BString.H, which defines the String class.

Variant

Basic calls it Variant. OLE calls it VARIANT. MFC calls it COleVariant. It's basically just a structure containing a union. The whole thing takes up 64 bits. I'll wrap the native structure up in the Variant class in Article 4 of this series. OleType.H includes Variant.H, which defines the Variant class.

IDispatch and IUnknown Pointers

Basic calls them Object, Control, Form, and Collection, or it may recognize them through an object-specific name. Whatever you call your objects, Visual Basic sees them as IDispatch pointers. IDispatch pointers are a specific type of IUnknown pointers. Unfortunately, I'm not going to get around to them in this series. Creating objects is a subject well beyond the scope of these articles, but using objects created by others—such as Visual Basic programs calling our C++ DLL functions—should be covered. I'm sorry I didn't have time to discuss this subject.

Arrays

A Visual Basic array is actually what OLE calls a SAFEARRAY structure. We'll be encapsulating this concept in Article 5 of this series. For now, let's just say that an array is a subtype that can contain multiple instances of any other subtype. You can have an array of Integers or of Strings or of Objects or of any of the other standard OLE types. You can even have a Variant containing an array of Variants. OleType.H includes SafeArray.H, which defines the SafeArray template class.

Using Simple Types

When it comes to simple types such as integers and floating point numbers, the Windows application programming interface (API) way is good enough for everyone. You can do it the same way the Window system DLLs do it. The C++ side is as easy as the Basic side.

Numbers by Value

When you pass a number as input to a function, there's not much reason to pass it any way other than by value. The Basic default is to pass by reference, but the C++ default is by value. Inasmuch as you're writing your DLL in C++, you might as well pass by value. Visual Basic won't know the difference if you provide a type library. Here's a typical function passing parameters by value:

Long DLLAPI MakeDWord(Long wHi, Long wLo)
{
   return (wHi << 16) | (wLo & 0x0000FFFFL);
}

The type library entry for this function looks like this:

[
entry("MakeDWord"),
helpstring("Returns a DWord from two passed words"),
]
DWORD WINAPI MakeDWord([in] DWORD dwHi, [in] DWORD dwLo);

The code should be clear, but why am I using Windows types, such as DWORD, rather than C types? That's a tricky question. I'm not a big fan of DWORD and WORD rather than unsigned long and unsigned short, but a lot of code you write for DLLs will call the Windows API, which expects Window type names. Using typedefs lets you use the same type names in your C++ and ODL source files. For example, DWORD in the type library actually means signed long, while DWORD in the C++ source means unsigned long. This issue was discussed in Article 1.

You can see the source for MakeDWord and other similar functions in TOOLS.CPP and TOOLS.H. The Bits button in the Cpp4VB sample program tests these functions.

Floating-point parameters work the same as integer arguments:

Double DLLAPI DoubleDouble(Double rInput)
{
   return (rInput * 2);
}

Numbers by Reference

A number passed by reference in Visual Basic is a pointer to a numeric variable in C++. Normally, you use ByRef parameters to receive multiple return values. For example, let's say you wanted a Basic-style wrapper for the Windows API GetCurrentPositionEx function. Every device context (hDC) has a current X/Y coordinate that can be returned by reference in a POINT structure. (The original GetCurrentPosition returned a POINT structure directly, but that doesn't work well in 32-bit, so GetCurrentPositionEx took over.) The C++ prototype looks like this:

BOOL GetCurrentPositionEx(HDC hdc, LPPOINT pPoint);

Note   For reasons unknown, the Windows API uses the abbreviation LP or lp to refer to pointers. I believe LP stands for long pointer, which is what everyone else in the world used to call a far pointer. Almost all Windows pointers were far even in the 16-bit world, so there was never any need to qualify them. In the 32-bit world, far (or long) pointers are just a bad dream, but we still see the bizarre LP notation in Windows include files and Windows documentation. Enough is enough. This book uses the old names for types because that's what they are in the include files (LPSTR, LPPOINT, and so on). But we'll just use p rather than lp for the variable names we control (pPoint, pch, and so on).

You can certainly call GetCurrentPositionEx from Basic, but it's a little awkward. Basic doesn't recognize structures in type libraries, so you have to write both the Declare statement and the UDT on the Basic side. It would be a lot simpler if the original name were reused for a function such as this:

BOOL GetCurrentPosition(HDC hDC, int * pX, int * pY);

You could easily write a Basic wrapper for this function, but it's easier (and slightly more efficient) in C++.

BOOL GetCurrentPosition(HDC hDC, int * pX, int * pY)
{
    POINT pt;
    f = GetCurrentPositionEx(hdc, &pt);
    *pX = pt.x;
    *pY = pt.y;
    return (f);
}

The ODL version looks like this:

[
entry("GetCurrentPosition"),
helpstring("Returns the X and Y coordinates of a given device context"),
]
BOOL GetCurrentPosition([in] HDC hDC, [out] int * pX, [out] int * pY);

The same technique works for floating point numbers, integers of every size, and Boolean variables.

Structures

The first question to ask yourself about structures is, do you really want to use them in your DLL functions? If your target client is Visual Basic, you should certainly think twice. If you're using small API structures such as POINT and RECT, it's probably better to pass coordinates in and receive coordinate results as separate parameters. If you're using large structures in order to pass in or receive chunks of mixed-type data, an object may be a better choice than a structure.

Remember, you can't use type libraries for your structures or functions that receive them. Everything has to be on the Basic side. The complications increase if you want your structures to contain strings, arrays, or other structures. Although you can use structures in DLLs like the ones in this chapter, you can't use structures reliably in the methods and properties of OLE objects. You have no choice when writing type libraries that map API functions, but if you're writing your own functions, you can design them any way you want to. If you decide, despite my advice, to use structures, here are a few things to remember.

Basic user-defined types have the same default 4-byte alignment as C++ structures. For example, the following UDT takes 20 bytes:

Type BadBunchOStuff
    bLittle As Byte        ' One byte data, three bytes padding
    lBig As Long           ' Four bytes data
    iMedium As Integer     ' Two bytes data, two bytes padding
    sText As String        ' Four bytes data (pointer)
    bMoreLittle As Byte    ' One byte data, three bytes padding    
End Type

When designing your structures, you'll save a little memory space if you put Integer and Byte fields next to each other. Here's a better way to organize the same data:

Type BunchOStuff
    bLittle As Byte        ' One byte data
    bMoreLittle As Byte    ' One byte data
    iMedium As Integer     ' Two bytes data
    lBig As Long           ' Four bytes data
    sText As String        ' Four bytes data (pointer)
End Type

This one uses only 12 bytes.

On the C++ side, the data looks like this:

struct BunchOStuff {
    BYTE bLittle;           // One byte data
    BYTE bMoreLittle;       // One byte data
    SHORT iMedium;          // Two bytes data
    LONG lBig;              // Four bytes data
    BSTR sText;             // Four bytes data (pointer)
};

A function that uses this data might look like this:

void GetBunchOStuff(BunchOStuff * pStuff)
{
    pStuff->bLittle = GetLittle();
    pStuff->bMoreLittle = GetLittle();
    pStuff->lBig = GetBig();
    ...
}

Because you can't define this function in a type library, you'll need a Basic Declare statement like the following:

Declare Sub GetBunchOStuff Lib "MyDll" (stuff As BunchOStuff)

Strings the Windows Way

You can write DLL functions to take Windows lpstr parameters like API functions, but this isn't politically correct. I've been known to pass input parameters as LPSTR type as a shortcut, but your Basic clients will consider you extremely rude if you define output parameters this way. Your C++ DLLs are supposed to serve your Basic clients, making everything easy and hiding the messy details. We'll get to the Basic way of doing this with Strings soon, but first let's take a short look at the shortcut for input parameters.

Assume you want a function that looks like this in Basic:

f = ExistFile("FILE.TMP")

The politically correct way to define it in ODL is this:

Boolean ExistFile([in] BSTR sFile);

This says Basic will pass in a normal Basic string (a BSTR) and the DLL will accept and handle it as a BSTR, as we'll discuss in Article 3. Basic won't do any Unicode translation, but your DLL will have to. It's kind of a messy business, but we'll provide a String class to clean things up.

The other alternative is to define the function like this in ODL:

        Boolean WINAPI ExistFile([in] LPSTR lpFileName);

In this case, Basic will do the same Unicode conversion it does for Windows API functions. The string is originally Unicode, but Basic makes an ANSI copy of it for the C++ side. Because this function doesn't need to pass anything back, the copy is perfectly adequate for the DLL. The function can be implemented like this:

Boolean DLLAPI ExistFile(LPCSTR szFile)
{                  
    return ((szFile == NULL) ? FALSE : !_access(szFile, 0));
}

This is one of those rare functions that is actually easier to implement in C++ than in Basic. Notice that it uses the constant version LPCSTR to enforce that the string will be used only for input and never modified.

The ExistFile function is tested by the Exist File button in the sample program.

Arrays, Typeless Variables, and Pointers

Don't do it. We have to learn to deal with such things (arrays, that is) in the Windows API way when mapping system DLLs to type libraries. The system DLLs are what they are. But when you write your own DLLs, you shouldn't impose this inconvenience on your clients.

Instead of API-style arrays, use SafeArrays, as described in Article 5. Your clients can pass arrays naturally:

c = CountArray(ai())

This is much better than the unnatural way you have to pass API arrays by passing the first element:

c = CountApiArray(ai(0))

Besides, you get the better index protection of the SafeArray type.

Typeless variables are usually represented in C++ as void pointers (void *). You have to do a lot of type-casting to get such functions to compile without warnings. OLE offers Variants as a safer (although less efficient) way to do typeless variables. Article 4 describes Variants.

Basically, you destroy Basic's type safety net when you write C++ functions that take void pointers or any other kind of pointer. You can usually figure out a hack to write a Declare or type library entry for such functions, but you'll have to do it without my help.