VS 2015 DLL的创建、静态调用和动态调用

原文地址: https://blog.csdn.net/chy555chy/article/details/53021250

DLL的创建

创建步骤

文件 -> 新建 -> 项目 -> “新建项目”对话框 -> “已安装” -> 模板 -> 其他语言 -> Vistual C++ -> Win32 控制台应用程序。


1.jpg

在”Win32 应用程序向导”对话框中

“控制台程序类型”选择”DLL(D)”
“附加选项”勾选”导出符号(X)”


2.jpg

3.jpg

导入(导出)标记的宏定义
下列 ifdef 块是创建使从 DLL 导出更简单的宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的DLLDEMO_EXPORTS符号编译的。在使用此 DLL 的任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将DLLDEMO_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的符号视为是被导出的

#ifdef DLLDEMO_EXPORTS
#define DLLDEMO_API __declspec(dllexport)
#else
#define DLLDEMO_API __declspec(dllimport)
#endif

导出符号标识的宏定义位于:解决方案资源管理器 -> 项目属性 -> “项目属性页”对话框 -> “配置属性” -> C/C++ -> 预处理器 -> 预处理器定义

4.jpg

修改DLL项目

DllDemo.h

#ifdef DLLDEMO_EXPORTS
#define DLLDEMO_API __declspec(dllexport)
#else
#define DLLDEMO_API __declspec(dllimport)
#endif

// 此类是从 DllDemo.dll 导出的
class DLLDEMO_API CDllDemo {
public:
    CDllDemo(void);
    // TODO:  在此添加您的方法。
};

extern DLLDEMO_API int nDllDemo;
extern "C" extern DLLDEMO_API int nExternCDllDemo;

DLLDEMO_API int fnDllDemo(void);
extern "C" DLLDEMO_API int fnExternCDllDemo(void);

char DLLDEMO_API fnDefault(char, int, float);
char DLLDEMO_API __stdcall fnstdcall(char, int, float);
char DLLDEMO_API __cdecl fncdecl(char, int, float);
char DLLDEMO_API __fastcall fnfastcall(char, int, float);

dllmain.cpp

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include <stdio.h>
#include <tchar.h>

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    /*
    获取模块文件名,如果是dll的话,获取的是调用它的可执行文件路径
    WINBASEAPI 
    _Success_(return != 0)
    _Ret_range_(1, nSize) 
    DWORD WINAPI GetModuleFileNameW(
        _In_opt_ HMODULE hModule,
        _Out_writes_to_(nSize, ((return < nSize) ? (return + 1) : nSize)) LPWSTR lpFilename,
        _In_ DWORD nSize
    );
    */
    TCHAR lpFilename[MAX_PATH];
    DWORD ret = GetModuleFileName(NULL, lpFilename, MAX_PATH);
    if (ret) {
        _tprintf(_T("GetModuleFileName -> lpFilename=%s\n"), lpFilename);
    }
    else {
        printf("GetModuleFileName -> fail(%ld)", GetLastError());
    }

    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        printf("DLL_PROCESS_ATTACH\n");
        break;
    case DLL_THREAD_ATTACH:
        printf("DLL_THREAD_ATTACH\n");
        break;
    case DLL_THREAD_DETACH:
        printf("DLL_THREAD_DETACH\n");
        break;
    case DLL_PROCESS_DETACH:
        printf("DLL_PROCESS_DETACH\n");
        break;
    }
    return TRUE;
}

DllDemo.cpp

// DllDemo.cpp : 定义 DLL 应用程序的导出函数。

#include "stdafx.h"
#include "DllDemo.h"

// 这是导出变量的一个示例
DLLDEMO_API int nDllDemo = 1;
DLLDEMO_API int nExternCDllDemo = 2;

// 这是导出函数的一个示例。
DLLDEMO_API int fnDllDemo(void)
{
    return 42;
}
DLLDEMO_API int fnExternCDllDemo(void)
{
    return 142;
}

char DLLDEMO_API fnDefault(char, int, float) 
{
    return 'a';
}
char DLLDEMO_API __stdcall fnstdcall(char, int, float)
{
    return 'b';
}
char DLLDEMO_API __cdecl fncdecl(char, int, float)
{
    return 'c';
}
char DLLDEMO_API __fastcall fnfastcall(char, int, float)
{
    return 'd';
}

// 这是已导出类的构造函数。
// 有关类定义的信息,请参阅 DllDemo.h
CDllDemo::CDllDemo()
{
    return;
}

生成DLL有以下几种方案

解决方案资源管理器 -> “DLL项目”右键 -> 生成(U)
菜单栏 -> 生产DLL(U)(Shift + F6)
然后会在项目的根目录的Debug或Release文件夹下生成相应的文件。

dumpbin查看dll(或lib)的导出符号

dumpbin.exe 位于 :C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin

5.jpg

查看编译后的DLL函数名用到的只是”EXPORTS”参数而已。函数名为name列等号的左侧。

可以看到extern “C”修饰的函数名与原函数名一致。原因可以参看《函数重载与Extern “C”》一文: http://blog.csdn.net/chy555chy/article/details/53015808。另外说明一点,使用extern “C”修饰的C++函数不能使用C++的重载特性,因为导出的函数符号只是原函数名,编译器没有将参数类型信息加到导出的函数名中,因此无法区分重载函数。

6.jpg

DLL的调用

导入DLL

(1)当”DLL项目”和”可执行项目”属于同一个解决方案时。在”DLL项目”右键”生成”可以在项目的Debug目录下生成相应的DLL和LIB。然后在”可执行项目”右键”设为启动项目”,然后点击”本地Windows调试器”即可。

静态调用
直接运行会报无法打开LIB(不加后缀默认是LIB)或DLL


7.jpg

8.jpg

解决方案:在”可执行项目”右键 -> 添加(D) -> 引用(R)…


9.jpg

可以看到引用处多了相应的DLL文件
1.jpg

动态调用
如果是动态调用,此时可正常运行
2.jpg

(2)如果是外部项目的dll

静态调用(有以下2种方法)

#param comment("lib", "path\\*lib")中指定其路径。
解决方案资源管理器 -> “可执行项目”右键”属性” -> “项目属性页”对话框 -> 配置属性 -> “链接器” -> 输入 -> 附加依赖项目 -> 编辑 -> 输入lib文件的完整路径(而不是lib的文件名)

3.jpg

动态调用
LoadLibrary(_T("path\\*.dll"));中指定其路径。

DLL调用有两种方式,一种是静态调用,另外一种是动态调用

静态调用(同时需要头文件、LIB和DLL文件,缺一不可)

静态调用是一种显式的调用方式,即在编程的时候便知道了被调用的DLL中的接口函数,在编译链接的时候将DLL与工程生成的exe相关联。

#include "stdafx.h"
#include "../DllDemo/DllDemo.h"

//lib后缀可以省略,但不可以改为dll
#pragma comment(lib, "DllDemo.lib")

int main()
{
    printf("%d\n", nDllDemo);
    printf("%d\n", fnDllDemo());
    printf("%d\n", fnExternCDllDemo());
    _tsystem(_T("pause"));
    return 0;
}

动态调用(仅需要DLL,不需要头文件和LIB)

动态调用是一种隐式的调用方式,即程序运行过程中装载DLL,然后获取指定函数名称的接口函数,然后再调用之。

#include "stdafx.h"
#include <Windows.h>

int main() 
{
    //参考 http://blog.csdn.net/g5dsk/article/details/6680698
    HMODULE hModule = LoadLibrary(_T("DllDemo.dll"));// 虽然 MSDN Library 说这里如果指定了路径,要用 backslashes (\),不要用 forward slashes (/),但其实用二者都可以。注:如果用 \,要用 \\。
    if (hModule == NULL || hModule == INVALID_HANDLE_VALUE) {
        return -1;
    }
    /*
    WINBASEAPI FARPROC WINAPI GetProcAddress(
        _In_ HMODULE hModule,
        _In_ LPCSTR  lpProcName //这个是dump /EXPORT *.dll “name”列等号前的值
    );
    返回的是函数或变量的地址,即函数指针或指向变量地址的指针
    */
    typedef int(*TYPE_fnDllDemo) ();
    typedef int(*TYPE_fnExternCDllDemo) ();
    int *nDllDemo = (int *)GetProcAddress(hModule, "?nDllDemo@@3HA");
    TYPE_fnDllDemo fnDllDemo = (TYPE_fnDllDemo)GetProcAddress(hModule, "?fnDllDemo@@YAHXZ");
    int *nExternCDllDemo = (int *)GetProcAddress(hModule, "nExternCDllDemo");
    TYPE_fnExternCDllDemo fnExternCDllDemo = (TYPE_fnExternCDllDemo)GetProcAddress(hModule, "fnExternCDllDemo");
    if(nDllDemo != NULL)
        printf("*nDllDemo = %d\n", *nDllDemo);
    if(fnDllDemo != NULL)
        printf("fnDllDemo() = %d\n", fnDllDemo());
    if (nExternCDllDemo != NULL)
        printf("*nExternCDllDemo = %d\n", *nExternCDllDemo);
    if(fnExternCDllDemo != NULL)
        printf("fnExternCDllDemo() = %d\n", fnExternCDllDemo());
    _tsystem(_T("pause"));
    return 0;
}

运行截图

注意:DLL_PROCESS_DETACH是在关闭命令行的时候被调用,而不是不会调用。这里被system(“pause”)暂停了,并不是说程序结束。另外即使点击关闭,基本也看不到,因为打印完窗口立马就被关闭了,这些都是一瞬间完成的。


4.jpg
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容