LIB和DLL的使用

内容列表

  • lib的创建和使用
  • 最简单的DLL
  • 显式加载DLL
  • 用def文件定义输出函数
  • 显式调用DLL中的函数
  • 使用__declspec(dllexport)定义DLL输出的函数
  • 使用extern "c"隐式创建和使用DLL
  • __declspec(dllexport)和__declspec(dllimport)配对使用
  • DLL中导出全局变量和对象
  • VC中编写和调用DLL

 

参考

DLL编写教程

http://www.blogjava.net/wxb_nudt/archive/2007/09/11/144371.html

比较及创建例子:静态链接库和动态链接库

http://www.cnblogs.com/Winston/archive/2008/07/05/1236273.html

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

LIB相关

  • lib的创建
    • 建立Win32 Project , 选择lib,支持MFC;之后直接在工程中添加类;
    • 编译,生成lib
    • 提供给用户头文件和.lib文件
    • 注:lib文件输出路径设置Linker-Advanced-Import Library下,例如: ..\lib2007\$(TargetName).lib
  • lib的使用
    • 包含头文件
    • 在工程设置中添加.lib文件,使用类时添加头文件即可。
    • 或者在要使用类的cpp文件头部加入下述语句:
      • #pragma comment(lib,"**.lib") 

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

最简单的DLL

  • 程序
  • #include <objbase.h>
    #include <iostream.h>
    BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
    {
        HANDLE g_hModule;
        switch(dwReason)
        {
        case DLL_PROCESS_ATTACH:
           cout<<"Dll is attached!"<<endl;
           g_hModule = (HINSTANCE)hModule;
           break;
        case DLL_PROCESS_DETACH:
           cout<<"Dll is detached!"<<endl;
           g_hModule=NULL;
           break;
        }
        return true;
    }
  • 参数说明
    • hModule:表示本dll的实例句柄
    • dwReason:dll当前所处的状态
    • DLL_PROCESS_ATTACH表示dll刚刚被加载到一个进程中
    • DLL_PROCESS_DETACH表示dll刚刚从一个进程中卸载

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

用LoadLibrary显式加载DLL

  • 程序
  • #include <windows.h>
    #include <iostream.h>
    int main(void)
    {
        //加载我们的dll
        HINSTANCE hinst=::LoadLibrary("dll_nolib.dll"); 
        if (NULL != hinst)
        {
           cout<<"dll loaded!"<<endl;
        }
        return 0;
     }
  • DLL的位置
    • 将DLL放到sln项目所在的当前目录,可以加载;
    • 问题
      • 如何放到其他目录,然后通过包含目录的方式加载?
      • 解决:在linker-General选项卡下有lib目录的设置

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

用LoadLibraryEx显式加载DLL

  • 原型

HMODULE WINAPI LoadLibraryEx(

  _In_        LPCTSTR lpFileName,

  _Reserved_  HANDLE hFile,

  _In_        DWORD dwFlags

);

  • 例子

HMODULE m_hModule;

m_hModule = NULL;

CString strPath = teamworkEnv();

strPath += _T("\\Bin\\Win32\\CooBaseInterface.dll");

m_hModule = LoadLibraryEx( strPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);

 

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

使用Def文件定义输出函数

  • Def文件格式
    • 首先是LIBRARY关键字,指定dll的名字
    • 然后一个可选的关键字DESCRIPTION,后面写上版权等信息(不写也可以);
    • 最后是EXPORTS关键字,后面写上dll中所有要输出的函数名或变量名,然后接上@以及依次编号的数字(从1到N),最后接上修饰符
      • eg:FuncInDll @1 PRIVATE
      • 注意@前的空格!

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

显式调用DLL里的函数

  • 定义函数指针
    • typedef void (* DLLWITHLIB )(void); 
  • 定义一个函数指针变量
    • DLLWITHLIB pfFuncInDll = NULL; 
  • 加载DLL
    • HINSTANCE hinst=::LoadLibrary("dll_def.dll"); 
    • 之后判断hinst是否为NULL
  • 使用GetPorcAddress找到dll中导出的函数,赋值给函数指针变量
    • pfFuncInDll = (DLLWITHLIB)GetProcAddress(hinst, "FuncInDll"); 
    • 之后判断pfFuncInDll是否为空
    • 注意函数 GetPorcAddress
      • 用途:这个API是用来查找dll中的函数地址的
      • 第一个参数是DLL的句柄,即LoadLibrary返回的句柄
      • 第二个参数是dll中的函数名称,即dumpbin中输出的函数名(注意,这里的函数名称指的是编译后的函数名,不一定等于dll源代码中的函数名)。
  • 调用dll里的函数
    •  (*pfFuncInDll)();   

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

使用__declspec(dllexport)定义dll的输出函数

  • 方法:
    • 去掉def文件,并在每个要输出的函数前面加上声明__declspec(dllexport)
  • 容易产生的问题
    • 编译后的函数名为?FuncInDll@@YAXXZ,而并不是FuncInDll;这是因为c++编译器基于函数重载的考虑,会更改函数名
  • 解决方法
    • 使用extern“C”指令来命令c++编译器以c编译器的方式来命名该函数
    • 修改后的函数声明:extern "C" __declspec(dllexport) void FuncInDll (void)

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

隐式调用DLL

  • DLL的创建
    • .h文件:包含函数声明
    • .cpp文件:包含DllMain函数及函数实现
    • 例子:
    • 代码如下:
      dll_withlibAndH.h
      extern "C" __declspec(dllexport) void FuncInDll (void);
      dll_withlibAndH.cpp
      #include <objbase.h>
      #include <iostream.h>
      #include "dll_withLibAndH.h"//看到没有,这就是我们增加的头文件
      extern "C" __declspec(dllexport) void FuncInDll (void)
      {
          cout<<"FuncInDll is called!"<<endl;
      }
      BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
      {
          HANDLE g_hModule;
          switch(dwReason)
          {
          case DLL_PROCESS_ATTACH:
             g_hModule = (HINSTANCE)hModule;
             break;
          case DLL_PROCESS_DETACH:
              g_hModule=NULL;
              break;
          }
          return TRUE;
      }
  • DLL的使用
    • 步骤
      • 包含头文件 dll_withLibAndH.h
        • .h的路径可以通过C++-常规选项卡下设置
      • 加载lib
        • #pragma comment(lib,"dll_withLibAndH.lib")
        • 也可以通过linker选项卡设置
        • lib的路径可以通过linker-常规选项卡下设置
      • 将DLL文件拷贝到当前项目目录下
        • 如果不拷贝DLL文件,运行时会报错!
      • 第三步也是使用DLL方式和使用LIB方式操作上的主要区别!两种方式前两步的设置是一样的!
    • 客户端代码:
    • #include "dll_withLibAndH.h"
      //注意路径,加载 dll的另一种方法是 Project | setting | link 设置里
      #pragma comment(lib,"dll_withLibAndH.lib")
      int main(void)
      {
          FuncInDll();//只要这样我们就可以调用dll里的函数了
          return 0;
      }

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

问题:

  • (显式调用) 如何将DLL放到其他位置?然后通过设置路径调用?

使用LoadLibraryEx, 第一个参数传入DLL路径;

例如:

m_hModule = LoadLibraryEx( strPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);

  • 说明:DLL生成后直接放置在Debug下,同一个Solution的project之间的引用不用设置也不会出问题;不同的Solution需要将DLL拷贝至使用者的Debug下

 

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

配对使用__declspec(dllexport)和__declspec(dllimport)

这时要考虑一个情况:若DLL1.CPP是源,DLL2.CPP使用了DLL1中的函数,但同时DLL2也是一个DLL,也要输出一些函数供Client.CPP使用。那么在DLL2中如何声明所有的函数,其中包含了从DLL1中引入的函数,还包括自己要输出的函数。这个时候就需要同时使用__declspec(dllexport)和__declspec(dllimport)了。前者用来修饰本dll中的输出函数,后者用来修饰从其它dll中引入的函数。

DLL1,DLL2,client的创建和使用过程

  • 创建DLL1
    • 包括.h和.cpp文件,h文件中包含函数声明,cpp文件中包含函数实现
    • h文件中要有如下语句:
      • #ifdef DLL_DLL1_EXPORTS
      • #define DLL_DLL1_API __declspec(dllexport)
      • #else
      • #define DLL_DLL1_API __declspec(dllimport)
      • #endif
    • 说明:
      • 在头文件中以这种方式定义宏DLL_DLL2_EXPORTS和DLL_DLL2_API,可以确保DLL端的函数用__declspec(dllexport)修饰,而客户端的函数用__declspec(dllimport)修饰。当然,记得在编译dll时加上参数/D “DLL_DLL2_EXPORTS”,或者干脆就在dll的cpp文件第一行加上#define DLL_DLL2_EXPORTS
    • h文件中的函数声明写为:
      • DLL_DLL1_API void FuncInDll1(void);
      • DLL_DLL1_API void FuncInDll1(int);
    • cpp文件中要定义宏:
      • #define DLL_DLL1_EXPORTS
    • cpp文件中的函数实现,开头添加:
      • DLL_DLL1_API
  • 创建DLL2
    • .h和.cpp文件的写法与DLL1中类似
    • 不同1:.h中包含DLL1.h
      • 需要设置DLL1.h的路径(可以从C++-常规下设置)
    • 不同2:.cpp中包含语句
      • #pragma comment(lib,"dll1.lib")
      • 也可以从linker-input下设置
      • 需要设置dll1.lib的路径(可以从linker-常规下设置)
  • 创建client
    • 需要包含DLL2.h(里边已包含DLL1.h)
    • 需要包含lib
      • #pragma comment(lib,"dll2.lib")
      • #pragma comment(lib,"dll1.lib")
    • 自己的Solution中采用的方法
      • 将.h文件全放到Solution目录下lib文件夹中
      • .lib的路径设置为Solution的Debug目录
        • 需要设置project之间的依赖关系(dll2依赖dll1,client依赖上述两个),不然编译时顺序会有问题
      • DLL生成后直接放置在Debug下,同一个Solution的project之间的引用不用设置也不会出问题;不同的Solution需要将DLL拷贝至使用者的Debug下。

 

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

导出全局变量

  • 语法
    • 头文件中:extern DLL_OBJECT_API int g_nDll;
    • cpp文件中:DLL_OBJECT_API int g_nDll = 9;

 

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

小结

  • 显式调用和隐式调用的使用时机
    • 只有一个时候使用显式调用是合理的,就是当客户端不是C/C++的时候。这时是无法隐式调用的。
  • Def的使用
    • 其实def的功能相当于extern “C” __declspec(dllexport),所以它也仅能处理C函数,而不能处理重载函数。
    • 而__declspec(dllexport)和__declspec(dllimport)配合使用能够适应任何情况,因此__declspec(dllexport)是更为先进的方法。
  • C语言调用DLL
    • 若使用extern “C”,则函数名称保持不变,调用较方便,但是不支持函数重载等一系列c++功能
    • 若不使用extern “C”,则调用前要查看编译后的符号,非常不方便
    • 这两个问题DLL都不能很好的解决,只能说凑合着用。但是在COM中,都得到了完美的解决。所以,要在Windows平台实现语言无关性,还是只有使用COM中间件。

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

posted @ 2013-03-05 13:00  知音  阅读(390)  评论(0编辑  收藏  举报