Code Monkey home page Code Monkey logo

testdll's People

Contributors

tnie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

sichitong

testdll's Issues

为什么使用智能指针就能避免跨DLL动态内存管理的问题

Item 18:让接口容易被正确使用,不易被误用

shared_ptr 带来的好处还不仅仅是移除了客户的责任,同时还解决了跨 DLL 动态内存管理的问题。 在DLL中 new 的对象,如果在另一个 DLL 中 delete 往往会发生运行时错误,但shared_ptr进行资源销毁时, 总会调用创建智能指针的那个DLL中的delete,这意味着shared_ptr可以随意地在DLL间传递而不需担心跨DLL的问题。

只导出类的成员函数,是什么使用场景?

// The whole CXyz class is exported with all its methods and members.
//
class XYZAPI CXyz
{
public:
    int Foo(int n);
};

// Only CXyz::Foo method is exported.
//
class CXyz
{
public:
    XYZAPI int Foo(int n);
};

因为不想导出类的成员变量呗,或者类成员变量类型未导出

导出类的成员函数意味着:要么拷贝控制函数也要导出,要么提供创建实例的接口(比如单例)。

Determining Which Exporting Method to Use

导出

首先,了解一下两种导出方式:

有了简单认识之后,我们应该选择哪一种呢?

好在两者是可以混用的。

导入

导出方式确定后,导入也是有形式的:Importing into an Application Using __declspec(dllimport)

新标准?

msvc/va 为什么自动插入 std::make_shared<std::vector<std::string>>&() 为什么多个 &

即便不导出类,exe 也可以使用 dll 中类型的默认构造与析构

class /*_ADD_API*/ Student
{
public:
    class Date
    {
    public:
        int year();
        int month();
        int _ADD_API day();
    private:
        long _date;
    };
    /*Student();
    ~Student();*/

    _ADD_API int age();
    bool sex();
    _ADD_API void change(std::string name);
private:
    int m_age;
    bool m_sex;
    std::string m_name;
};

因为使用的编译器自动生成的构造与析构,所以肯定是无法显式导出函数的。

image

经过查看,发现也并未隐式导出。所以推测,类似是在头文件中直接 内联定义 了构造与析构,故 exe 主项目能够直接看到拷贝控制函数。

dll 导出常规的 C++ 类缺点很多!应该避免

HowTo: Export C++ classes from a DLL

缺点

  • Exporting C++ classes from a DLL does not prevent very tight coupling(无法阻止非常紧密的耦合) between an object and its user. The DLL should be seen as a static library with respect to code dependencies.

    导出类容易造成对象和用户之间的紧密关联?想来参照“降低文件之间的编译依存关系”就能填平这个坑,Handle Class 或者 Interface Class。避免滚雪球,Handle Class 不能用,所以 Interface Class 会是最优解。

    不太理解后一句话,“如静态库”是想表达丧失动态库(和静态库相比)的优点吗?

  • Both client code and a DLL must link dynamically with the same version of CRT. It is necessary in order to enable correct bookkeeping of CRT resources between the modules. If a client and DLL link to different versions of CRT, or link with CRT statically, then resources that have been acquired in one instance of the CRT will have been freed in a different instance of the CRT. It will corrupt the internal state of the CRT instance that attempts to operate on foreign resources, and most likely will lead to crash. (跨模块内存管理 #3

    使用限制太多,同一编译器且相同版本,相同 CRT 配置 相同 CRT 版本……

  • Both the client code and the DLL must agree on the exception handling/propagating model, and use the same compiler settings with respect to C++ exceptions.

  • Exporting a C++ class requires exporting everything that is related to this class: all its base classes, all classes that are used for the definition of data members, etc.(牵一发而动全身,滚雪球)

    不包括函数参数、返回值的类型,也不包括函数具体实现中涉及的类型!!只限于基类类型、数据成员。

    目前导出 datadef.h/cpp 中的类,其未涉及第三方类以及 std::string、stl 等,虽然不是 pod,但很接近。

    除了作者提到的,导出整个类还会导出私有成员函数,编译器默认生成拷贝控制函数以及在类定义头文件中给出了定义的函数(默认内联) #17 ——可是前者用户用不到,后两者无需导出(用户也能看到)。

滚雪球举例

#pragma once
#ifdef __ADD_DLL
#define _ADD_API __declspec(dllexport)
#else
#define _ADD_API __declspec(dllimport)
#endif
#include <string>
class Student
{
    int m_nothing;
};

class _ADD_API Person
{
private:
    // warning C4251: “Person::m_address”: class“std::basic_string<char,std::char_traits<char>,std::allocator<char>>”需要有 dll 接口由 class“Person”的客户端使用
    std::string m_address;
};

class Utility
{
    int m_nothing;
};

// warning C4275: 非 dll 接口 class“Utility”用作 dll 接口 class“Test”的基
class _ADD_API Test : public Utility
{
public:
    void println() {}
    void println(std::string abc) {}
    Student getStudent() { return Student();  }
private:
    // warning C4251: “Test::m_ss”: class“Student”需要有 dll 接口由 class“Test”的客户端使用
    Student m_ss;
    Person m_ps;
};

二进制接口

However, opposite to the C language, where the binary interface between a caller and a callee is well-defined and widely accepted, in the C++ world, there is no recognized application binary interface (ABI).

C++ 没有统一的、规范的的 ABI 接口,o(╥﹏╥)o 不同的 C++ 编译器生成的 ABI 不兼容,甚至同一编译器的不同版本生成的 ABI 也不兼容 (吐槽 msvc,but 这个锅微软是不是已经解决了?)

参考 C++静态库与动态库 “显式调用C++动态库注意点” 一节

C++ 的 name mangling 可以通过 extern "C" 解决。

extern "C" 声明的函数将使用函数名作符号名,就像 C 函数一样。因此,只有非成员函数才能被声明为 extern "C",并且不能被重载

冠以 extern "C" 限定符后,并不意味着函数中无法使用 C++ 代码了,相反,它仍然是一个完全的 C++ 函数,可以使用任何 C++ 特性和各种类型的参数。

传值和传常量引用,在作为公共接口时有区别吗?

void hello(std::string name);
void hello(const std::string& name);  // std::string const * const 

后者不会造成跨模块管理内存,前者呢?——运行时报错 __acrt_first_block == header

std::string hello(void);

会产生「跨模块内存管理」问题吗?——会

std::string& hello(void)

返回引用呢?——不会,但需要注意引用本体的生存周期

为什么会多次加载/卸载 dll?——多线程?

2018-06-21 10:08:59.163 [12984] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:08:59.163 [12984] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:08:59.164 [13740] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:08:59.164 [13740] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:08:59.164 [13748] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:08:59.164 [13748] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:08:59.203 [13456] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:08:59.203 [13456] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:08:59.460 [13460] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:08:59.460 [13460] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:08:59.461 [3748] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:08:59.461 [3748] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:08:59.462 [12116] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:08:59.462 [12116] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:08:59.465 [12320] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:08:59.466 [12320] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:08:59.473 [1960] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:08:59.473 [1960] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:08:59.486 [3748] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:08:59.486 [3748] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:00.399 [14008] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:00.399 [14008] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:00.399 [13612] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:00.399 [13612] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:00.400 [13336] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:00.400 [13336] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:00.401 [11608] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:00.401 [11608] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:00.402 [13452] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:00.402 [13452] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:00.403 [14008] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:00.403 [14008] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:00.462 [11608] LOG_DEBUG http://gw.yundzh.com/ws?token=0000002f:1529550540:cd8a71768890c99fd9c11362e396cde8583b412e dzhyunrequestimpl.cpp:166
2018-06-21 10:09:00.463 [14112] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:00.463 [14112] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:00.464 [12052] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:00.464 [12052] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:00.485 [13460] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:00.485 [13460] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:00.527 [13336] LOG_INFO 1CONNECTION Open wsclient.cpp:28
2018-06-21 10:09:08.022 [13612] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:08.023 [13612] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:08.041 [13800] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:08.041 [13800] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:08.107 [13816] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:08.107 [13816] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:08.108 [13772] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:08.108 [13772] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:08.283 [13560] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:08.283 [13560] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:08.315 [11884] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:08.315 [11884] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:08.350 [14148] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:08.350 [14148] LOG_DEBUG Thread attach. yddata.cpp:379
2018-06-21 10:09:08.894 [13452] LOG_WARN parse UA failed 223 {"code":301,"desc":"Specified object cannot be found"} dzhyunresponse.cpp:214
2018-06-21 10:09:10.519 [12116] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:10.519 [12116] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:13.329 [13452] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:13.329 [13452] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:13.334 [11608] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:13.334 [11608] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:13.334 [13800] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:13.334 [13800] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:13.366 [13336] LOG_INFO 1CONNECTION Close wsclient.cpp:44
2018-06-21 10:09:13.368 [13336] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:13.368 [13336] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:13.369 [12052] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:13.369 [12052] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:13.369 [14112] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:13.369 [14112] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:13.573 [14148] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:13.573 [14148] LOG_DEBUG Thread detach. yddata.cpp:383
2018-06-21 10:09:13.576 [13456] LOG_DEBUG hModule.0F530000 lpReserved.00000000 yddata.cpp:366
2018-06-21 10:09:13.576 [13456] LOG_DEBUG Thread detach. yddata.cpp:383

dll 卸载(unload)

什么时候卸载

https://stackoverflow.com/questions/5164551/c-static-variables-in-dynamic-dll-lifetime-or-why-they-dead

  1. 明确调用 FreeLibrary()
  2. when?

In a Windows C++ DLL, all global objects (including static members of classes) will be constructed just before the calling of the DllMain with DLL_PROCESS_ATTACH, and they will be destroyed just after the call of the DllMain with DLL_PROCESS_DETACH.引用来源

dll 中的智能指针(全局变量!)

在 YDStock.exe 退出时会陆续调用 websocket_endpoint::~websocket_endpoint(void) DZHYUNRequest::~DZHYUNRequest() 等析构函数

在 YDdata.cpp 中作为全局变量的智能指针:

// std::shared_ptr<YDdataManager> _ydm;
std::shared_ptr<DZHYUNRequest> _req;

构造

void YDdata_init() 对 smart pointer 再次赋值时,调用 DZHYUNRequest 的构造函数

_req = std::make_shared<DZHYUNRequest>();
// _ydm = std::make_shared<YDdataManager>();

未显式释放指针 how?

YDdata.cpp 文件中的 YDdata_fini() 在 YDStock 项目等整个解决方案中并未调用。

// _ydm.reset();
_req.reset();

所以是卸载 dll 时捎带的“自动释放指针”,进而调用析构了吗?yes 因为这些智能指针变量是全局的。

dll 中的 static 变量

类 LogerManager 继承自接口 ILog4zManager。

创建

log4z.cpp 中静态变量

ILog4zManager * ILog4zManager::getInstance()
{
	static LogerManager m;
	return &m;
}

释放 why?

log4z.h 文件中 virtual ~ILog4zManager(){};

此析构函数执行要早于 websocket_endpoint::~websocket_endpoint(void) DZHYUNRequest::~DZHYUNRequest() ,所以在后者析构中打印日志(LOGI)会崩溃。但是,为什么呢?为什么会早于后者执行呢?

——因为 static LogerManager m; 构造晚于作为全局变量的智能指针。所以智能指针先析构,引用计数为零后直接执行了后者的析构,然后才是前者的析构。

卸载 dll 有什么复杂的关系?

动态库中的线程如何自理?

feature_thread 分支

如果动态库内线程在卸载动态库(DLL_THREAD_DETACH)之前结束:

hModule.0F910000 lpReserved.006FFCC8
Process attach.
> 主线程开始
hModule.0F910000 lpReserved.00000000
Thread attach.
子线程开始
子线程第0次循环抢占;
子线程第1次循环抢占;
子线程第2次循环抢占;
子线程第3次循环抢占;
子线程第4次循环抢占;
子线程结束
hModule.0F910000 lpReserved.00000000
Thread detach.
> 主线程结束
hModule.0F910000 lpReserved.00000001
Process detach.
请按任意键继续. . .

如果线程调用了 ExitThread 来结束线程(线程函数返回时,系统也会自动调用 ExitThread),系统查看当前映射到进程空间中的所有 DLL 文件映像,并用 DLL_THREAD_DETACH 来调用 DllMain 函数,通知所有的 DLL 去执行线程级的清理工作。
注意:如果线程的结束是因为系统中的一个线程调用了 TerminateThread,系统就不会用值DLL_THREAD_DETACH 来调用所有 DLL 的 DllMain 函数。引用来源

如果线程函数耗时较长,即便代码中做了等待指示,但卸载动态库就好会发生莫名其妙的现象:
(等待指示放在动态库全局变量/静态变量析构函数中,如果是在普通函数(直接或间接被主程序调用,而只要函数调用未结束,就意味着动态库正在使用,不会卸载)就会按部就班等线程函数执行结束)

hModule.0FC30000 lpReserved.0056F7AC
Process attach.
> 主线程开始
hModule.0FC30000 lpReserved.00000000
Thread attach.
子线程开始
子线程第0次循环抢占;
子线程第1次循环抢占;
子线程第2次循环抢占;
子线程第3次循环抢占;
子线程第4次循环抢占;
子线程第5次循环抢占;
子线程第6次循环抢占;
子线程第7次循环抢占;
子线程第8次循环抢占;
子线程第9次循环抢占;
> 主线程结束
hModule.0FC30000 lpReserved.00000001
Process detach.
请按任意键继续. . .

swap() 函数意味着重新分配内存吗?

void MyStockData::sortApply(const std::string& group, const std::deque<std::string>& codes, 
    const std::deque<ObjInfo>& objs)
{
  if (m_data.find(group) == m_data.end() ||
    m_keys.find(group) == m_keys.end())
  {
    return;
  }
  m_data[group].swap(objs);  // ERR
  m_keys[group].swap(codes);  // ERR
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.