如何使用 PostThreadMessage 使用 shared_ptr?

2022-01-12 00:00:00 tuples future c++11 mfc sendmessage

我想升级我的 MFC 生产代码以在调用其他窗口或线程时使用 std::shared_ptr 智能指针.此类调用是 SendMessagePostMessagePostThreadMessage,它们通过 wparamlparam 并且分别是 unsigned intlong.目前,我创建一个类对象,新建一个对象,调用传递一个指向该对象的指针,在接收端使用该对象,然后将其删除.

I would like to upgrade my MFC production code to use the std::shared_ptr smart pointer when calling other windows or threads. Such calls are SendMessage, PostMessage and PostThreadMessage which pass wparam and lparam and which respectively are an unsigned int and long. Currently, I create a class object, new an object, make the call passing a pointer to the object, use the object on the receiving end and then delete it.

由于 shared_ptr 在我的其余代码中运行良好,我想至少探索一下为什么我不能对 Windows 调用执行相同操作.

Since shared_ptr works so well in the rest of my code I wanted to at least explore the reasons why I can't do the same for windows calls.

当前通话:

auto myParams = new MyParams(value1, value2, value3);
PostThreadMessage(MSG_ID, 0, reinterpret_cast< LPARAM >( myParams );

ReceivingMethod::OnMsgId( WPARAM wParam, LPARAM lParam)
{
  auto myParams = reinterpret_cast< MyParams * >( lParam );
  ... // use object
  delete myParams;
}

到类似 C++11 的智能指针调用:

to a C++11-like smart pointer call:

std::shared_ptr< MyParams > myParams( new MyParams( value1, value2, value3 ) );
PostThreadMessage( MSG_ID, 0, ???myParams??? );

ReceivingMethod::OnMsgId( WPARAM wParam, LPARAM lParam )
{
  auto myParams = ???lParam???;
  ... // use object
}

编辑 1:

@Remy Lebeau:这是修改为使用 unique_ptr 传递方法的示例代码,但是,在传递对象时,我的代码中存在泄漏:

@Remy Lebeau: Here is the sample code revised to use the unique_ptr passing approach, however, there are leaks in my code when passing the object:

struct Logger
{
  Logger()
  {
    errorLogger = ( ErrorLogger * )AfxBeginThread( RUNTIME_CLASS( ErrorLogger ), THREAD_PRIORITY_BELOW_NORMAL );
  }

  ~Logger()
  {
    // gets properly dtor'ed upon app exit
  }

  void MakeLogMsg( ... );

  ErrorLogger * errorLogger;
  std::unique_ptr< LogParams > logParams;
};

Logger logger;
std::recursive_mutex logParamsRecursiveMu; // because of multiple requests to lock from same thread

struct ErrorLogger : public CWinThread
{
  ErrorLogger()
  {
  }

  ~ErrorLogger()
  {
    // gets properly dtor'ed before logger upon app exit
  }

  afx_msg void OnLog( WPARAM wParam, LPARAM lParam );
};

void Logger::MakeLogMsg( ... )
{
  // construct msg from logparams 

  // make msg smart object using unique ptr and send to errorlogger thread queue
  logParams = std::make_unique< LogParams >();

  // set logparams

  // with the addition of the mutex guard, the leaks are gone
  logParamsRecursiveMu.lock();
  logger.errorLogger->PostThreadMessage( ONLOG_MSG, 0, reinterpret_cast< LPARAM >( logParams.get() ) );
  logParams.release(); // no longer owns object
  logParamsRecursiveMu.unlock();
}

void ErrorLogger::OnLog( WPARAM wParam, LPARAM lParam )
{
  std::unique_ptr< LogParams > logParams( reinterpret_cast< LogParams * >( lParam ) );
}

请注意,当我注释掉 unique_ptr 的传递时,泄漏消失了.我的代码与您使用这种方法并有效的代码有何不同?

Notice that when I comment out the passing of the unique_ptr, the leaks disappear. How does my code differ from your code that uses this approach and works?

关于 @Remy Lebeau 的回答显示如何使用 std::unique_ptr 代替 std::shared_ptr,我在下面的评论中指出,……没有额外的对象要实现.没有明显的缺点.".好吧,这并不完全正确.MyParams 对象必须为每种不同类型的消息创建.有些应用程序可能只有几种类型,但有些可能有 100 种或更多.每次我想在另一边执行一个函数时,我都必须制作一个新的结构,它有一个构造函数,它接受目标调用的所有参数.如果有很多,实施起来非常繁琐且难以维护.

With regard to @Remy Lebeau‘s answer showing how std::unique_ptr could be used instead of std::shared_ptr, I stated in a comment below that there were "…no extra objects to implement. No obvious cons.". Well, that is not quite true. The MyParams object has to be created for each different type of message. Some apps might only have a few types, but some might have 100s or more. Each time I want to execute a function on the other side I have to craft a new struct which has a constructor that takes all the arguments of the destination call. Very tedious to implement and hard to maintain if there are many.

我认为只需传递参数就可以消除结构构建阶段.

I think that it would be possible to eliminate that struct-building phase by merely passing the arguments.

显然有新的 C++1x 结构可以帮助实现这一点.一个可能是 std::forward_as_tuple,它构造一个对 args 中的参数的引用的元组,适合作为函数的参数转发."

Apparently there are new C++1x constructs that can help accomplish this. One is perhaps the std::forward_as_tuple which "Constructs a tuple of references to the arguments in args suitable for forwarding as an argument to a function."

对于我的应用,我通过模板化 MyParams 解决了这个问题,但对于任何想要避免添加大量结构的人来说,他可能想考虑使用元组等.

For my app I solved the problem by templatizing MyParams, but for anyone whose wants to avoid adding a lot of structs, he may want to look at using tuples and the like.

编辑 3:@RemyLebeau 的答案 1 和 @rtischer8277 的答案 5 都是正确的.不幸的是,StackOverflow 不能识别多个正确答案.这个 StackOverflow 限制反映了一个有缺陷的心理语言学假设,即语言上下文对于同一语言组是通用的,但事实并非如此.(参见 Roger Harris 关于固定代码语言神话的综合语言学简介",第 34 页).在回复我最初的帖子时,@RemyLebeau 根据发布的代码所描述的上下文回答了这个问题,该代码显示了 newed MyParams(请参阅编辑 2:了解更多说明).很久以后,在答案 5 (rtischer8277) 中,我自己根据问题的原始措辞回答了这个问题,该问题询问是否可以使用 PostThreadMessage 跨线程使用 std::shared_ptr.作为一个合理的结果,我已将正确答案重新分配给@RemyLebeau,它是第一个正确答案.

Edit 3: Both @RemyLebeau's answer number 1 and @rtischer8277’s answer number 5 are correct. Unfortunately, StackOverflow doesn’t recognize multiple correct answers. This StackOverflow limitation reflects a flawed PsychoLinguistic assumption that linguistic context is universal for the same language group, which it is not. (see "Introduction to Integrational Linguistics" by Roger Harris on the fixed-code language myth, p.34). In response to my original posting, @RemyLebeau answered the question based on the context described by the posted code that shows a newed MyParams (see Edit 2: for more explanation). Much later in Answer 5 (rtischer8277), I answered the question myself based on the original wording of the question which asked if std::shared_ptr could be used across threads using PostThreadMessage. As a reasonable consequence, I have re-assigned the correct answer back to @RemyLebeau it being the first correct answer.

我在这篇文章中添加了第三个合法答案.请参阅以 @Remy Lebeau 和 @rtischer8277 开头的第 6 个答案,到目前为止,我已为我的原始帖子提交了两个答案…….这个方案的效果是把跨线程访问变成了概念上简单的RPC(远程过程调用).虽然这个答案展示了如何使用 future 来控制所有权和同步,但它没有展示如何创建一个可以安全使用的具有任意数量参数的消息对象在 PostThreadMessage 调用的任一侧.该功能已在 StackOverflow 的 Answer 中解决,以发布 使用 forward_as_tuple 将参数包传递给旧函数签名.

I added a third legitimate answer to this posting. See the 6th Answer beginning with @Remy Lebeau and @rtischer8277 have so far submitted two answers to my original posting…. The effect of this solution is to turn cross-thread access into the conceptually simple RPC (remote procedure call). Although this Answer shows how to use a future to control ownership and synchronization, it does not show how to create a message object with an arbitrary number of parameters that can safely be used on either side of the PostThreadMessage call. That functionality is addressed in StackOverflow’s Answer to issue passing a parameter pack over a legacy function signature using forward_as_tuple.

推荐答案

由于消息参数必须超过调用范围,所以在这个例子中使用 shared_ptr 没有多大意义,因为source shared_ptr 最有可能在消息被处理之前超出范围.我建议改用 unique_ptr,这样您就可以在指针运行时 release() 指针,然后在处理完消息后获得它的新所有权:

Since the message parameters have to outlive the calling scope, it does not make much sense to use shared_ptr in this example, as the source shared_ptr is most likely to go out of scope before the message is processed. I would suggest using unique_ptr instead, so that you can release() the pointer while it is in flight, and then obtain new ownership of it once the message is processed:

SendingMethod::SendMsgId( ... )
{
    ...

    std::unique_ptr<MyParams> myParams( new MyParams(value1, value2, value3) );
    if (PostThreadMessage(MSG_ID, 0, reinterpret_cast<LPARAM>(myParams.get()))
        myParams.release();

    ...
}

ReceivingMethod::OnMsgId( WPARAM wParam, LPARAM lParam)
{
    std::unique_ptr<MyParams> myParams( reinterpret_cast<MyParams*>(lParam) );
    ... // use object
}

相关文章