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:
Collapse | Copy
Code
#include <span class="code-string">"stdafx.h"</span>
#define DLL3_EXPORTS
#include <span class="code-string">"DLL3.h"</span>
BOOL APIENTRY DllMain( HANDLE ,
DWORD ul_reason_for_call,
LPVOID
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
static inline unsigned __int64 GetCycleCount()
{
unsigned int timehi, timelo;
__asm
{
rdtsc
mov timehi, edx mov timelo, eax }
return ((unsigned __int64)timehi << 32) + (unsigned __int64)timelo;
}
CDLL3::CDLL3()
{
}
int CDLL3::GetCpuSpeed()
{
const unsigned __int64 ui64StartCycle = GetCycleCount();
Sleep(1000);
return static_cast<int>((GetCycleCount() - ui64StartCycle) / 1000000);
}
DLL2.h looks
like:
Collapse | Copy
Code
#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
class DLL3_API CDLL3
{
public:
CDLL3();
int GetCpuSpeed();
};
#endif
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:
Collapse | Copy
Code
void * __stdcall CreateDll3();
void __stdcall DestroyDll3(void * objptr);
Each of the wrapper functions includes the class object pointer as
its first parameter:
Collapse | Copy
Code
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:
Collapse | Copy
Code
#include <span class="code-string">"stdafx.h"</span>
#define DLL3_EXPORTS
#include <span class="code-string">"DLL3.h"</span>
BOOL APIENTRY DllMain( HANDLE ,
DWORD ul_reason_for_call,
LPVOID
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
static inline unsigned __int64 GetCycleCount()
{
unsigned int timehi, timelo;
__asm
{
rdtsc
mov timehi, edx mov timelo, eax }
return ((unsigned __int64)timehi << 32) + (unsigned __int64)timelo;
}
CDLL3::CDLL3()
{
}
int CDLL3::GetCpuSpeed()
{
const unsigned __int64 ui64StartCycle = GetCycleCount();
Sleep(1000);
return static_cast<int>((GetCycleCount() - ui64StartCycle) / 1000000);
}
void * __stdcall CreateDll3()
{
return new CDLL3;
}
void __stdcall DestroyDll3(void * objptr)
{
CDLL3 *dll3 = (CDLL3 *) objptr;
if (dll3)
delete dll3;
}
int __stdcall GetCpuSpeedDll3(void * objptr)
{
CDLL3 *dll3 = (CDLL3 *) objptr;
if (dll3)
return dll3->GetCpuSpeed();
else
return 0;
}
Here's the new DLL3.h:
Collapse | Copy
Code
#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
class DLL3_API CDLL3
{
public:
CDLL3();
int GetCpuSpeed();
};
void * __stdcall CreateDll3();
void __stdcall DestroyDll3(void * objptr);
int __stdcall GetCpuSpeedDll3(void * objptr);
#endif
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:
Collapse | Copy
Code
; 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:
Collapse | Copy
Code
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:
Collapse | Copy
Code
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:
Collapse | Copy
Code
#include <span class="code-string">"afxres.h"</span>
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif
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
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! |