﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-小不点,努力学习中......-随笔分类-DLL </title><link>http://www.cppblog.com/amyvmiwei/category/5910.html</link><description>        战胜自己就是战胜一切!
</description><language>zh-cn</language><lastBuildDate>Tue, 20 May 2008 00:05:01 GMT</lastBuildDate><pubDate>Tue, 20 May 2008 00:05:01 GMT</pubDate><ttl>60</ttl><item><title>DLL 中 .def文件的使用</title><link>http://www.cppblog.com/amyvmiwei/archive/2008/01/02/40203.html</link><dc:creator>小不点</dc:creator><author>小不点</author><pubDate>Tue, 01 Jan 2008 21:37:00 GMT</pubDate><guid>http://www.cppblog.com/amyvmiwei/archive/2008/01/02/40203.html</guid><wfw:comment>http://www.cppblog.com/amyvmiwei/comments/40203.html</wfw:comment><comments>http://www.cppblog.com/amyvmiwei/archive/2008/01/02/40203.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/amyvmiwei/comments/commentRss/40203.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/amyvmiwei/services/trackbacks/40203.html</trackback:ping><description><![CDATA[<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DLL中导出函数的声明有两种方式：一种为在函数声明中加上__declspec(dllexport)，这里不再举例说明；另外一种方式是采用模块定义(.def) 文件声明，.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 首先创建 一个DLL程序，.cpp中<br>int __stdcall Add(int numa, int numb)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return (numa + numb);<br>}</p>
<p>int __stdcall Sub(int numa, int numb)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return (numa - numb);<br>}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 然后创建一个.def的文件，在里面加上<br></p>
<p class=code>;DllTestDef.lib : 导出DLL函数<br>;作者：----<br>LIBRARY DllTestDef<br>EXPORTS <br>Add @ 1<br>Sub @ 2<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;最后创建一个测试程序：.cpp文件如下：<br>#include &lt;iostream&gt;<br>#include &lt;windows.h&gt;</p>
<p class=code>using namespace std;</p>
<p class=code>typedef int (__stdcall *FUN)(int, int);<br>HINSTANCE hInstance;<br>FUN&nbsp;&nbsp; fun;</p>
<p class=code>int main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hInstance = LoadLibrary("DLLTestDef.dll");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(!hInstance)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout &lt;&lt; "Not Find this Dll" &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fun = (FUN)GetProcAddress(hInstance, MAKEINTRESOURCE(1));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (!fun)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout &lt;&lt; "not find this fun" &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout &lt;&lt; fun(1, 2) &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FreeLibrary(hInstance);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>}</p>
<br><br>说明：<br>.def文件的规则为：<br><br>　　(1)LIBRARY语句说明.def文件相应的DLL；<br><br>　　(2)EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n，表示要导出函数的序号为n（在进行函数调用时，这个序号将发挥其作用）；<br><br>　　(3).def 文件中的注释由每个注释行开始处的分号 (;) 指定，且注释不能与语句共享一行。<br><br>
<img src ="http://www.cppblog.com/amyvmiwei/aggbug/40203.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/amyvmiwei/" target="_blank">小不点</a> 2008-01-02 05:37 <a href="http://www.cppblog.com/amyvmiwei/archive/2008/01/02/40203.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>DLL 调用方式</title><link>http://www.cppblog.com/amyvmiwei/archive/2008/01/02/40202.html</link><dc:creator>小不点</dc:creator><author>小不点</author><pubDate>Tue, 01 Jan 2008 21:24:00 GMT</pubDate><guid>http://www.cppblog.com/amyvmiwei/archive/2008/01/02/40202.html</guid><wfw:comment>http://www.cppblog.com/amyvmiwei/comments/40202.html</wfw:comment><comments>http://www.cppblog.com/amyvmiwei/archive/2008/01/02/40202.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/amyvmiwei/comments/commentRss/40202.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/amyvmiwei/services/trackbacks/40202.html</trackback:ping><description><![CDATA[<p>DLL(动态连接库)，然而可以分为动态调用于静态调用。下面我分别举一个例子说说。<br><br>1）动态调用：<br>首先：在VC++6.0中创建 Win32 Dynamic-link library工程创建一个动态连接库工程：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在头文件TestDll.h中写下代码<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;extern "C" int __declspec(dllexport) add(int numa, int numb);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在源文件TestDll.cpp中实现改函数：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int __declspec(dllexport) add(int numa, int numb)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return numa + numb;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>其次，创建一个测试程序，TestDemo，创建一个.cpp文件，然后放下代码：<br>HINSTANCE hinstance;</p>
<p>typedef int (*lpAdd)(int a, int b);</p>
<p><br>lpAdd lpadd;</p>
<p>int main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hinstance = LoadLibrary("E:\\vc\\DLL\\TestDll\\Debug\\TestDll.dll");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lpadd = (lpAdd)GetProcAddress(hinstance, "add");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout &lt;&lt; "2 + 3 = " &lt;&lt; lpadd(2, 3) &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FreeLibrary(hinstance);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>}<br>而应用程序对本DLL的调用和对第2节静态链接库的调用却有较大差异，下面我们来逐一分析。</p>
<p>　　首先，语句typedef int ( * lpAddFun)(int,int)定义了一个与add函数接受参数类型和返回值均相同的函数指针类型。随后，在main函数中定义了lpAddFun的实例addFun；</p>
<p>&nbsp; 　　其次，在函数main中定义了一个DLL HINSTANCE句柄实例hDll，通过Win32 Api函数LoadLibrary动态加载了DLL模块并将DLL模块句柄赋给了hDll；</p>
<p>&nbsp;　　再次，在函数main中通过Win32 Api函数GetProcAddress得到了所加载DLL模块中函数add的地址并赋给了addFun。经由函数指针addFun进行了对DLL中add函数的调用；</p>
<p>&nbsp;&nbsp; 　　最后，应用工程使用完DLL后，在函数main中通过Win32 Api函数FreeLibrary释放了已经加载的DLL模块。</p>
<p>&nbsp;&nbsp;　　通过这个简单的例子，我们获知DLL定义和调用的一般概念：</p>
<p>&nbsp;&nbsp;&nbsp; 　　(1)DLL中需以某种特定的方式声明导出函数（或变量、类）；</p>
<p>&nbsp;&nbsp;&nbsp;　　(2)应用工程需以某种特定的方式调用DLL的导出函数（或变量、类）。<br><br><br>2）静态连接：<br>代码如下：<br>#include &lt;iostream&gt;<br>using namespace std;<br><br></p>
<p class=code>#pragma comment(lib,"Testlib.lib") <br><br>//.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息<br>extern "C" __declspec(dllimport) add(int x,int y); <br><br>int main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int result = add(2,3);&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout &lt;&lt;　result &lt;＜　endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br></p>
<img src ="http://www.cppblog.com/amyvmiwei/aggbug/40202.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/amyvmiwei/" target="_blank">小不点</a> 2008-01-02 05:24 <a href="http://www.cppblog.com/amyvmiwei/archive/2008/01/02/40202.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>(转)论函数调用约定</title><link>http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40169.html</link><dc:creator>小不点</dc:creator><author>小不点</author><pubDate>Tue, 01 Jan 2008 07:33:00 GMT</pubDate><guid>http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40169.html</guid><wfw:comment>http://www.cppblog.com/amyvmiwei/comments/40169.html</wfw:comment><comments>http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40169.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/amyvmiwei/comments/commentRss/40169.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/amyvmiwei/services/trackbacks/40169.html</trackback:ping><description><![CDATA[假设我们有这样的一个函数：
<p align=center><strong>int function(int a,int b)</strong></p>
<p>调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是，当高级语言被编译成计算机可以识别的机器码时，有一个问题就凸现出来：在CPU中，计算机没有办法知道一个函数调用需要多少个、什么样的参数，也没有硬件可以保存这些参数。也就是说，计算机不知道怎么给这个函数传递参数，传递参数的工作必须由函数调用者和函数本身来协调。为此，计算机提供了一种被称为栈的数据结构来支持参数传递。</p>
<p>栈是一种先进后出的数据结构，栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项（被称为栈顶）。用户可以在栈顶上方向栈中加入数据，这个操作被称为压栈(Push)，压栈以后，栈顶自动变成新加入数据项的位置，栈顶指针也随之修改。用户也可以从堆栈中取走栈顶，称为弹出栈(pop)，弹出栈后，栈顶下的一个元素变成栈顶，栈顶指针随之修改。</p>
<p>函数调用时，调用者依次把参数压栈，然后调用函数，函数被调用以后，在堆栈中取得数据，并进行计算。函数计算结束以后，或者调用者、或者函数本身修改堆栈，使堆栈恢复原装。</p>
<p>在参数传递中，有两个很重要的问题必须得到明确说明：</p>
<ul>
    <li>当参数个数多于一个时，按照什么顺序把参数压入堆栈
    <li>函数调用后，由谁来把堆栈恢复原装 </li>
</ul>
<p>在高级语言中，通过函数调用约定来说明这两个问题。常见的调用约定有：
<ul>
    <li>stdcall
    <li>cdecl
    <li>fastcall
    <li>thiscall
    <li>naked call</li>
</ul>
<h2>stdcall调用约定</h2>
<p>stdcall很多时候被称为pascal调用约定，因为pascal是早期很常见的一种教学用计算机程序设计语言，其语法严谨，使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中，常常用PASCAL宏来声明这个调用约定，类似的宏还有WINAPI和CALLBACK。</p>
<p>stdcall调用约定声明的语法为(以前文的那个函数为例）：</p>
<p>int __<font color=#0000ff><strong>stdcall</strong></font> function(int a,int b)</p>
<p>stdcall的调用约定意味着：1）参数从右向左压入堆栈，2）函数自身修改堆栈 3)函数名自动加前导的下划线，后面紧跟一个@符号，其后紧跟着参数的尺寸</p>
<p>以上述这个函数为例，参数b首先被压栈，然后是参数a，函数调用function(1,2)调用处翻译成汇编语言将变成：</p>
<pre><blockquote>
push 2      第二个参数入栈
push 1      第一个参数入栈
call function  调用参数，注意此时自动把cs:eip入栈
</blockquote>
</pre>
<p>而对于函数自身，则可以翻译为：
<pre><blockquote>
push ebp     保存ebp寄存器，该寄存器将用来保存堆栈的栈顶指针，可以在函数退出时恢复
mov ebp,esp  保存堆栈指针
mov  eax,[ebp + 8H]  堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
add eax,[ebp + 0CH]  堆栈中ebp + 12处保存了b
mov  esp,ebp         恢复esp
pop ebp
ret 8
</blockquote></pre>
<p>而在编译时，这个函数的名字被翻译成_function@8
<p>注意不同编译器会插入自己的汇编代码以提供编译的通用性，但是大体代码如此。其中在函数开始处保留esp到ebp中，在函数结束恢复是编译器常用的方法。</p>
<p>从函数调用看，2和1依次被push进堆栈，而在函数中又通过相对于ebp(即刚进函数时的堆栈指针）的偏移量存取参数。函数结束后，ret 8表示清理8个字节的堆栈，函数自己恢复了堆栈。</p>
<h2>cdecl调用约定</h2>
<p>cdecl调用约定又称为C调用约定，是C语言缺省的调用约定，它的定义语法是：
<pre><blockquote>
int function (int a ,int b)  //不加修饰就是C调用约定
int __cdecl function(int a,int b)//明确指出C调用约定
</blockquote>
</pre>
<p>在写本文时，出乎我的意料，发现cdecl调用约定的参数压栈顺序是和stdcall是一样的，参数首先由有向左压入堆栈。所不同的是，函数本身不清理堆栈，调用者负责清理堆栈。由于这种变化，C调用约定允许函数的参数的个数是不固定的，这也是C语言的一大特色。对于前面的function函数，使用cdecl后的汇编码变成：
<pre><blockquote>
<strong>调用处</strong>
push 1
push 2
call function
add esp,8   <strong>注意：这里调用者在恢复堆栈</strong>
<strong>被调用函数_function处</strong>
push ebp     保存ebp寄存器，该寄存器将用来保存堆栈的栈顶指针，可以在函数退出时恢复
mov ebp,esp  保存堆栈指针
mov  eax,[ebp + 8H]  堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
add eax,[ebp + 0CH]  堆栈中ebp + 12处保存了b
mov  esp,ebp         恢复esp
pop ebp
ret         <strong>注意，这里没有修改堆栈</strong>
</blockquote>
</pre>
<p>MSDN中说，该修饰自动在函数名前加前导的下划线，因此函数名在符号表中被记录为_function，但是我在编译时似乎没有看到这种变化。</p>
<p>由于参数按照从右向左顺序压栈，因此最开始的参数在最接近栈顶的位置，因此当采用不定个数参数时，第一个参数在栈中的位置肯定能知道，只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来，就可以使用不定参数，例如对于CRT中的sprintf函数，定义为：
<p><strong>int sprintf(char* buffer,const char* format,...)</strong></p>
<p>由于所有的不定参数都可以通过format确定，因此使用不定个数的参数是没有问题的。</p>
<h2>fastcall</h2>
<p>fastcall调用约定和stdcall类似，它意味着：
<ul>
    <li>函数的第一个和第二个DWORD参数（或者尺寸更小的）通过ecx和edx传递，其他参数通过从右向左的顺序压栈
    <li>被调用函数清理堆栈
    <li>函数名修改规则同stdcall </li>
</ul>
<p>其声明语法为：int fastcall function(int a,int b)</p>
<h2>thiscall</h2>
<p>thiscall是唯一一个不能明确指明的函数修饰，因为thiscall不是关键字。它是C++类成员函数缺省的调用约定。由于成员函数调用还有一个this指针，因此必须特殊处理，thiscall意味着：
<ul>
    <li>参数从右向左入栈
    <li>如果参数个数确定，this指针通过ecx传递给被调用者；如果参数个数不确定，this指针在所有参数压栈后被压入堆栈。
    <li>对参数个数不定的，调用者清理堆栈，否则函数自己清理堆栈 </li>
</ul>
<p>为了说明这个调用约定，定义如下类和使用代码：
<pre>class A
{
public:
&nbsp;&nbsp; int function1(int a,int b);
&nbsp;&nbsp; int function2(int a,...);
};
int A::function1 (int a,int b)
{
&nbsp;&nbsp; return a+b;
}
#include <stdarg.h>
int A::function2(int a,...)
{
&nbsp;&nbsp; va_list ap;
&nbsp;&nbsp; va_start(ap,a);
&nbsp;&nbsp; int i;
&nbsp;&nbsp; int result = 0;
&nbsp;&nbsp; for(i = 0 ; i &lt; a ; i ++)
&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;  result += va_arg(ap,int);
&nbsp;&nbsp; }
&nbsp;&nbsp; return result;
}
void callee()
{
&nbsp;&nbsp; A a;
&nbsp;&nbsp; a.function1 (1,2);
&nbsp;&nbsp; a.function2(3,1,2,3);
}
</pre>
<p>callee函数被翻译成汇编后就变成：
<pre><blockquote>
//函数function1调用
0401C1D   push        2
00401C1F   push        1
00401C21   lea         ecx,[ebp-8]
00401C24   call  function1           注意，这里this没有被入栈
//函数function2调用
00401C29   push        3
00401C2B   push        2
00401C2D   push        1
00401C2F   push        3
00401C31   lea         eax,[ebp-8]   这里引入this指针
00401C34   push        eax
00401C35   call   function2
00401C3A   add         esp,14h
</blockquote></pre>
<p>可见，对于参数个数固定情况下，它类似于stdcall，不定时则类似cdecl</p>
<h2>naked call</h2>
<p>这是一个很少见的调用约定，一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码，更特殊的是，你不能用return返回返回值，只能用插入汇编返回结果。这一般用于实模式驱动程序设计，假设定义一个求和的加法程序，可以定义为：
<pre>__declspec(naked) int  add(int a,int b)
{
__asm mov eax,a
__asm add eax,b
__asm ret
}
</pre>
<p>注意，这个函数没有显式的return返回值，返回通过修改eax寄存器实现，而且连退出函数的ret指令都必须显式插入。上面代码被翻译成汇编以后变成：
<pre><blockquote>
mov eax,[ebp+8]
add eax,[ebp+12]
ret 8
</blockquote>
</pre>
<p>注意这个修饰是和__stdcall及cdecl结合使用的，前面是它和cdecl结合使用的代码，对于和stdcall结合的代码，则变成：
<pre>__declspec(naked) int __stdcall function(int a,int b)
{
__asm mov eax,a
__asm add eax,b
__asm ret 8        //注意后面的8
}
</pre>
<p>至于这种函数被调用，则和普通的cdecl及stdcall调用函数一致。</p>
<h2>函数调用约定导致的常见问题</h2>
<p>如果定义的约定和使用的约定不一致，则将导致堆栈被破坏，导致严重问题，下面是两种常见的问题：
<ol>
    <li>函数原型声明和函数体定义不一致
    <li>DLL导入函数时声明了不同的函数约定 </li>
</ol>
<p>以后者为例，假设我们在dll种声明了一种函数为：
<pre>__declspec(dllexport) int func(int a,int b);//注意，这里没有stdcall，使用的是cdecl</pre>
<p>使用时代码为：
<pre>      typedef int (*WINAPI DLLFUNC)func(int a,int b);
hLib = LoadLibrary(...);
DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定
result = func(1,2);//导致错误
</pre>
<p>由于调用者没有理解WINAPI的含义错误的增加了这个修饰，上述代码必然导致堆栈被破坏，MFC在编译时插入的checkesp函数将告诉你，堆栈被破坏了。<br><br><br><br><br>
<table height=849 cellSpacing=0 cellPadding=0 width=780 align=center border=0>
    <tbody>
        <tr>
            <td class=text width=739 height=64>
            <div align=left>
            <p>DLL中调用约定和名称修饰- -<br><br>调用约定（Calling Convention）是指在程序设计语言中为了实现函数调用而建立的一种协议。这种协议规定了该语言的函数中的参数传送方式、参数是否可变和由谁来处理堆栈等问题。不同的语言定义了不同的调用约定。</p>
            <p>在C++中，为了允许操作符重载和函数重载，C++编译器往往按照某种规则改写每一个入口点的符号名，以便允许同一个名字（具有不同的参数类型或者是不同的作用域）有多个用法，而不会打破现有的基于C的链接器。这项技术通常被称为名称改编（Name Mangling）或者名称修饰（Name Decoration）。许多C++编译器厂商选择了自己的名称修饰方案。</p>
            <p>因此，为了使其它语言编写的模块（如Visual Basic应用程序、Pascal或Fortran的应用程序等）可以调用C/C++编写的DLL的函数，必须使用正确的调用约定来导出函数，并且不要让编译器对要导出的函数进行任何名称修饰。<br>1．调用约定（Calling Convention）<br>调用约定用来处理决定函数参数传送时入栈和出栈的顺序（由调用者还是被调用者把参数弹出栈），以及编译器用来识别函数名称的名称修饰约定等问题。在Microsoft VC++ 6.0中定义了下面几种调用约定，我们将结合汇编语言来一一分析它们：<br>1、__cdecl<br>__cdecl是C/C++和MFC程序默认使用的调用约定，也可以在函数声明时加上__cdecl关键字来手工指定。采用__cdecl约定时，函数参数按照从右到左的顺序入栈，并且由调用函数者把参数弹出栈以清理堆栈。因此，实现可变参数的函数只能使用该调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码，所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。</p>
            <p>下面将通过一个具体实例来分析__cdecl约定：</p>
            <p>在VC++中新建一个Win32 Console工程，命名为cdecl。其代码如下：</p>
            <p>int __cdecl Add(int a, int b); //函数声明</p>
            <p>void main()<br>{<br>Add(1,2); //函数调用<br>}</p>
            <p>int __cdecl Add(int a, int b) //函数实现<br>{<br>return (a + b);<br>}</p>
            <p>函数调用处反汇编代码如下：</p>
            <p>;Add(1,2);<br>push 2 ;参数从右到左入栈，先压入2<br>push 1 ;压入1<br>call @ILT+0(Add) (00401005) ;调用函数实现<br>add esp,8 ;由函数调用清栈<br>2、__stdcall<br>__stdcall调用约定用于调用Win32 API函数。采用__stdcal约定时，函数参数按照从右到左的顺序入栈，被调用的函数在返回前清理传送参数的栈，函数参数个数固定。由于函数体本身知道传进来的参数个数，因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。__stdcall可以写成_stdcall。</p>
            <p>还是那个例子，将__cdecl约定换成__stdcall：</p>
            <p>int __stdcall Add(int a, int b)<br>{<br>return (a + b);<br>}</p>
            <p>函数调用处反汇编代码：<br><br>; Add(1,2);<br>push 2 ;参数从右到左入栈，先压入2<br>push 1 ;压入1<br>call @ILT+10(Add) (0040100f) ;调用函数实现</p>
            <p>函数实现部分的反汇编代码：</p>
            <p>;int __stdcall Add(int a, int b)<br>push ebp<br>mov ebp,esp<br>sub esp,40h<br>push ebx<br>push esi<br>push edi<br>lea edi,[ebp-40h]<br>mov ecx,10h<br>mov eax,0CCCCCCCCh<br>rep stos dword ptr [edi]<br>;return (a + b);<br>mov eax,dword ptr [ebp+8]<br>add eax,dword ptr [ebp+0Ch]<br>pop edi<br>pop esi<br>pop ebx<br>mov esp,ebp<br>pop ebp<br>ret 8 ;清栈<br>3、__fastcall<br>__fastcall约定用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节（DWORD）的参数分别放在ECX和EDX寄存器，其余的参数仍旧自右向左压栈传送，被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall。</p>
            <p>依旧是相类似的例子，此时函数调用约定为__fastcall，函数参数个数增加2个：</p>
            <p>int __fastcall Add(int a, double b, int c, int d)<br>{<br>return (a + b + c + d);<br>}</p>
            <p>函数调用部分的汇编代码：</p>
            <p>;Add(1, 2, 3, 4);<br>push 4 ;后两个参数从右到左入栈，先压入4<br>mov edx,3 ;将int类型的3放入edx<br>push 40000000h ;压入double类型的2<br>push 0<br>mov ecx,1 ;将int类型的1放入ecx<br>call @ILT+0(Add) (00401005) ;调用函数实现</p>
            <p>函数实现部分的反汇编代码：<br><br>; int __fastcall Add(int a, double b, int c, int d)<br>push ebp<br>mov ebp,esp<br>sub esp,48h<br>push ebx<br>push esi<br>push edi<br>push ecx<br>lea edi,[ebp-48h]<br>mov ecx,12h<br>mov eax,0CCCCCCCCh<br>rep stos dword ptr [edi]<br>pop ecx<br>mov dword ptr [ebp-8],edx<br>mov dword ptr [ebp-4],ecx<br>;return (a + b + c + d);<br>fild dword ptr [ebp-4]<br>fadd qword ptr [ebp+8]<br>fiadd dword ptr [ebp-8]<br>fiadd dword ptr [ebp+10h]<br>call __ftol (004011b8)<br>pop edi<br>pop esi<br>pop ebx<br>mov esp,ebp<br>pop ebp<br>ret 0Ch ;清栈</p>
            <p>关键字__cdecl、__stdcall和__fastcall可以直接加在要输出的函数前，也可以在编译环境的Setting...-&gt;C/C++-&gt;Code Generation项选择。它们对应的命令行参数分别为/Gd、/Gz和/Gr。缺省状态为/Gd，即__cdecl。当加在输出函数前的关键字与编译环境中的选择不同时，直接加在输出函数前的关键字有效。<br>4、thiscall<br>thiscall调用约定是C++中的非静态类成员函数的默认调用约定。thiscall只能被编译器使用，没有相应的关键字，因此不能被程序员指定。采用thiscall约定时，函数参数按照从右到左的顺序入栈，被调用的函数在返回前清理传送参数的栈，只是另外通过ECX寄存器传送一个额外的参数：this指针。</p>
            <p>这次的例子中将定义一个类，并在类中定义一个成员函数，代码如下：</p>
            <p>class CSum<br>{<br>public:<br>int Add(int a, int b)<br>{<br>return (a + b);<br>}<br>};</p>
            <p>void main()<br>{ <br>CSum sum;<br>sum.Add(1, 2);<br>}</p>
            <p>函数调用部分汇编代码：</p>
            <p>;CSum sum;<br>;sum.Add(1, 2);<br>push 2 ;参数从右到左入栈，先压入2<br>push 1 ;压入1<br>lea ecx,[ebp-4] ;ecx存放了this指针<br>call @ILT+5(CSum::Add) (0040100a) ;调用函数实现</p>
            <p>函数实现部分汇编代码：</p>
            <p>;int Add(int a, int b)<br>push ebp<br>mov ebp,esp<br>sub esp,44h ;多用了一个4bytes的空间用于存放this指针<br>push ebx<br>push esi<br>push edi<br>push ecx<br>lea edi,[ebp-44h]<br>mov ecx,11h<br>mov eax,0CCCCCCCCh<br>rep stos dword ptr [edi]<br>pop ecx<br>mov dword ptr [ebp-4],ecx<br>;return (a + b);<br>mov eax,dword ptr [ebp+8]<br>add eax,dword ptr [ebp+0Ch]<br>pop edi<br>pop esi<br>pop ebx<br>mov esp,ebp<br>pop ebp<br>ret 8 ;清栈<br>5、naked属性<br>采用上面所述的四种调用约定的函数在进入函数时，编译器会产生代码来保存ESI、EDI、EBX、EBP寄存器中的值，退出函数时则产生代码恢复这些寄存器的内容。对于定义了naked属性的函数，编译器不会自动产生这样的代码，需要你手工使用内嵌汇编来控制函数实现中的堆栈管理。由于naked属性并不是类型修饰符，故必须和__declspec共同使用。下面的这段代码定义了一个使用了naked属性的函数及其实现：</p>
            <p>__declspec ( naked ) func()<br>{<br>int i;<br>int j;<br><br>_asm<br>{<br>push ebp<br>mov ebp, esp<br>sub esp, __LOCAL_SIZE<br>}<br><br>_asm<br>{<br>mov esp, ebp<br>pop ebp<br>ret<br>}<br>}</p>
            <p>naked属性与本节关系不大，具体请参考MSDN。<br>6、WINAPI<br>还有一个值得一提的是WINAPI宏，它可以被翻译成适当的调用约定以供函数使用。该宏定义于windef.h之中。下面是在windef.h中的部分内容：</p>
            <p>#define CDECL _cdecl<br>#define WINAPI CDECL<br>#define CALLBACK __stdcall<br>#define WINAPI __stdcall<br>#define APIENTRY WINAPI</p>
            <p>由此可见，WINAPI、CALLBACK、APIENTRY等宏的作用。<br></p>
            2．名称修饰（Name Decoration）<br>C或C++函数在内部（编译和链接）通过修饰名（Decoration Name）识别。函数的修饰名是编译器在编译函数定义或者原型时生成的字符串。编译器在创建.obj文件时对函数名称进行修饰。有些情况下使用函数的修饰名是必要的，如在模块定义文件里头指定输出C++重载函数、构造函数、析构函数，又如在汇编代码里调用C或C++函数等。
            <p>在VC++中，函数修饰名由编译类型（C或C++）、函数名、类名、调用约定、返回类型、参数等多种因素共同决定。下面分C编译、C++编译（非类成员函数）和C++类及其成员函数编译三种情况说明：<br>1、C编译时函数名称修饰<br>当函数使用__cdecl调用约定时，编译器仅在原函数名前加上一个下划线前缀，格式为_functionname。例如：函数int __cdecl Add(int a, int b)，输出后为：_Add。</p>
            <p>当函数使用__stdcall调用约定时，编译器在原函数名前加上一个下划线前缀，后面加上一个@符号和函数参数的字节数，格式为_functionname@number。例如：函数int __stdcall Add(int a, int b)，输出后为：_Add@8。</p>
            <p>当函数是用__fastcall调用约定时，编译器在原函数名前加上一个@符号，后面是加一个@符号和函数参数的字节数，格式为@functionname@number。例如：函数int __fastcall Add(int a, int b)，输出后为：@Add@8。</p>
            <p>以上改变均不会改变原函数名中的字符大小写。<br>2、C++编译时函数（非类成员函数）名称修饰<br>当函数使用__cdecl调用约定时，编译器进行以下工作：</p>
            <p>1．以?标识函数名的开始，后跟函数名；<br>2．函数名后面以@@YA标识开始，后跟返回值和参数表；<br>3．当函数的返回值或者参数与C++类无关的时候，返回值和参数表以下列代号表示：<br>B：const<br>D：char<br>E：unsigned char<br>F：short<br>G：unsigned short<br>H：int<br>I：unsigned int<br>J：long<br>K：unsigned long<br>M：float<br>N：double<br>_N：bool<br>PA：指针（*，后面的代号表明指针类型，如果相同类型的指针连续出现，以0<br>代替，一个0代表一次重复）<br>PB：const指针<br>AA：引用（&amp;）<br>AB：const引用<br>U：类或结构体<br>V：Interface（接口）<br>W4：enum<br>X：void<br>4、@@YA标识之后紧跟的是该函数的返回值类型，其后依次为参数的数据类型，指针标识在其所指数据类型前。当函数的返回值或者参数与C++类无关的时候，其处理符合本条规则，否则按照5、6规则处理；<br>5、当函数返回值为某个类或带有const性质的类的时候，返回值的命名为：?A/?B+V+类名+@@（不带加号）。当函数返回值为某个类的指针/引用或者带有const性质的类的指针/引用的时候，返回值的命名为：PA/AA或者PB/AB+V+类名+@@（不带加号）；<br>6、函数参数为某个类的时候，并且该参数所使用的类曾经出现过的话（也就是与函数返回值所使用的类相同或者与前一个参数使用的类相同），则该参数类型格式为：V+1+@（不带加号）。如果该参数所使用的类没有出现过的话，则该参数类型格式为：V+类名+@@（不带加号）。函数参数为某个类的指针/引用或者带有const性质指针/引用的时候，则该参数类型格式是在上述格式的基础上在V前面加上代表指针/引用类型或者带有const性质指针/引用类型的标识符（PA/AA或PB/AB）；<br>7、参数表后以@Z标识整个名字的结束，如果该函数无参数，则以Z标识结束。</p>
            <p>当函数使用__stdcall调用约定时，编译器所做工作的规则同上面的__cdecl调用约定，只是参数表的开始标识由上面的@@YA变为@@YG。</p>
            <p>当函数使用__fastcall调用约定时，编译器所做工作的规则同上面的__cdecl调用约定，只是参数表的开始标识由上面的@@YA变为@@YI。<br>3、C++编译类及其成员函数时名称修饰<br>对于导出的C++类，仅能使用__cdecl调用约定。在编译器编译过程中，编译器会对C++类进行处理。如：class __declspec(dllexport) MyClass会被处理为class MyClass &amp; MyClass::operator=(class MyClass const &amp;)。在C++编译器对C++类进行名称修饰的时候，编译器进行以下工作：</p>
            <p>1．以?标识函数名的开始，后跟?4+类名；<br>2．类名后面跟@@QAE标识，对于导出类来说这是固定的；<br>3．@@QAE后面跟AAV0@ABV0@，即引用类型标识符AA+V+0（重复的类的标识符）+@（不带加号）和const性质的引用AB+V+ 0（重复的类的标识符）+@（不带加号）；<br>4．最后以@Z标识整个名字的结束。</p>
            <p>对于导出的C++类中的成员函数（非构造函数和析构函数），可以使用不同的调用约定。当导出的C++类中的成员函数使用__cdecl调用约定时，编译器进行以下工作：</p>
            <p>1．以?标识函数名的开始，后跟函数名+@+类名（不带加号）；<br>2．之后以@@QAE标识开始，后跟返回值和参数表；<br>3．当函数的返回值或者参数与C++类无关的时候，返回值和参数表以下列代号表示：<br>B：const<br>D：char<br>E：unsigned char<br>F：short<br>G：unsigned short<br>H：int<br>I：unsigned int<br>J：long<br>K：unsigned long<br>M：float<br>N：double<br>_N：bool<br>PA：指针（*，后面的代号表明指针类型，如果相同类型的指针连续出现，以0<br>代替，一个0代表一次重复）<br>PB：const指针<br>AA：引用（&amp;）<br>AB：const引用<br>U：类或结构体<br>V：Interface（接口）<br>W4：enum<br>X：void<br>4、@@QAE标识之后紧跟的是该函数的返回值类型，其后依次为参数的数据类型，指针标识在其所指数据类型前。当函数的返回值或者参数与C++类无关的时候，其处理符合本条规则，否则按照5、6规则处理；<br>5、当函数返回值为当前类或带有const性质的当前类的时候，返回值的命名为：?A或?B+V+1+@@（不带加号）。当函数返回值为当前类的指针/引用或者带有const性质的当前类的指针/引用的时候，返回值的命名为：PA/AA或PB/AB+V+1+@@（不带加号）；<br>6、当函数返回值为某个类或带有const性质的类的时候，返回值的命名为：?A/?B+V+类名+@@（不带加号）。当函数返回值为某个类的指针/引用或者带有const性质的类的指针/引用的时候，返回值的命名为：PA/AA或者PB/AB+V+类名+@@（不带加号）；<br>7、函数参数为某个类的时候，并且该参数所使用的类曾经出现过的话（也就是当前要导出的类、与函数返回值所使用的类相同或者与前一个参数使用的类相同的类），则该参数类型格式为：V+1+@（不带加号）。如果该参数所使用的类不是当前要导出的类的话，则该参数类型格式为：V+类名+@@（不带加号）。函数参数为某个类的指针/引用或者带有const性质指针/引用的时候，则该参数类型格式是在上述格式的基础上在V前面加上代表指针/引用类型或者带有const性质指针/引用类型的标识符（PA/AA或PB/AB）；<br>8、参数表后以@Z标识整个名字的结束，如果该函数无参数，则以Z标识结束。</p>
            <p>当函数使用__stdcall调用约定时，编译器所做工作的规则同上面的__cdecl调用约定，只是参数表的开始标识由上面的@@YA变为@@YG。</p>
            <p>当函数使用__fastcall调用约定时，编译器所做工作的规则同上面的__cdecl调用约定，只是参数表的开始标识由上面的@@YA变为@@YI。<br>4、C++编译导出数据时名称修饰<br>对于导出的数据，仅使用__cdecl调用约定。在C++编译器对C++类进行名称修饰的时候，编译器进行以下工作：</p>
            <p>1．以?标识数据的开始，后跟数据名；<br>2．数据名后面以@@3标识开始，后跟数据类型；<br>3．当数据类型与C++类无关的时候，数据类型以下列代号表示：<br>B：const<br>D：char<br>E：unsigned char<br>F：short<br>G：unsigned short<br>H：int<br>I：unsigned int<br>J：long<br>K：unsigned long<br>M：float<br>N：double<br>_N：bool<br>PA：指针（*，后面的代号表明指针类型，如果相同类型的指针连续出现，以0<br>代替，一个0代表一次重复）<br>PB：const指针<br>AA：引用（&amp;）<br>AB：const引用<br>U：类或结构体<br>V：Interface（接口）<br>W4：enum<br>X：void<br>4．如果数据类型是某个类的时候，数据类型的命名为：V+类名+@@（不带加号）。当数据类型为当前类的指针/引用或者带有const性质的当前类的指针/引用的时候，数据类型的命名为：PA/AA或PB/AB+V+类名+@@（不带加号）；<br>5．最后，如果数据类型是const性质，则修饰名以B结尾。如果数据类型是非const性质，则修饰名以A结尾。<br></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
</p>
<img src ="http://www.cppblog.com/amyvmiwei/aggbug/40169.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/amyvmiwei/" target="_blank">小不点</a> 2008-01-01 15:33 <a href="http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40169.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Lib 静态链接库</title><link>http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40164.html</link><dc:creator>小不点</dc:creator><author>小不点</author><pubDate>Tue, 01 Jan 2008 07:02:00 GMT</pubDate><guid>http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40164.html</guid><wfw:comment>http://www.cppblog.com/amyvmiwei/comments/40164.html</wfw:comment><comments>http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40164.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/amyvmiwei/comments/commentRss/40164.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/amyvmiwei/services/trackbacks/40164.html</trackback:ping><description><![CDATA[静态连接库的创建：<br><br>// TestLib01.h<br>#ifndef TESTLIB_H<br>#define TESTLIB_H<br>//声明函数为C编译，连接方式的外部函数<br>extern "C" int Add(int numa, int numb);<br>#endif<br><br><br>//TestLib01.cpp<br>#incldue "TestLib01.h"<br>int Add(int numa, int numb)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return (numa + numb);<br>}<br><br>编译得到一个.lib的静态库，把.lib文件以及头文件TestLib01.h拷贝到用户工程目录下面（两个文件都放在工程目录下面，不用吧lib文件放在debug下面）。<br><br>下面来一个用户程序来测试一下<br>//TestLibProject<br>//Test.cpp<br><br>#include &lt;iostream&gt;<br>#include &lt;"TestLib01.h"&gt;<br><br>using namespace std;<br><br>#pragma comment(lib,"TestLib01.lib")<br>int main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout &lt;&lt; Add(1, 4) &lt;&lt; endl;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>}<br><br><strong>代码中#pragma comment( lib , TestLib01.lib" )的意思是指本文件生成的.obj文件应与TestLib01.lib一起连接。</strong><br><br>
<img src ="http://www.cppblog.com/amyvmiwei/aggbug/40164.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/amyvmiwei/" target="_blank">小不点</a> 2008-01-01 15:02 <a href="http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40164.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>DLL（动态链接库）编程 （概论）</title><link>http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40157.html</link><dc:creator>小不点</dc:creator><author>小不点</author><pubDate>Tue, 01 Jan 2008 06:35:00 GMT</pubDate><guid>http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40157.html</guid><wfw:comment>http://www.cppblog.com/amyvmiwei/comments/40157.html</wfw:comment><comments>http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40157.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/amyvmiwei/comments/commentRss/40157.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/amyvmiwei/services/trackbacks/40157.html</trackback:ping><description><![CDATA[<p align=left><span><br><br>dll是现在常见的文件，它集成了程序的很多功能在里面。一般情况下，它不能直接被执行，常见的使用方法是用其他的*.exe调用其执行，以使其内部功能表现出来。还有*.ocx文件也与之类似，也就是人们常说的com<br>1.简要<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Windows API</span><span>中所有的函数都包含在</span><span>dll</span><span>中，其中有</span><span>3</span><span>个最重要的</span><span>DLL</span><span>。<br></span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1)<span>&nbsp;&nbsp;&nbsp;</span></span></span><span>Kernel32.dll<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 它包含那些用于管理内存、进程和线程的函数，例如</span><span>CreateThread</span><span>函数；<br></span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2)<span>&nbsp;&nbsp;&nbsp;</span></span></span><span>User32.dll<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 它包含那些用于执行用户界面任务</span><span>(</span><span>如窗口的创建和消息的传送</span><span>)</span><span>的函数，例如</span><span>CreateWindow</span><span>函数；<br></span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3)<span>&nbsp;&nbsp;&nbsp;</span></span></span><span>GDI32.dll<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 它包含那些用于画图和显示文本的函数。<br></span><span><span>2.<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span><span>静态库和动态库<br></span><span><span>(1)<span>&nbsp;&nbsp;&nbsp;</span></span></span><span>静态库<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数和数据被编译进一个二进制文件</span><span>(</span><span>通常扩展名为</span><span>.LIB)</span><span>。在使用静态库的情况下，在编译链接可执行文件时，链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件</span><span>(.Exe</span><span>文件</span><span>).</span><span>当发布产品时，只需要发布这个可执行文件，并不需要发布被使用的静态库。<br></span><span><span>(2)<span>&nbsp;&nbsp;&nbsp;</span></span></span><span>动态库<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在使用动态库的时候，往往提供两个文件：一个引入库</span><span>(.lib)</span><span>文件和一个</span><span>DLL(.dll)</span><span>文件。虽然引入库的后缀名也是</span><span>&#8221;lib&#8221;</span><span>，但是动态库的引入库文件和静态库文件有着本质上的区别，对一个</span><span>DLL</span><span>来说，其引入库文件</span><span>(.lib)</span><span>包含该</span><span>DLL</span><span>导出的函数和变量的符号名，而</span><span>.dll</span><span>文件包含该</span><span>DLL</span><span>实际的函数和数据。在使用动态库的情况下，在编译链接可执行文件时，只需要链接该</span><span>DLL</span><span>的引入库文件，该</span><span>DLL</span><span>中的函数代码和数据并不复制到可执行文件中，直到可执行程序运行时，才去加载所需的</span><span>DLL</span><span>，将该</span><span>DLL</span><span>映射到进程的地址空间外，然后访问</span><span>DLL</span><span>中导出的函数。这时，发布产品时，除了发布可执行文件以外，同时还要发布该程序将要调用的动态链接库。<br></span><span><span>3.<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span><span>在导出库头文件中的标准写法：<br></span><span>#ifdef LIBDAQ_EXPORTS<br></span><span>#define LIBDAQ_API __declspec(dllexport)<br></span><span>#else<br></span><span>#define LIBDAQ_API __declspec(dllimport)<br></span><span>#endif<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp; 将该头文件添加到某客户代码中时，会自动展开。如果客户代码没有定义</span><span>LIBDAQ_EXPORTS</span><span>，那么</span><span>LIBDAQ_EXPORTS</span><span>会被定义为</span><span>__declspec(dllimport)</span><span>表示有</span><span>LIBDAQ_EXPORTS</span><span>头的函数都是从该</span><span>DLL</span><span>中导入的。<br></span><span><span>4.<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span><span>名字改编和</span><span>&#8221;extern &#8220;C&#8221;&#8221;<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; C++</span><span>编译器在生成</span><span>DLL</span><span>时，会对导出的函数进行名字改编，并且不同的编译器使用的改变规则不一样，因此改编后的名字会不一样。这样，如果利用不同的编译器分别生成</span><span>DLL</span><span>和访问该</span><span>DLL</span><span>的客户端代码程序的话，后者在访问该</span><span>DLL</span><span>的导出函数时会出现问题。为了实现通用性，需要加上限定符：</span><span>extern &#8220;C&#8221;</span><span>。<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 但是利用限定符</span><span>extern &#8220;C&#8221;</span><span>可以解决</span><span>C++</span><span>和</span><span>C</span><span>之间相互调用时函数命名的问题，但是这种方法有一个缺陷，就是不能用于导出一个类的成员函数，只能用于导出全局函数。<br></span><span><span>5.<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span><span>显示加载方式加载</span><span>DLL<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用动态方式来加载动态链接库时，需要用到</span><span>LoadLibrary</span><span>函数。该函数的作用就是将指定的可执行模块映射到调用进程的地址空间。调用原型为：<br></span><span>HMODULE LoadLibrary(LPCTSTR lpFileName);<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LoadLibrary</span><span>函数不仅可以加载</span><span>DLL</span><span>，还可以加载可执行模块</span><span>(Exe)</span><span>。当加载可执行模块时，主要是为了访问该模块内的一些资源，例如对话框资源、位图资源或图标资源等。</span><span>LoadLibrary</span><span>函数有一个字符串类型</span><span>(LPCTSTR)</span><span>的参数，该参数指定了可执行模块的名称，既可以是一个</span><span>dll</span><span>文件，也可以是一个</span><span>exe</span><span>文件。如果调用成功，</span><span>LoadLibrary</span><span>函数将返回所加载的那个模块的句柄。返回类型</span><span>HMODULE</span><span>和</span><span>HINSTANCE</span><span>可以通用。<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当加载到动态链接库模块的句柄后，接下来就要想办法获取该动态链接库中导出函数的地址，这可以通过调用</span><span>GetProcAddress</span><span>函数来实现。该函数用来获取</span><span>DLL</span><span>导出函数的地址，其原型声明如下所示：<br></span><span>FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);<br></span><span>参数</span><span>hModule</span><span>：指定动态链接库模块的句柄，即</span><span>LoadLibrary</span><span>函数的返回值。<br></span><span>参数</span><span>lpProcName</span><span>：一个指向常量的字符指针，指定</span><span>DLL</span><span>导出函数的名字或函数的序号。如果是序号，则序号必须在低位字节中，高位字节必须是</span><span>0</span><span>。<br></span><span>如果调用成功，</span><span>GetProcAddress</span><span>函数将返回指定导出函数的地址；否则返回</span><span>NULL</span><span>。<br></span><span>例如：<br></span><span>HINSTANCE hInst;<br></span><span>hInst = LoadLibrary(&#8220;DllTest.dll&#8221;);<br></span><span>typedef int (*ADDPROC)(int a, int b);<br></span><span>ADDPROC add = (ADDPROC)GetProcAddress(hInst, &#8220;add&#8221;);<br></span><span>if (!add) <br></span><span>print(&#8220;Failure&#8221;);<br></span><span>else <br></span><span>process next events<br></span><span>FreeLibrary(hInst);<br></span><span>调用语法：<br></span><span>BOOL FreeLibrary(HMODULE hModule);<br></span><span><span>6.<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span><span>加载</span><span>DLL</span><span>的两种方式优缺点：<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>采用动态加载方式，那么可以在需要时才加载</span><span>DLL</span><span>，而隐式链接方式实现起来比较简单，在编写客户端代码时就可以把链接工作做好，在程序中可以随时调用</span><span>DLL</span><span>导出的函数。但是如果程序需要访问十多个</span><span>DLL</span><span>时，如果都采用隐式链接方式加载它们的话，那么在该程序启动时，这些</span><span>DLL</span><span>都需要被加载到内存中，并映射到调用进程的地址空间，这样将加大程序的启动时间。而且一般来说，在程序运行过程中只是在某个条件满足时才需要访问某个</span><span>DLL</span><span>中的某个函数，其它情况下都不需要访问这些</span><span>DLL</span><span>中的函数。但是这时所有的</span><span>DLL</span><span>都已经被加载到内存中，资源浪费是比较严重的。这个时候就需要采用显示加载的方式来访问</span><span>DLL</span><span>，在需要时才加载所需的</span><span>DLL</span><span>。也就是说在需要时才被加载到内存中，并被映射到调用进程的地址控件中。需要说明的是，隐式链接方式访问</span><span>DLL</span><span>时，在程序启动时也是通过</span><span>LoadLibrary</span><span>函数加载该进程需要的动态链接库的。<br></span><span><span>7.<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span><span>DllMain</span><span>函数<br></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果提供了</span><span>DllMain</span><span>函数</span><span>(</span><span>该函数是可以选择存在的</span><span>)</span><span>，那么在此函数中不要进行太复杂的调用。因为在加载该动态链接库时，可能还有一些核心动态链接库没有被加载。例如</span><span>Use32.dll</span><span>或</span><span>GDI32.dll</span><span>。我们自己编写的</span><span>DLL</span><span>会比较靠前地被加载。<br></span></p>
8.&nbsp;&nbsp;&nbsp;&nbsp; 。def的应用<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在不同语言间调用约定的不同，甚至有的编译器对名字的修饰也不是完全相同，所以，要使DLL能广泛应用加上一个.def文件。<br>&nbsp;<br>
<img src ="http://www.cppblog.com/amyvmiwei/aggbug/40157.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/amyvmiwei/" target="_blank">小不点</a> 2008-01-01 14:35 <a href="http://www.cppblog.com/amyvmiwei/archive/2008/01/01/40157.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>