美文网首页C++实现的
VS 2015 DLL的创建、静态调用和动态调用

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

作者: Caiaolun | 来源:发表于2018-10-25 10:30 被阅读26次

    原文地址: 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

    相关文章

      网友评论

        本文标题:VS 2015 DLL的创建、静态调用和动态调用

        本文链接:https://www.haomeiwen.com/subject/kgkqtqtx.html