YOU CAN CODE!

 

With The Case Of UCanCode.net  Release The Power OF  Visual C++ !   Home Products | Purchase Support | Downloads  
View in English
View in Japanese
View in
참고
View in Français
View in Italiano
View in 中文(繁體)
Download Evaluation
Pricing & Purchase?
E-XD++Visual C++/ MFC Products
Overview
Features Tour 
Electronic Form Solution
Visualization & HMI Solution
Power system HMI Solution
CAD Drawing and Printing Solution

Bar code labeling Solution
Workflow Solution

Coal industry HMI Solution
Instrumentation Gauge Solution

Report Printing Solution
Graphical modeling Solution
GIS mapping solution

Visio graphics solution
Industrial control SCADA &HMI Solution
BPM business process Solution

Industrial monitoring Solution
Flowchart and diagramming Solution
Organization Diagram Solution

Graphic editor Source Code
UML drawing editor Source Code
Map Diagramming Solution

Architectural Graphic Drawing Solution
Request Evaluation
Purchase
VX++ Cross-Platform C/C++
Overview
Download
Purchase
ActiveX COM Products
Overview
Download
Purchase
Technical Support
  General Q & A
Discussion Board
Contact Us

Links

Get Ready to Unleash the Power of UCanCode .NET

Call VC++ / C++ and MFC DLL function with VB Program

1

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 <span class="code-string">"stdafx.h"</span>
#define DLL3_EXPORTS
#include <span class="code-string">"DLL3.h"</span>

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 <span class="code-string">"stdafx.h"</span>
#define DLL3_EXPORTS
#include <span class="code-string">"DLL3.h"</span>

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 theGetCpuSpeedDll3() wrapper function, and the second button will call the class methodCDLL3::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 <span class="code-string">"afxres.h"</span>

/////////////////////////////////////////////////////////////////////////////
// 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
 FILEFLAGSMucancode.net 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, selectProperties, 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.

vc_in_vb.zip

News:

1 UCanCode Advance E-XD++ CAD Drawing and Printing Solution Source Code Solution for C/C++, .NET V2023 is released!

2 UCanCode Advance E-XD++ HMI & SCADA Source Code Solution for C/C++, .NET V2023 is released!

3 UCanCode Advance E-XD++ GIS SVG Drawing and Printing Solution Source Code Solution for C/C++, .NET V2023 is released!

 

Ask any questions by MSN: UCanCode@hotmail.com Yahoo: ucan_code@yahoo.com


 

Copyright ?1998-2023 UCanCode.Net Software , all rights reserved.
Other product and company names herein may be the trademarks of their respective owners.

Please direct your questions or comments to webmaster@UCanCode.net