关于Delphi中调用C语言动态链接库的问题

dandycheung 2000-08-23 10:39:00
用 C 编写了一个动态链接库,其中输出了一个要用 char * 作为参数的函数,在 delphi 中调用时采用如下形式:

fxn(PChar('Some chars'));

当 PChar 中强制转换的字符串仅包含一个字符时(如:'a')调用失败,而只要多于一个字符则正常,敢问是什么原因?

题外话:我觉得 delphi 本是个不错的东西,但 Borland 把它做得太糙了,在细微的地方经常出毛病,这一点不如 VC++。
...全文
709 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
JGTM2000 2000-09-05
  • 打赏
  • 举报
回复
而且这句话:“相反的,我不动我的DLL,把delphi中对我DLL的输出函数原型中的参数类型由PChar改为Integer,调用的时候用Integer(@StrBuffer)的形式传入到DLL中,执行结果完全正常”。稍微明白一点Delphi的同志都应该能看出来前后是没有任何差别的,计算机执行程序是不知道什么PChar还是Integer的,总之这是一个32bit DWORD,类型无非编译器的概念。所以为什么结果忽对忽错,我虽然还是不能确认,但总不宜就说是开发环境的错误吧。
JGTM2000 2000-09-04
  • 打赏
  • 举报
回复
请注意前例中'Test'在需要接受PChar类型参数处的直接使用。
JGTM2000 2000-09-04
  • 打赏
  • 举报
回复
我C是不好,不过汇编不错。我不明白的是你说的这句话:"delphi中传入的参数原封不动的传入到MessageBox函数里,结果照样出错。您知道,MessageBox函数是必然会将该值作为字符缓冲区的首址"。怎么我编同样的程序就没有任何错误,我想只可能是您有粗心的错误。另外,fxn(PChar(String('a constant string')))和直接写fxn('a constant string')是没有区别的,如果有一个常量const s = 'a constant string',或者const s:PChar = 'a constant string',或者const s: array [0..255] of char = 'a constant string',则fxn(s)都是正确的。只有当s的类型为显式的string({$H+}时等同于AnsiString),需要用PChar(s)的形式强制转换(注:改转换的目的仅是为了骗过编译器,因为s作为指针时其目标地址的布局和PChar是完全兼容的)。

为了进一步说明问题不在Delphi,我们找一个C写成的Win32 API来测试一下:

implementation

const
sConst = 'a';
pcConst : PChar = 'a';
caConst0: array [0..0] of char = 'a'; // !!
caConst1: array [0..1] of char = 'a';
strConst: string = 'a';
cConst: char = 'a';
nullChar: char = #0;

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
// asm int 3; end; // comment/uncomment this line to trigger breakpoint here
Windows.MessageBox(Handle,sConst,'Test',MB_OK);
Windows.MessageBox(Handle,pcConst,'Test',MB_OK);
Windows.MessageBox(Handle,caConst0,'Test',MB_OK); // cause incorrect tail
Windows.MessageBox(Handle,caConst1,'Test',MB_OK);
Windows.MessageBox(Handle,PChar(strConst),'Test',MB_OK);
Windows.MessageBox(Handle,@cConst,'Test',MB_OK); // cause incorrect tail
Windows.MessageBox(Handle,@nullChar,'Test',MB_OK);
Windows.MessageBox(Handle,nil,'Test',MB_OK);
end;

什么情况编译和结果都正确,为什么那两句结果不正确,希望大家好好体会一下(最好在CPU窗口中研究一下内存布局,相信体会更深)。BTW, 评论一件产品(或一个人)的时候,最好先确保自己完全理解他,否则坏了人家的名声多冤呀。哈哈

dandycheung 2000-08-28
  • 打赏
  • 举报
回复
各位好,请原谅我的粗心,kxy 是对的,应该以fxn(PChar(String('Some chars')));这样的形式来调用,我原来试的时候把代码改了一下没有编译就执行了(并且还不在集成环境中)。昨天又试才发现。JGTM2000 朋友非常热心,但可能由于对 C 语言了解不够,所以未能找到问题要害。
顺便再问一下,为什么在 Delphi 的集成环境中不能调试对 Oracle 数据有操作的程序?无论是使用 ADO 还是其他控件我都能遇见这个问题。
JGTM2000 2000-08-26
  • 打赏
  • 举报
回复
From: <dandycheung>
To: <JGTM2000>

> 您好,
> 首先为您能关注我的问题致谢。
> 您的考虑有一定的正确之处,但好像和我的问题不能吻合。
> 我为了验证这一问题,曾经专门另外做了一个DLL,输出的函数里面只有一行代码,就是将delphi中传入的参数原封不动的传入到MessageBox函数里,结果照样出错。您知道,MessageBox函数是必然会将该值作为字符缓冲区的首址。
> 相反的,我不动我的DLL,把delphi中对我DLL的输出函数原型中的参数类型由PChar改为Integer,调用的时候用Integer(@StrBuffer)的形式传入到DLL中,执行结果完全正常。
> 由此推断,错误必然发生在delphi上,而不是我的DLL。

能否将您所说的相关代码贴来?根据我的判断,PChar和Integer不是重要的,因为那是对编译器而言的概念,只要在内存中的地址和布局一致,结果一定是一样的,无论用谁来写。我想搞清楚一个问题,就是您如何能够判断传入的指针的目标内存区到底是一个字符还是一个字串?从Delphi的角度讲,无论多长的字串内存布局都是一样的,即字串本身内容+ASCIIZ(对于array [0..x] of char, PChar(AnsiString), string const)。


JGTM2000 2000-08-26
  • 打赏
  • 举报
回复
我希望能通过类比相似的例子解释这个问题的出处,您想,在Delphi中调用Win32 API不也是调用C的DLL函数吗?Win32 API中有很多的函数同样接收LPCZSTR即char*类型的参数,无论你用Delphi传入多长的字串它也不会出现错误,为什么?因为函数把char*总视为指向ASCIIZ字符串的指针,这种串占用内存空间的大小为实际字串长度+1(结尾的NULL),因此如果你传入的缓冲区大小为1,它只能代表长度为0的字串,而且这仅有的一个字节也必须为NULL。然而,如果函数把char*理解为指向一个字符的指针,则当同样大小为1的内存区域就被认为是字符,也就可以不是0。这明显的带来了歧义:长度为1的目标内存区域到底是什么内容?!

因此,这个问题的来源不一定是Delphi,而是C函数的内部实现没有处理这种明显的二异性。可以肯定的说,你用C程序以同样的内存布局去调用它,同样会出问题。所以解决方法很简单,明确定义char*的实际含义,目标内存区域是字符还是字串,如果是字串,一个字符的字串,同样按两个字节的内存占用处理,就不会出现任何问题。这也是为什么API定义中引入LPCZSTR宏的原因之一。
dandycheung 2000-08-25
  • 打赏
  • 举报
回复
再没有人回答了吗?
ckbmail 2000-08-25
  • 打赏
  • 举报
回复
我觉得delphi处理指针的能力不是太强。不知道各位意下如何?
please mailto :ckbmail@elong.com和我讨论
Sayhigh 2000-08-25
  • 打赏
  • 举报
回复
var
Buffer: PChar;
begin
GetMem(Buffer, 1024);//你的字串长度
StrCopy(Buffer, 'some chars');
fxn(Buffer);
Freemem(Buffer);
end;
应该可以的,如果不行传指针过去试一下.
dandycheung 2000-08-23
  • 打赏
  • 举报
回复
首先感谢yinfudan的精彩讲解。我是一个VC++的程序员,但也经常用到delphi。
问题是我在delphi 中调用时,该参数既可能是一个字符的字符串,也可能是多个字符的字符串。所以当前我的代码是这个样子的:

var
s: string;
...
// assigns value to s here, 'a' or 'abc'
...
// then:
fxn(PChar(s));

如何能最简单的解决这个问题?
mytulip 2000-08-23
  • 打赏
  • 举报
回复
试一下,function StrPCopy(Dest: PChar; const Source: string): PChar;吧,将string转成PChar
yinfudan 2000-08-23
  • 打赏
  • 举报
回复
同意dandycheung的题外话

问题解决方案:
PChar(...)可以把String型转成PChar型,具体实现方案是
建立一个Buffer,和这个String一样长,然后返回这个Buffer的首地址,首地址为32比特
例如PChar('abcde')的值可能等于$00450F03
PChar(...)可以把Integer或Char型转成PChar型,具体实现方案是
直接把这个整数或字符转成32比特的指针型
例如PChar(65535)=$0000FFFF,PChar('2')=$00000032

所以,传入参数PChar('a')当然不行了。应该改为
var
c:char;
begin
c:='a';
CFunc(@c);
end;
一定可以
dandycheung 2000-08-23
  • 打赏
  • 举报
回复
to guanxuegong:
肯定不是这种问题,LPTSTR 在非 Unicode 的版本中就等于 char *,那只不过是一个宏而已。
guanxuegong 2000-08-23
  • 打赏
  • 举报
回复
试试把c参数设为LPTSTR。
dandycheung 2000-08-23
  • 打赏
  • 举报
回复
to kxy: 我的第二次将问题提出,已经证明我曾经试过fxn(PChar(String('Some chars')));这样的形式或其下面的形式。一样行不通。如果你有兴趣的话,可以自己试一下。顺便说一下,我用的是Delphi 5 + UP1。也多谢kxy。
dandycheung 2000-08-23
  • 打赏
  • 举报
回复
错误是读写非法内存。各位,我已经相信yinfudan的讲解了,也请大家看清楚他的讲解,如果对于一个字符PChar就直接把它的十六进制值转换为指针类型的话,出现读写非法内存几乎是肯定的。问题是如何让delphi把一个字符时的串能够和多个字符时的串同等对待起来。多谢各位捧场。
kxy 2000-08-23
  • 打赏
  • 举报
回复
pascal中'a',可以是char类型,也可能是string
你可以这样
fxn(PChar(String('Some chars')));
如果你先申明一个string的变量,
s:string;
s:='Some chars';
fxn(PChar(s));也可以。

我不同意题外话:)
用C,C++,你更要注意不要让编译器产生歧义
kxy 2000-08-23
  • 打赏
  • 举报
回复
调用失败?,错误信息是什么?
dandycheung 2000-08-23
  • 打赏
  • 举报
回复
to goodman1999:
当然加了,要不然怎么能其他的长度就正常。
~~~
goodman1999 2000-08-23
  • 打赏
  • 举报
回复
在Delphi的函数声明中,最后加stdcall;了没有。
NiceBASIC测试版3是新型编程工具跟 VC,VB,DELPHI类似。 NiceBASIC测试版3 上传日期:2008年12月13日11:02:21 NiceBASIC文编程语言,是采用类似BASIC语法,并兼有C++的一些高级特性(比如:指针操作运算、自动化类、重载函数、重载操作符等等)集于一身的全文关键字的编程语言,简称NB。NB内置有标准BASIC函数库,和图像库(用于编写游戏),还可以使用标准C语言函数库里的函数(调用静态库形式链接),也就是说NB可以在编译时链接所有用标准C语言编写的静态库(LIB)做为函数功能扩展,并且还可以调用WIN32API的大部分函数,已经在内部定义声明,直接引用即可,就像C++的WINDOWS开发包。并且NB的编译器是永久免费的,除了可以编译自身的源码外,还可以编译RC资源脚本,合成到可执行文件,NB的编译器提供了构建完整标准应用程序的所有功能。 其独特个性化的全文式编程模式,更易于国人理解和方便学习编程,不用懂英文也可以编写自己的程序软件。变量和函数等标识符名称,也可以用文表示,方便于源码的交流和省略注释说明。 NB可以编译四种类型的可执行文件: 控制台程序。类似于DOS界面的命令行提示符,但只能运行在WIN32平台。 WINDOWS图形界面程序。调用系统的WIN32API来构建GUI窗体组件。 WIN32 DLL 动态链接库。导出函数可以为多种调用约定,比如:Stdcall(标准WINAPI)、 Cdecl(兼容C语言)、Pascal 。可供给其它语言使用。 静态链接库。供给标准的C语言调用链接。就是说NB的静态库是兼容C语言的LIB,互相通用。
目录 第1章 matlab概述. 1.1 matlab的发展历程 1.2 matlab产品组成及语言特点 1.2.1 matlab的主要产品构成 1.2.2 matlab语言的特点 1.3 matlab 7.0的新功能和新产品 1.3.1 matlab 7.0的新功能 1.3.2 matlab升级及新增的模块 1.4 小结 第2章 matlab程序设计及代码优化 2.1 matlab的表达式和变量 2.1.1 表达式 2.1.2 变量 2.2 细胞数组与结构数组 2.2.1 细胞数组 2.2.2 结构数组 2.3 类与对象 2.4 流程控制 2.4.1 for循环结构 2.4.2 while循环结构 .2.4.3 if-else-end分支结构 2.4.4 switch-case结构 2.4.5 try-catch结构 2.5 m文件编程 2.6 m文件编程规范 2.7 m文件评述器 2.8 提高m文件执行效率的技巧 2.8.1 矢量化操作 2.8.2 给数组预定义维 2.8.3 下标或者索引操作 2.8.4 尽量多使用函数文件而少使用非脚本文件 2.8.5 将循环体的内容转换为c-mex 2.8.6 内存优化 2.9 小结 第3章 matlab混合编程简介 3.1 进行混合编程的出发点 3.2 matlab应用程序接口简介 3.3 几种常见的混合编程方法简介 3.3.1 使用matlab自带的matlab compiler 3.3.2 利用matlab引擎 3.3.3 利用activex控件 3.3.4 利用mat文件 3.3.5 c-mex 3.3.6 利用mideva/matcom 3.3.7 利用matrix[lib]实现混合编程 3.3.8 利用matlab add-in 3.3.9 matlab com builder 3.3.10 matlab和excel混合编程 3.4 小结 第4章 c-mex编程 4.1 c-mex简介 4.2 mex文件系统的配置 4.3 mex文件的结构和运行 4.3.1 mex文件结构 4.3.2 mex函数的执行流程 4.3.3 mex文件的结构和使用 4.3.4 mex文件与独立应用程序的区别 4.4 c语言mex函数 4.5 c-mex混合编程 4.6 visual c++mex文件的建立和调试 4.6.1 visual c++mex程序的建立和环境设置 4.6.2 mex程序的调试 4.6.3 mex独立应用程序的发布 4.7 mex编程实例 4.8 小结 第5章 通过matlab引擎实现混合编程 5.1 matlab引擎简介 5.2 matlab引擎库函数 5.3 visual c++调用matlab引擎时的环境设置 5.4 matlab引擎类的封装 5.4.1 cmatlabeng类的定义和实现代码 5.4.2 cmatlabeng说明 5.4.3 cmatlabeng说明和使用方法 5.5 应用实例 5.6 小结 第6章 mat文件实现数据共享 6.1 mat文件简介 6.2 操作mat文件 6.2.1 mat文件格式 6.2.2 操作mat文件的matlab api 6.3 visual c++调用mat时的环境设置 6.4 实例 6.5 小结 第7章 利用mideva实现混合编程 7.1 mideva简介 7.2 mideva的安装 7.3 mideva环境下m文件到dll/exe文件的转换 7.4 visual c++环境下使用mideva混合编程 7.4.1 混合编程环境的设置 7.4.2 通过外壳函数调用 7.5 matrix[lib] 7.6 混合编程实例 7.7 小结 第8章 利用matrix[lib]实现混合编程 8.1 matrix[lib]简介 8.2 matrix[lib]与visual c++混合编程 8.2.1 matrix[lib]的安装 8.2.2 visual c++环境配置 8.2.3 初始化库 8.3 matrix[lib]函数使用参考 8.3.1 矩阵操作 8.3.2 库常量 8.3.3 访问库函数 8.3.4 矩阵i/o 8.3.5 图形函数 8.4 混合编程实例 8.5 matlab数学库 8.5.1 简介 8.5.2 visual c++工程调用matlab数学函数库的环境设置 8.6 小结.. 第9章 通过matlab add-in实现混合编程 9.1 matlab add-in简介 9.2 matlab add-in安装和在visual c++的环境设置 9.3 通过matlab add-in生成独立应用程序 9.4 matlab add-in实例 9.5 小结 第10章 matlab和delphi混合编程 10.
为何DLL 先看看静态库与DLL的不同之处 可执行文件的生成(Link期):前者很慢(因为要将库的所有符号定义Link到EXE文件),而后者很快(因为后者被Link的引入库文件无符号定义) 可执行文件的大小:前者很大,后者很小(加上DLL的大小就和前者差不多了) 可执行文件的运行速度:前者快(直接在EXE模块的内存查找符号),后者慢(需要在DLL模块的内存查找,在另一个模块的内存查找自然较慢) 可共享性:前者不可共享,也就是说如果两个EXE使用了同一个静态库,那么实际在内存存在此库的两份拷贝,而后者是可共享的。 可升级性:前者不可升级(因为静态库符号已经编入EXE,要升级则EXE也需要重新编译),后者可以升级(只要接口不变,DLL即可被升级为不同的实现) 综合以上,选择静态库还是DLL 1. 静态库适于稳定的代码,而动态库则适于经常更改代码(当然接口要保持不变),当DLL更改(仅实现部分)后,用户不需要重编工程,只需要使用新的Dll即可。 2. 由于静态库很吃可执行文件的生成(Link期)时间,所以如果对可执行文件的Link时间比较敏感,那么就用DLL。 使用DLL 在介绍如何创建DLL之前,让我们先了解它是如何被使用的。 1. 显式调用(也叫动态调用) 显 示调用使用API函数LoadLibrary或者MFC提供的AfxLoadLibrary将DLL加载到内存,再用GetProcAddress()在 内存获取引入函数地址,然后你就可以象使用本地函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的 AfxLoadLibrary释放DLL。 下面是个显示调用的例子,假定你已经有一个Test.dll,并且DLL有个函数名为Test,其声明式是void(); #include < iostream > using namespace std; typedef void(*TEST )(); int main( char argc, char* argv[] ) { const char* dllName = "Test.dll"; const char* funcName = "Test"; HMODULE hDLL = LoadLibrary( dllName ); if ( hDLL != NULL ) { TEST func = TEST( GetProcAddress( hDLL, funcName ) ); if ( func != NULL ) { func(); } else { cout << "Unable to find function \'" << funcName << "\' !" << endl; } FreeLibrary( hDLL ); } else { cout << "Unable to load DLL \'" << dllName << "\' !" << endl; } return 0; } 注意 1. 显示调用使用GetProcAddress,所以只能加载函数,无法加载变量和类。 2. 此外GetProcAddress是直接在.dll文件寻找同名函数,如果DLL的Test函数是个C++函数,那么由于在.dll文件的实际文件名会被修饰(具体被修饰的规则可参考函数调用约定详解或者使用VC自带的Depends.exe查看),所以直接找Test是找不到的,这时必须把函数名修改为正确的被修饰后的名称,下面是一种可能(此函数在DLL调用约定为__cdecl): const char* funcName = "?Test@@YAXXZ"; 2. 隐式调用(也叫静态调用) 隐式调用必须提供DLL的头文件和引入库(可以看作轻量级的静态库(没有符号定义,但是说明了符号处于哪个DLL))。 有了头文件和引入库,DLL的使用就跟普通静态库的使用没啥区别,只除了DLL要和EXE一起发布。 显示调用与隐式调用的优缺点 显示调用使用复杂,但能更加有效地使用内存,因为DLL是在EXE运行时(run time)加载,必须由用户使用LoadLibrary和FreeLibrary来控制DLL从内存加载或卸载的时机。此外还可以加载其他语言编写的DLL函数。 静态调用使用简单,但不能控制DLL加载时机,EXE加载到内存同时自动加载DLL到内存,EXE退出时DLL也被卸载。 创建DLL 下面我们着重讲解如何在VC下创建DLL 首先要建立一个Win32的DLL工程。再把普通静态库的所有文件转移到DLL工程,然后: 为所有的类声明加上__declspec(dllexport)关键字,这有这样编译器才能自动为你产生引入库文件(否则你需要自己写.def文件来产生,因为不常用所以在此不再阐述其细节) 对于DLL的用户来讲,类声明就需要用另外一个关键字__declspec(dllimport)(此关键字对于类和函数而言并非必须,但对于变量而言则是必须的)。所以通常我们会定义一个宏来包装之,比如 #ifdef MYDLL_EXPORTS # define MYDLL_API __declspec(dllexport) #else # define MYDLL_API __declspec(dllimport) #endif 这样我们就能写出如下的类 class MYDLL_API MyClass { ... }; 当然在创建DLL的工程里需要包含preprocessor(预处理指示器)MYDLL_EXPORTS,而在用户工程里面则不应该包含MYDLL_EXPORTS。 其实上面说的VC早就帮我们做好了。如果你创建的DLL工程叫做Test,那么此工程自动包含TEST_EXPORTS。如果你在创建工程的时候选择了Exprot Symbols, 那么VC还会自动帮你创建一个示例文件Test.h,此文件定义出 #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API __declspec(dllimport) #endif 你自定义的文件都应该包含此文件以使用TEST_API。(如果没有选择Exprot Symbols,那么就得自己动手写出这段宏了) 示例文件还包括了一个类,变量,以及全局函数的写法 class TEST_API CTest { public: CTest(void); // TODO: add your methods here. }; extern TEST_API int nTest; TEST_API int fnTest(void); 通过上面的示例我们也可以看出全局(或者名字空间)变量和函数的声明方法 细节讨论 1. DLL的入口函数DllMain并非必须,如果没有它,编译器会帮你自动生成一个不做任何事的DllMain。 2. 如果是可以定义在头文件里面的东西:包括宏,常量,内联函数(包括成员内联函数)以及模板。那么在DLL的定义可以不必包含TEST_API,和普通的定义没有区别。 如果一个类完全由内联函数和纯虚函数构成,那么也不需要TEST_API这样的关键字。一个不带成员函数的struct也一样。 3. 所有未经__declspec(dllexport)导出的类都只能作为DLL内部类使用。当然外部仍然可以使用其内联成员函数(前提是该成员函数不应该调用任何未经导出的函数或者类成员函数) 发布DLL 1. 程序库的作者应该将三件套:头文件,引入库文件和DLL一同发布给用户,其头文件和引入库是专为静态调用的用户准备,也就是C/C++用户。(此外有些 DLL内部使用的头文件,如果没有被接口头文件直接#include,那么该头文件就不必发布,这和静态库是一样的)。 2. DLL的用户只需将DLL和可执行程序一同发布即可。 C++程序使用C语言DLL(静态库规则一样) C不存在class, 所以由C创建的DLL必然也不会出现class;对于全局变量的使用C和C++没有什么不同,所以我们把焦点集在全局函数上(此外全局变量的规则一样)。 我们知道C++程序要使用C语言的函数就必须在函数声明前加上extern "C"关键字,这对于静态库和DLL没有什么不同。 但是这个关键字不能直接出现在头文件函数声明,否则DLL无法通过编译, 原因很简单,C语言并没有extern "C"这个关键字。 1. 一种作法是把C向C++迁移的责任交给DLL创建者。定义出一个宏,以使DLL(C工程)不出现extern "C"或者只是extern,而在用户工程(C++工程)保持原样。幸运的是windows早已为我们设计好一切,这个宏就是EXTERN_C(存在于 winnt.h) : #ifdef __cplusplus #define EXTERN_C extern "C" #else #define EXTERN_C extern #endif 注意上面必须是extern而不是空。因为虽然C的函数声明不是必须添加extern,但是变量声明则必须添加extern。 有了EXTERN_C,在头文件这样定义函数: EXTERN_C TEST_API int fnTest(void); 2. 另外一种做法是把把C向C++迁移的责任交给用户,用户在包含DLL头文件的时候以extern "C"来include: extern "C" { #include "mydll.h" #include "mydllcore.h" ... } 可以把上述的extern "C"段放在新的头文件和DLL一起发布,这样C++用户只需要包含这个头文件就可以了。比如Lua库就自带了一个etc/lua.hpp文件。通常此文件会由DLL作者提供,所以此时迁移的责任仍在DLL创建者。 注意用户不要试图以extern "C"来重新声明函数,因为重复声明是允许的,但是必须保证和头文件的声明相同。 3. 这种做法的一个变形就是直接在C头文件以extern "C"将所有函数和变量声明包含之,这样就无需像上面那样多提供一个额外的头文件了。通常像这样(mydll头文件): #include 头文件段 #ifdef __cplusplus extern "C" { #endif 函数和变量声明 ... #ifdef __cplusplus } #endif 创建标准的DLL,使其可被其他语言使用 通常我们会希望DLL能够被其他语言使用,因而我们的DLL往往不会提供类定义,而只提供函数定义。(因为许多编程语言是不支持类的)。 此时函数的调用约定必须是__stdcall(在vc下默认是__cdecl,所以你不得不手工添置),因为大部分语言不支持__cdecl,但支持__stdcall(比如VBScript,Delphi等)。 此外我们希望导出到DLL的函数名能够更易被识别(用户使用才会更方便),也就是说DLL应该编译出无修饰的C函数名。 所以我们可能会 1. 如果你只用C语言,那么必然以C文件创建DLL(自动编出C符号名),考虑到潜在的C++用户(此类用户多以静态调用方式使用DLL,因而需要看到其函数声明),我们还需要使用EXTERN_C关键字(详见上面的讨论)。 2. 如果你使用C++,那么必然以C++文件创建DLL,这时你应该直接以extern "C"修饰。 结论 所以要创建能够为任意编程语言使用之DLL,我们应该 1. 只创建函数 2. 声明__stdcall调用约定(或者WINAPI,CALLBACK,前提是你必须包含windows头文件) 3. 以CPP文件 + extern "C" 或者 C文件 + EXTERN_C 4. 当然还需要DLL_API的声明(如果光有dllexport,那么DLL也只能被显示调用)。 更完美一点 不应该使用dllexport和extern "C"而应该使用.def文件。虽然经过上面的4个步骤,我们的DLL里面的C函数名已经相当简洁了,但仍不是最完美的:比如一个声明为function的函数,实际在DLL的名字确可能是function@#。而使用.def文件,我们可以让DLL的函数名和声明的函数名保持一致。 关于.def的详细用法可参考: 微软DLL专题 使用 DEF 文件从 DLL 导出 在DLL与静态库之间切换 前面我曾经提到对于使用DLL的用户__declspec(dllimport)关键字可有可无,当然前提是DLL不包括变量定义。 所以要把库既能够做成DLL,也能够做成静态库,那么就应该作出类似下面这样的定义: 1. DLL不包括变量的定义 #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API #endif 然 后只要把工程的配置属性(Configuration Type)简单地在Static Library (.lib)或者Dynamic Library (.dll)切换即可(VC会自动地为DLL添加预处理器TEST_EXPORTS,为静态库取消TEST_EXPORTS)。 2. DLL包含变量定义,也是标准的做法 #ifdef TEST_BUILD_AS_DLL #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API __declspec(dllimport) #endif #else #define TEST_API #endif 如果要将库做成DLL,那么需要DLL创建者添加预处理器TEST_BUILD_AS_DLL和TEST_EXPORTS,后者通常由编译器自动添加;如果做成静态库则不需要添加任何预处理器。 用户则可以通过添加或取消TEST_BUILD_AS_DLL来使用动态库或静态库。 对于DLL的用户,TEST_BUILD_AS_DLL这个名称似乎起得不好。下面的做法或许会更合理些: #if defined(TEST_API) #error "macro alreday defined!" #endif #if defined(TEST_BUILD_DLL) && defined(TEST_USE_DLL) #error "macro conflict!" #endif #if defined(TEST_BUILD_DLL) // build dll # define TEST_API __declspec(dllexport) #elif defined(TEST_USE_DLL) // use dll # define TEST_API __declspec(dllimport) #else // build or use library, no preprocessor needs # define TEST_API #endif 相关链接 微软DLL专题 Windows API编程之动态链接库(DLL)

5,379

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 开发及应用
社区管理员
  • VCL组件开发及应用社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧