﻿<?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++博客-ASHI-随笔分类-转载</title><link>http://www.cppblog.com/ashi/category/15086.html</link><description /><language>zh-cn</language><lastBuildDate>Sun, 03 Oct 2010 17:56:29 GMT</lastBuildDate><pubDate>Sun, 03 Oct 2010 17:56:29 GMT</pubDate><ttl>60</ttl><item><title>用VC创建并使用DLL（转）</title><link>http://www.cppblog.com/ashi/archive/2010/10/04/128536.html</link><dc:creator>ASHI</dc:creator><author>ASHI</author><pubDate>Sun, 03 Oct 2010 17:52:00 GMT</pubDate><guid>http://www.cppblog.com/ashi/archive/2010/10/04/128536.html</guid><wfw:comment>http://www.cppblog.com/ashi/comments/128536.html</wfw:comment><comments>http://www.cppblog.com/ashi/archive/2010/10/04/128536.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ashi/comments/commentRss/128536.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ashi/services/trackbacks/128536.html</trackback:ping><description><![CDATA[<p>为何DLL</p>
<p>先看看静态库与DLL的不同之处</p>
<p>可执行文件的生成（Link期）：前者很慢（因为要将库中的所有符号定义Link到EXE文件中），而后者很快（因为后者被Link的引入库文件无符号定义）<br>可执行文件的大小：前者很大，后者很小（加上DLL的大小就和前者差不多了）<br>可执行文件的运行速度：前者快（直接在EXE模块的内存中查找符号），后者慢（需要在DLL模块的内存中查找，在另一个模块的内存中查找自然较慢）<br>可共享性：前者不可共享，也就是说如果两个EXE使用了同一个静态库，那么实际在内存中存在此库的两份拷贝，而后者是可共享的。<br>可升级性：前者不可升级（因为静态库符号已经编入EXE中，要升级则EXE也需要重新编译），后者可以升级（只要接口不变，DLL即可被升级为不同的实现）<br>&nbsp;<br>综合以上，选择静态库还是DLL<br>1. 静态库适于稳定的代码，而动态库则适于经常更改代码（当然接口要保持不变），当DLL更改（仅实现部分）后，用户不需要重编工程，只需要使用新的Dll即可。<br>2. 由于静态库很吃可执行文件的生成（Link期）时间，所以如果对可执行文件的Link时间比较敏感，那么就用DLL。</p>
<p>使用DLL</p>
<p>在介绍如何创建DLL之前，让我们先了解它是如何被使用的。</p>
<p>1. 显式调用（也叫动态调用）<br>显示调用使用API函数LoadLibrary或者MFC提供的AfxLoadLibrary将DLL加载到内存，再用GetProcAddress()在内存中获取引入函数地址，然后你就可以象使用本地函数一样来调用此引入函数了。在应用程序退出之前，应该用FreeLibrary或MFC提供的AfxLoadLibrary释放DLL。</p>
<p>下面是个显示调用的例子，假定你已经有一个Test.dll，并且DLL中有个函数名为Test，其声明式是void();</p>
<p>#include &lt; iostream &gt;<br>using namespace std;</p>
<p>typedef void(*TEST )();<br>int main( char argc, char* argv[] ) {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; const char* dllName = "Test.dll";<br>&nbsp;&nbsp;&nbsp; const char* funcName = "Test";</p>
<p>&nbsp;&nbsp;&nbsp; HMODULE hDLL = LoadLibrary( dllName );<br>&nbsp;&nbsp;&nbsp; if ( hDLL != NULL ) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TEST func = TEST( GetProcAddress( hDLL, funcName ) );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ( func != NULL ) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; func();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "Unable to find function \&amp;apos;" &lt;&lt; funcName &lt;&lt; "\&amp;apos; !" &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FreeLibrary( hDLL );<br>&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp; else {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "Unable to load DLL \&amp;apos;" &lt;&lt; dllName &lt;&lt; "\&amp;apos; !" &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; return 0;<br>}</p>
<p>注意<br>1. 显示调用使用GetProcAddress，所以只能加载函数，无法加载变量和类。<br>2. 此外GetProcAddress是直接在.dll文件中寻找同名函数，如果DLL中的Test函数是个C++函数，那么由于在.dll文件中的实际文件名会被修饰（具体被修饰的规则可参考函数调用约定详解或者使用VC自带的Depends.exe查看），所以直接找Test是找不到的，这时必须把函数名修改为正确的被修饰后的名称，下面是一种可能（此函数在DLL中的调用约定为__cdecl）：</p>
<p>const char* funcName = "<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#63;&#84;&#101;&#115;&#116;&#64;&#64;&#89;&#65;&#88;&#88;&#90;">?Test@@YAXXZ</a>";</p>
<p>2. 隐式调用（也叫静态调用）<br>隐式调用必须提供DLL的头文件和引入库（可以看作轻量级的静态库（没有符号定义，但是说明了符号处于哪个DLL中））。<br>有了头文件和引入库，DLL的使用就跟普通静态库的使用没啥区别，只除了DLL要和EXE一起发布。</p>
<p>显示调用与隐式调用的优缺点</p>
<p>显示调用使用复杂，但能更加有效地使用内存，因为DLL是在EXE运行时（run time）加载，必须由用户使用LoadLibrary和FreeLibrary来控制DLL从内存加载或卸载的时机。此外还可以加载其他语言编写的DLL函数。<br>静态调用使用简单，但不能控制DLL加载时机，EXE加载到内存同时自动加载DLL到内存，EXE退出时DLL也被卸载。</p>
<p>创建DLL</p>
<p>下面我们着重讲解如何在VC下创建DLL<br>首先要建立一个Win32的DLL工程。再把普通静态库的所有文件转移到DLL工程中，然后：<br>为所有的类声明加上__declspec(dllexport)关键字，这有这样编译器才能自动为你产生引入库文件（否则你需要自己写.def文件来产生，因为不常用所以在此不再阐述其细节）<br>对于DLL的用户来讲，类声明就需要用另外一个关键字__declspec(dllimport)（此关键字对于类和函数而言并非必须，但对于变量而言则是必须的）。所以通常我们会定义一个宏来包装之，比如</p>
<p>#ifdef MYDLL_EXPORTS<br>#&nbsp;&nbsp;&nbsp; define MYDLL_API __declspec(dllexport)<br>#else<br>#&nbsp;&nbsp;&nbsp; define MYDLL_API __declspec(dllimport)<br>#endif</p>
<p>这样我们就能写出如下的类</p>
<p>class MYDLL_API MyClass {<br>&nbsp;...<br>};</p>
<p>当然在创建DLL的工程里需要包含preprocessor(预处理指示器)MYDLL_EXPORTS，而在用户工程里面则不应该包含MYDLL_EXPORTS。</p>
<p>其实上面说的VC早就帮我们做好了。如果你创建的DLL工程叫做Test，那么此工程自动包含TEST_EXPORTS。如果你在创建工程的时候选择了Exprot Symbols, 那么VC还会自动帮你创建一个示例文件Test.h，此文件定义出</p>
<p>#ifdef TEST_EXPORTS<br>#&nbsp;&nbsp;&nbsp; define TEST_API __declspec(dllexport)<br>#else<br>#&nbsp;&nbsp;&nbsp; define TEST_API __declspec(dllimport)<br>#endif</p>
<p>你自定义的文件都应该包含此文件以使用TEST_API。（如果没有选择Exprot Symbols，那么就得自己动手写出这段宏了）</p>
<p>示例文件中还包括了一个类，变量，以及全局函数的写法</p>
<p>class TEST_API CTest {<br>public:<br>&nbsp;&nbsp;&nbsp; CTest(void);<br>&nbsp;&nbsp;&nbsp; // TODO: add your methods here.<br>};</p>
<p>extern TEST_API int nTest;</p>
<p>TEST_API int fnTest(void);<br>&nbsp;&nbsp;&nbsp; <br>通过上面的示例我们也可以看出全局(或者名字空间)变量和函数的声明方法<br>&nbsp;&nbsp;&nbsp; <br>细节讨论<br>&nbsp;&nbsp;&nbsp; <br>1.&nbsp; DLL的入口函数DllMain并非必须，如果没有它，编译器会帮你自动生成一个不做任何事的DllMain。<br>2.&nbsp; 如果是可以定义在头文件里面的东西：包括宏，常量，内联函数（包括成员内联函数）以及模板。那么在DLL中的定义中可以不必包含TEST_API，和普通的定义没有区别。<br>如果一个类完全由内联函数和纯虚函数构成，那么也不需要TEST_API这样的关键字。一个不带成员函数的struct也一样。</p>
<p>3. 所有未经__declspec(dllexport)导出的类都只能作为DLL内部类使用。当然外部仍然可以使用其内联成员函数（前提是该成员函数不应该调用任何未经导出的函数或者类成员函数）</p>
<p>发布DLL<br>&nbsp; <br>1. 程序库的作者应该将三件套：头文件，引入库文件和DLL一同发布给用户，其中头文件和引入库是专为静态调用的用户准备，也就是C/C++用户。（此外有些DLL内部使用的头文件，如果没有被接口头文件直接#include，那么该头文件就不必发布，这和静态库是一样的）。<br>2. DLL的用户只需将DLL和可执行程序一同发布即可。</p>
<p>C++程序使用C语言DLL（静态库规则一样）</p>
<p>C不存在class, 所以由C创建的DLL必然也不会出现class；对于全局变量的使用C和C++没有什么不同，所以我们把焦点集中在全局函数上（此外全局变量的规则一样）。 <br>我们知道C++程序要使用C语言的函数就必须在函数声明前加上extern "C"关键字，这对于静态库和DLL没有什么不同。 但是这个关键字不能直接出现在头文件函数声明中，否则DLL无法通过编译，&nbsp; 原因很简单，C语言并没有extern "C"这个关键字。<br>&nbsp; <br>1. 一种作法是把C向C++迁移的责任交给DLL创建者。定义出一个宏，以使DLL（C工程）中不出现extern "C"或者只是extern，而在用户工程（C++工程）中保持原样。幸运的是windows早已为我们设计好一切，这个宏就是EXTERN_C（存在于 winnt.h中） :<br>&nbsp; <br>#ifdef __cplusplus<br>&nbsp;&nbsp;&nbsp; #define EXTERN_C&nbsp;&nbsp;&nbsp; extern "C"<br>#else<br>&nbsp;&nbsp;&nbsp; #define EXTERN_C&nbsp;&nbsp;&nbsp; extern<br>#endif <br>&nbsp; <br>注意上面必须是extern而不是空。因为虽然C的函数声明不是必须添加extern，但是变量声明则必须添加extern。<br>有了EXTERN_C，在头文件中这样定义函数：<br>&nbsp; <br>EXTERN_C TEST_API int fnTest(void);</p>
<p>2. 另外一种做法是把把C向C++迁移的责任交给用户，用户在包含DLL头文件的时候以extern "C"来include:</p>
<p>extern "C" {<br>&nbsp;&nbsp;&nbsp; #include "mydll.h"<br>&nbsp;&nbsp;&nbsp; #include "mydllcore.h"<br>&nbsp;&nbsp;&nbsp; ...<br>}<br>&nbsp; <br>可以把上述的extern "C"段放在新的头文件中和DLL一起发布，这样C++用户只需要包含这个头文件就可以了。比如Lua库就自带了一个etc/lua.hpp文件。通常此文件会由DLL作者提供，所以此时迁移的责任仍在DLL创建者。<br>注意用户不要试图以extern "C"来重新声明函数，因为重复声明是允许的，但是必须保证和头文件中的声明相同。</p>
<p>3. 这种做法的一个变形就是直接在C头文件中以extern "C"将所有函数和变量声明包含之，这样就无需像上面那样多提供一个额外的头文件了。通常像这样（mydll头文件）：</p>
<p>#include 头文件段</p>
<p>#ifdef __cplusplus<br>extern "C" {<br>#endif</p>
<p>函数和变量声明<br>...</p>
<p>#ifdef __cplusplus<br>}<br>#endif</p>
<p>创建标准的DLL，使其可被其他语言使用</p>
<p>通常我们会希望DLL能够被其他语言使用，因而我们的DLL往往不会提供类定义，而只提供函数定义。（因为许多编程语言是不支持类的）。<br>此时函数的调用约定必须是__stdcall（在vc下默认是__cdecl，所以你不得不手工添置），因为大部分语言不支持__cdecl，但支持__stdcall（比如VBScript，Delphi等）。<br>此外我们希望导出到DLL中的函数名能够更易被识别（用户使用才会更方便），也就是说DLL应该编译出无修饰的C函数名。<br>所以我们可能会<br>1. 如果你只用C语言，那么必然以C文件创建DLL（自动编出C符号名），考虑到潜在的C++用户（此类用户多以静态调用方式使用DLL，因而需要看到其函数声明），我们还需要使用EXTERN_C关键字（详见上面的讨论）。<br>2. 如果你使用C++，那么必然以C++文件创建DLL，这时你应该直接以extern "C"修饰。</p>
<p>结论</p>
<p>所以要创建能够为任意编程语言使用之DLL，我们应该<br>1. 只创建函数<br>2. 声明__stdcall调用约定（或者WINAPI，CALLBACK，前提是你必须包含windows头文件）<br>3. 以CPP文件 + extern "C" 或者 C文件 + EXTERN_C<br>4. 当然还需要DLL_API的声明（如果光有dllexport,那么DLL也只能被显示调用）。</p>
<p>更完美一点</p>
<p>不应该使用dllexport和extern "C"而应该使用.def文件。虽然经过上面的4个步骤，我们的DLL里面的C函数名已经相当简洁了，但仍不是最完美的：比如一个声明为function的函数，实际在DLL中的名字确可能是function@#。而使用.def文件，我们可以让DLL中的函数名和声明的函数名保持一致。<br>关于.def的详细用法可参考：<br>微软DLL专题<br>使用 DEF 文件从 DLL 导出</p>
<p>在DLL与静态库之间切换</p>
<p>前面我曾经提到对于使用DLL的用户__declspec(dllimport)关键字可有可无，当然前提是DLL中不包括变量定义。<br>所以要把库既能够做成DLL，也能够做成静态库，那么就应该作出类似下面这样的定义：</p>
<p>1. DLL不包括变量的定义</p>
<p>#ifdef TEST_EXPORTS<br>#&nbsp;&nbsp;&nbsp; define TEST_API __declspec(dllexport)<br>#else<br>#&nbsp;&nbsp;&nbsp; define TEST_API <br>#endif</p>
<p>然后只要把工程的配置属性（Configuration Type）简单地在Static Library (.lib)或者Dynamic Library (.dll)切换即可（VC会自动地为DLL添加预处理器TEST_EXPORTS，为静态库取消TEST_EXPORTS）。</p>
<p>2. DLL包含变量定义，也是标准的做法</p>
<p>#ifdef TEST_BUILD_AS_DLL<br>&nbsp;&nbsp;&nbsp; #ifdef TEST_EXPORTS<br>&nbsp;&nbsp;&nbsp; #&nbsp;&nbsp;&nbsp; define TEST_API __declspec(dllexport)<br>&nbsp;&nbsp;&nbsp; #else<br>&nbsp;&nbsp;&nbsp; #&nbsp;&nbsp;&nbsp; define TEST_API __declspec(dllimport)<br>&nbsp;&nbsp;&nbsp; #endif<br>#else<br>&nbsp;&nbsp;&nbsp; #define TEST_API<br>#endif</p>
<p>如果要将库做成DLL，那么需要DLL创建者添加预处理器TEST_BUILD_AS_DLL和TEST_EXPORTS，后者通常由编译器自动添加；如果做成静态库则不需要添加任何预处理器。<br>用户则可以通过添加或取消TEST_BUILD_AS_DLL来使用动态库或静态库。</p>
<p>对于DLL的用户，TEST_BUILD_AS_DLL这个名称似乎起得不好。下面的做法或许会更合理些：</p>
<p>#if defined(TEST_API)<br>#error "macro alreday defined!"<br>#endif</p>
<p>#if defined(TEST_BUILD_DLL) &amp;&amp; defined(TEST_USE_DLL)<br>#error "macro conflict!"<br>#endif</p>
<p>#if defined(TEST_BUILD_DLL) // build dll<br>#&nbsp;&nbsp;&nbsp; define TEST_API __declspec(dllexport)<br>#elif defined(TEST_USE_DLL) // use dll<br>#&nbsp;&nbsp;&nbsp; define TEST_API __declspec(dllimport)<br>#else // build or use library, no preprocessor needs<br>#&nbsp;&nbsp;&nbsp; define TEST_API&nbsp;&nbsp;&nbsp; <br>#endif<br><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/hjsunj/archive/2008/01/16/2047376.aspx">http://blog.csdn.net/hjsunj/archive/2008/01/16/2047376.aspx</a></p>
<img src ="http://www.cppblog.com/ashi/aggbug/128536.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ashi/" target="_blank">ASHI</a> 2010-10-04 01:52 <a href="http://www.cppblog.com/ashi/archive/2010/10/04/128536.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>