确定静态初始化是否完成

13

简化问题(Y)

假设你需要在函数内部知道这个函数是否作为静态对象初始化的一部分被调用。是否有标准或平台特定的方法来实现这一点?

背景故事(X)

我深入研究了许多应用程序使用的 DLL 的源代码。这个 DLL 公开了一个 Init 函数,这个函数应该构造一个 boost::asio::deadline_timer 以便稍后使用(但是 DLL 可以在降级模式下工作而不需要它)。

问题在于计时器不能在静态对象初始化期间构造(DLL 加载时间),否则它的构造函数会死锁

当然,每个人都会从各个地方(是的,多次!)调用 Init,包括我无法编辑的源代码中的静态构造函数,因此需要检测这种情况并退出。

在实用主义战胜厌恶之后,我最终 沿着调用堆栈向上走,尝试找到 wWinMain 并推断出静态初始化已经完成。这很糟糕,并且不能与动态加载的二进制文件一起使用,但幸运的是这超出了我特定情况的范围。我真希望有一种更简洁的方法。


1
你尝试过在DLLMain中适当的时候设置全局变量来指示静态初始化已完成吗? - 1201ProgramAlarm
1
@JustinFinnerty 他正在编写一个库,他无法控制 main() - Barmar
1
啊,对了,因为SetTimerUser32.dll中,而你不能在加载锁定期间调用它。是否有其他函数或一小组函数,您的DLL用户会调用,例如创建或打开类型的函数,虽然不经常调用但在某种程度上是必要的?如果尚未创建计时器,则可以创建计时器。 - 1201ProgramAlarm
1
标准并未涵盖DLL的行为。例如,我观察到调用FreeLibrary意味着静态对象被销毁(并且可能通过再次调用LoadLibrary而重新创建),这与标准不符。我建议添加平台标签。 - M.M
1
@DanielH 确实,那看起来像是一个错误...这里有一个 pastebin ,其中包含在 DLL_PROCESS_ATTACH 触发断点时的调用堆栈。你可以看到我仍然在 DLL 的初始化过程中。那可能是过程中的最后一次调用,但是从 DllMain 构造 io_service 也会死锁,因此我得出了这个结论。 - Quentin
显示剩余18条评论
1个回答

1

假设您的DLL有两个入口点:InitFrobnicate

Init实际上什么也不做。 Frobnicate检查全局布尔值以查找DLL是否已经真正初始化。 如果没有,则首先进行真正的初始化,这也设置了我已经真正初始化标志,然后才进行frobnication。

可能您还有更多的入口点(Frobnicate2Fronbnicate3,...),因此您必须将此逻辑添加到每个入口点中。 这很繁琐,但并非闻所未闻。 早些时候,我们曾经不得不编写自己的延迟加载机制,这些机制非常相似。 当然,那时我们只提供来自DLL的C风格接口,而不是具有缩略方法名称的C ++风格对象,但它仍然可以做到。

这假设延迟初始化直到第一个非Init调用进入DLL为止。 这可能是个好假设,也可能不是。

其他hacky的想法:

  • 找到一种检测加载器锁的方法(除了实际死锁)。如果在调用Init时,加载器锁已经被持有,那么这就是其中一种“太早”的情况。这与你寻找栈中的WinMain解决方案相当。
  • 做些什么来确保在调用Init时会出现死锁,并让客户端修复他们该死的代码。
  • 创建一个隐藏的消息窗口并向其发送消息。假设客户端是传统的GUI应用程序,将在同一线程上泵送消息,则在接收到消息时,应该可以安全地进行真正的初始化(希望不会太晚)。

1
可以确认...已经发布了一个具有两阶段初始化的DLL,其中每个入口点都以if (init_has_been_called())开头。 - M.M
如果在另一个DLL的Init代码中调用Frobnicate,则问题仍然存在。 - Justin Finnerty
@Justin Finnerty:是的,这是一个无法取胜的局面。客户不应该从静态初始化方法中调用任何内容,但他们确实这样做了,我们正在尽最大努力防止出现故障。如果他们正在从静态初始化中进行“真正的工作”,那么可能已经没有希望了。 - Adrian McCarthy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接