65.9K
CodeProject 正在变化。 阅读更多。
Home

分步:从 VC++ 和 VB 调用 C++ DLL - 第 3 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (46投票s)

2004年2月29日

CPOL

6分钟阅读

viewsIcon

242480

downloadIcon

4209

本系列文章是一个分步指南,旨在构建包含 C++ 函数和 C++ 类的 C++ DLL,然后从 VC++ 和 VB 程序中调用这些 DLL 函数和类。

引言

本系列文章讨论了使用 DLL 时的四种常见情况:

第一部分 从 VC++ 应用程序调用 DLL 中的 C++ 函数
从 VC++ 应用程序调用 DLL 中的 C++ 类
第二部分 从 VB 应用程序调用 DLL 中的 C++ 函数
第三部分 从 VB 应用程序调用 DLL 中的 C++ 类
第四部分 从 VC++ 应用程序动态加载 C++ DLL

从 VB 应用程序调用 DLL 中的 C++ 类

第 2 部分中,我讨论了如何从 VB 应用程序调用 C++ DLL 中的函数。以这种方式使用 DLL 的好处在于它们封装了函数,从外部您只能看到接口。在 DLL2.cpp 中,实际上有两个函数。但由于其中一个被声明为 static 并且没有出现在 DLL2.def 文件中,外部应用程序甚至不知道它的存在。因此,没有副作用,该 DLL 可以在许多项目中重用。

C++ 类也是如此。将它们封装在 DLL 中非常有用——特别是因为这让我们有机会在 VB 应用程序中使用它们。以下是具体做法:

步骤 1

我从 DLL2.cpp 的代码开始,并添加 CDLL3 类。

// 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 如下所示:

#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

请注意,我已经重新使用 __declspec,因为稍后我想用 VC++ 应用程序来测试这个 DLL。就目前的代码而言,用 VB 尝试它没有太大意义,因为唯一导出的是 C++ 类 CDLL3,而 VB 无法处理它。

第二步

当 C++ 程序想要使用一个 C++ 类时,它必须首先创建该类的一个实例——无论是在栈上还是在堆上。当调用类的成员函数时,会有一个隐式的“this”指针作为第一个参数传递。因为 VB 不理解 C++ 类或“this”指针,所以 VB 程序无法直接使用 C++ 类。

然而,我能做的是从 DLL 内部的函数中调用 C++ 类的方法。但首先我必须做一件事:我必须访问该类并处理“this”指针的问题。技巧如下:我将在 DLL 中实现可以由 VB 程序调用的函数。对于每个类方法,都会有一个相应的 VB 包装函数。此外,还会有另外两个函数来创建和销毁类的实例,这将解决“this”指针的问题。

创建和销毁函数的原型如下:

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

每个包装函数的第一个参数都包含类对象指针:

int __stdcall GetCpuSpeedDll3(void * objptr);

提示:我为这些函数都加上了 "Dll3" 后缀,以提示这些函数的来源——您可以使用任何您喜欢的命名约定。

要开始使用 C++ 类,VB 程序首先调用 CreateDLL3(),它通过 new 在堆上创建一个类的实例,并返回指向该类对象的指针。VB 程序将此对象指针传递给每个类包装函数(这些函数对应于类的方法)。在 DLL 内部,类包装函数使用该对象指针来访问类的方法。最后,当 VB 程序使用完 C++ 类后,它会调用 DestroyDLL3()

这听起来很复杂,但实际上并非如此。这是新的 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;
}

这是新的 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

步骤 3

要在 VB 程序中使用这些包装函数,我需要用正确的名称导出它们。同样,我将通过一个模块定义 (.DEF) 文件来完成此操作:

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

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

EXPORTS
    GetCpuSpeedDll3
    CreateDll3
    DestroyDll3

现在可以编译 DLL3 了。

步骤 4

定义了 DLL3 包装函数后,我可以编辑 VB3 程序:

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

我运行这个程序,点击按钮,看到的是:

好的!我将一个 C++ 类放入了 DLL 中,并且可以通过使用包装函数从 VB 中调用该类的方法。还剩一个问题:我能从 C++ 程序中调用这个 DLL 吗?

步骤 5

我修改了第 2 部分中的 EXE2 程序,并为两个按钮添加了代码;第一个按钮将调用 GetCpuSpeedDll3() 包装函数,第二个按钮将直接调用类方法 CDLL3::GetCpuSpeed()

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);
}

我编译这段代码并尝试点击按钮:

所以,现在我有一个可以被 VC++ 和 VB 程序调用的 DLL;VB 程序调用包装函数来访问 C++ 类的方法,而 VC++ 程序既可以调用包装函数,也可以直接调用类的方法。

步骤 6

有了这个 DLL,我现在可以开始编写 VC++ 和 VB 程序来调用它的函数和类方法了。但我绝不会将这个 DLL 放入生产环境或用于商业应用程序。原因是什么?我无法跟踪这个 DLL。文件的时间戳可以被修改,因此是无用的。我需要一种方法来确定 DLL 的版本号,这样可以将其与版本控制或缺陷异常跟踪系统关联起来。

应用程序通常使用基于 VS_VERSION_INFO 资源类型的嵌入式资源。但我如何将它添加到 DLL3 中呢?这是我发现的向 DLL 添加版本资源的最简单方法:找到一个带有版本资源的应用程序(EXE)。将该应用程序的资源 (.RC) 文件复制到 DLL 的目录中,并将其重命名为 DLL3.rc。用您喜欢的文本编辑器打开 DLL3.rc 文件,并删除除下面看到的内容之外的所有内容——同时,您也可以更新版本信息:

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

保存更改,并打开 Visual Studio DLL 项目。在文件视图中,右键单击 DLL3 files,选择 Add Files to Project...,然后选择 DLL3.rc 文件。重新生成 DLL。在 Windows 资源管理器中,右键单击 DLL3.dll 文件,选择属性,您将看到一个版本选项卡,如下所示:

注意:尽管 DLL3.rc 文件包含了 AFXRES.H,但这并不意味着该 DLL 链接到了任何 MFC 库,或者调用了任何 MFC 代码。AFXRES.H 只是为了解析一些符号定义所必需的。

关键概念

  • 使用 __declspec(dllexport)__declspec(dllimport) 导出类,以便能够在 VC++ 应用程序中使用该 DLL。
  • 使用以 __stdcall 定义的包装函数,以允许 VB 访问类方法。
  • 通过模块定义 (.DEF) 文件导出包装函数。
  • 在您的 DLL 中包含版本资源,以跟踪更改。

修订历史

版本 1.0 - 2004年2月29日

  • 首次公开发布。

用法

本软件发布到公共领域。您可以自由地以任何方式使用它,但不得出售此源代码。如果您修改或扩展了它,请考虑在此处发布新代码以供大家分享。本软件按“原样”提供,无任何明示或暗示的保证。我对此软件可能造成的任何损害或业务损失不承担任何责任。

分步指南:从 VC++ 和 VB 调用 C++ DLL - 第 3 部分 - CodeProject - 代码之家
© . All rights reserved.