C++builder调用VC dll.docx
《C++builder调用VC dll.docx》由会员分享,可在线阅读,更多相关《C++builder调用VC dll.docx(12页珍藏版)》请在冰豆网上搜索。
C++builder调用VCdll
c++builder调用VC的dll以及VC调用c++builder的dll
解析__cdecl,__fastcall,__stdcall的不同:
在函数调用过程中,会使用堆栈,这三个表示不同的堆栈调用方式和释放方式。
比如说__cdecl,它是标准的c方法的堆栈调用方式,就是在函数调用时的参数压入堆栈是与函数的声明顺序相反的,其它两个可以看MSDN,不过这个对我们编程没有太大的作用
---------------------------------------------------------------
调用约定
调用约定(Callingconvention)决定以下内容:
函数参数的压栈顺序,由调用者还是被调用者把参数弹出栈,以及产生函数修饰名的方法。
MFC支持以下调用约定:
_cdecl
按从右至左的顺序压参数入栈,由调用者把参数弹出栈。
对于"C"函数或者变量,修饰名是在函数名前加下划线。
对于"C++"函数,有所不同。
如函数voidtest(void)的修饰名是_test;对于不属于一个类的"C++"全局函数,修饰名是_test@@ZAXXZ(怎么感觉像乱码?
?
)。
这是MFC缺省调用约定。
由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定的参数,如printf函数。
_stdcall
按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。
对于"C"函数或者变量,修饰名以下划线为前缀,然后是函数名,然后是符号"@"及参数的字节数,如函数intfunc(inta,doubleb)的修饰名是_func@12。
对于"C++"函数,则有所不同。
所有的Win32API函数都遵循该约定。
_fastcall
头两个DWORD类型或者占更少字节的参数被放入ECX和EDX寄存器,其他剩下的参数按从右到左的顺序压入栈。
由被调用者把参数弹出栈,对于"C"函数或者变量,修饰名以"@"为前缀,然后是函数名,接着是符号"@"及参数的字节数,如函数intfunc(inta,doubleb)的修饰名是@func@12。
对于"C++"函数,有所不同。
未来的编译器可能使用不同的寄存器来存放参数。
Dll中用__declspec(dllexport)声明的函数:
__declspec(dllexport)只是表示这个函数是一个DLL导出函数,而__stdcall是一种函数调用约定,两者应该是没有冲突的.
如:
__declspec(dllexport) void __stdcall aTry();
c++builder和vc描述符定义的区别
在c++builder中
__cdecl的函数输出前会带:
"_"
__stdcall无特征,只输出函数名
__fastcall函数输出前带:
"@"
都无"@nn"后缀格式!
在vc中
__cdecl无特征,只输出函数名
__stdcall的函数输出前会带:
"_"后缀带:
"@nn"
__fastcall函数输出前带:
"@"后缀带:
"@nn
c++builder调用VC的dll:
在VC中编写DLL时,使用了.def文件,在出口函数声明时也在前面加上了__declspec(dllexport)说明。
把VC生成的DLL文件放在了当前目录下,使用BCB的命令行工具implib生成的.lib文件,具体格式为implibbcb.libvc.dll,再把implib根据dll生成的LIB文件加入到工程中,再在工程中加入DLL出口函数的声明(函数名前加上了WINAPI,即__stdcall;每个函数定义的最前面也加上了__declspec(dllimport))。
而且由于BCB和VC++成立函数名转换的做法不同。
所以在VC中最好是输出函数为C函数的DLL,如果输出函数是C++类,则可能无法调用。
我的解决办法(经过本人实验证明的,共2种)
方法1:
VC编译c文件生成dll时导出函数头文件加上extern"C"{}关键字,函数声明和定义处再加调用约定描述符__cdecl,然后将函数声明和定义处都加上一个下划线就没有问题了。
EXAMPLE:
假设我VC的dll中包含intmyFunction(void),.c文件中函数实现处的正确写法是:
__declspec(dllexport)int__cdecl_myFunction(void)
{
//addyourcodehere
}
.h文件中函数声明处的正确写法如下
__declspec(dllexport)int__cdecl_myFunction(void);
BCB调用时只要包含lib文件,具体操作步骤:
运行implibbcb.libvc.dll
project->addto...下拉框中选择.lib类型,打开刚才通过implib和vc的dll生成的lib文件
在工程中用到dll的.c源文件中包含该dll的头文件
调用时直接写inti=myFunction();即可。
方法2:
仅对VC编译C文件生成dll时有效,导出函数头文件加上extern"C"{}关键字。
BCB的Project->option->advancedcompiler下的Callingconvention中选择Stdcall就可以直接调用VC的.c文件编译生成的动态链接库了。
VC调用c++builder的dll:
(参考:
MSDN2000)
VC中无LIB时的DLL隐式链接,制作与VC++相符合的LIB函数符号输入库(转)请大家注意!
这种方法只能应用于输出为C格式的__stdcall调用方式!
1.使用VC++的工具DUMPBIN将DLL中的导出函数表导出到一定义(.DEF)文件
EXAMPLE:
DUMPBINVideoDeCoder.dll/EXPORTS/OUT:
VideoDeCoder.def
2.将导出的.DEF文件整理为一符合.DEF个数的函数导出文件(整理过程巨乱巨复杂,懒得举例了,后面有简便方法^_^)
3.使用VC++的LIB工具,带/DEF:
(.def文件名)/MACHINE:
IX86(80X86机器),就输出符合VC++格式的的LIB文件了.
EXAMPLE:
LIB/DEF:
VideoDeCoder.def/MACHINE:
IX86
4.连接时带上LIB文件链接;注意的是当有些动态库DUMPBIN的只有函数名,无"@nn"的参数格式,如C++Builder写的DLL,输出就只有函数名符号,链接时就会报错:
errorLNK2002:
unresolvedexternalsymbol"functionname@nn"
提示程序中引入的函数符号无法识别,这时只要将DEF文件中相应的函数名称改为functionname@nn方式,重新建立LIB,重新链接即可.
这样就制作成功了符合VC调用方式的LIB了!
要值得一说的是!
BORLANDC++BUILDER有一个很好的工具IMPDEF可以直接将DLL中的函数输出到.DEF文件中,这种方法只能应用于输出为C格式的__stdcall调用方式,只要做一点点修改就可以成为符合VC的DEF文件!
IMPDEFxxx.defxxx.dll
只要将BCB的DEF文件中函数申明格式转换为vc识别的格式就可以利用LIB工具生成LIB;要使用C分格输出(extern"C")才是必须的!
而且别忘了在DEF文件中的函数申明不要带“_”啊!
:
)不然会出现errorLNK2001的链接错误!
vc调用bcb的我没试过,不过可以参照上面的格式自己改改好了:
)
@@@@@@@@@@@@@@@@@@@@@@另一种方法@@@@@@@@@@@@@@@@@@@@@@@@
为了在C++Builder程序中调用DLL文件中的函数,你需要满足3个条件:
DLL文件、一个声明了函数原型的头文件和一个输入接口库文件(import library,当然你也可以让程序在运行时调用这个库文件,而不必在编译时调用)。
有了输入接口库文件后就要把它加入到你的C++Builder工程中去,这个操作可以通过点C++Builder主菜单的Project|Add to Project来实现。
下一步,在C++源文件中加入一条#include语句,把头文件包含到C++源文件中去。
最后一步要做的就是添加调用DLL中函数的代码了。
程序列表A和程序列表B中包含了一个DLL文件的源代码,这个DLL文件将作为本文例子程序中的试验DLL之用。
在这个试验DLL的代码中,需要注意的是我使用了两种不同的函数调用方式:
__stdcall和__cdecl。
This is for a very good reason.当你在C++Builder中调用Visual C++编写的DLL文件时,最令人头疼的地方就是VC和C++Builder对函数调用的方式不同。
还有一点需要注意的是代码中也包含一个未注明调用方式的函数:
UnknownFunction(int Value)。
所以我们可以在后面的程序中看看这些不同的函数调用方式声明对函数的DLL调用有什么不同的影响。
//程序列表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
//程序列表B:
DLL.CPP
#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;
}
为了建立这个测试用的DLL文件,请打开C++Builder并新建一个DLL工程。
于是C++Builder为我们创建了一个C++源文件作为DLL的模板,这个文件包含了一个DLL引入函数和一些include语句。
保存这个文件为DLL.CPP。
把程序列表A中的代码复制到头文件DLL.H中,复制程序列表B中的代码,插入到DLL.CPP文件中。
请确认“#define _BUILD_DLL_”语句放到了“#include "dll.h"”语句的前面。
保存这个工程为BCBDLL.BPR。
接着编译这个工程,C++Builder就会为我们生成了一个DLL文件和一个输入接口库文件(扩展名是LIB)。
到现在,你已经有了从C++Builder中调用DLL的3个要素:
DLL文件本身,包含函数声明的头文件和一个用于连接程序的输入接口库文件。
下面,我们就需要再编写一个C++Builder程序来调用这个DLL文件。
好,新建一个C++Builder工程,并保存在你的硬盘的某个目录下。
把刚才生成的DLL文件、头文件和输入接口库文件复制到新建的工程目录下。
在新建的工程中,点C++Builder菜单Project|Add To Project,把输入接口库的LIB文件加入到当前的工程中。
然后,在源程序中加入一条include语句,引入DLL.H文件:
#include "dll.h"。
最后,加入调用DLL函数的代码。
程序列表C中包含的就是调用这个试验DLL中函数的程序代码。
//程序列表C:
MAINFORM.CPP - DLLTest program
#include
#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和调用C++Builder编写的DLL在技术上没有什么难处。
然而,Borland和Microsoft公司在开发工具的设计上有几点是不兼容的。
首先,Borland和Microsoft在目标文件(*.obj)和输入接口库文件(*.lib)的格式上不统一,Visual C++使用一种叫做COFF的库文件格式而Borland使用OMF格式。
这意味着你不可能把Visual C++编写的输入接口库文件加入到C++Builder程序的工程中来。
幸运的是,Borland为我们提供了一个很好的库文件格式转换工具--implib.exe,于是这个不兼容的问题就迎刃而解了。
Visual C++和C++Builder连接器命名规则上也有所不同,这导致了在C++Builder程序中调用Visual C++编写的DLL的最大的问题。
DLL和OBJ文件中的每一个函数都有一个连接时的函数名称。
连接器在编译程序时使用连接器名称处理函数。
如果连接器不能处理某个函数就会产生一个外部错误(external error)。
关于连接器函数命名,Borland和Microsoft有以下几点不兼容:
1、Visual C++一般使用__stdcall作为引出函数的类型。
2、Borland C++Builder使用__cdecl作为引入函数的类型。
这样会有多大的问题呢?
如果你用Visual C++编写了一个包含__stdcall类型函数MyFunction()的DLL文件,编译时Visual C++将会给这个函数一个类似于这样的连接器名称:
_MyFunction@4。
当C++Builder的连接器努力处理这个DLL中的函数时,它会去查找一个名为MyFunction的函数。
由于Visual C++编写出来的DLL中不含有这个名字的函数,所以C++Builder的连接器就会报出连接错误--“an unresolved external error”。
对这个问题的解决要看我们如何编译这个Visual C++的DLL了。
我用了下面4个步骤终于攻破了这个难关。
第一步:
明确Visual C++ DLL函数的调用方式
为了解决两种编程工具的命名冲突,你必须首先了解它们各自对DLL函数的调用方式。
可以通过研究头文件中对函数的声明方式得知这一点。
DLL头文件中对函数原型的声明类似于下面的方式:
__declspec(dllimport) void CALLING_CONVENTION MyFunction(int nArg);
CALLING_CONVENTION应该是__stdcall或者__cdecl(具体例子请参看程序列表A)。
有时,CALLING_CONVENTION会被忽略掉,在这种情况下,CALLING_CONVENTION默认为__cdecl。
第二步:
确定DLL中的连接器名称
如果在第一步中已经得知DLL文件中使用__stdcall方式调用函数,你仍然需要再检查一下Visual C++连接DLL时使用的函数命名规则。
默认情况下,Visual C++会对__stdcall类型的函数进行一些修改以生成连接器函数名称。
但是如果你的工程中包含有一个DEF文件(DEF文件是模块定义文件,供生成动态链接库时使用)的话就可以避免Visual C++的这些修改。
否则的话,我们下面的工作就会更加麻烦了。
命令行工具tdump.exe可以用来检测DLL引出函数的连接器名称。
你可以使用下面的方式执行这个命令:
TDUMP -ee MYDLL.DLL > MYDLL.LST
TDUMP会为我们报告很多有关这个DLL文件的信息。
在这里,我们只对DLL的引出函数感兴趣。
命令行中的“-ee”参数告诉TDUMP只需列出DLL引出函数的信息。
如果DLL文件很大,那么可以把TDUMP工具输出的结果重定向到一个文本文件中,如:
MYDLL.LST。
TDUMP工具对上面那个试验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'
从这个分析结果可以看出,StdCallFunction函数名称字符串的最前面被增加了一个下划线,最后面增加了一个“@4”字符串,而CdeclFunction和UnknownFunction函数并没有被做任何的修改。
如果Visual C++ DLL被编译时使用了DEF文件,这里对所有引出函数的名称就不会做任何修改了。
第三步:
为Visual C++ DLL生成输入接口库文件
这是最困难的一步。
由于C++Builder和Visual C++的库文件格式不同,因此你不能直接把Visual C++的输入接口库文件引入到C++Builder工程中来。
我们在上面提到了Visual C++在目标文件(*.obj)和输入接口库文件(*.lib)中使用COFF格式,而C++Builder使用了OMF格式,因此我们要使用C++Builder的一个命令行工具把Visual C++的库文件转换成使用OMF格式。
上面已经说明,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和Visual C++各自的连接器对函数命名的区别。
只有当两个编程工具都使用__stdcall类型的引出函数并且VC++使用DEF文件时,它们的连接器函数命名方式才是相同的。
这篇文章下面的部分就是告诉你如何