(1)vc6下面生成dll学习

1.使用 VC6.0 生成 DLL
新建项目 “Win32 Dynamic-Link Library”,输入项目名称,确定后选择 “A simple DLL project” 点击“完成”。

以下为cpp文件自动生成的代码:
#include "stdafx.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}

编辑cpp文件:
在#include "stdafx.h"的下一行加入
extern "C" __declspec(dllexport) int fun(int a, int b);
/*
这是C格式导出函数;
这种写法一般用在C++写的DLL中,指按C的规则导出这个函数,否则导出的函数会很怪;
加上 extern "C" 表示按标准C格式导出函数.如果去掉仅兼容C++;
其中 int fun(int a, int b) 这部分代码是我们想用 dll 实现的函数原型声明
如果还想加入其他的可以继续加入 extern "C" __declspec(dllexport) int fun1(int a, int b);
*/

DllMain 是 DLL 的默认入口函数,类似于C语言的main函数,该例子无需修改此处,在 DllMain 的后面加入:
int fun(int a,int b)
{
return a+b;
}

这就是我们想用 DLL 实现的函数的定义,build 之后就会在 debug 目录下生成我们想要的 dll 文件
2.调用 DLL
新建一个 Win32 Console Application 工程,把刚才生成的 dll 文件拷贝到工程的根目录下

在 stdafx.h 文件中加入:#include <windows.h>

编辑cpp文件:
#include "stdafx.h"
typedef int (*PFUN)(int,int);
void main()
{
HMODULE hModule = ::LoadLibrary("dlltest.dll");
PFUN newfun = (PFUN)::GetProcAddress(hModule,"fun");
int i = newfun(1,2);
printf("The result is %d\n",i);
::FreeLibrary(hModule);
}

然后,运行就可以看到结果了

转 VC6.0下调用Dll文件提供的函数接口和全局变量
函数接口:
首先把生成的Dll文件(如RegularDll.dll和RegularDll.lib)拷贝到当前工程所在文件夹,调用有两种方法:

1)动态方法:
使用LoadLibrary和GetProcAddress等函数,例
typedef void (*lpFun)(void);
HINSTANCE hDll;
hDll = LoadLibrary("RegularDll.dll");
if (NULL==hDll) {
MessageBox("Dll load failed!");
}
lpFun pShowDlg = (lpFun)GetProcAddress(hDll,"ShowDlg");
if (NULL == pShowDlg) {
MessageBox("Load function \"ShowDlg\" failed!");
}
pShowDlg();*/

2)静态声明方法:
创建Dll的工程中,函数声明和定义时用 _stdcall 修饰,例
void _stdcall ShowDlg(void){...}
在调用dll的工程的文件中,在文件头部声明库和函数,如下例
#pragma comment(lib,"RegularDll.lib")
void _stdcall ShowDlg(void);
调用时直接ShowDlg()就可以了。
以上两种方法在VC6.0中调试成功,保证可用!

有的文章中说这样声明即可:
#pragma comment(lib,"RegularDll.lib")
void ShowDlg(void);
但在VC6.0下会报找不到函数ShowDlg的错误,不知是不是我没有设置好?

全局变量(访问Dll文件中的全局变量)
在创建Dll的工程中,在头文件中这样声明:
//lib.h
#ifndef _LIB_H
#define _LIB_H
#ifdef DLL_FILE
extern int dllGlobalVar;
#else
extern int _declspec(dllimport) dllGlobalVar;
#endif

在创建Dll的工程的cpp文件中先定义DLL_FILE,再定义该变量,如下例:
//lib.cpp
#define DLL_FILE
#include "lib.h"
int dllGlobalVar; //define
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch(ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
dllGlobalVar = 100; // initialization when dll attached
break;
……
}
int _stdcall GetGlobalVar()
{
return dllGlobalVar; // use the global variable
}

在调用Dll文件的工程中,需要访问该全局变量时,声明如下:
#include "..\\lib.h"
#pragma comment(lib,"dllTest.lib")
然后可以当作普通的全局值变量使用和修改了!
printf("%d \n", dllGlobalVar);
dllGlobalVar = 234;
printf("%d \n", dllGlobalVar);
修改会影响本工程使用的值,不过内部实现的原理还不清楚:(

据说还有一种方法可以访问全局变量的指针形式,不过我一直没有试通,希望高人指点!
如有其他问题,欢迎邮件交流!(邮件地址见公告)

初识dll,在VS2010平台上创建并使用dll(revised)

一、为什么需要dll

代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,如ATL、MFC等,它们都以源代码的形式发布。由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。“白盒复用”的缺点比较多,总结起来有4点。

  1. 暴露了源代码;
  2. 容易与程序员的“普通”代码发生命名冲突;
  3. 多份拷贝,造成存储浪费;
  4. 更新功能模块比较困难。

实际上,以上4点概括起来就是“暴露的源代码”造成“代码严重耦合”。为了弥补这些不足,就提出了“二进制级别”的代码复用。使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为“黑盒复用”。

在Windows操作系统中有两种可执行文件,其后缀名分别为.exe和.dll。它们的区别在于,.exe文件可被独立的装载于内存中运行;.dll文件却不能,它只能被其它进程调用。然而无论什么格式,它们都是二进制文件。上面说到的“二进制级别”的代码复用,可以使用.dll来实现。

与白盒复用相比,.dll很大程度上弥补了上述4大缺陷。.dll是二进制文件,因此隐藏了源代码;如果采用“显式调用”(后边将会提到),一般不会发生命名冲突;由于.dll是动态链接到应用程序中去的,它并不会在链接生成程序时被原原本本拷贝进去;.dll文件相对独立的存在,因此更新功能模块是可行的。

说明:实现“黑盒复用”的途径不只dll一种,静态链接库甚至更高级的COM组件都是。本文只对dll进行讨论。

二、创建dll

接下来用一个简单的例子来说明创建dll的方法。本例采用VS2010,使用C++编程语言,具体操作步骤如下。

通过Start Page或者File菜单栏,新建一个Project,将会弹出新建项目对话框。选择Win32 Project向导,项目名为CreateDLL,解决方案名为DLLTEST(注意Create directories for solution是勾选上的),点击OK,接着点击Next,到Application Settings,选择应用程序类型为dll,并勾选“Export Symbols”,点击Finish。完成这一步之后,VS界面上左边的Solution Explorer中将会看到向导自动生成的文件列表,如图1所示。

图1 wizard自动生成的文件列表

在VS界面的编辑窗口中,展示了自动生成的CreateDLL.cpp的代码。

  1. // CreateDLL.cpp : Defines the exported functions for the DLL application.
  2. //
  3. #include "stdafx.h"
  4. #include "CreateDLL.h"
  5. // This is an example of an exported variable
  6. CREATEDLL_API int nCreateDLL = 0;
  7. // This is an example of an exported function.
  8. CREATEDLL_API int fnCreateDLL(void)
  9. {
  10. return 42;
  11. }
  12. // This is the constructor of a class that has been exported.
  13. // see CreateDLL.h for the class definition
  14. CCreateDLL::CCreateDLL()
  15. {
  16. return;
  17. }

这里有3种类型的example,分别为导出变量nCreateDLL、导出函数fnCreateDLL以及导出类CCreateDLL。为了简化起见,本例只考虑导出函数。修改CreateDLL.h文件为:

  1. #ifdef CREATEDLL_EXPORTS
  2. #define CREATEDLL_API __declspec(dllexport)
  3. #else
  4. #define CREATEDLL_API __declspec(dllimport)
  5. #endif
  6. CREATEDLL_API void printMax(int&,int&);
  7. CREATEDLL_API void printMax(int&,int&,int&);

修改CreateDLL.cpp文件为:

  1. CREATEDLL_API void printMax(int& a,int& b)
  2. {
  3. std::cout<<"Among ("<<a<<","<<b<<"), the Max Number is "<<(a>b?a:b)<<"\n";
  4. }
  5. CREATEDLL_API void printMax(int& a,int& b,int& c)
  6. {
  7. std::cout<<"Among ("<<a<<","<<b<<","<<c<<"), the Max Number is "<<(((a>b?a:b)>c)?(a>b?a:b):c)<<"\n";
  8. }

不难发现,printMax函数的作用就是打印出两个整数或三个整数中的最大值。需要说明的是,这里故意使用同名函数是为了引出导出函数的修饰名称,具体将在第四节中阐述。

接下来,选择菜单Build->Build CreateDLL,Output窗口提示CreateDLL.dll文件生成成功,如图2所示。

图2 CreateDLL.dll成功生成

三、使用dll

本例采用“显式调用”的方式使用CreateDLL.dll。显式调用方式相比于”隐式调用“有好有坏。显式调用只需要一个.dll文件就可以了,灵活性更好,更新模块方便;相对的,程序员需要做的事情更多,使用方法更为复杂。

右键单击Solution Explorer中的Solution 'DLLTEST',在弹出的菜单中选择Add->New Project,选择Win32 Console Application,输入项目名为UseDLL,点击OK,接着点击Next,在Application Settings界面勾选EmptyProject并点击Finish。右键单击项目UseDLL,给它添加源文件UseDLL.cpp。这样操作之后,Solution Explorer的信息如图3所示。

图3 向Solution'DLLTEST'添加项目UseDLL

编写UseDLL.cpp的代码为:

  1. /*--UseDLL.cpp
  2. *Author: ume(李优米)
  3. *Use CreateDLL.dll explicitly
  4. */
  5. #include<Windows.h>
  6. #include<iostream>
  7. typedef void(*FUNA)(int&,int&);
  8. typedef void(*FUNB)(int&,int&,int&);
  9. int main()
  10. {
  11. const char* dllName = "CreateDLL.dll";
  12. const char* funName1 = "printMax";
  13. const char* funName2 = "printMax";
  14. int x(100), y(100), z(100);
  15. HMODULE hDLL = LoadLibrary(dllName);
  16. if(hDLL != NULL)
  17. {
  18. FUNA fp1 = FUNA(GetProcAddress(hDLL,funName1));
  19. if(fp1 != NULL)
  20. {
  21. std::cout<<"Input 2 Numbers:";
  22. std::cin>>x>>y;
  23. fp1(x,y);
  24. }
  25. else
  26. {
  27. std::cout<<"Cannot Find Function "<<funName1<<std::endl;
  28. }
  29. FUNB fp2 = FUNB(GetProcAddress(hDLL,funName2));
  30. if(fp2 != NULL)
  31. {
  32. std::cout<<"Input 3 Numbers:";
  33. std::cin>>x>>y>>z;
  34. fp2(x,y,z);
  35. }
  36. else
  37. {
  38. std::cout<<"Cannot Find Function "<<funName2<<std::endl;
  39. }
  40. FreeLibrary(hDLL);
  41. }
  42. else
  43. {
  44. std::cout<<"Cannot Find "<<dllName<<std::endl;
  45. }
  46. return 1;
  47. }

代码比较长,但是并不难理解,这里仅说明代码中的一些要点。

  • 包含头文件Windows.h,原因在于程序中用到了LoadLibrary、FreeLibrary、GetProcAddress等Win32 API函数。
  • FUNA和FUNB是函数指针类型的声明。
  • 当程序不再使用dll时,应该调用FreeLibrary及时释放它占用的内存空间。
  • 如果在const char* dllName和funName底部出现红色波浪线提示,说明采用的字符集不匹配,需要修改项目UseDLL的属性CharaterSet为Not Set。
  • 为方便项目的调试,建议修改解决方案的Startup Project属性为Single startup project并以UseDLL为首选。

然而,这个程序还有错误。编译并运行,结果如图4所示。

图4 UseDLL的运行结果

这并不是期望中的结果。实际上,正如第二节提到的那样,造成这种错误的原因正是导出函数的修饰名称。虽然在CreateDLL.cpp中两个printMax函数有相同的名称,但在dll二进制文件中,经过编译器的“加工”,它们实际上各自有不同的名称了。这也是函数重载机制得以实现的一个技术支持。

使用VS2010附带工具dumpbin,查看CreateDLL.dll的导出函数名,结果如图5所示。

图5 查看CreateDLL.dll的导出函数名

观察图5可以发现,CreateDLL.dll导出函数名为?printMax@@YAXAAH00@Z和?printMax@@YAXAAH0@Z。它们分别对应着三个整数的printMax和两个整数的printMax。因此,Use.DLL中funName应当相应修改为:

  1. const char* funName1 = "?printMax@@YAXAAH0@Z";
  2. const char* funName2 = “?printMax@@YAXAAH00@Z”;

修改之后,再次编译运行,结果正确,如图6所示。

图6 UseDLL正常运行

四、dll导出函数名称规范化

创建、使用dll并不复杂,走过前三节,相信读者肯定有这样的体会。然而,一个问题仍然值得思考:导出函数的修饰名称太“奇怪”,为dll的使用带来了不便,能不能让导出函数的修饰名称规范一些?

答案是肯定的,而且方法至少有两种:一是运用extern "C"修饰printMax;二是运用模块定义文件.def。后者的效果更好,所以本节将使用.def来规范化导出函数的修饰名称。

CreateDLL.dll导出的两个函数功能很简单,根据功能描述,理想的函数名称是pMaxA2和pMaxA3。在CreateDLL项目中添加CreateDLL.def文件:

  1. LIBRARY CreateDLL
  2. EXPORTS
  3. pMaxA2 = ?printMax@@YAXAAH0@Z
  4. pMaxA3 = ?printMax@@YAXAAH00@Z

重新build项目CreateDLL,使用dumpbin再次查看CreateDLL.dll的导出函数名称,结果如图7所示。

图7 规范化的函数名,奇怪的修饰名称还存在

出现了期望的结果,但仍有小缺憾:奇怪的修饰名称仍然存在。能否去掉这些不太规范的修饰名称呢?当然是可以的。只需要将CreateDLL.h中#define CREATEDLL_API __declspec(dllexport) 修改为#define CREATEDLL_API即可。修改之后重新编译生成CreateDLL.dll,使用dumpbin查看导出函数名称,结果如图8所示。

图8 规范化的函数名,去除了奇怪的修饰名称

回到UseDLL.cpp,修改funName:

  1. const char* funName1 = "pMaxA2";
  2. const char* funName2 = "pMaxA3";

重新编译运行UseDLL,结果正确,与图6类似。
五、dll的不足

动态链接库虽然一定程度上实现了“黑盒复用”,但仍存在着诸多不足,笔者能够想到的有下面几点。

  1. dll节省了编译期的时间,但相应延长了运行期的时间,因为在使用dll的导出函数时,不但要加载dll,而且程序将会在模块间跳转,降低了cache的命中率。
  2. 若采用隐式调用,仍然需要.h、.lib、.dll文件(“三件套”),并不能有效支持模块的更新。
  3. 显式调用虽然很好地支持模块的更新,但却不能导出类和变量。
  4. dll不支持Template。

二进制级别的代码复用相比源码级别的复用已经有了很大的进步,但在二进制级别的代码复用中,dll显得太古老。想真正完美实现跨平台、跨语言的黑盒复用,采用COM才是正确的选择。

python 与cpp接口编程的更多相关文章

  1. 初识Django —Python API接口编程入门

    初识Django —Python API接口编程入门 一.WEB架构的简单介绍 Django是什么? Django是一个开放源代码的Web应用框架,由Python写成.我们的目标是用Python语言, ...

  2. Python 中的面向接口编程

    前言 "面向接口编程"写 Java 的朋友耳朵已经可以听出干茧了吧,当然这个思想在 Java 中非常重要,甚至几乎所有的编程语言都需要,毕竟程序具有良好的扩展性.维护性谁都不能拒绝 ...

  3. python调用zabbix接口实现Action配置

    要写这篇博客其实我的内心是纠结的,老实说,我对zabbix的了解实在不多.但新公司的需求不容置疑,当我顶着有两个头大的脑袋懵懵转入运维领域时,面前摆着两百多组.上千台机器等着写入zabbix监控的需求 ...

  4. 【循序渐进学Python】15.网络编程

    Python 内置封装了很多常见的网络协议的库,因此Python成为了一个强大的网络编程工具,这里是对Python的网络方面编程的一个简单描述. 1. 常用的网络设计模块 在标准库中有很多网络设计相关 ...

  5. 关于python测试webservice接口的视频分享

    现在大公司非常流行用python做产品的测试框架,还有对于一些快速原型产品的开发也好,很好地支持OO编程,代码易读.Python的更新挺快的,尤其是第三方库. 对于测试人员,代码基础薄弱,用pytho ...

  6. Python中的并发编程

    简介 我们将一个正在运行的程序称为进程.每个进程都有它自己的系统状态,包含内存状态.打开文件列表.追踪指令执行情况的程序指针以及一个保存局部变量的调用栈.通常情况下,一个进程依照一个单序列控制流顺序执 ...

  7. Python(五)编程小实例

    Python(五)编程小实例 抓取网页信息,并生成txt文件内容! Python抓取网页技能--Python抓取网页就是我们常看见的网络爬虫,我们今天所要用到的就是我们Python中自带的模块,用这些 ...

  8. python入门以及接口自动化实践

    一.Python入门必备基础语法# 标识符:python中我们自己命名的都是标识符# 项目名 包名 模块名# 变量名 函数名 类名# 1:字母 下划线 数字组成 命名的时候不能以数字开头# 2:见名知 ...

  9. python套接字编程基础

    python套接字编程 目录 socket是什么 套接字的工作流程 基于tcp的套接字 基于udp的套接字 socket是什么 客户端/服务器架构(C/S架构) 服务端:提供服务的一端 客户端:请求服 ...

随机推荐

  1. Linux下实时查看GPU状况

    1. 显示当前GPU使用情况 Nvidia自带了一个nvidia-smi的命令行工具,会显示显存使用情况: $ nvidia-smi 输出如下: 2. 周期性输出GPU使用情况 但是有时我们希望不仅知 ...

  2. 2019-03-25 Python Pandas 基本操作

    新建表 data1 = { "name": ["Tom", "Bob", "Mary", "James&quo ...

  3. 阿里云Linux系统Nginx配置多个域名的方法

    Nginx绑定多个域名,可通过把多个域名规则写一个配置文件里实现,也可通过分别建立多个域名配置文件实现,为了管理方便,建议每个域名建一个文件,有些同类域名则可写在一个总的配置文件里. 1. 比如我想建 ...

  4. 【BZOJ 1433】[ZJOI2009]假期的宿舍

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 把每个人都分为左边和右边两个人 xi,yi 如果第i个人不回家或者是外校学生 那么它可以和他认识的人连一条容量为1的边(前提是这个认 ...

  5. HDU 4165

    一块药看成括号配对就行了.很明显的直接求卡特兰数. 今晚看了HDU 3240的题,有一点思路,但无情的TLE.想不到什么好方法了,看了别人的解答,哇...简直是天才的做法啊....留到星期六自己思考一 ...

  6. cocos2d-x3.2 下使用多线程

    事实上在cocos2dx下使用多线程事实上就是用C++去写,这里提供几个简单的样例: 原文地址:http://blog.csdn.net/qqmcy/article/details/36227377 ...

  7. node11---相册

    app.js /* littleAlbum --.idea --controller(控制层相当于action层) --package.json --router.js --models(做事的是mo ...

  8. 用自定义的函数将gps转换为高德坐标

    <?php echo<<<_END <!doctype html> <html> <head> <meta charset=" ...

  9. linux 终端提示符

    默认的当路径一长就难看得出奇. 我的设置: export PS1="|\W$>\[\e[0m\]" 最后效果就是|目录名$> 参考:https://www.cnblog ...

  10. nyoj--586--疯牛(二分&&枚举)

    疯牛 时间限制:1000 ms  |  内存限制:65535 KB 难度:4 描述 农夫 John 建造了一座很长的畜栏,它包括N (2 <= N <= 100,000)个隔间,这些小隔间 ...