订阅所有文章
文章搜索

高级搜索这是社么?这是顶尖最新推出的文章增强型搜索功能!
全网 本站
您现在的位置: 顶尖设计 >> IT学院 >> 编程开发 >> VC >> 文章正文

在C++Builder里创建可以被Visual C++使用的DLL

作者:shadowst…  来源http://www.bcbdev.com/articles  点击  更新:2006-12-19 7:07:20  编辑: 画王w  字体

在C++Builder里创建可以被Visual C++使用的DLL shadowstar's home: http://shadowstar.126.com/

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

  在前两篇文章里,我们讨论了如何在C++Builder工程里调用用MS Visual C++创建的DLL。这篇文章讨论相反的一种情形,举例说明如何用C++Builder创建一个DLL,使它可以在Visual C++工程里调用。

  • 简介: 为什么这个这么难
  • 指导方针摘要
  • 例1: 隐式连接
  • 例2: 显式连接
  • 例3: 用#define组装隐式连接
  • 例4: 用stdcall函数隐式连接
  • 结论

简介:为什么这个这么难

  如果你用BCB创建了一个DLL,它可以被BCB的可执行文件调用,你知道这种使用DLL的方式没什么难度。当你构造一个DLL,BCB生成一个带“.LIB”扩展名的引入库。把这个LIB文件添加到你的工程里。连接器按引入库决定DLL内部调用。当你运行你的程序时,DLL隐式的被载入,你不必去考虑DLL内部调用工作是。

  当EXE文件是由Microsoft Visual C++编译的时候,情况会变得比较复杂。有3个主要的问题。首先,BCB和MSVC对DLL中的函数命名方式是不一致的。BCB使用一种习惯,MSVC使用另一种不同的习惯。当然,两种习惯是不兼容的。命名问题在如何在C++Builder工程里使用VC++编译的DLL那篇文章里已经讨论过了。表1总结了各个编译器在各自的调用习惯下,导出的MyFunction函数。注意Borland给__cdecl函数前加了一个下划线,而MSVC没有。另一方面,MSVC认为导出的__stdcall函数前面带有下划线,后面还有一些垃圾。

表1: Visual C++ and C++Builder 命名习惯



调用习惯       VC++命名       VC++(使用了DEF)     C++Builder命名

-----------------------------------------------------------------------

__stdcall           _MyFunction@4   MyFunction          MyFunction

__cdecl             MyFunction      MyFunction          _MyFunction

  第2个问题是Borland引入库与MSVC不是二进制兼容的。当你编译DLL时,由BCB创建的引入库不能被MSVC用来连接。如果你想使用隐式连接,那么你需要创建一个MSVC格式的引入库。另一种可选择的办法就是采用显式连接(LoadLibrary和GetProcAddress)。

第3个问题是不能从DLL里导出C++类和成员函数,如果你想让MSVC的用户也可以调用它。好吧,那不完全属实。你的DLL能导出C++类,但是MSVC不能使用它们。原因就是C++成员函数名被编译器改编(mangled)。这个改编的名字结果了DLL。为了调用在DLL里被改编的函数,你必需知道被改编的是哪个函数。Borland和Microsoft使用了不同的名字改编方案。结果是,MSVC不能恰好看到Borland编译的DLL里的C++类和成员函数。

Tip 注意:

Borland和Microsoft没有采用相同的方式改编函数,因为依照ANSI C++标准,C++编译器不被假定追随相同的指导方针。名字改编只是实现的细节。


  这三个问题使得Borland创建的DLL可以在MSVC里被调用变得非常困难,但并非不可能的。这篇文章描述了一套指导方针,你可以跟着制作与Microsoft兼容的BCB DLL。我们讨论四种不同的技术。三种采用引入库隐式连接调用,一种在运行时利用显式连接。

指导方针摘要

  你可以跟着下面的指导方针摘要列表建造你的DLL。第1个列表讨论隐式连接;第2个列表描述显式连接;第3种技术采用#define组装隐式连接;最后一个例子利用假的MSVC DLL工程为__stdcall函数创建引入库。

技术1: 隐式连接

------------------------------------------------------------------------------

1- 使用__cdecl调用习惯代替__stdcall。

2- 导出简单的"C"风格函数,没有C++类或成员函数。

3- 确定你有一个 extern "C" {} 包围你的函数原型。

4- 创建DEF文件,包含与Microsoft兼容的导出函数别名。别名也就是不包含前面的下划线。

   DEF文件内容如下:



   EXPORTS

   ; MSVC name    = Borland name

     Foo          = _Foo

     Bar          = _Bar



5- 把DEF文件加入到你的工程里重新编译它。

6- 把DLL和DLL头文件拷贝到你的MSVC工程目录里。

7- 运行impdef为DLL创建第2个DEF文件。这个DEF文件用来创建引入库。

     > impdef mydll.def mydll.dll

8- 运行Microsoft的LIB工具,用上一步创建的DEF文件创建COFF引入库。调用格式为:

     > lib /DEF mydll.def

9- 把用LIB.EXE创建的LIB文件添加到你的MSVC工程里。

技术2: 显式连接
------------------------------------------------------------------------------
1- 使用__cdecl或__stdcall,如果你使用__stdcall可以跳过第4,5步。
2- 导出简单的"C"风格函数,没有C++类或成员函数。
3- 确定你有一个extern "C" {}包围你的函数原型。
4- 如果你使用__cdecl,那么你可能想去掉导出函数前面的下划线,但你不必这么做。你可以用例1的第4,5步去掉下划线。如果你没有去掉下载线,在调用GetProcAddress函数时函数名必须前面的下划线。
5- 把DLL拷贝到MSVC工程目录里。
6- 在MSVC应用程序中,使用LoadLibrary API函数载入DLL。
7- 调用GetProcAddress API在DLL里查找你想要的调用函数,保存GetProcAddress函数返回的函数指针。当你想调用函数的时候,提取函数指针。
8- 当你用完DLL时调用FreeLibrary。

技术3: 用#define组装隐式连接
------------------------------------------------------------------------------
1- 用__cdecl调用习惯代替__stdcall。
2- 导出简单的"C"风格函数,没有C++类或成员函数。
3- 确定你有一个extern "C" {}包围你的函数原型。
4- 在你的DLL头文件里,为每一个导出函数名创建一个#define。
   #define会调用预编译器在每一个函数名前加上下划线。因为我们只想为MSVC创建别名,所以代码检查_MSC_VER。

     #ifdef _MSC_VER
     #define Foo _Foo
     #define Bar _Bar
     #endif

5- 把DLL和DLL头文件拷贝到MSVC工程目录里。
6- 运行impdef为DLL函数DEF文件。
     > impdef mydll.def mydll.dll
7- 使用Microsoft的LIB工具为DEF文件创建COFF格式的引入库。
     >lib /def mydll.def
8- 把LIB.EXE创建的LIB文件添加到MSVC工程里。

技术4: 用__stdcall函数隐式连接
------------------------------------------------------------------------------
1- 当建造你的DLL时使用__stdcall调用习惯。
2- 导出简单的"C"风格函数,没有C++类或成员函数。
3- 确定你有一个extern "C" {}包围你的函数原型。
4- 为MSVC创建一个引入库。这一部分比较困难。你不能用LIB.EXE为__stdcall函数创建引入库。你必须创建一个由MSVC编译的的假的DLL。这样做,按这些步骤:
     4a- 用MSVC创建一个不使用MFC的DLL
     4b- 从BCB里拷贝覆盖DLL头文件和DLL源代码
     4c- 编辑你的DLL源代码,抛开每一个例程的函数体部分,使用一个假的返回值返回
     4d- 配置MSVC工程生成的DLL,采用和BCB DLL同的的名字
     4e- 把DEF文件添加到MSVC工程,禁止它对__stdcall命名进行修饰(_Foo@4)
5- 编译第4步得到的虚假DLL工程。这将会生成一个DLL(你可以把它丢到垃圾筒里)和一个LIB文件(这是你需要的)。
6- 把从第5步得到的LIB文件添加到你需要调用这个BCB DLL的MSVC工程里。LIB文件会确保连接。为MSVC可执行文件配置BCB DLL(不是虚假DLL)。

Tip 注意:

  一般情况下,隐式连接比显式连接要优先考虑,因为对程序员来说隐式连接更简单,而且它是类型安全的(错误发生在连接时而不是运行时)。不管用哪种方法,当你在编译器间共享DLL时,如果你选择坚持使用隐式连接,就必须为每一个编译器创建兼容的引入库。创建兼容的引入库比用显式连增加的负担就是要注意更多的要求。


 

Tip 注意:

  如果你想使你的DLL可以被Visual Basic的开发者使用,显式连接的指导方针同样适用。如果你想把你的DLL给VC开发者,按显式连接的指导方针,采用__stdcall调用习惯。


 

下面4个部分详细描述每一种技术。

例1: 显式连接

  这个例子详细描述了上一部分技术1的指导方针。技术1的指针方针可以分为两组。1-5项处理在BCB这边编译DLL;6-9项处理在MSVC这边使用DLL。我们将沿这条主线分别进行讨论。

  在这个例子里,我们将用BCB建造一个DLL,它导出两个函数: Foo和Bar。两个函数都返回一个整型值。函数原型为:

int Foo (int Value);

int Bar (void);

然后我们在MSVC里建造一个测试EXE,用来调用Borland DLL。

用BCB编译DLL

  下面两个程序清单包含我们的DLL源代码。清单1要在BCB和MSVC之间共享的头文件;清单2包含我们的DLL函数实现部分。创建一个BCB DLL工程,从清单1和2中拷贝代码粘贴到工程里。或者你可以下载这篇文章的源代码以节省时间。BCB DLL工程已经为你设置好了。(参见最下面的下载部分)

// ----------------------------------------------

// Listing 1- DLL header file

#ifndef BCBDLL_H

#define BCBDLL_H



#ifdef __cplusplus

extern "C" {

#endif



#ifdef BUILD_DLL

#define IMPORT_EXPORT __declspec(dllexport)

#else

#define IMPORT_EXPORT __declspec(dllimport)

#endif



IMPORT_EXPORT int __cdecl  Foo  (int Value);

IMPORT_EXPORT int __cdecl  Bar  (void);



#ifdef __cplusplus

}

#endif



#endif

// ----------------------------------------------

// ----------------------------------------------

// Listing 2- DLL source code

#include <windows.h>

#pragma hdrstop



#define BUILD_DLL

#include "bcbdll.h"



int __cdecl  Foo  (int Value)

{

    return Value + 1;

}



int __cdecl  Bar  (void)

{

    static int ret = 0;

    return ret++;

}

// ----------------------------------------------

  关于头文件有两个要注意的地方。首先,观察我们用 extern "C" 的方法确保函数名不会被C++编译器改编;其次,注意到在我们建造DLL时,导出函数有一个特殊指示的前缀__declspec(dllexport)。当我们从MSVC里使用DLL时,函数前缀变为__declspec(dllimport)。这个指示的改变是通过IMPORT_EXPORT宏定义实现的。

    最后,注意我们显式声明了__cdecl为调用习惯。技术上,我们可以省略__cdecl关键字,因为__cdecl已经是默认的。但是,我想不管怎样把它列出来是一个好习惯。通过列出调用习惯,你显式的告诉人们你选择了__cdecl作为一个前提。同样,默认的调用习惯在两个编译器里可以通过编译开关改变。你肯定不想这些编译器开关影响到你DLL的可用性。

    头文件本身满足了指导方针中的1-3项 。我们需要做的下一件事情是处理第4项: 给导出函数建立别名。

    首先,按现在的情况建造DLL代码。其次,运行TDUMP工具检查函数的函数名确实包含前面的下划线。

c:> tdump -m -ee bcbdll.dll

Turbo Dump  Version 5.0.16.12 Copyright (c) 1988, 2000 Inprise Corporation

                    Display of File BCBDLL.DLL



EXPORT ord:0001='_Bar'

EXPORT ord:0002='_Foo'

EXPORT ord:0003='___CPPdebugHook'

 

Tip 注意:

    使用TDUMP时别忘了用 -m 开关。TDUMP尝试反改编(unmangle)被修饰的名字,使他们更容易阅读。但是,当你查看一个DLL的时候,明智的选择是查看函数的原始格式。-m 开关告诉TDUMP显示原始函数名。


    像你看到的那样,Foo和Bar都包含前端下划线。至于__CPPdebugHook,你可以不理它,它是幕后操纵的,当它不存在好了。它对你没什么意义,你也不能让它走开,因此就不要把它放在心上了。

    为了用别名去掉下划线,我们需要做三件事:首先创建DLL的DEF文件;然后调整DEF文件,为Borland名字创建MSVC的别名;最后,把DEF文件添加到你的BCB工程里,重建DLL。

    要创建DEF文件,对DLL运行Borland的IMPDEF工具。

C:> impdef bcbdllx.def bcbdll.dll

    我选择bcbdllx.def为文件名,因为稍后(在我们创建MSVC引入库之前)我们将使用其它DEF文件。我想避免两者混淆。bcbdllx.def内容如下:

LIBRARY     BCBDLL.DLL



EXPORTS

    _Bar                           @1   ; _Bar

    _Foo                           @2   ; _Foo

    ___CPPdebugHook                @3   ; ___CPPdebugHook

    注意到在Foo和Boo前端的下划线。如果DLL把Foo和Bar导出为_Foo和_Bar,当MSVC用户设法建造他们的工程的时候,将看到连接错误。我们需要剥去下划线。我们用在DEF文件里给函数别名的方法实现。

    DEF文件别名允许我们为真实的函数导出担当代理或占位符的函数名。在DLL里的真实的函数仍然是_Foo和_Bar。代理名将是Foo和Bar(注意没有了下划线)。当我们给两个函数别名的时候,DLL将导出两个将的符号,它们归诸于原来的函数。

    完成别名, 编辑DEF文件,改变成下面的样子:

LIBRARY     BCBDLL.DLL



EXPORTS

    Bar = _Bar

    Foo = _Foo

    这个DEF文件创建两个新的出口,Foo和Bar,它们分别担当_Foo和_Bar的点位符。把这个DEF文件保存到你的硬盘上。一旦你完成了这些工作,便可以把DEF文件添加到你的BCB工程里,使用Project-Add菜单项。添加后,BCB会在工程管理器(Project Manager)的树状结构里显示出DEF文件。

    一旦你把DEF文件加入到工程里,做一次完全的重建。工程连接好之后,再次对DLL运行TDUMP,检查从DLL里导出的带下划线函数。

>tdump -m -ee bcbdll.dll

Turbo Dump  Version 5.0.16.12 Copyright (c) 1988, 2000 Inprise Corporation

                    Display of File BCBDLL.DLL



EXPORT ord:0004='Bar'

EXPORT ord:0005='Foo'

EXPORT ord:0002='_Bar'

EXPORT ord:0001='_Foo'

EXPORT ord:0003='___CPPdebugHook'

    对TDUMP的输出有两点要注意的事情要注意。首先,观察Foo和Bar到场了(没有前端下划线)。现在DLL导出函数名与MSVC的一致了。还注意到原来的函数,_Foo和_Bar,还在那儿。被修饰过的函数仍就从DLL里导出。使用DEF文件别名

[1] [2] [3] 下一页

  • 上一篇文章:

  • 下一篇文章:
  •      
    热门文章  
    推荐文章  
    相关文章    
     发表评论
      关于我们 | 联系我们 | 站点地图 | 广告投放 | 友情链接 | 在线留言 | 版权申明
    版权所有 © 2004-2007 顶尖设计(bobd.cn)
    未经授权禁止转载,摘编,复制本站内容或建立镜像. 沪ICP备05002835号