从线程发送CString的安全方法是什么?

5

如何安全/最佳地通过线程从CString发送PostMessage

创建堆上的CString并在接收者获取此CString时清理,这是最好的方法吗?

解决方案1:在线程中:

CString* pError = new CString(_T("Unknown error"));
::PostMessage(...(LPARAM)pError);

在主线程中,在GUI的某个地方:

CString* pError = (CString*)lParam;
GetDocument()->DoSomething(*pError);
delete pError;

解决方案2: 或者,将CString对象作为CThread类的成员变量保留?

class CPlanThread : public CThread [: public CObject]
{
public:
DECLARE_DYNAMIC(CPlanThread)

...
protected:
CString* m_pMessage;

};

并且。
CPlanThread::CPlanThread()
:m_pMessage(NULL)
{
m_pMessage = new CString(_T(""));
}

CPlanThread::~CPlanThread()
{
if(NULL != m_pMessage)
    delete m_pMessage;
}

并且在某个线程中:

::PostMessage(m_hWndMain, WMU_NOTIFYTHREAD, 0, (LPARAM)m_pMessage);

在主线程中,某个GUI界面的位置:

CString* pError = (CString*)lParam;
GetDocument()->DoSomething(*pError);

以上两种解决方案都是安全的吗?感谢您的任何解释。

我认为在第一个解决方案中,存在消息可能无法被主线程处理的可能性,这将导致崩溃... - Flaviu_
如果我在VC6上使用这个解决方案,而该编译器上没有unique_ptr呢? - Flaviu_
1
@flaviu2:那我建议你升级编译器。今天有些成年人出生在VC6发布之后! - Martin Bonner supports Monica
@RichardHodges:使用std::unique_ptr并不能带来任何好处,因为你正在转移所有权。PostMessage调用将立即在其上调用release(),发送方无法保留所有权而不进行额外的同步(例如通过SendMessage)。此时,为什么要建议将std::unique_ptr用于CString*,而不是使用具有自动存储期的CString对象呢? - IInspectable
1
@TomTom:这看起来可能更简单,但现在你需要与线程通信,告诉它使用指向一个它不拥有的对象的指针是安全的时间有多长。而且你还需要同步访问,因为当工作线程运行到一半时,你不希望它使用该对象。如果你正在寻找一个简单而安全的解决方案,请参见这个答案 - IInspectable
显示剩余6条评论
2个回答

3
第一种选择是更加安全的方案。*唯一可能导致资源泄露的原因是调用 ::PostMessage 失败,而在发送方没有进行清理。请注意,这不会导致崩溃。

第二种方案会产生竞争条件,因为您持有一个指针,其所有权应该被转移。如果GUI线程尝试在线程对象被销毁后访问字符串,则正在访问随机内存。如果幸运的话,这可能会导致立即崩溃。

根据您特定的用例,您可能需要考虑使用第三种方案:使用具有自动存储期和通过消息传递进行线程同步的CString对象,例如:

CStringW err( L"Unknown error" );
::SendMessage( ..., (LPARAM)&err );

接收线程可以在其消息处理程序中使用字符串对象,只要它在其中,而发送方将自动清理资源。


* 假设两个线程都在同一模块中实现。如果不是,请务必阅读《跨DLL界限传递CRT对象的潜在错误》


哦!自动和SendMessage - 是的,这是处理生命周期的一个非常好的选项。 - Martin Bonner supports Monica
@MartinBonner:有点像,不过还是有区别的。SendMessage调用必须传递一个(非拥有)指针,而C++没有表达这一点的方式(对于C来说也是如此,因为它实际上是在调用C代码)。接收者仍然可以存储该指针以供以后使用,并且可能会发生错误。无论哪种解决方案都需要可信赖的开发人员,总之。 - IInspectable
请注意,这不会导致崩溃。但如果我在堆上创建了一个对象,并没有清理它,为什么这不会导致崩溃呢? - Flaviu_
唯一可能导致资源泄漏的原因是,如果对 ::PostMessage 的调用失败了。我该如何知道 ::PostMessage 是否失败? - Flaviu_
@flaviu2:如果 PostMessage 失败,它会返回零。SendMessage 可能也会失败,但如果你采用我建议的使用具有自动存储期的对象的替代方法,则不需要执行任何清理操作。 如果在堆上分配了一个对象并丢弃了指向它的指针,则该内存将被简单浪费,无法回收。这不会导致崩溃,只是一种地址空间的浪费。 如果这种情况经常发生,最终可能会耗尽内存,并且分配将开始失败。 - IInspectable
我可以将两篇帖子标记为答案吗?因为我得到了两个很好的替代方案...我请求管理员将上面的任何一个答案标记为答案,它们都很好!非常感谢大家! - Flaviu_

2

我总是更喜欢将东西存储在成员变量中(这意味着有一个对象负责清理它们)。 但是,请注意下面的重要警告。 我也更喜欢按值持有CString,而不是按指针持有。 存储指针只会使你需要管理另一部分内存。 因此:

class CPlanThread : public CThread [: public CObject]
{
public:
DECLARE_DYNAMIC(CPlanThread)

...
protected:
CString m_Message;

};
and

CPlanThread::CPlanThread()
:m_Message(L"")
{
}

CPlanThread::~CPlanThread()
{
}

然后

::PostMessage(m_hWndMain, WMU_NOTIFYTHREAD, 0, (LPARAM)&m_Message);

请注意,这种方法意味着您不需要在析构函数中执行任何操作,构造函数可以初始化变量(实际上,您应该在指针上使用初始化)。
我已经删除了_T()宏。这是一个非常糟糕的想法,除非您实际上使用两种类型的字符版本构建软件(这会使您的测试工作量增加一倍,但没有任何好处)。只需习惯于在文字前面加上'L'。
最后一点,删除指针之前没有测试指针是否为nullptr的意义 - delete语句会自动进行检查。
重要提示:这种方法意味着您需要确保CPlanThread对象存在直到消息被处理 - 但您必须在指针成员上进行此操作。
如果您无法确保生命周期,但可以使用字符串字面值,则发布const wchar_t *,您不必管理生命周期。
如果不能确保生命周期足够长,也不能仅使用文字,那么您将不得不使用new/delete方法。

使用宽字符字面值构造一个 CString 对象可能导致缩窄转换。如果你不希望发生这种情况,请在字符串类型上明确指定,并使用 CStringW - IInspectable
@IInspectable:说得好。我假设在他们的代码库中,CString始终是CStringW。 - Martin Bonner supports Monica
1
好的。然后你被告知他们正在使用VC6......更严肃的问题是,您可以通过定义“_CSTRING_DISABLE_NARROW_WIDE_CONVERSION”预处理器符号来禁用ANSI / Unicode转换(请参见[CString :: CString](https://msdn.microsoft.com/en-us/library/5bzxfsea.aspx#cstringt__cstringt))。 - IInspectable
是的。如果他们正在使用VC6,则CString可能是CStringA。 - Martin Bonner supports Monica
如果您无法确保生命周期足够长,也不能仅使用文字常量,那么您将不得不使用new/delete方法。您的意思是第一种解决方案,对吗? - Flaviu_
@flaviu2:完全正确。 - Martin Bonner supports Monica

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