分享
 
 
 

在 C++Builder 工程里使用 Visual C++ DLL——第1部分:C函数

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

在 C++Builder 工程里使用 Visual C++ DLL——第1部分:C函数

译者序:

第一次读这篇文章是在 2001 年 10 月,帮我解决了一点小问题。本来不好意思翻译,因为英语水平实在太差。最近发现不少网友在问在 C++Builder 的工程里调用 Visual C++ DLL 的问题,也许是用 C++Builder 的人比以前多了吧。于是把心一横,不就是板儿砖嘛?“抛砖引玉”,希望它能给你帮点小忙,也欢迎指出翻译中的错误。

shadowstar 2003-6-3

shadowstar's home: http://shadowstar.126.com/

source:http://www.bcbdev.com/articles/vcdll.htm

很可能有一天你的老板问你是否能用 C++Builder 创建一个 GUI,调用现有的用 Microsoft Visual C++ 编译的 32 位 DLL。经常地,原始 DLL 的源代码不会提供给你,也许因为 DLL 来自第三方供应商,也可能是 22 岁的实习生不小心从网络上删除了 \DLL\SRC 目录。给你一个 DLL 和头文件,这篇文章为你示范如何在你的 C++Builder 工程里调用这种 DLL。

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

Visual C++ DLL 带来的问题

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

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

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

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

结束语

在 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 步。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有