• VC++DLL动态链接库程序


    VC++DLL动态链接库程序

    最近查找了一下VC++中关于编写DLL动态库的资料,主要是导出函数和导出类的编写。因为在实际项目开发中有时需要使用C++编写好DLL接口,控制设备,提供给其他语言如Nodejs、C#等使用。

    C++ DLL 导出函数

    使用VS2017等IDE生成dll程序,示例如下:

    DllDemo
    DllDemo.h

    // 下列 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
    
    // 此类是从 DllDemo.dll 导出的
    class DLLDEMO_API CDllDemo {
    public:
    	CDllDemo(void);
    	// TODO:  在此添加您的方法。
    };
    
    extern DLLDEMO_API int nDllDemo;
    
    // 加上extern "C"表示是一个C函数,不重载
    extern "C" DLLDEMO_API int fnDllDemo(void);
    

    DllDemo.cpp

    // DllDemo.cpp : 定义 DLL 应用程序的导出函数。
    //
    
    #include "stdafx.h"
    #include "DllDemo.h"
    
    #include <iostream>
    using namespace std;
    
    // 这是导出变量的一个示例
    DLLDEMO_API int nDllDemo=0;
    
    // 这是导出函数的一个示例。
    DLLDEMO_API int fnDllDemo(void)
    {
    	cout << "fnDllDemo(void) called" << endl;
    
    	return 42;
    }
    
    // 这是已导出类的构造函数。
    // 有关类定义的信息,请参阅 DllDemo.h
    CDllDemo::CDllDemo()
    {
    	cout << "CDllDemo::CDllDemo() called" << endl;
    
    	return;
    }
    

    dllmain.cpp

    // dllmain.cpp : 定义 DLL 应用程序的入口点。
    #include "stdafx.h"
    
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
    					 )
    {
    	switch (ul_reason_for_call)
    	{
    	case DLL_PROCESS_ATTACH:
    	case DLL_THREAD_ATTACH:
    	case DLL_THREAD_DETACH:
    	case DLL_PROCESS_DETACH:
    		break;
    	}
    	return TRUE;
    }
    

    DLL的测试程序
    使用VS2017创建一个基于Win32的控制台应用程序testDllDemo:
    testDllDemo.cpp

    // testDllDemo.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    
    #include <stdio.h>
    #include <Windows.h>
    
    typedef int(*dllpFun)(void);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	HMODULE hDll = LoadLibrary(L"DllDemo.dll");
    
    	if (!hDll)
    	{
    		return -1;
    	}
    
    	dllpFun pFunc = (dllpFun)GetProcAddress(hDll, "fnDllDemo");
    
    	pFunc();
    
    	FreeLibrary(hDll);
    
    	getchar();
    
    	return 0;
    }
    

    C++ DLL 导出类

    1.导出类中第一种方法:简单导出类(不推荐使用)

    简单导出类的示例程序

    NaiveApproach.h

    //2011.10.6
    //cswuyg
    //dll导出类,比较差劲的方法
    #pragma once
    // The following ifdef block is the standard way of creating macros which make exporting 
    // from a DLL simpler. All files within this DLL are compiled with the NAIVEAPPROACH_EXPORTS
    // symbol defined on the command line. this symbol should not be defined on any project
    // that uses this DLL. This way any other project whose source files include this file see 
    // NAIVEAPPROACH_API functions as being imported from a DLL, whereas this DLL sees symbols
    // defined with this macro as being exported.
    #ifdef NAIVEAPPROACH_EXPORTS
    #define NAIVEAPPROACH_API __declspec(dllexport)
    #else
    #define NAIVEAPPROACH_API __declspec(dllimport)
    #endif
    
    
    
    //基类也必须导出,否则警告:
    class  NAIVEAPPROACH_API CBase
    {
    public:
    	void Test();
    private:
    	int m_j;
    };
    
    //也必须导出
    class NAIVEAPPROACH_API CDate
    {
    public:
    	void Test2();
    private:
    	int m_k;
    };
    
    class NAIVEAPPROACH_API CNaiveApproach : public CBase
    {
    public:
    	CNaiveApproach(int i = 0);
    	// TODO: add your methods here.
    	void Func();
    private:
    	int m_iwuyg;
    	CDate m_dobj;
    };
    

    NaiveApproach.cpp

    // NaiveApproach.cpp : Defines the entry point for the DLL application.
    //
    
    #include "stdafx.h"
    #include "NaiveApproach.h"
    
    
    #ifdef _MANAGED
    #pragma managed(push, off)
    #endif
    
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
    					 )
    {
    	switch (ul_reason_for_call)
    	{
    	case DLL_PROCESS_ATTACH:
    	case DLL_THREAD_ATTACH:
    	case DLL_THREAD_DETACH:
    	case DLL_PROCESS_DETACH:
    		break;
    	}
        return TRUE;
    }
    
    #ifdef _MANAGED
    #pragma managed(pop)
    #endif
    
    
    // This is the constructor of a class that has been exported.
    // see NaiveApproach.h for the class definition
    CNaiveApproach::CNaiveApproach(int i) : m_iwuyg(i)
    {
    }
    
    void CNaiveApproach::Func()
    {
    	wcout << L" cswuyg test dll , i = " << m_iwuyg << endl;
    	system("pause");
    }
    
    void CBase::Test()
    {
    	wcout << L"Just a  Test" << endl;
    	system("pause");
    }
    
    void CDate::Test2()
    {
    	wcout << L"Test 2" << endl;
    	system("pause");
    }
    

    UserDll.cpp

    // User.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "NaiveApproach.h"
    #pragma comment(lib, "../debug/NaiveApproach.lib")
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//////////////////////////////////////////////////////////////////////////
    	CNaiveApproach obj(10);
    	obj.Test();
    	obj.Func();
    	//////////////////////////////////////////////////////////////////////////
    	return 0;
    }
    

    简单导出类的缺点

    这种简单导出类的方式,除了导出的东西太多、使用者对类的实现依赖太多以外,还有其他问题:必须保证使用同一种编译器。导出类的本质是导出类里的函数,因为语法上直接导出了类,没有对函数的调用方式、重命名进行设置,导致了产生的dll并不通用。

    2.导出类的一种通用方法(推荐):使用接口

    定义一个抽象类(都是纯虚函数),调用者跟dll共用一个抽象类的头文件,dll中实现此抽象类的派生类,dll最少只需要提供一个用于获取抽象类对象指针的接口。
    写一个基类,方法都为虚函数,这样使用dll的exe使用都没问题。
    dll的类从基类 派生,然后返回基类接口即可。
    面向抽象设计优点:这种方式利用了C++类的虚函数,类似COM思想,采用接口跟实现分离,可以使得工程的结构更清晰,使用者只需要知道接口,而无需知道具体实现,产生的DLL通用没有特定环境限制。
    注意事项:调用者跟DLL共用一个抽象类的头文件,调用者依赖于DLL的东西很少,只需要知道抽象类的接口,以及获取对象指针的导出函数,对象内存空间的申请和释放都在DLL模块中完成

    导出类的较好方式 DLL示例程序

    ExportClassImpl.h

    //2011.10.6
    //cswuyg
    //dll导出类
    // 实现类
    #pragma once
    
    #include "MatureApproach.h"
    
    class ExportImpl : public IExport
    {
    public:
      virtual void Hi();
      virtual void Test();
      virtual void Release();
      ~ExportImpl();
    private:
    };
    

    MatureApproach.h

    //2011.10.6
    //cswuyg
    //dll导出类
    //dll跟其使用者共用的头文件
    #pragma  once
    // The following ifdef block is the standard way of creating macros which make exporting 
    // from a DLL simpler. All files within this DLL are compiled with the MATUREAPPROACH_EXPORTS
    // symbol defined on the command line. this symbol should not be defined on any project
    // that uses this DLL. This way any other project whose source files include this file see 
    // MATUREAPPROACH_API functions as being imported from a DLL, whereas this DLL sees symbols
    // defined with this macro as being exported.
    #ifdef MATUREAPPROACH_EXPORTS
    #define MATUREAPPROACH_API __declspec(dllexport)
    #else
    #define MATUREAPPROACH_API __declspec(dllimport)
    #endif
    
    class IExport 
    {
    public:
    	virtual void Hi() = 0;
    	virtual void Test() = 0; 
    	virtual void Release() = 0;
    };
    
    
    extern "C" MATUREAPPROACH_API IExport* _stdcall CreateExportObj();
    extern "C" MATUREAPPROACH_API void _stdcall DestroyExportObj(IExport* pExport);
    

    ExportClassImpl.cpp

    #include "stdafx.h"
    #include "ExportClassImpl.h"
    
    void ExportImpl::Hi()
    {
    	wcout << L"Hello World" << endl;
    }
    
    void ExportImpl::Test()
    {
    	wcout << L"Hi cswuyg" << endl;
    }
    
    void ExportImpl::Release()
    {
    	delete this;
    }
    
    ExportImpl::~ExportImpl()
    {
    	cout << "Release OK" << endl;
    }
    

    MatureApproach.cpp

    #include "stdafx.h"
    #include "ExportClassImpl.h"
    
    void ExportImpl::Hi()
    {
    	wcout << L"Hello World" << endl;
    }
    
    void ExportImpl::Test()
    {
    	wcout << L"Hi cswuyg" << endl;
    }
    
    void ExportImpl::Release()
    {
    	delete this;
    }
    
    ExportImpl::~ExportImpl()
    {
    	cout << "Release OK" << endl;
    }
    

    MatureApproach.def

    LIBRARY	"MatureApproach"
    EXPORTS
    CreateExportObj @ 1
    DestroyExportObj @ 2
    

    测试调用DLL的Win32控制台程序 UserDll.cpp

    // User.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include "MatureApproach.h"
    
    #pragma comment(lib, "../debug/MatureApproach.lib")
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	IExport* pExport = CreateExportObj();
    	pExport->Hi();
    	pExport->Test();
    	DestroyExportObj(pExport);
    	system("pause");
    	return 0;
    }
    

    参考资料

    参考了相关的资料网址:
    [HowTo: Export C++ classes from a DLL]: https://www.codeproject.com/Articles/28969/HowTo-Export-C-classes-from-a-DLL
    [Explicitly Linking to Classes in DLL’s ]: http://www.codeguru.com/Cpp/W-P/dll/importexportissues/article.php/c123
    [C++ DLL导出类 知识大全]: https://www.cnblogs.com/lidabo/p/7121745.html
    [怎样从一个DLL中导出一个C++类]: https://blog.csdn.net/liubing8609/article/details/82156067
    [DLL导出C++类]: https://blog.csdn.net/qq_33757398/article/details/82229325
    VC++DLL动态链接库编程-网易云课堂视频基础教程

  • 相关阅读:
    第一次团队作业
    第二次结对作业
    动态代理与AOP
    笔试题
    java并发面试题(带答案)
    线程问题——同步和死锁
    java线程的方法
    java实现多线程的方法
    使用java闭锁实现并发
    Java多线程——同步问题
  • 原文地址:https://www.cnblogs.com/ccf19881030/p/12004817.html
Copyright © 2020-2023  润新知