Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
426 views
in Technique[技术] by (71.8m points)

c++11 - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...