C++Builder 工程里使用 Visual C++ DLL

 

在 C++Builder 工程里调用 DLL 函数

  调用 Visual C++ DLL 给 C++Builder 程序员提出了一些独特的挑战。在我们试图解决 Visual C++ 生成的 DLL 之前,回顾一下如何调用一个 C++Builder 创建的 DLL 可能会有所帮助。调用 C++Builder 创建的 DLL 要比 Visual C++ 的少了许多障碍。

  为了在你的 C++Builder 工程里调用 DLL,你需要三种元素:DLL 本身,带有函数原型的头文件,和引入库(你可以在运行时载入 DLL,而不是使用引入库,但为了简单我们按引入库的方法做)。调用 DLL 函数,首先通过选择菜单 Project | Add to Project 的方法,把引入库添加到你的 C++Builder 工程里;其次,在需要调用 DLL 函数的 C++ 源文件里为 DLL 头文件插入 #include 声明;最后添加调用 DLL 函数的代码。

  程序清单 A 和 B 包含了做为测试 DLL 的源代码。注意,测试代码实现了两种不同的调用习惯(__stdcall 和 __cdecl)。这样帮是有充分的理由的。当你设法调用一个用 Visual C++ 编译的 DLL 时,大多让你头疼的事情都是由于处理不同的调用习惯产生的。还要注意一点,有一个函数,它没有明确列出使用的调用习惯。这个未知函数作为不列出调用习惯的 DLL 函数的标识。

//------------------------------------------
// Listing A: DLL.H

#ifdef __cplusplus
extern "C" {
#endif

#ifdef _BUILD_DLL_
#define FUNCTION __declspec(dllexport)
#else
#define FUNCTION __declspec(dllimport)
#endif

FUNCTION int __stdcall StdCallFunction(int Value);
FUNCTION int __cdecl CdeclFunction (int Value);
FUNCTION int UnknownFunction(int Value);

#ifdef __cplusplus
}
#endif


//------------------------------------------
//Listing B: DLL.C

#define _BUILD_DLL_
#include "dll.h"

FUNCTION int __stdcall StdCallFunction(int Value)
{
return Value + 1;
}

FUNCTION int __cdecl CdeclFunction(int Value)
{
return Value + 2;
}

FUNCTION int UnknownFunction(int Value)
{
return Value;
}

  从清单 A 和 B 创建测试 DLL,打开 C++Builder,选择菜单 File | New 调出 Object Repository。选择 DLL 图标,单击 OK 按钮。C++Builder 会创建一个新的工程,带有一个源文件。这个文件包含一个 DLL 的入口函数和一些 include 声明。现在选择 File | New Unit。保存新的单元为 DLL.CPP。从清单 A 拷贝粘贴文本插入头文件 DLL.H。从清单 B 拷贝代码,把它插入 DLL.CPP。确定 #define _BUILD_DLL_ 位于 #include "DLL.H" 声明的上面。

  保存工程为 BCBDLL.BPR。接下来,编译工程,看看生成的文件。C++Builder 生成了一个 DLL 和以 .LIB 为扩展名的引入库。

  这时,你有了在 C++Builder 里调用 DLL 所需的三个元素:DLL 本身,带有函数原型的头文件,用来连接的引入库。现在我们需要一个用来调用 DLL 函数的 C++Builder 工程。在 C++Builder 里创建一个新的工程,保存到你的硬盘上。从 DLL 工程目录里拷贝 DLL、引入库、DLL.H 头文件到新的目录。其次,在主单元里添加 #include 声明,包含 DLL.H。最后,添加调用 DLL 函数的代码。清单 C 列出了调用由清单 A 和 B 生成的 DLL 中每个函数的代码。

//------------------------------------------
// Listing C: MAINFORM.CPP - DLLTest program
#include <vcl/vcl.h>
#pragma hdrstop

#include "MAINFORM.h"
#include "dll.h"
//---------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
int Value = StrToInt(Edit1->Text);
int Result= StdCallFunction(Value);
ResultLabel->Caption = IntToStr(Result);
}
//---------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
int Value = StrToInt(Edit1->Text);
int Result= CdeclFunction(Value);
ResultLabel->Caption = IntToStr(Result);
}
//---------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
int Value = StrToInt(Edit1->Text);
int Result= UnknownFunction(Value);
ResultLabel->Caption = IntToStr(Result);
}

Visual C++ DLL 带来的问题

  在理想世界里,调用 Visual C++ 创建的 DLL 不会比调用 C++Builder 建造的 DLL 难。不幸地,Borland 和 Microsoft 有几点不一致的地方。首先,Borland 和 Microsoft 在 OBJ 和引入库的文件格式上不同(Visual C++ 使用 COFF 库格式,而 Borland 使用 OMF 格式)。这就意味着你不能把一个 Microsoft 生成的引入库添加到C++Builder 的工程里。感谢 Borland IMPLIB 这个实用工具,文件格式的不同得以克服。

  两个产品在连接名字(linker name)习惯上也不同。这是 C++Builder 调用 Visual C++ DLL 的主要障碍。在 DLL 或 OBJ 里的每一个函数有一个连接名字。连接器用连接名字在连接期间解决(resolve)声明了原型的函数。如果连接器不能找到它认为是程序需要的连接名字的函 数,它将产生一个未解决的外部错误(unresolved external error)。

  关于函数连接名字,Borland 和 Microsoft 在下面两点上不同:

  • 1- Visual C++ 有时修饰导出的 __stdcall 函数。
  • 2- Borland C++Builder 在引入这个被修饰的函数时,认为是 __cdecl 函数。

  那么,这件事为什么这样重要呢?拿分歧#1 __stdcall 调用习惯来说。如果你用 Visual C++ 创建了一个 DLL,它包含一个 __stdcall 修饰的函数叫做 MyFunction(),Visual C++ 将给函数一个连接名字,为 _MyFunction@4。当 Borland 连接器设法解决调用构造这个函数的时候,它认为要找一个名为 MyFunction 的函数。因为 Visual C++ DLL 引入库不包含叫作 MyFunction 的函数,Borland 连接器报告一个未解决的外部错误,意识是没有找到函数。

  解决这三个问题的方法要依赖 Visual C++ DLL 的编译方式。我把整个过程分为四步。

第1步:识别在 Visual C++ DLL 里使用的调用习惯

  为了与命名习惯缠结交战,你必须首先确定在 DLL 里函数使用的调用习惯。你可以通过查看 DLL 的头文件来确定。在 DLL 头文件里的函数原型形式如下:

  __declspec(dllimport) void CALLING_CONVENTION MyFunction(int nArg);

  CALLING_CONVENTION 应该是 __stdcall 或 __cdecl(具体例子参见清单 A)。很多时候,调用习惯没有被指定,在这种情况下默认为 __cdecl。

第2步:检查 DLL 里的连接名字

  如果在第 1 步中显示 DLL 利用 __stdcall 调用习惯,你需要进一步检查 DLL,确定 Visual C++ 在创建它时采用的命名习惯。Visual C++ 默认情况下要修饰 __stdcall 函数,但如果写这个 DLL 的程序员在他们的工程里增加一个 DEF 文件,可以阻止命名修饰。如果供应商没有使用 DEF 文件,你的工会稍微繁琐一些。

  命令行工具 TDUMP 允许你检查 DLL 导出函数的连接名字。下面向 DLL 调用 TDUMP 的命令。

  TDUMP -ee -m MYDLL.DLL > MYDLL.LST

  TDUMP 能报告许多关于 DLL 的信息。我们仅对 DLL 的导出函数感兴趣。-ee 命令选项指示 TDUMP 仅列出导出信息。-m 开关告诉 TDUMP 按 DLL 函数的原始格式显示。如果没有 -m 开关,TDUMP 将尝试把修饰过的函数转化为人们易读的格式。如果 DLL 很大的话,你应该重定向 TDUMP 的输出到一个文件里(通过附加的 > MYDLL.LST)。

  TDUMP 为源程序清单 A 和 B 的测试 DLL 输出如下:

  Turbo Dump Version 5.0.16.4 Copyright (c) 1988, 1998 Borland International
  Display of File DLL.DLL

  EXPORT ord:0000='CdeclFunction'
  EXPORT ord:0002='UnknownFunction'
  EXPORT ord:0001='_StdCallFunction@4'

  注意在 __stdcall 函数上的前缀下划线和后缀 @4。__cdecl 和未指定调用方式的函数没有任何修饰符。如果 Visuall C++ DLL 编译的时候带 DEF 文件,在 __stdcall 函数上的修饰符将不会出现。

第3步:为 Visual C++ DLL 生成一个引入库

  这是关键部分。由于 C++Builder 和 Visual C++ 的库文件格式不同,你不能把 Visual C++ 创建的引入库添加到你的 C++Builder 工程里。你必须用随 C++Builder 一起发行的命令行工具创建一个 OMF 格式的引入库。依靠上面两步得出的结论,这一步或者很顺利,或者需要一些时间。

  如前面所述,C++Builder 和 Visual C++ 在关于怎样给 DLL 函数命名上是不一致的。由于命名习惯的不同,如果 C++Builder 和 Visual C++ 对 DLL 调用习惯的实现不一致,你需要创建一个带有别名的引入库。表 A 列出了不一致的地方。

表A:Visual C++和C++Builder命名习惯

调用习惯    VC++ 命名       VC++ (使用了DEF)    C++Builder 命名
-----------------------------------------------------------------
__stdcall _MyFunction@4 MyFunction MyFunction
__cdecl MyFunction MyFunction _MyFunction

  C++Builder 栏列出 Borland 连接器想要找的连接名字。第一个 Visual C++ 栏列出 Visual C++ 工程里没有使用 DEF 文件时的连接名字。第二个 Visual C++ 栏包含了使用 DEF 文件时 Visual C++ 创建的连接名字。注意,两个产品仅在一种情况下一致:Visual C++ 工程包含 DEF 文件的 __stdcall 函数。下一关,你需要创建一个带有别名的引入库,使 Visual C++ 命名与 C++Builder 命名相一致。

表 A 显示出几种你在创建引入库时可能需要处理的组合。我把组合分成两种情况。

第 1 种情况:DLL 只包含 __stdcall 函数,DLL 供应商利用了 DEF 文件

  表 A 显示,仅当 DLL 使用了 __stdcall 函数时 VC++ 和 C++Builder 是一致的。而且,DLL 必须带有 DEF 文件编译,以防止 VC++ 修饰连接名字。头文件会告诉你是否使用了 __stdcall 调用习惯(第 1 步),TDUMP 将显示函数是否被修饰(第 2 步)。如果 DLL 包含没有被修饰的 __stdcall 函数,Visual C++ 和 C++Buidler 在给函数命名上保持一致。你可以运行 IMPLIB 为 DLL 创建一个引入库。不需要别名。

IMPLIB 的命令格式如下:

  IMPLIB (destination lib name) (source dll)

例如:

  IMPLIB mydll.lib mydll.dll

第 2 种情况:DLL 包含 __cdecl 函数或者被修饰的 __stdcall 函数

  如果你的 DLL 供营商坚持创建于编译器无关的 DLL,你很幸运地可以把它归入第 1 种情况。不幸地,有几种可能使你不能把它归入第 1 种情况。第一,如果 DLL 供应商在函数声明的时候省略了调用习惯,则默认为 __cdecl,__cdecl 强迫你进入情况 2。第二,即使你的供应商利用了 __stdcall 调用习惯,他们可能忽视了利用 DEF 文件去掉 Visual C++ 的修饰符。

  然而你找到了这里,Good Day,欢迎来到第 2 种情况。你被用一个函数名与 C++Builder 不同的 DLL 困住。摆脱这个麻烦的唯一办法就是创建一个引入库,为 Visual C++ 的函数名定义一个和 C++Builder 的格式兼容的别名。幸运地,C++Builder 命令行工具允许你创建一个带有别名的引入库。

  第一步,用 C++Builder 带的 IMPDEF 程序给 Visual C++ DLL 创建一个 DEF 文件。IMPDEF 创建的 DEF 文件可以列出 DLL 导出的所有函数。你可以这样调用IMPDEF:

  IMPDEF (Destination DEF file) (source DLL file)

例如:

  IMPDEF mydll.def mydll.dll

  运行 IMPDEF 之后,选择一个编辑器打开产生的 DEF 文件。对用 Visual C++ 编译源程序清单 A 和 B 生成 DLL,IMPDEF 创建的 DEF 文件如下:

  EXPORTS
   ; use this type of aliasing
   ; (Borland name) = (Name exported by Visual C++)
   _CdeclFunction = CdeclFunction
   _UnknownFunction = UnknownFunction
   StdCallFunction = _StdCallFunction@4

  下一步将修改 DEF 文件,让 DLL 函数的别名看起来和 C++Builder 的函数一样。你可以这样创建一个 DLL 函数的别名,列出一个 C++Builder 兼容的名字,后面接原始的 Visual C++ 连接名字。对于程序清单 A 和 B 的测试 DLL 来说,带别名的 DEF 如下:

  EXPORTS
   ; use this type of aliasing
   ; (Borland name) = (Name exported by Visual C++)
   _CdeclFunction = CdeclFunction
   _UnknownFunction = UnknownFunction
   StdCallFunction = _StdCallFunction@4

  注意,在左边的函数名与表 A 中 Borland 兼容的名字相匹配。在右边的函数名是真实的 Visual C++ DLL 函数的连接名字。

  最后一步将从别名 DEF 文件创建一个别名引入库。你又要靠 IMPLIB 实用程序了,只是这一次,用别名 DEF 文件做为源文件代替它原来的 DLL。格式为:

  IMPLIB (dest lib file) (source def file)

例如:

  IMPLIB mydll.lib mydll.def

  创建了引入库,还要继续进行到第四步。你首先应该检查引入库,以保证每一个 DLL 函数与 C++Builder 具有一致的命名格式。你可以用 TLIB 实用程序检查引入库。

  TLIB mydll.lib, mydll.lst

为测试 DLL 生成的列表文件如下:

    Publics by module

StdCallFunction size = 0
StdCallFunction

_CdeclFunction size = 0
_CdeclFunction

_UnknownFunction size = 0
_UnknownFunction

第 4 步:把引入库添加到你的工程里

  一旦你为 Visual C++ DLL 创建了一个引入库,你可以用菜单 Project | Add to Project 把它添加到你的 C++Builder 工程里。你使用引入库的时候不必考虑它是否包含有别名。把这个引入库添加到你的工程里的之后,建造(build)你的工程,看看是不是可以成功的连接。

结束语:

  这篇文章为你示范了如何在 C++Builder 工程里调用 Visual C++ DLL 的函数。这些技巧对 C++Builder 1 和 C++Builder 3,Visual C++ 4.x 或 Visual C++ 5 创建的 DLL 生效(我还没有测试 Visual C++ 6)。

  你可能注意到,这篇文章仅讨论了如何调用 DLL 里 C 风格的函数。没有尝试去做调用 Visual C++ DLL 对象的方法。因为对于成员函数的连接名字被改编(mangled),C++ DLL 表现出更加困难的问题。编译器要使用一种名字改编(name mangling)方案,以支持函数重载。不幸地,C++ 标准没有指定编译器应当如何改编类的方法。由于没有一个严格的标准到位,Borland 和 Microsoft 各自为名字改编发展了他们自己的技术,并且两者的习惯是不兼容的。在理论上,你可以用同样的别名技术调用位于 DLL 里的一个类的成员函数。但你应该考虑创建一个 COM 对象来代替。COM 带来了许多它自己的问题,但它强制执行以一种标准方式调用对象的方法。由 Visual C++ 创建的 COM 对象可以在任一开发环境里被调用,包括 Delphi 和 C++Builder。

  C++Builder 3.0 引入了一个新的命令行实用程序叫做 COFF2OMF.EXE。这个实用程序可以把 Visual C++ 引入库转化为 C++Builder 的引入库。此外,对 __cdecl 函数,这个程序还会自动的产生从 Visual C++ 格式到 C++Builder 格式的别名。如果 DLL 专用 __cdecl 调用习惯,自动别名可以简化第 3 步。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C Builder是一种集成开发环境(IDE),用于开发Windows平台的应用程序。C Builder提供了一个强大的图形用户界面(GUI)设计器和一个可视化编程环境,使开发者能够轻松地创建和构建应用程序。 DLL(动态链接)是一种可执行文件,包含在多个应用程序中的可重复使用的功能和代码。DLL文件可以在运行时被动态地加载和链接到应用程序中,以提供特定的功能或服务。 因此,C Builder DLL demo是一个使用C Builder开发的示例程序,演示了如何使用DLL文件实现特定功能。这个示例程序可能包含一个使用C Builder构建的界面,用户可以通过界面与DLL文件进行交互。 C Builder DLL demo的目的是向开发者展示如何使用C Builder来创建一个使用DLL文件的程序。它可能包含了示例代码和文档,解释了如何加载、链接和调用DLL函数。 通过这个示例程序,开发者可以学习如何使用C Builder创建应用程序,并使用DLL文件来扩展和增强应用程序的功能。他们可以了解DLL的概念、使用DLL的优势以及如何与DLL进行交互。 总而言之,C Builder DLL demo是一个使用C Builder开发的示例程序,旨在向开发者展示如何使用DLL文件来实现特定功能,以帮助他们学习和掌握C Builder开发环境的使用。 ### 回答2: C Builder DLL Demo 是一个使用 C++ Builder 开发的演示程序,用来展示如何创建并使用动态链接DLL)。 动态链接是一种可重复使用的组件,可以被多个程序共享使用。C Builder 是一个用于快速开发 Windows 应用程序的集成开发环境,它提供了方便的工具和来创建 DLL。 在 C Builder DLL Demo 中,首先需要创建一个新的 DLL 项目。通过设置项目的属性,可以选择生成 DLL 的类型和平台。 在项目中,开发者可以添加自定义的函数和类,用来实现特定的功能。这些函数和类可以在 DLL 中进行封装,供其他应用程序调用。 在开发完成后,开发者可以编译和构建 DLL 项目。C Builder 提供了简单易用的构建工具,方便生成可执行的 DLL 文件。 在使用 DLL 的应用程序中,需要先加载 DLL 文件。一般通过调用 LoadLibrary 函数来加载 DLL,并获取 DLL 中导出的函数和类。 加载 DLL 后,应用程序可以使用 DLL 中导出的函数和类来调用其中的功能。通过调用函数或实例化类的对象,应用程序可以与 DLL 进行交互,并获取所需的功能。 C Builder DLL Demo 可以作为学习和理解使用 DLL 的示例程序。通过查看和分析代码,开发者可以了解如何创建和使用 DLL,以及如何在不同的应用程序中共享代码和功能。此外,开发者还可以根据自己的需求,对 Demo 中的代码进行修改和扩展,以实现自己的功能。 总之,C Builder DLL Demo 是一个展示如何创建和使用动态链接的示例程序,通过它可以学习与理解 DLL 的概念和用法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值