【问题标题】:How to use a mutex/critical section within a member function inside a DLL如何在 DLL 中的成员函数中使用互斥锁/临界区
【发布时间】:2020-04-10 06:07:03
【问题描述】:

我正在创建一个驻留在 DLL 中的 COM 类(带有 ATL)。对于我的成员函数之一,根据某些条件,我可能会使用必须初始化和终止的第三方库(Adobe 的 XMP SDK)。所以基本上,我会有一个看起来像这样的成员函数:

void CMyClass::MyMemberFunction()
{
    SXMPMeta::Initialize();

    // ...

    SXMPMeta::Terminate();
}

现在根据 Adob​​e XMP 库文档,

你必须调用初始化和终止函数 单线程方式。 . . .

同时,我相信文件资源管理器可能会在不同的线程上创建我的类的多个实例。所以如果我理解正确,听起来我会有一个关键部分,我需要在库初始化和终止周围使用互斥锁(但如果有错误请纠正我)。

我不清楚该怎么做。我不确定我是否应该使用ATL critical section classes 之一或者可能是CRITICAL_SECTION。 CRITICAL_SECTION 似乎是一个不错的选择,但 example 显示它正在 main() 中初始化。如果您在 DLL 中会发生什么?我不想开始弄乱 DllMain()。任何帮助或想法将不胜感激。

根据 Gem 的建议,我尝试了以下方法:

struct XMPLibraryInitializer
{
    XMPLibraryInitializer()
    {
        // Initialize libraries
        if (!SXMPMeta::Initialize() || !SXMPFiles::Initialize())
        {
            XMP_StringPtr pszErrorMessage = "Libraries failed to load";
            throw XMP_Error(kXMPErr_InternalFailure, pszErrorMessage);
        }
        ATLTRACE("\nXMP library initialized on thread %lu\n", GetCurrentThreadId());
    }
    ~XMPLibraryInitializer()
    {
        // Terminate XMP libraries
        SXMPFiles::Terminate();
        SXMPMeta::Terminate();
        ATLTRACE("\nXMP library terminated on thread %lu\n", GetCurrentThreadId());
    }
};

HRESULT MyFunc()
{
    // Statically initialize the Adobe XMP library
    try
    {
        static XMPLibraryInitializer xmpLibraryInitializer;
    }
    catch (const XMP_Error & e)
    {
        return E_UNEXPECTED;
    }

    // ...

}

这似乎运行良好,除了我看到的输出是

XMP 库在线程 5820 上初始化 ... XMP 库在线程 3104 上终止

对于不同的线程号有什么解释吗?如果数字不同,是否意味着它不符合要求单线程初始化和终止的文档?

【问题讨论】:

  • Explorer 不这样做,它不需要扩展是线程安全的。主要是因为这会使正确编写 shell 扩展变得非常困难。所以它自己处理它,确保所有调用都来自同一个线程(又名“公寓线程”)。
  • 哦,那也许我的整个前提是错误的。为了测试它,我从班级的 FinalConstruct() 中打印了 GetCurrentThreadId(),它似乎打印出了几个不同的数字。你知道为什么会这样吗?附加信息:我的类是一个属性处理程序,我导航到一个包含一堆不同文件的文件夹以获取读数。附加信息:我是新手,所以我不太清楚自己在做什么。
  • 所以可能发生的情况是,它是在第一次使用时在碰巧的任何线程上构建的,然后在程序退出时被破坏,这很可能是程序的主线程,尽管有很多讨论它是否可能是程序的最后退出线程,通常是主线程,但不一定适用于所有架构。通常这对于需要以“单线程/线程保存”方式初始化但可以在多个线程上使用的对象来说已经足够了。如果要从多个线程中使用,则该对象不能保持任何线程特定的状态。
  • 好的,只要它不泄漏资源或导致崩溃,我很满意。非常感谢您的帮助。

标签: c++ winapi com mutex atl


【解决方案1】:

一个简单的静态单例函数就足够了,如果你想保持它直到程序退出。 静态初始化保证只以一种有效的线程安全方式(来自 C++11)完成,通常涉及对保护标志的互斥体双重检查。

在您无法确定是否已设置 ATX 的所有地方,您都可以调用它。在一些重复事务的中间调用它没有意义,但也没有坏处。所以一定要在 GiveMeAToken() 中调用它,但不要在 UseToken() 中调用它,因为您知道必须调用它才能获取令牌?

我更新了这个以显示如何保持简单的 bool Initialise 成功状态,尽管 SXMPMeta::Initialize() 的实际返回可能更复杂:

struct SomeATXThing
{
    bool good;
    SomeATXThing()
    {
        good = SXMPMeta::Initialize();
    }
    ~SomeATXThing()
    {
        if (good)
            SXMPMeta::Terminate();
    }
};

SomeATXThing* AtxEnsureSingleton()
{
    static SomeATXThing someATXThing;
    return someATXThing.good ? &someATXThing : nullptr;
}

请注意,单例的一个警告是,它们在构造结束时会以相反的顺序被破坏。其他一些独立的较早构造的单例在其销毁期间不得依赖此单例的存在,但一个单例构造函数在构造期间调用另一个单例构造函数并在销毁期间再次调用是安全的。除了 ABEND 之外,这条规则的执行并不容易。

你可以在对象所在的地方做凤凰重新激活(一般依赖于对象实例地址销毁后的未定义行为存在),但它是一个悲伤的负担。

【讨论】:

  • 另外一个问题:初始化函数返回一个布尔值来表示成功或失败。有没有办法向对象构造部分发出成功或失败的信号?
  • c'tor 报告错误的唯一方法是抛出异常。由于您正在实现一个 COM 对象,我假设您已经在处理 C++ 异常,因此它们永远不会跨越 COM ABI。
  • @loop123123 或者您向 SomeATXThing 对象添加一个成员,该成员由构造函数设置。根据您的心情,您可以让单例函数在初始化失败时返回一个空指针。请注意,您可以在该对象中添加任何其他有意义的内容。
  • 在 SXMPMeta::Initialize 返回简单布尔状态的基础上进行了更新,尽管它可能更复杂。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-07
  • 1970-01-01
  • 2015-10-08
  • 2010-10-22
  • 2016-10-25
  • 2021-04-16
  • 1970-01-01
相关资源
最近更新 更多