【问题标题】:Which are the safe way to send CString through PostMessage from thread哪些是从线程通过 PostMessage 发送 CString 的安全方法
【发布时间】:2017-06-24 11:49:47
【问题描述】:

从线程发送CStringPostMessage 的安全/最佳方式是什么? 在堆上创建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);

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

【问题讨论】:

  • 我认为在第一个解决方案中,主线程可能无法处理消息,这将导致崩溃......
  • 解决方案 1,但将其捕获在 unique_ptr 中,这样您就不会忘记删除它。
  • 如果我在 VC6 上使用这个解决方案,我在那个编译器上没有 unique_ptr 怎么办?
  • @flaviu2:那我建议升级你的编译器。今天还有VC6发布后出生的大人!
  • @TomTom:这可能看起来更简单,但现在您需要与线程通信,使用该指针指向它不拥有的对象的安全时间。而且您还需要同步访问,因为您不希望工作线程在其析构函数运行到一半时使用该对象。如果您正在寻找简单安全的解决方案,请参阅this anwers

标签: c++ multithreading visual-c++ mfc thread-safety


【解决方案1】:

第一个选项是更安全的选择。* 这可能导致资源泄漏的唯一原因是,如果对 ::PostMessage 的调用失败,并且您没有清理发件人。请注意,这不会导致崩溃。

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

根据您的具体用例,您可能需要考虑使用第三种选择:使用具有自动存储持续时间和通过消息发送进行线程同步的CString 对象,例如:

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

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


*假设两个线程都在同一个模块中实现。请务必阅读 Potential Errors Passing CRT Objects Across DLL Boundaries 以防万一。

【讨论】:

  • 哦! Automatic 和 SendMessage - 是的,这是一个非常处理生命周期的好选择。
  • @MartinBonner:无论如何。 SendMessage-call 必须传递一个(非拥有的)指针,而 C++ 无法表达这个(或 C,就此而言,因为这实际上是在 C 代码中调用)。接收器仍然可以存储该指针以供以后使用,并采取行动。无论哪种解决方案都需要值得信赖的开发人员,无论是哪种方式。
  • "请注意,这不会导致崩溃。"你能解释一下为什么这不会导致崩溃吗?只要我在堆上创建一个对象,并且我没有清理它,这不会导致崩溃吗?
  • “这可能导致资源泄漏的唯一原因是,如果对 ::PostMessage 的调用失败”我如何知道 ::PostMessage 是否失败?
  • @flaviu2: PostMessage 如果失败,则返回零。 SendMessage 也可能失败,但如果您使用我建议的替代方法来使用具有自动存储持续时间的对象,则不需要任何清理。如果你在堆上分配一个对象,然后扔掉指向它的指针,那块内存就被浪费了,没有任何方法可以回收它。这不会崩溃。这只是浪费地址空间。如果这种情况经常发生,你最终会耗尽内存,并且分配将开始失败。
【解决方案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:好点。我假设 CString 在他们的代码库中总是 CStringW。
  • 对。然后你被告知他们正在使用 VC6 ... 更重要的是,你可以通过定义 _CSTRING_DISABLE_NARROW_WIDE_CONVERSION 预处理器符号来禁用 ANSI/Unicode 转换(参见 CString::CString)。
  • 是的。如果他们使用的是 VC6,CString 可能是 CStringA。
  • "如果你不能确保生命周期足够长,而且你不能只使用文字,那么你将不得不使用 new/delete 方法。"你的意思是,第一个解决方案,我是对的?
猜你喜欢
  • 2010-12-20
  • 1970-01-01
  • 1970-01-01
  • 2021-11-20
  • 2014-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多