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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (63投票s)

2004年2月29日

CPOL

4分钟阅读

viewsIcon

468436

downloadIcon

7133

本系列文章将分步指导如何构建包含 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++ 函数

第一部分 中,我讨论了如何创建 C++ DLL,然后在 VC++ 应用程序中使用这些 DLL。有时您可能希望从 VB 应用程序调用 C++ DLL 中的函数。

步骤 1

这是 DLL2 的代码,取自第一部分的 DLL1

// DLL2.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#define DLL2_EXPORTS
#include "DLL2.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 DLL2.cpp.  The static keyword ensures
//                 that this function name is not visible outside DLL2.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 function
///////////////////////////////////////////////////////////////////////////////
// GetCpuSpeed - returns CPU speed in MHz;  for example, ~2193 will be 
//               returned for a 2.2 GHz CPU.
DLL2_API int __stdcall GetCpuSpeed()
{
    const unsigned __int64 ui64StartCycle = GetCycleCount();
    Sleep(1000);
    return static_cast((GetCycleCount() - ui64StartCycle) / 1000000);
}

DLL2.h 看起来像这样

#ifndef DLL2_H
#define DLL2_H

// The following ifdef block is the standard way of creating macros which 
// make exporting from a DLL simpler.  The DLL2.cpp file is compiled with 
// the symbol DLL2_EXPORTS defined at the top of DLL2.cpp.  This symbol 
// should *not* be defined in any project that uses DLL2.  This way any 
// other project whose source files include DLL2.h will see DLL2_API defined 
// as __declspec(dllimport), whereas within DLL2.cpp, DLL2_API is defined as
// __declspec(dllexport).

#ifdef DLL2_EXPORTS
    #define DLL2_API __declspec(dllexport)
#else
    #define DLL2_API __declspec(dllimport)
#endif

///////////////////////////////////////////////////////////////////////////////
// This function is exported from the DLL2.dll
DLL2_API int __stdcall GetCpuSpeed();

#endif //DLL2_H

DLL2 代码与 DLL1 代码的一个区别是 GetCpuSpeed() 是使用 __stdcall 声明的。这是任何您想在 VB 中使用的 C/C++ 函数所必需的。以下是 MSDN 关于 __stdcall 调用约定的说明:

元素 实现
参数传递顺序 从右到左。
参数传递约定 按值传递,除非传递了指针或引用类型。
堆栈维护责任 被调用函数自己从堆栈中弹出其参数。
名称修饰约定 在名称前面加上下划线 (_)。名称后面跟着 at 符号 (@),后面跟着参数列表中的字节数(十进制)。因此,声明为 int func( int a, double b ) 的函数被修饰为:_func@12
大小写转换约定

第二步

要测试 DLL2.dll,我使用了这段 VB 代码

Private Declare Function GetCpuSpeed Lib "DLL2.dll" () 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
    
    Screen.MousePointer = vbHourglass
    nSpeed = GetCpuSpeed()
    Screen.MousePointer = 0
    
    s = nSpeed
    
    Form1.Text1.Text = "GetCpuSpeed() returned " + s

End Sub

Private Sub Form_Load()

    Form1.Text1.Text = ""
    
End Sub

DLL2.dll 复制到 VB 目录后,我运行 VB2.exe,看到的是这样的界面

当我点击按钮时,我得到了这个错误

因此,我知道 VB 找不到 DLL 中的函数 GetCpuSpeed()。为了查看 VB 看到的内容,我从命令行使用 dumpbin /exports dll2.dll

Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Dump of file dll2.dll

File Type: DLL

  Section contains the following exports for DLL2.dll

           0 characteristics
    403FE342 time date stamp Fri Feb 27 08:21:30 2004
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00001010 ?GetCpuSpeed@@YGHXZ

  Summary

        4000 .data
        1000 .rdata
        1000 .reloc
        4000 .text

这是您从 VC++ 预期看到的——它“修饰”了 GetCpuSpeed 的名称,并导出了新的混淆名称?GetCpuSpeed@@YGHXZVB 不理解这个。这意味着什么?以下是 MSDN 的说法

Microsoft C++ 编译器会在 C++ 程序中对符号名称进行编码,以包含类型信息。这被称为“名称修饰”或“名称混淆”。目的是确保类型安全的链接。C++ 语言允许函数重载,其中同名函数仅通过函数参数的数据类型来区分。名称修饰使链接器能够区分不同版本的重载函数,因为函数名称被编码或修饰的方式不同。

步骤 3

为了解决 VB 遇到的问题,我需要告诉 VB 出口 #1 相关的名称是 GetCpuSpeed,而不是 VC++ 修饰的名称。有一个简单的方法可以做到这一点:创建一个 DLL2.def 文件,其内容如下:

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

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

EXPORTS
    GetCpuSpeed

现在我重新编译 DLL2。要检查发生了什么变化,我再次运行 dumpbin

Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Dump of file dll2.dll

File Type: DLL

  Section contains the following exports for DLL2.dll

           0 characteristics
    403F2B8D time date stamp Fri Feb 27 08:35:41 2004
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00001010 GetCpuSpeed

  Summary

        4000 .data
        1000 .rdata
        1000 .reloc
        4000 .text

这表明出口 #1 现在被命名为 GetCpuSpeed。所以我将新的 Dll2.dll 文件复制到 VB 目录,运行 VB 应用,然后点击按钮。我看到了这个

成功了!我有一个 VB 应用程序正在调用从 VC++ DLL 导出的函数。但是对于 VC++ 应用程序呢?VC++ 应用程序可以调用与 VB 应用程序相同的 DLL 吗?

步骤 4

我将 EXE 代码从第一部分复制过来,删除测试 C++ 类的代码,编译并运行它。按下按钮时,我看到的是这个

所以,我有一个 DLL 可以被 VC++ 应用程序和 VB 应用程序调用。这是通过使用 **模块定义 (.DEF) 文件** 来实现的。

步骤 5

由于 DLL 现在有了 .DEF 文件,因此不再需要 __declspec(dllexport)__declspec(dllimport)。我将 DLL2.h 修改为:

#ifndef DLL2_H
#define DLL2_H

///////////////////////////////////////////////////////////////////////////////
// This function is exported from the DLL2.dll
int __stdcall GetCpuSpeed();

#endif //DLL2_H

并更新 DLL2.cpp

关键概念

  • 为了避免名称混淆问题并避免在 VB 中使用 Alias 函数名,请使用 **模块定义 (.DEF) 文件**。
  • 使用 .DEF 文件时,无需使用 __declspec(dllexport)__declspec(dllimport)
  • 对于 VB 调用过的函数,请使用 __stdcall
  • .DEF 文件实现的 DLL 可以被 VC++ 和 VB 程序调用(无需使用 Alias)。

演示

EXE2.exeVB2.exe 演示程序测试了 DLL2.dll 中的 GetCpuSpeed() 函数

修订历史

版本 1.1 - 2009 年 10 月 3 日

  • 为第二部分添加了字符串示例。

版本 1.0 - 2004 年 2 月 29 日

  • 首次公开发布

用法

此软件发布至公共领域。您可以随意使用它,但不得出售此源代码。如果您修改或扩展它,请考虑将新代码在此处发布供大家分享。此软件按“原样”提供,不附带任何明示或暗示的保证。对于本软件可能造成的任何损坏或业务损失,本人概不负责。

© . All rights reserved.