Как использовать общий ptr с помощью PostThreadMessage?

я хотел бы обновить мой код MFC использовать std::shared_ptr умный указатель при вызове других окон или потоков. Такие звонки SendMessage, PostMessage и PostThreadMessage которые проходят wparam и lparam и которые, соответственно, являются unsigned int и long. В настоящее время я создаю объект класса, новый объект, делаю вызов, передавая указатель на объект, использую объект на принимающей стороне, а затем удаляю его.

С shared_ptr работает так хорошо в остальной части моего кода I хотел, по крайней мере, изучить причины, по которым я не могу сделать то же самое для вызовов windows.

текущий вызов:

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-like:

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: вот пример кода, пересмотренный для использования подхода передачи unique_ptr, однако в моем коде есть утечки при передаче объекта:

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, утечки исчезают. Как мой код отличается от кода, который использует этот подход и работает?

Edit2:

в отношении @Remy Lebeauответ показывает, как std::unique_ptr может использоваться вместо std::shared_ptr, я заявил в комментарии ниже, что "...нет дополнительных объектов для реализации. Никаких очевидных минусов.". Ну, это не совсем так. The MyParams объект должен быть создан для каждого типа сообщения. Некоторые приложения могут иметь только несколько типов, но некоторые могут иметь 100 или более. Каждый раз, когда я хочу чтобы выполнить функцию с другой стороны, я должен создать новую структуру, которая имеет конструктор, который принимает все аргументы называет пункт назначения. Очень утомительно реализовать и трудно поддерживать, если их много.

я думаю, что можно было бы устранить эту фазу построения структуры, просто передав аргументы.

по-видимому, есть новые конструкции C++1x,которые могут помочь в этом. Один из них, возможно,std::forward_as_tuple который "создает кортеж ссылки на аргументы в args подходит для передачи в качестве аргумента функции."

для моего приложения я решил проблему, шаблонизировав MyParams, но для тех, кто хочет избежать добавления много структур, он может захотеть посмотреть на использование кортежей и тому подобное.

Edit 3: оба ответа @RemyLebeau номер 1 и @rtischer8277 номер ответа 5 являются правильными. К сожалению, StackOverflow не распознает несколько правильных ответов. Это ограничение StackOverflow отражает ошибочное психолингвистическое предположение, что лингвистический контекст универсален для одной и той же языковой группы, а это не так. (см. "введение в интеграционную лингвистику" Роджера Харриса о мифе о языке с фиксированным кодом, стр. 34). В ответ на мою первоначальную публикацию @RemyLebeau ответил на вопрос, основанный на контексте, описанном опубликованным кодом, который показывает newЭд MyParams (см. Edit 2: для получения дополнительных объяснений). Гораздо позже в ответе 5 (rtischer8277) я сам ответил на вопрос на основе оригинальной формулировки вопроса, который спросил, если std::shared_ptr может использоваться в потоках с помощью PostThreadMessage. Как разумное следствие, я повторно назначил правильный ответ на @RemyLebeau, это был первый правильный ответ.

EDIT4: Я добавил третий законный ответ на это сообщение. Смотрите 6-й ответ начиная с @Remy Lebeau и @rtischer8277 до сих пор представили два ответа на мою оригинальную публикацию.... Эффект от этого решение состоит в том, чтобы превратить кросс-потоковый доступ в концептуально простой RPC (удаленный вызов процедуры). Хотя это ответ показывает, как использовать future чтобы контролировать владение и синхронизацию, он не показывает, как создать объект сообщения с произвольным числом параметров, которые можно безопасно использовать с любой стороны вызова PostThreadMessage. Эта функциональность рассматривается в stackoverflow в ответ на вопрос передача пакета параметров по наследию подпись функции с помощью forward_as_tuple.

6 ответов


поскольку параметры сообщения должны пережить область вызова, нет смысла использовать shared_ptr в этом примере в качестве источника shared_ptr, скорее всего, выйдет за рамки до обработки сообщения. Я бы предложил использовать unique_ptr вместо этого, так что вы можете release() указатель, пока он находится в полете, а затем получить новое владение им после обработки сообщения:

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
}

в одну сторону:

  1. производные Params с std::enable_shared_from_this<Params> или из общего-из-этого включенного базового класса.

  2. передать исходный указатель в сообщении.

  3. на приемном конце позвоните p->shared_from_this() получить новый shared_ptr.

обратите внимание, что для проводки сообщение, в отличие от отправка, вам нужно убедиться, что объект по-прежнему действителен при обработке сообщения.

один из способов сделать это можно использовать статический shared_ptr. Чтобы обеспечить правильный протокол, вы можете ограничить доступность shared_from_this и оберните его в геттер, который аннулирует статические shared_ptr. Отказ от ответственности: я этого не делал, поэтому могут возникнуть проблемы.


Если это не критическая для скорости часть вашего приложения, то решением может быть создание синглтон это будет содержать каждый shared_ptr, который вы хотите передать.

когда вам нужно передать shared_ptr, вы сохраните его в этом синглтоне и получите взамен идентификатор, который ссылается на него. Это тот ID, который вы бы передали с PostThreadMessage.

С другой стороны, когда сообщение получено, вы можете получить shared_ptr, используя этот идентификатор. Конечно, синглтон должен удалить ссылку на shared_ptr.

плюсы:

  • этот метод довольно прост в реализации и должен быть совместим с вашим существующим кодом,
  • использование синглтона для хранения shared_ptr гарантирует, что они не могут быть уничтожены до передачи сообщения.

минусы:

  • что произойдет, если сообщение не получили? (это вообще возможно с PostThreadMessage?) Вы должны убедиться, что shared_ptr не останется навсегда в вашем синглтоне.
  • доступ к shared_ptr требует, чтобы найти его в std:: map (он может стать медленным, если у вас есть много сообщений одновременно),
  • поскольку это многопоточный код, вам нужно использовать мьютексы для обеспечения доступа к карте (shared_ptr являются потокобезопасными),
  • вам понадобится объект, который может хранить любой вид shared_ptr и который может быть сохранен в a std:: map. Подумайте о том, чтобы иметь класс шаблона, который может хранить любой shared_ptr, и который был бы производным от базового класса без шаблона (я не помню название этого трюка).
  • очевидно, что этот метод не будет работать, если вам нужно отправить сообщения между различными процессами.

решены. Я завернул std:: make_unique в logParams.инструкции release () в пересмотренном примере кода выше в мьютексе. Никаких утечек после добавления этих двух заявлений.

операторы удерживают поток errorLogger от удаления уникального объекта до освобождения его владельца.

есть еще проблема.

при комментировании добавленных 2 операторов мьютекса по-прежнему нет утечек. Они должны были снова появился. Не только утечки были исправлены путем добавления операторов, но и исправлены навсегда утечки в обоих случаях на моей машине dev. Код второго экземпляра не был изменен. Эта проблема выглядит как ошибка компилятора. К счастью, у меня есть ночная резервная копия на другой машине, и она все еще показывает поведение утечки.

что ясно, хотя, это, подход unique_ptr Реми Лебо к передаче смарт-ptrs через вызовы сообщений windows работает до тех пор, пока объекты завернуты в взаимное исключение. Он предупредил, что объекты будут протекать, если будут проблемы с жизнью.


в Edit2: я идентифицировал мошенника с ответом @Remy Lebeau:MyParams объект должен быть std:: unique_ptr-создан, а затем передан, а затем повторно принадлежит целевому потоку. Кроме того, моя первоначальная мотивация желания использовать std::shared_ptrs Через PostThreadMessage осталась в моем приложении. В моем OP-коде я сделал new на конструкторы, которые были доступны. Используйте std::weak_ptr< A >&(ref) конструктор. Вот принимающий код:

void ReceivingMethod::OnMsgId( WPARAM wParam, LPARAM lParam )
{
  std::weak_ptr< MyParams > myParamsX( reinterpret_cast< std::weak_ptr< MyParams >& >( lParam ) ); // ok: nn:34 but strong and weak refs reporting are random numbers
  //std::weak_ptr< MyParams > myParamsX( reinterpret_cast< std::weak_ptr< MyParams >& >( lParam ) ); // ok: also works
  myParamsX.lock()->nn++; // ok: nn: 35
  int nnX = myParamsX.lock()->nn; // ok: nnX: 35
} // ok: weak_ptr releases resource properly, but intellisense strong and weak ref counts are still bad

я протестировал incrementing myParamи myParamX ' s nn участники и оба myParamsWptr.lock()->nn++ и myParams->nn++ может увеличить интеллектуальный объект. На этот раз отпуская myParamобъект s не потерпел неудачу. Я предполагаю, потому что myParamsWptr блокирует объект, никаких проблем с доступом к потоку не возникнет.

как и ожидалось, в программе не было утечек.

зачем продолжать использовать PostThreadMessage на всех? Для связи между потоками пользовательского интерфейса windows (message pump) вы используете PostThreadMessage. Я все еще ищу современные методы C++, которые так же хороши, как это. std::promise, std::future и std::get_future отлично работать с рабочими потоками, у которых нет насосов сообщений. В тем временем, я использую PostThreadMessage в моих приложениях MFC C++.


@ Remy Lebeau и @rtischer8277 до сих пор представили два ответа на мою оригинальную публикацию. Первый использовал std:unique_ptr и оно std::shared_ptr. Оба ответа работают, но имеют одно и то же ограничение. @Rem Lebeau ставит это так:...так что вы можете отпустить() указатель, пока он находится в полете, а затем получить новое владение им после обработки сообщения..., что нарушает дух идиомы smart pointer. Что нужно, так это решение, которое работает как пульт Вызов процедуры (RPC), который существует с начала вычислений (RPC, DCE, CORBA, SQL и базы данных, а также поиск GOOBLE/BING/YAHOO, не говоря уже обо всех ссылках на веб-страницы и запросах на сегодняшний день). Очевидно, что существует четкая мотивация для легко запрограммированной кросс-потоковой функциональности RPC MFC. SendMessage является RPC для вызовов одного потока для объектов, производных от CWnd (aka, "window"). Решение, которое я предоставляю здесь, - это просто такое не обходное решение, которое я вызываю "SendThreadMessage".

ниже вы увидите проект, который демонстрирует эту возможность. Справочная информация: PostThreadMessage имеет две "перегрузки". Тот, который работает с windows и использует вызываемый поток m_hThreadID в его подписи, а другой -CWinThread::PostThreadMessage и не содержит классов windows (читай: производные CWnd). Практический пример и четкое объяснение бывшей перегрузки показано в Codeproject's PostThreadMessage Demystified по ThatsAlok.

второй обходной путь для иллюзорного Функциональность "SendThreadMessage" заключается в том, чтобы просто отправить сообщение в другой поток, и когда эта процедура будет завершена, отправьте его обратно. Горлэш:... для этого я использовал систему из двух сообщений, где основной поток отправил сообщение потоку обработчика задач, и когда обработчик задач был выполнен, он отправил другое сообщение обратно вызывающему объекту (оба из них являются пользовательскими сообщениями)..., и OReubens: ... PostThreadMessage и Post back (либо в окно, либо как сообщение потока) - это более безопасный/лучший / более контролируемый выход. Примечание: SendMessageCallback не является решением, потому что обратный вызов не является синхронным для перекрестных потоков.

две "перегрузки" вызвали большую путаницу, и не было никаких окончательных примеров кода CWinThread::PostThreadMessage перегрузка. Я не видел решения, которое не было бы утомительным по сравнению с чистым синхронным вызовом RPC.

Итак, вот мое решение: никогда не освобождайте владение unique_ptr объект, пока он естественным образом не выходит из масштаб. Использовать <future> создать promise это автоматически передается в другой поток через