Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
28 Feb 2004 1  
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 1 Calling a DLL C++ function from a VC++ application
Calling a DLL C++ class from a VC++ application
Part 2 Calling a DLL C++ function from a VB application
Part 3 Calling a DLL C++ class from a VB application
Part 4 Loading 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:

// 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:

#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:

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

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

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:

// 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:

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:

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:

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:

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here