Click here to Skip to main content
15,884,298 members
Articles / Programming Languages / Visual Basic
Article

Step by Step: Calling C++ DLLs from VC++ and VB - Part 3

Rate me:
Please Sign up or sign in to vote.
4.94/5 (50 votes)
28 Feb 2004CPOL6 min read 238.9K   4.2K   87   18
This series of articles is a step-by-step guide to constructing C++ DLLs that include C++ functions and C++ classes, and then calling the DLL functions and classes from VC++ and VB programs.

Introduction

This series of articles discusses four common situations when working with DLLs:

Part 1Calling a DLL C++ function from a VC++ application
Calling a DLL C++ class from a VC++ application
Part 2Calling a DLL C++ function from a VB application
Part 3Calling a DLL C++ class from a VB application
Part 4Loading a C++ DLL dynamically from a VC++ application

Calling a DLL C++ class from a VB application

In Part 2, I talked about calling a function in a C++ DLL from a VB application. The nice thing about using DLLs in this way is that they encapsulate functions, and what you see from the outside is only the interface. In DLL2.cpp, there are actually two functions. But since one is declared static and does not appear in the DLL2.def file, an external application has no idea it even exists. Thus, there are no side effects and the DLL will be reusable in many projects.

It is the same way with C++ classes. Encapsulating them in DLLs is very useful - especially because this gives us an opportunity to use them in VB applications too. Here's how to do this:

Step 1

I start with the code from DLL2.cpp and add the CDLL3 class:

MC++
// DLL3.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#define DLL3_EXPORTS
#include "DLL3.h"

BOOL APIENTRY DllMain( HANDLE /*hModule*/, 
                       DWORD  ul_reason_for_call, 
                       LPVOID /*lpReserved*/
                     )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}


///////////////////////////////////////////////////////////////////////////////
// GetCycleCount - private function of DLL3.cpp.  The static keyword ensures
//                 that this function name is not visible outside DLL3.cpp.
static inline unsigned __int64 GetCycleCount()
{
    unsigned int timehi, timelo;

    // Use the assembly instruction rdtsc, which gets the current
    // cycle count (since the process started) and puts it in edx:eax.
    __asm
    {
        rdtsc
        mov timehi, edx;
        mov timelo, eax;
    }

    return ((unsigned __int64)timehi << 32) + (unsigned __int64)timelo;
}


///////////////////////////////////////////////////////////////////////////////
// Example of an exported class
///////////////////////////////////////////////////////////////////////////////
// This is the constructor of class CDLL3 that has been exported;
// see DLL3.h for the class definition
CDLL3::CDLL3()
{ 
}

int CDLL3::GetCpuSpeed()
{
    const unsigned __int64 ui64StartCycle = GetCycleCount();
    Sleep(1000);
    return static_cast<int>((GetCycleCount() - ui64StartCycle) / 1000000);
}

DLL2.h looks like:

MC++
#ifndef DLL3_H
#define DLL3_H

#ifdef DLL3_EXPORTS
    #define DLL3_API __declspec(dllexport)
#else
    #pragma message("automatic link to DLL3.LIB")
    #pragma comment(lib, "DLL3.lib")
    #define DLL3_API __declspec(dllimport)
#endif


///////////////////////////////////////////////////////////////////////////////
// This class is exported from DLL3.dll
class DLL3_API CDLL3 
{
public:
    CDLL3();
    int GetCpuSpeed();
};

#endif //DLL3_H

Note that I have gone back to the use of __declspec because later on I want to test the DLL with a VC++ application. As the code stands right now, there's not much point in trying it with VB, because the only thing that is exported is the C++ class CDLL3, and VB cannot deal with that.

Step 2

When a C++ program wants to use a C++ class, it must first create an instance of that class - either on the stack or on the heap. When the member functions of a class are then called, there is an implicit "this" pointer that gets passed as the first parameter. Because VB doesn't understand C++ classes or "this" pointers, there is no way for VB programs to use C++ classes directly.

What I can do, however, is call the C++ class methods from functions internal to the DLL. But first I must do one thing: I must get access to the class and deal the problem of the "this" pointer. Here is the trick: I will implement functions in the DLL that can be called by a VB program. For each class method, there will be a corresponding VB wrapper function. In addition, there will be two more functions that will create and destroy an instance of the class, which will take care of the problem with the "this" pointer.

The create and destroy functions are prototyped as follows:

MC++
void * __stdcall CreateDll3();
void __stdcall DestroyDll3(void * objptr);

Each of the wrapper functions includes the class object pointer as its first parameter:

MC++
int __stdcall GetCpuSpeedDll3(void * objptr);

TIP: I have named each of these functions with a "Dll3" suffix, to give a hint as to where these functions reside - you may use whatever naming conventions you wish.

To start using a C++ class, the VB program first calls CreateDLL3(), which creates an instance of the class on the heap (via new) and returns the pointer to the class object. The VB program passes the object pointer to each of the class wrapper functions (which correspond to the class methods). Inside the DLL, the class wrapper functions use the object pointer to access the class methods. Finally, the VB program calls DestroyDLL3() when it is finished with the C++ class.

It sounds complicated, but it really isn't. Here's the new DLL3.cpp:

MC++
// DLL3.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#define DLL3_EXPORTS
#include "DLL3.h"

BOOL APIENTRY DllMain( HANDLE /*hModule*/, 
                       DWORD  ul_reason_for_call, 
                       LPVOID /*lpReserved*/
                     )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}


///////////////////////////////////////////////////////////////////////////////
// GetCycleCount - private function of DLL3.cpp.  The static keyword ensures
//                 that this function name is not visible outside DLL3.cpp.
static inline unsigned __int64 GetCycleCount()
{
    unsigned int timehi, timelo;

    // Use the assembly instruction rdtsc, which gets the current
    // cycle count (since the process started) and puts it in edx:eax.
    __asm
    {
        rdtsc
        mov timehi, edx;
        mov timelo, eax;
    }

    return ((unsigned __int64)timehi << 32) + (unsigned __int64)timelo;
}


///////////////////////////////////////////////////////////////////////////////
// Example of an exported class
///////////////////////////////////////////////////////////////////////////////
// This is the constructor of class CDLL3 that has been exported;
// see DLL3.h for the class definition
CDLL3::CDLL3()
{ 
}

int CDLL3::GetCpuSpeed()
{
    const unsigned __int64 ui64StartCycle = GetCycleCount();
    Sleep(1000);
    return static_cast<int>((GetCycleCount() - ui64StartCycle) / 1000000);
}

///////////////////////////////////////////////////////////////////////////////
// Class wrapper functions
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// CreateDLL3 - create an instance of the class CDLL3
void * __stdcall CreateDll3()
{
    return new CDLL3;
}

///////////////////////////////////////////////////////////////////////////////
// DestroyDLL3 - free the memory for the class instance 
void __stdcall DestroyDll3(void * objptr)
{
    CDLL3 *dll3 = (CDLL3 *) objptr;
    if (dll3)
        delete dll3;
}

///////////////////////////////////////////////////////////////////////////////
// GetCpuSpeed - returns CPU speed in MHz;  for example, ~2193 will be 
//               returned for a 2.2 GHz CPU.
int __stdcall GetCpuSpeedDll3(void * objptr)
{
    CDLL3 *dll3 = (CDLL3 *) objptr;
    if (dll3)
        return dll3->GetCpuSpeed();
    else
        return 0;
}

Here's the new DLL3.h:

#ifndef DLL3_H
#define DLL3_H

#ifdef DLL3_EXPORTS
    #define DLL3_API __declspec(dllexport)
#else
    #pragma message("automatic link to DLL3.LIB")
    #pragma comment(lib, "DLL3.lib")
    #define DLL3_API __declspec(dllimport)
#endif


///////////////////////////////////////////////////////////////////////////////
// This class is exported from DLL3.dll
class DLL3_API CDLL3 
{
public:
    CDLL3();
    int GetCpuSpeed();
};

void * __stdcall CreateDll3();
void __stdcall DestroyDll3(void * objptr);
int __stdcall GetCpuSpeedDll3(void * objptr);

#endif //DLL3_H

Step 3

To use the wrapper functions in a VB program, I need to export them with the correct names. Once again, I will do this with a module definition (.DEF) file:

; DLL3.def - defines the exports for DLL3.dll

LIBRARY DLL3
DESCRIPTION 'A C++ dll that can be called from VB'

EXPORTS
    GetCpuSpeedDll3
    CreateDll3
    DestroyDll3

Now DLL3 can be compiled.

Step 4

With the DLL3 wrapper functions defined, I can edit the VB3 program:

VB
Private Declare Function CreateDll3 Lib "DLL3.dll" () As Long
Private Declare Sub DestroyDll3 Lib "DLL3.dll" (ByVal objptr As Long)
Private Declare Function GetCpuSpeedDll3 Lib "DLL3.dll" _
                                (ByVal objptr As Long) As Integer
Private Declare Sub InitCommonControls Lib "comctl32.dll" ()

Private Sub Form_Initialize()

    InitCommonControls
    ChDir App.Path

End Sub

Private Sub Command1_Click()

    Dim nSpeed As Integer
    Dim s As String
    Dim objptr As Long
    
    Screen.MousePointer = vbHourglass
    objptr = CreateDll3()
    nSpeed = GetCpuSpeedDll3(objptr)
    DestroyDll3(objptr)
    Screen.MousePointer = 0
    
    s = nSpeed
    
    Form1.Text1.Text = "GetCpuSpeedDll3() returned " + s

End Sub

Private Sub Form_Load()

    Form1.Text1.Text = ""
    
End Sub

I run this program, click the button, and this is what I see:

Image 1

OK! I've got a C++ class in a DLL, and I can call the class methods from VB by using wrapper functions. There's one question left: can I also call this DLL from a C++ program?

Step 5

I modify the EXE2 program from Part 2 and add code for two buttons; the first button will call the GetCpuSpeedDll3() wrapper function, and the second button will call the class method CDLL3::GetCpuSpeed() directly:

void CEXE3Dlg::OnButton1() 
{
    CWaitCursor wait;
    void * objptr = CreateDll3();
    int nSpeed = GetCpuSpeedDll3(objptr);
    CString s;
    s.Format(_T("GetCpuSpeedDll3() returned %d"), nSpeed);
    m_Speed1.SetWindowText(s);
    DestroyDll3(objptr);
}

void CEXE3Dlg::OnButton2() 
{
    CWaitCursor wait;
    CDLL3 dll3;
    int nSpeed = dll3.GetCpuSpeed();
    CString s;
    s.Format(_T("CDLL3::GetCpuSpeed() returned %d"), nSpeed);
    m_Speed2.SetWindowText(s);
}

I compile this code and try out the buttons:

Image 2

So, now I have one DLL that can be called from VC++ and VB programs; the VB program calls wrapper functions to access the C++ class methods, and the VC++ program can call either the wrapper functions or the class methods directly.

Step 6

With this DLL, I can now begin writing VC++ and VB programs to call its functions and class methods. But I would never put this DLL in a production environment or use it in a commercial application. Reason? I have no way of tracking this DLL. The file timestamp can be modified, and so is useless. What I need is a way to determine the DLL's version number, which can be tied in to a version control or bug anomaly tracking system.

Applications typically use an embedded resource based on the VS_VERSION_INFO resource type. But how can I add this to DLL3? Here is the simplest way I have found to add a version resource to a DLL: find an application (EXE) with a version resource. Copy the application's resource (.RC) file to the DLL's directory, and rename it to DLL3.rc. Open the DLL3.rc file with your favorite text editor, and remove everything except what you see below - while you're at it, you can update the version information, too:

//Microsoft Developer Studio generated resource script.
//

#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32

/////////////////////////////////////////////////////////////////////////////
//
// Version
//

VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,0,0,1
 PRODUCTVERSION 1,0,0,1
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x4L
 FILETYPE 0x1L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904B0"
        BEGIN
            VALUE "CompanyName", "\0"
            VALUE "FileDescription", "DLL3 Dynamic Link Library\0"
            VALUE "FileVersion", "1, 0, 0, 1\0"
            VALUE "InternalName", "DLL3\0"
            VALUE "LegalCopyright", "Copyright (C) 2004 by Hans Dietrich\0"
            VALUE "ProductName", "DLL3 Dynamic Link Library\0"
            VALUE "ProductVersion", "1, 0, 0, 1\0"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1200
    END
END


#endif    // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////

Save the changes, and open the Visual Studio DLL project. In File View, right-click on DLL3 files, select Add Files to Project..., and select the DLL3.rc file. Rebuild the DLL. In Windows Explorer, right-click on the DLL3.dll file, select Properties, and you will see a Version tab that looks like:

Image 3

NOTE: Even though the DLL3.rc file includes AFXRES.H, it does not mean that the DLL is linked to any of the MFC libraries, or is calling any of the MFC code. AFXRES.H is necessary only to resolve some symbol definitions.

Key Concepts

  • Use __declspec(dllexport) and __declspec(dllimport) for exporting classes, to be able to use the DLL with a VC++ application.
  • Use wrapper functions defined with __stdcall to allow VB access to class methods.
  • Export the wrapper functions via a module definition (.DEF) file.
  • Include a version resource in your DLL to keep track of changes.

Revision History

Version 1.0 - 2004 February 29

  • Initial public release.

Usage

This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions

 
BugYour code seems to not work on x64 bits version of Excel ? Pin
Member 1108632227-Jul-20 22:07
Member 1108632227-Jul-20 22:07 
QuestionActually VB(A) can pass the "this" pointer Pin
Member 1087172812-Sep-14 8:27
Member 1087172812-Sep-14 8:27 
QuestionVB CALLING C++ DLL FUNCTIONS Pin
Member 1003034114-May-13 15:25
Member 1003034114-May-13 15:25 
Questiontype casting for VB wrappers Pin
ucarcamagnu28-Nov-08 3:09
ucarcamagnu28-Nov-08 3:09 
GeneralProblems by sending values in the dll Pin
andreas7211-May-08 5:30
andreas7211-May-08 5:30 
GeneralRe: Problems by sending values in the dll Pin
pollypunter27-May-08 18:56
pollypunter27-May-08 18:56 
GeneralRe: Problems by sending values in the dll Pin
Adeel8912-Aug-09 9:16
Adeel8912-Aug-09 9:16 
Generalneed help on VC++2005 Pin
filo6520-Feb-07 22:03
filo6520-Feb-07 22:03 
Generale wrapping DLLs made in VB into VC++ Pin
Tapan Pathak24-Jan-07 7:34
Tapan Pathak24-Jan-07 7:34 
GeneralVery Usefull Pin
Programm3r25-Oct-06 3:52
Programm3r25-Oct-06 3:52 
GeneralANSI / UNICODE variations when going to/from VB and C++ Pin
Jan R Hansen7-Aug-06 23:20
Jan R Hansen7-Aug-06 23:20 
QuestionHow to send array of double from VB to C++ DLL Pin
kmaku4-Apr-06 0:49
kmaku4-Apr-06 0:49 
QuestionVb.Net - Part 5 ?? Pin
Vitoto10-Jan-06 13:14
Vitoto10-Jan-06 13:14 
GeneralIMPORTANT: passing C++ class through odl file Pin
nautilus7512-May-05 8:32
nautilus7512-May-05 8:32 
GeneralReally Good Work Pin
Mohammad A Gdeisat4-May-04 11:43
Mohammad A Gdeisat4-May-04 11:43 
GeneralC++ dll for VB Pin
Anonymous3-Apr-04 13:29
Anonymous3-Apr-04 13:29 
Generalusing C++ class in VB Pin
vray9-Mar-04 3:36
vray9-Mar-04 3:36 
GeneralGreat work Pin
Anonymous29-Feb-04 9:00
Anonymous29-Feb-04 9:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.