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






4.93/5 (63投票s)
本系列文章将分步指导如何构建包含 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@@YGHXZ
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.exe 和 VB2.exe 演示程序测试了 DLL2.dll 中的 GetCpuSpeed()
函数
修订历史
版本 1.1 - 2009 年 10 月 3 日
- 为第二部分添加了字符串示例。
版本 1.0 - 2004 年 2 月 29 日
- 首次公开发布
用法
此软件发布至公共领域。您可以随意使用它,但不得出售此源代码。如果您修改或扩展它,请考虑将新代码在此处发布供大家分享。此软件按“原样”提供,不附带任何明示或暗示的保证。对于本软件可能造成的任何损坏或业务损失,本人概不负责。