Interfacing Visual Basic And C++ by (04 March 2002) | Return to The Archives

Introduction

One of the most important steps in the development process is the selection of the programming language with which an application will be developed. This task frequently presents the developer with a very difficult decision. The reason for this is that several programming languages may have good features that the developer would like to implement. However, the developer does not have to select a language with one set of features to sacrifice another. Through the use of DLLs (Dynamic Link Libraries), the developer can have the benefit of many different languages and all the features they offer. The development task can be divided into several modules; each of which can be developed in a different language. This tutorial will instruct the reader on how this task is accomplished using two of the most popular programming languages: C++ and Visual Basic.

Creating an ActiveX DLL in Visual Basic

The DLL Creating a DLL in Visual Basic is very similar to creating a class module. The only difference is that some additional overhead is required for compiling and referencing the DLL from the client application. To create the DLL, complete the following steps:
1) Open Visual Basic and select new ActiveX DLL.
2) Rename the Class and Project to reflect the name that the client application will use in its code.
3) Add any member functions as you would to a normal class module.
The DLL is now ready to compile, however, it is usually a good idea to create a test application for debugging purposes. This is done by clicking File -> Add Project… -> Standard EXE.
One important part of the DLL is the initialize and terminate functions. These are called when the DLL is first loaded into memory and also when it is removed. The following code illustrates the use of both functions:

    ' Initializes the Class
    Private Sub Class_Initialize()
        MsgBox "This procedure is called when the DLL is loaded into memory.", vbOKOnly, "Initializing Class..."
    End Sub

    ' Terminates the Class
    Private Sub Class_Terminate()
    MsgBox "This procedure is called when the DLL is removed from memory.", vbOKOnly, "Cleaning up..."
    End Sub

The Client Application
Before a DLL can be accessed by a client application, Visual Basic must make a reference to it. This is done by clicking Project -> References. If the DLL is not in the list, select browse to indicate where the file is located. If the EXE is intended to be a test application for debugging a DLL, select the project file for the DLL. This feature makes it easy to create a DLL because it does not need to be recompiled as often.
At this stage, the client application can use the ActiveX DLL as though it were a standard class module, located within the application itself. The following is the line of code required to declare an instance of the class and call its initialization member function:

    Set Library = New testDLL

When the class instance is created, its initialization function is also called. This is demonstrated in the example code.
Calling member functions is exactly the same as a standard class module:

    Call Library.Basic_Sub_Call

When the instance of the class is destroyed, its terminate function is called so the DLL can clean up any memory it has allocated.

Creating a C++ DLL

The DLL DLLs are more difficult to create in Visual C++ than in Visual Basic, but they are still relatively simple. There are two methods with which to link to a DLL: with or without an Import Library. Using an Import Library is much easier if the target language is a C++ application, however, Import Libraries are not used when linking from Visual Basic, so they will not be covered here.
Using an Import Library can also reduce the flexibility of both the DLL and client application, but more overhead is required to ensure it functions properly. This is especially true for applications that use plugins because the Import Library would be linked in with the final executable of the client, thus eliminating the ability to add new plugins to an application. In order to create a simple DLL, follow these initial steps:
1) Open Visual C++ and select New Win-32 Dynamic Link Library.
2) Select the radio button labeled A Simple DLL Project.
3) Click Project -> Add to Project -> New… -> Text File
4) Name the file the same as your module with the .DEF extension (e.g. testDLL.def).
Setting up for Visual Basic
In the main module (testDLL.cpp in the example), make sure the windows.h header file is included. Once this is complete, functions may now be added to the DLL. However, there declaration is slightly different than a normal C function because of the way function calls are handled in Visual Basic:

    void __declspec(dllexport) CALLBACK TestFunc()
    {
    	cout << "Inside the DLL!";
    }

After all the necessary functions are created, they must be included in the .DEF file so Visual Basic knows what to look for. Follow these steps to accomplish this:

    LIBRARY "testDLL_Library"
    DESCRIPTION "An example DLL for interfacing with C++"

    EXPORTS
    	TestFunc  @1
    	RetInt    @2
    	OtherFunc @3

One very important requirement for Visual Basic is that the LIBRARY keyword must be the same as the final DLL filename. Otherwise, Visual Basic will not understand what it is looking at and produce an error.
The DllMain Function
This function is called during various times of the DLL’s existence in memory. There are four possible times when it is called:
DLL_PROCESS_ATTACH - Indicates that the DLL has just been loaded into memory.
DLL_THREAD_ATTACH - Indicates that the client application is creating a new thread (not covered).
DLL_THREAD_DETACH - Indicates that a thread is exiting (not covered).
DLL_PROCESS_DETACH - Indicates that the DLL is being unloaded from memory.
When DllMain is called, the ul_reason_for_call argument will hold one of these four values. It is usually a good idea to create a simple case statement and test for each of these events.
The function must return true if it successfully accomplished its task or false if an error was produced. In the event of an error, Windows automatically stops loading the DLL and produces an error message. This is only accounted for when the DLL is first loaded; the return value is ignored when it is unloaded from memory.
The Client Application (C++)
Setting up the client application can be very tedious for large DLLs. This is why developers should include a header file, which defines the function prototypes and properly loads them into memory. However, for simplicity this will not be done in the example code. The first task is to define a typedef for every function in the DLL. Several examples follow:

    typedef void (WINAPI*cfunc)();
    typedef int (WINAPI*ifunc)(int t);

The name of the typedef statement appears after the WINAPI keyword (e.g. cfunc and ifunc).
Next, pointers must be declared for each function; just as they were done for the typedef statements. This is done just like a normal variable would be declared:

    cfunc TestFunc;
    ifunc RetInt;

The next step is to load the DLL into memory and obtain an instance of it. This is accomplished as follows:

    HINSTANCE hLib = LoadLibrary("testdll_library.dll");
    if(hLib == NULL)
    {
    	cout << "ERROR: Unable to load library!" << endl;
    	getch();
    	return;
    }

It is very important that error checking is included here. Otherwise, the client application will crash when it attempts to call a function from the DLL.
Finally, the client application must search through the DLL and find the address of each function within the DLL’s memory. This can be done by calling the GetProcAddress function:

    TestFunc = (cfunc)GetProcAddress((HMODULE)hLib,"TestFunc");
    RetInt = (ifunc)GetProcAddress((HMODULE)hLib,"RetInt");

Now, the functions can be called as though they were declared within the client application.
Before the client application terminates, it is important to unload the DLL from memory. This is accomplished by calling FreeLibrary:

    FreeLibrary((HMODULE)hLib);

The Client Application (Visual Basic)
Accessing a C++ DLL from Visual Basic is much easier than from C++. The only task that must be done is to declare the function prototype; Visual Basic will handle everything else. In order to do this, include a code module by clicking Project -> Add Module -> Open. In the new module, declare the function prototypes in the following manner:

    Declare Sub TestFunc Lib "../testdll_library.dll" ()
    Declare Function RetInt Lib "../testdll_library.dll" (ByVal t As Integer) As Integer

One important note is that all parameters must be passed with the ByVal keyword. This is because Visual Basic always passes parameters ByRef by default. Since C/C++ usually passes by value, this must be specified in the Visual Basic declaration. This is all that is necessary when standard C variable types (int, long, etc.) are used, however complex data types like strings and arrays require more overhead on both the client and DLL sides.
The following table illustrates the equivalent conversion between several simple data types:

VB Type | C++ Type
Byte | unsigned char
Integer | short
Long | long
Single | float
Double | double
Currency | __int64

Advanced Data Types

When interfacing C++ and Visual Basic, most simple data types are very easy to pass as parameters because they are represented in similar ways. However, more advanced data types such as arrays and objects are more complicated. Passing By Reference
Passing variables by reference is useful if a function must return more than one value. This process is actually very simple to do; it is the same way in C++ as it would be for local functions. The following is a simple example of this in both C++ and Visual Basic:

    // C++ Code:
    void __declspec(dllexport) CALLBACK RetIntByRef(short a,short *t)
    {
    	*t = a * testvar;
    }

    // Visual Basic Code:
    Declare Sub RetIntByRef Lib "../testdll_library.dll" (ByVal a As Integer, ByRef t As Integer)

Structures and User-Defined Types
Structures and UDTs are almost as easy as passing by reference. However, there are a few conventions to keep in mind when using them. One important warning is that they have the potential of becoming very complicated when strings and arrays are included in them. Also, remember that memory can be saved if Integers and Bytes are defined next to each other.
The only rule for structures is that they must be passed by reference. Since Visual Basic passes by reference by default, only the C++ DLL must account for this.
Arrays
In C++, raw arrays are very dangerous and can easily cause terrible problems, especially when it receives data from a Visual Basic application. The reason for this is that Visual Basic arrays are much safer and the application will assume it has this “safety net” for arrays, even when they are passed to a C++ DLL. Luckily, Visual Basic stores arrays in the same way as the SAFEARRAY structure in C++.
To accept an array argument from a Visual Basic application, it must be declared as follows:

    short __declspec(dllexport) CALLBACK ArrayExample(LPSAFEARRAY FAR *ArrayData)
    {
    	short *temp;
    	temp = (short*)(*ArrayData)->pvData;
    	return(temp[0]);
    }

Before any operations are performed on the array, it must be converted into a standard C-Style array. First, a pointer must be declared of the same type of data in the array. Next, the address of the SAFEARRAY data must be copied to the new variable. Notice the (short *) typecast; this is necessary because Visual C++ will generate an error without it. Once the array has been converted, it can be modified as a normal C-Style array would.
If any data is modified in the newly created array, it is also modified within the Visual Basic application. This is because temp is just a pointer to the passed array’s location in memory.
Important note: When using arrays, remember that indexing is different in both languages. The array index begins at zero in C++ applications and (by default) at one in Visual Basic. This is very important when an application passes an array index to a C++ DLL.
Strings
In Visual Basic, strings are stored in the same way as the C++ BSTR type. However, when an application passes a string by value, it actually passes a pointer to the beginning of the string data. This makes it very easy to work with in C++ because the additional header information is not included. There is, however, a small amount of overhead required to work with strings:

    BSTR __declspec(dllexport) CALLBACK StringExample(BSTR stringVar)
    {
    	LPSTR buffer;
    	buffer = (LPSTR)stringVar;
    	::MessageBox(NULL,buffer,"in C++",0);
    	buffer = _strrev(buffer);
    	return(SysAllocString(stringVar));
    }

Notice the use of typecasting to convert the string. It is possible to discard the use of the BSTR type altogether, but this is not recommended. The reason for this is because it is accepted that BSTR is the data-type used for Visual Basic strings. If changes were made to how Visual Basic handles strings, it could break the DLL’s code.
There are many system functions that aid in the use of BSTRs. It is strongly recommended that the reader utilize these functions in his or her use of strings. In addition, the example code includes a header file (DLLutil.h) that has several functions to aid in the use of BSTRs.
Returning Strings
Unlike most data types, strings must be handled in a special manner in C++, especially when they are returned from a function. In order to return a string, a system function must be called to properly allocate memory for it. Otherwise, the string will disappear when the function terminates and the client will receive garbage. The following is a simple example of returning a string:

    return(SysAllocString(tempArray[index]));

Callback Functions

There are many situations in which a C++ DLL must call a function in the client application. In such instances, the DLL must be given a list of the application’s function pointers. This is done through the use of the AddressOf operator, which is only available in Visual Basic 5.0 or later.
Setting up callback functions requires a great deal of overhead from both Visual Basic and C++.
The Client Application
Before a DLL can access callback functions, the client application must give it the addresses of each function to be made available to it. This process requires a separate code module in the client application. Within the module, a procedure should be placed to pass the actual information. Next, the prototype for the receiving function must be declared. Any address in Visual Basic is stored as type long; therefore, the function should be declared in a manner similar to the following:

    Declare Sub CallbackExample Lib "../testdll_library.dll" (ByVal Addr As Long)

The actual callback functions usually should receive their arguments by value, unless the argument’s value will be altered.
The DLL
Handling callback functions in C++ is not necessarily difficult, but does require a great deal of work. First, a function must be declared that can receive the callback functions’ addresses in memory. Next, the appropriate declarations must be made to tell C++ how to call the function. The final stage is to assign the address of the function to a pointer, then it can be called as though it were a normal, C++ function. The following is a simple example of this process:

    // In C++
    void __declspec(dllexport) CALLBACK CallbackExample(long Addr1)
    {
    	typedef void (__stdcall *FNPTR)(BSTR stringVar);
    	FNPTR FunctionCall;
    	LPSTR buffer = "hello!";
    	FunctionCall = (FNPTR)Addr1;
    	BSTR temp;
    	temp = ChartoBSTR("hello");
    	FunctionCall(SysAllocString(temp));
    }

    // Here is the corresponding Visual Basic function:

    Public Sub voidFunc(ByVal stringVar As String)
        MsgBox stringVar
    End Sub

Miscellaneous Functions and Notes

Windows provides several functions with which to enhance a DLL. Two of these functions are GetModuleFileName and GetProcAddress. In order to find the full path of the client application, GetModuleFileName must be called in the following manner:

    char module[50];
    GetModuleFileName(NULL,(LPTSTR)module,50);

The GetProcAddress function is useful for finding the address of functions in another DLL:

    TestSub = (cfunc)GetProcAddress((HMODULE)hLib,"TestSub");

This tutorial also includes a header file with several useful functions. It includes useful typedefs to help declare proper data types across both modules as well as several functions to aid in the use of BSTRs. The reader is encouraged to examine this header file and use the included functions.
Another important aspect of developing DLLs is to make sure to make its use as easy as possible for the client. In order to do this, the developer should provide both a C++ header and a Visual Basic module, which declares everything the client needs to use the DLL file. In the case of C++ clients, the header file should also include a special procedure to setup all the available DLL functions. If callbacks are used, the Visual Basic application should also include such a procedure. It is also recommended that the Visual Basic typedefs are utilized to ensure that the proper data types are used.

Conclusion

Given the information in this tutorial, the reader should now have a firm understanding of how to link a C++ DLL with a Visual Basic application. Included with this tutorial (article_vbdllcode.zip (223k)) are several examples of each technique discussed. In addition, the zip file also contains a useful C++ header file with several useful functions and typedefs to aid in the development of DLLs.
If you have any questions or comments regarding this tutorial, please e-mail me at skapura.2@wright.edu.
http://skap.8k.com

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.