Home Articles Books Downloads FAQs Tips

Using Visual C++ DLLs in a C++Builder Project
Part 2: C++ classes.

Note: This article describes how to import C++ class from a Visual C++ DLL into an BCB project. Before we get started, I feel compelled to provide a small warning. This article is not really ready for mass distribution. I apologize if the 'article' seems choppy, difficult to read, or contains mistakes. I haven't have much time to polish it up. The only reason I decided to go ahead and publish this is because lots of developers ask how to do this. I figure that a poorly written article is better than nothing at all. I hope that this incoherent collection of ideas will prove helpful to you.

In a previous article on how to use VC++ DLLs from MSVC, I describe how to create a Borland compatible import library for an MSVC DLL. The main difficulty in using the VC++ DLL is that MSVC and Borland use different formats for function names. For example, Borland expects __cdecl functions to have a leading underscore in front of them, whereas MSVC does not. Fortunately, you can surmount these naming problems using some of Borland's command line utilites, namely TDUMP, IMPLIB, IMPDEF and COFF2OMF. The idea is that you use the command line tools to create a Borland compatible import library with Borland compatible function names in it. Once you have a Borland compatible import library, you're set. You can simply link with the import library to use the MSVC DLL.

Unfortunately, this strategy doesn't completely get you out of the woods. At the end of the DLL article, I drop a small bombshell. You can only call functions in the MSVC DLL if they are plain C functions. You can't import classes or class member functions. Doh!

So what do you do if you need to import C++ class from an MSVC DLL? Uh...well, in that case, you are boxed into a corner, and your choices are limited (and usually when your backed into a corner, your options are not that pleasant). This articles describes three ways to get yourself out of that corner.

Bad news: Before you waste too much time reading this diatribe, I feel, once again, compelled to issue a warning. All three of my techniques require that you have Microsoft Visual C++. You don't need to have the source code for the DLL that you need to call, but you do need to have the tool that can call it. Each of the three techniques are more or less wrapping techniques where we use MSVC to wrap the DLL in something we can use from BCB.


Summary of the three techniques

Ok, let the ugliness begin. Here are the three techniques.

  1. Create a DLL with MSVC that flattens the C++ classes into plain C functions. The plain C functions will be imported from BCB.
  2. Create a COM object with MSVC that wraps the C++ classes via containment. BCB will be a COM client of the VC++ COM object.
  3. Wrap the C++ classes using an abstract class with nothing but virtual functions in it. This is essentially COM without the ugly parts.

Each technique is described in more detail below. In each example, we will assume that the MSVC DLL exports a class that looks like this:

class CFoo
{
public:
    CFoo(int x);
    ~CFoo();

    int DoSomething(int y);
};

Technique 1: Flattening the C++ class into a C library

From the previous article on VC++ DLLs, we know that it is possible for a Borland project to call plain C functions that are exported from an MSVC DLL. Using this information, we can create a DLL project in MSVC that exports plain C functions for use in BCB. This MSVC wrapper DLL will be a client of the C++ DLL. The wrapper DLL will export plain C functions for creating CFoo objects, for calling CFoo member functions, and for deleting the CFoo object.

The CFoo class contains three functions that we care about: the constructor, the destructor, and the all important DoSomething function. We need to flatten each of these into an equivalent C function.

// original class
class CFoo
{
public:
    CFoo(int x);
    ~CFoo();

    int DoSomething(int y);
};

// flattened C code
void* __stdcall new_CFoo(int x)
{
    return new CFoo(x);
}

int __stdcall CFoo_DoSomething(void* handle, int y)
{
    CFoo *foo = reinterpret_cast<CFoo *>(handle);
    return foo->DoSomething(y);
}

void __stdcall delete_CFoo(void *handle)
{
    CFoo *foo = reinterpret_cast<CFoo *>(handle);
    delete foo;
}

There are lots of important things to notice here. First, note that each C++ member function has been mapped to a plain C function. Second, observe that we explicitly use the __stdcall calling convention for the C functions. From the previous DLL article, we know that simply calling plain C functions in an MSVC DLL can be a real chore. If we put our past sufferings to use, we can make this endeavor a little bit easier. The easiest way to for Borland to call a Microsoft DLL is if that DLL exports plain, undecorated, C functions that use the __stdcall calling convention. Borland and Microsoft don't agree on __cdecl functions. Normally, they don't agree on __stdcall functions either because MSVC decorates __stdcall functions, but we can suppress that behavior by adding a DEF file to the MSVC project. See the examples from the download section for an example of the DEF file.

Another thing to note about the code is that the new_CFoo function returns a pointer to the CFoo object. The BCB caller must store this pointer in some location. This may seem contradictory to the gist of this article. After all, I thought that BCB couldn't use the C++ objects from an MSVC DLL? If that's true, then why are we returning a pointer to a CFoo object?

The answer is that BCB cannot call the member functions of an class exported by an MSVC DLL. However, that doesn't mean that it can store the address of such an object. That's what new_CFoo returns, a pointer to a CFoo object. The BCB client can store that pointer, but there isn't much that it can do with it. It can't dereference it (and should not attempt to do so). To make this concept easier to understand, new_CFoo returns a void pointer (it can't really return anything else anyway). On the BCB side, there is not much you can safely do with this void pointer, other than storing it and passing it back into the DLL.

Ok, two more quick notes before we move on. First, notice that CFoo_DoSomething takes a void pointer argument as its first parameter. This void pointer is the same void pointer that is returned by new_CFoo. The void pointer is cast back into a CFoo object using reinterpret_cast (you know you're dealing ugly code when you see a reinterpret_cast). The DoSomething member function is called after the cast. Lastly, note that the void pointer is also an argument to the delete_CFoo function. It is crucial that the wrapper DLL delete the object. You should not call delete on the void pointer from BCB. That will certainly not do what you want it to.

The listing below shows a DLL header file for the C functions. This header file can be shared between MSVC and BCB.

// DLL header file
#ifndef DLL_H
#define DLL_H

#ifdef BUILD_DLL
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#else

#ifdef __cplusplus
extern "C" {
#endif

DLLAPI void* __stdcall new_CFoo(int x);
DLLAPI int   __stdcall CFoo_DoSomething(void* handle, int y);
DLLAPI void  __stdcall delete_CFoo(void *handle);

#ifdef __cplusplus
}
#endif

#endif

This is a typical DLL header file. One interesting thing to notice is that you don't see the CFoo class anywhere in the header file. The header file contaisn only plain C functions that will wrap CFoo.

The next listing shows how to call the DLL from BCB

#include "dll.h"

void bar()
{
    int x = 10;
    int y = 20;
    int z;

    void * foo = new_CFoo(x);
    z = CFoo_DoSomething(foo, y);
    delete_CFoo(foo);
}

That's it. Pretty it isn't, but it does work. In fact, despite the grotesqueness of this technique, it is amazingly handy in other situations that don't even involve DLLs. For instance, Delphi programmers employ this same technique because Delphi cannot call C++ member funtions. Delphi programmers must flatten the C++ class into C code, and then link with the C OBJ files. The open source SWIG (swig.org) tool is designed to generate wrapper functions like this, which allows you to call C++ objects from scripting languages such as Python.

Technique 2: Create a COM wrapper

Unfortunately, I don't have an example for this technique yet (hey, I said the article wasn't ready for prime time). But the idea works like this. You create a COM object in MSVC. There is probably a wizard that you can run. Create an inprocess server (ie a DLL, not an EXE). Also, make sure you create a COM object, and not an automation object. Automation just makes everything more difficult. Unless you also need to use the C++ class from VB or an ASP page, use plain COM and not automation.

Inside the COM project, create a new COM object. MSVC will probably want you to create an COM interface. Since we are wrapping a class called CFoo, a good interface name would be IFoo. MSVC will also want you to name the implementation class for the COM obect. CFooImpl is a good candidate.

The COM object should wrap the C++ DLL class using aggregation. In other words, the COM object contains a CFoo member variable. Don't try to inherit your COM class from CFoo. For each member function of the C++ DLL class (CFoo), create a similar function in your COM object. If possible, use the same name, pass the same arguments, and return the same type of value. You will need to tweak some things. For example, strings are usually passed as BSTR's in COM. Also, return values are typically passed as out parameters, because the COM method should return an error code. When you are done, each member function of the C++ class should have a corresponding function in the COM wrapper.

After you build the COM wrapper, register it with regsrv32.exe. Once you do that, you should be able to instantiate the COM object and call its wrapper member functions from your BCB code.

Once again, I apologize for not having a working demo of this technique ready to go.

Technique 3: Use an abstract base class with virtual functions (pseudo-COM)

Technique 3 is a pseudo-COM approach. COM is a binary object specification. A COM object can be called from both BCB and MSVC, regardless of the compiler that the COM object was compiled with. So how does this binary magic work? The answer is the foundation for this technique.

COM function calls are dispatched via a function lookup table. Miraculously, this function lookup table works exactly the same way that virtual function tables work in C++. In fact, they are one in the same. COM is a glorified form of virtual functions and vtables.

COM works because BCB and MSVC employ exactly the same virtual dispatching system. COM relies on the fact that most Win32 C++ compilers all generate and use vtables the same way. Because the two compilers use the same virtual dispatching system, we can create a wrapper class with virtual functions in MSVC that can be called from BCB. This is exactly what COM does.

Here is the DLL header file for the pseudo-COM wrapper class. It consists of an abstract base class, IFoo, that serves as the pseudo-COM interface. It also consists of two C functions for creating and deleting IFoo objects. This header file is shared between MSVC and BCB.

#ifndef DLL_H
#define DLL_H

#ifdef BUILD_DLL
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif

// psuedo COM interface
class IFoo
{
public:
    virtual int __stdcall DoSomething(int x) = 0;
    virtual __stdcall ~IFoo() = 0;
};

#ifdef __cplusplus
extern "C" {
#endif

DLLAPI IFoo*  __stdcall new_IFoo(int x);
DLLAPI void   __stdcall delete_IFoo(IFoo *f);

#ifdef __cplusplus
}
#endif

#endif

Notice that the two C functions resemble the functions from Technique 1, except that now they work with IFoo pointers instead of unsafe void pointers. This technique provides a little more type safety than the first.

Here is the source code for the MSVC wrapper. It contains a class call CFooImpl that inherits from IFoo. CFooImpl is an implementation of the IFoo interface.

#define BUILD_DLL

#include "dll.h"

IFoo::~IFoo()
{
	// must implement base class destructor
	// even if its abstract
}

// Note: we declare the class here because no one outside needs to be concerned
//       with it.
class CFooImpl : public IFoo
{
private:
    CFoo  m_Foo; // the real C++ class from the existing MSVC C++ DLL
public:
    CFooImpl(int x);
    virtual ~CFooImpl();
    virtual int __stdcall DoSomething(int x);
};

CFooImpl::CFooImpl(int x)
    : m_Foo(x)
{
}

int __stdcall CFooImpl::DoSomething(int x)
{
    return m_Foo.DoSomething(x);
}

CFooImpl::~CFooImpl()
{
}

IFoo * __stdcall new_IFoo(int x)
{
    return new CFooImpl(x);
}

void __stdcall delete_IFoo(IFoo *f)
{
    delete f;
}

There is lots of good stuff going on here. First of all, notice that now we have a class in the header file being shared between BCB and MSVC. That seems like it ought to be a good thing. More important than that, notice that the BCB project will only interact with the IFoo class. The actual implementation of IFoo is provided by a derived class call CFooImpl, which is internal to the MSVC wrapper project.

The BCB client code will work with IFoo objects polymorphically. To obtain a wrapper instance, the BCB code will call the new_IFoo function. new_IFoo works like a factory function, serving up new IFoo instances. new_Foo returns a pointer to an IFoo instance. However, that pointer is polymorphic. The static type of the pointer is IFoo, but its actual dynamic type will be a pointer to a CFooImpl (a fact that is unbeknownst to the BCB code).

Here is the code for the BCB client.

#include "dll.h"

void bar()
{
    int x = 10;
    int y = 20;
    int z;


    IFoo *foo = new_IFoo(x);
    z = foo->DoSomething(y);
    delete_IFoo(foo);
}

Now some parting comments on technique 3. First, it is crucial that you delete the IFoo pointer from the MSVC DLL. This is done by passing the IFoo pointer to the delete_IFoo function. Don't attempt to delete the object from BCB.

void bar()
{
    IFoo *foo = new_IFoo(x);
    delete foo;               // BOOM!!!
}

This code will surely die an agonizing death. The problem is that IFoo was created from within the new_IFoo function in the MSVC wrapper DLL. As such, the memory for the IFoo object is allocated by the MSVC memory manager. When you delete an object, it is important that you delete it with the same memory manager that was used to create it. If you call delete on the pointer from the BCB side, then you will be deleting it with Borland memory manager. Now, I could be wrong, but I would bet my house and a reproductive organ or two that the Microsoft memory manager and the Borland memory manager don't conspire to work together. When you delete the pointer with the Borland memory manager, it is doubtful that it will attempt to contact the Microsoft memory manager to let it know that it should free some memory.

Another item of note is that the BCB code works entirely in terms of the IFoo abstract interface. You don't see any occurrence of the class CFooImpl on the BCB side. CFooImpl is internal to the MSVC wrapper project. When you call DoSomething from the BCB side, the call is dispatched to CFooImpl via the virtual table.

If you are having troubling understanding this concept, don't worry. I am probably not describing it very well. To help understand what is going on, you might want to step through the code on the BCB side using the CPU viewer. This will allow you to step through each assembly instruction and see how the vtable lookup works.

Tip Note:

If you employ this pseudo-com technique, make sure that you do not attempt to overload virtual functions. In other words, don't create an interface that looks like this:

class IFoo
{
public:
    virtual int __stdcall DoSomething(int x) = 0;
    virtual int __stdcall DoSomething(float x) = 0;
    virtual int __stdcall DoSomething(const char *x) = 0;
};
The reason you don't want to overload the virtual interface functions is that MSVC and BCB may not (and probably won't) order the functions the same way in the vtable. When I tested overloading, calling DoSomething(int) on the BCB side seemed to get dispacted to DoSoemthing(float) on the MSVC side. Borland and Microsoft appear to agree on vtable formats as long as you don't overload. This may explain why you don't seem COM objects with overloaded functions.

If you need to wrap a C++ class with overloaded functions, then you should create a distinct function name for each one.

class IFoo
{
public:
    virtual int __stdcall DoSomething_int  (int x) = 0;
    virtual int __stdcall DoSomething_float(float x) = 0;
    virtual int __stdcall DoSomething_str  (const char *x) = 0;
};


Conclusion:

Ok, so where are we at? Well, at the start of the article, we talked about why BCB can't call C++ member functions in a DLL if that DLL was compiled with MSVC. The reason is that the two compilers don't agree on how those member functions should be named. We then discussed three (rather unpleasant) work arounds. Each workaround consisted of an MSVC wrapper DLL for the C++ DLL. The wrapper DLL exposed the C++ class using some format that BCB would understand. The first technique was to flatten each member function of the C++ class into a plain C function. The second technique mapped each member function to a member of a COM object. The last technique relied on the fact that virtual functions are dispatched via a lookup table instead of by name. In this strategy, each C++ member function is mapped to a virtual member function of an abstract class.

The downloads section contains the example code for this article. The first download contains the original MSVC C++ DLL that we were trying to work with. This same DLL is used by each of the three techniques. The example for Technique 2 is not ready yet.


Downloads


Downloads for this article
cppdll.zip VC++ 5 DLL project with exported C++ CFoo class
vcdll2tech1.zip Code for technique #1, flattening a class into C functions
vcdll2tech3.zip Code for technique #3, virtual function/abstract base class wrapper


Copyright © 1997-2002 by Harold Howe.
All rights reserved.