译者序:

  第一次读这篇文章是在 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 步。

参考:http://developer.51cto.com/art/201001/180477.htm

http://www.360doc.com/content/10/0913/09/1201448_53250808.shtml

在 C++Builder 工程里使用 Visual C++ DLL(3个工具) good的更多相关文章

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

    调用 Visual C++ DLL 给 C++Builder 程序员提出了一些独特的挑战.在我们试图解决 Visual C++ 生成的 DLL 之前,回顾一下如何调用一个 C++Builder 创建的 ...

  2. VC工程里,如何编译汇编语言的文件

    最近老是有朋友问,VC工程里,如何编译汇编语言的文件,接下来就说一下步骤: 1.将汇编语言文件,加入到工程里: 2.在Solution Explorer窗口中右键单击Visual C++项目,选择Bu ...

  3. 清理iOS工程里无用的图片,可瘦身ipa

    工程在经过多人后,往往会出现较多的垃圾,导致打包出来的ipa文件偏大,有时候我们会通过清理代码来给程序瘦身,而瘦身ipa效果明显的,主要通过清理程序里的无用图片. 推荐一个清理图片的应用 https: ...

  4. 向STM32 CUBE MX 生成的工程里移植stemwin

    我参考这个文章做的: http://bbs.armfly.com/read.php?tid=1678 这次添加的是没有os的版本 另外跟用不用hal库没关系 1. keil自带了emwin 2. 用c ...

  5. VS工程里的文件都是啥?如何打包? 2015-03-04

    打完补充:以下内容全部是我一家之言,只是愿意分享,内容如有不妥还请见谅. ====================================================== 刚才接收了一份代 ...

  6. ios 向工程里添加Fonts

    ios 向工程里添加Fonts 1.网上搜索字体文件(后缀名为.ttf,或.odf),假如你加入的字体为微软雅黑 2.把字体库导入到工程的resouce中 3.然后在你的工程的Info.plist文件 ...

  7. Altium Designer同一个工程里不同原理图导入到不同的PCB

    问题: 在用Altium Designer进行PCB工程设计时,有时一个工程里可能不止一块PCB,比如,一个设备里有主板和扩展板或者按键板等等,这时就需要在一个工程里添加多个PCB文件.如图: 我们知 ...

  8. android 工程里缺少 R.java 文件原因和解决方法

    作为新手,学习android 的时候难免要导入一些示例,目的为了更加了解android各种API用法,顺便也可以学习下别人代码的写法. 可是导入android源码后,基本都有错误,R.java也不会自 ...

  9. 解决将龙邱oled库移植到野火工程里,oled汉字无法显示问题

    第一,检查oled是否和单片机控制引脚正确相连. GND VCC CLK:时钟信号 miso RST: DC:DATE COMMAND/CONTROL CS:CHIP SELECT 第二,检查工程里是 ...

随机推荐

  1. js 高阶函数 filter

    filter用于过滤array中的一些值,通过带入的函数返回的ture 或false 保留或去除,返回一个新的array filter 使用演示:判断筛选出array中的素数: //判断素数自定义函数 ...

  2. json、xml ---- 数据格式生成类

    自己写的一个生成json/xml 格式数据的类,可用于api数据传输: <?php class Response{ /** *生成指定数据格式 *@param intval $code 状态码 ...

  3. 第五篇:python高级之面向对象高级

    python高级之面向对象高级   python高级之面向对象高级 本节内容 成员修饰符 特殊成员 类与对象 异常处理 反射/自省 单例模式 1.成员修饰符 python的类中只有私有成员和公有成员两 ...

  4. rabbitmq 消息持久化之receive and send

    二: 任务分发 &消息持久化   启用多个接收端的时候如果某一个receive 关闭要保证消息有反馈是否收到   send端 #-*- coding: UTF-8 -*-import pika ...

  5. Fileupload控件导致500错误

    问题: 今天遇到一个问题,用Fileupload控件上传Excel文件,用一个button控件调用“FileUpload1.SaveAs”方法,点击按钮后出现服务器500错误.如下图: 解决方法: 在 ...

  6. ViewPager + Fragment 实现类微信界面

    在如今的互联网时代,微信已是一个超级App.这篇通过ViewPager + Fragment实现一个类似于微信的界面,之前有用FragmentTabHost实现过类似界面,ViewPager的实现方式 ...

  7. Android--WebView控件

    WebView 一 简介: WebView一般用于将Android页面已HTML的形式展现,我们一般叫它HTML5开发: WebView可以使得网页轻松的内嵌到app里,还可以直接跟js相互调用,通过 ...

  8. 网站如何防Session冒名顶替和cookie防篡改

    做网站难免要面对安全性的问题,诸如sql注入拉,cookie冒名拉,等等,sql注入算是老生常谈,翻翻旧账有不少优秀的帖子在说明这个问题,所以我们来说说Session冒名顶替的风险以及应对的办法. 首 ...

  9. CentOS 7设置iptables防火墙开放proftpd端口

    由于ftp的被动模式是这样的,客户端跟服务器端的21号端口交互信令,服务器端开启21号端口能够使客户端登录以及查看目录.但是ftp被动模式用于传输数据的端口却不是21,而是大于1024的随机或配置文件 ...

  10. 微信小程序开发之 下拉刷新,上拉加载更多

    本文记载了如何在微信小程序里面实现下拉刷新,上拉加载更多 先开看一下界面 大致如此的界面吧. 这个Demo使用了微信的几个Api和事件,我先列出来. 1.wx.request (获取远程服务器的数据, ...