Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use shared_ptr using PostThreadMessage?

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.

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.

Current call:

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;
}

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
}

Edit 1:

@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 ) );
}

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?

Edit2:

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.

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.”

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.

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.

EDIT4: 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.

like image 485
rtischer8277 Avatar asked Sep 04 '14 13:09

rtischer8277


People also ask

Can postthreadmessage be used to notify a shared_ptr?

AFTERTHOUGHT: Note that PostThreadMessage may fail... and you don't want to leak a shared_ptr. In my experience it is generally better to use a std::deque to hold the data and use the PostThreadMessage to notify that there is data there. In this way you'll never lose an object! YMMV Show activity on this post.

When to create a shared_ptr from a raw pointer?

If the callee creates a shared_ptr from the raw pointer, the new shared_ptr is independent from the original, and doesn't control the underlying resource. Use this option when the contract between the caller and callee clearly specifies that the caller retains ownership of the shared_ptr lifetime.

Can the thread of a process post messages?

For more information, see the Remarks section. Message posting is subject to UIPI. The thread of a process can post messages only to posted-message queues of threads in processes of lesser or equal integrity level.

Why can’t I create two shared_ptr pointers to the same resource?

This also explains why independently creating two std::shared_ptr pointed to the same resource gets us into trouble. Each std::shared_ptr will have one pointer pointing at the resource. However, each std::shared_ptr will independently allocate its own control block, which will indicate that it is the only pointer owning that resource.


2 Answers

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
}
like image 58
Remy Lebeau Avatar answered Dec 01 '22 02:12

Remy Lebeau


One way:

  1. Derive your Params from std::enable_shared_from_this<Params> or from a shared-from-this enabled base class.

  2. Pass a raw pointer in the message.

  3. At the receiving end, call p->shared_from_this() to obtain a new shared_ptr.

Note that for posting a message, as opposed to sending, you need to ensure that the object is still valid when the message is processed.

One way to do that can be use a static shared_ptr. To ensure correct protocol you can then limit the accessibility of shared_from_this and wrap it in a getter that nulls out the static shared_ptr. Disclaimer: I haven't done that so there may be issues.

like image 39
Cheers and hth. - Alf Avatar answered Dec 01 '22 00:12

Cheers and hth. - Alf