Я открываю движок Matlab в потоке инициализации, делая:
bool MY_MATLAB_ENGINE_o::Open()
{
// Handle the case where engine is already open
if( MatlabEngine )
{
return true;
}
else if( !( MatlabEngine = engOpen( 0 ) ) )
{
return false;
}
IsEngineOpen.SetValue( true );
return true;
}
функция engOpen()
открывает канал COM для Matlab.
Как только двигатель открыт, поток переходит в режим ожидания события.
Затем в другой теме я делаю это:
bool MY_MATLAB_ENGINE_o::ChangeDirectory( QString strPath )
{
QString strPathToScript = "cd('" + strPath + "');";
QByteArray ba = strPathToScript.toLatin1();
const char* cPathToScript = ba.data();
if( MatlabEngine )
{
engEvalString( MatlabEngine, cPathToScript );
return true;
}
return false;
}
Я получаю CoInitialize has not been called
исключение первого шанса на engEvalString( MatlabEngine, cPathToScript );
который говорит мне, что сервер Matlab COM недоступен (но движок Matlab все еще работает).
Когда я помещаю все в одну и ту же ветку, все работает нормально, но это не тот дизайн, который я имел в виду.
Я считаю, что в документации по Matlab отсутствует информация о engine + COM. Любая идея, как иметь инициализацию двигателя и вызовы функций в отдельных потоках?
Спасибо !
РЕДАКТИРОВАТЬ после ответа RobH
Я добавил этот метод в свой класс (созданный во втором потоке):
bool MY_MATLAB_FUNCTION_CALL_o::PostThreadCreationHook()
{
HRESULT hr;
hr = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hr))
{
return false;
}
return true;
}
И теперь, когда я звоню engEvalString( MatlabEngine, cPathToScript );
я получил The application called an interface that was marshalled for a different thread
исключение из первого шанса 🙂 Мне так весело сегодня утром! 🙂
Так, CoMarshalInterface()
?
CoInitialize должен быть вызван из каждый поток, где вы используете COM-объект, а не только основной поток.
Прошло десять лет с тех пор, как я последний раз автоматизировал Matlab, так что извините за ржавчину в дальнейшем. То, что вы получили ошибку CoInitialize, говорит о том, что вызов engOpen переносит лежащие в основе COM-вызовы. К сожалению, это подвергает вас опасности для червей COM. Я предполагаю, что вы правы, и что engOpen включает в себя вызов CoInitialize, который инициализирует библиотеку COM в текущем потоке. Чтобы получить доступ к COM-объектам из потока, всегда должен вызываться CoInitialize. в этой теме перед любыми вызовами COM (кроме одной разрешенной функции COM, я забыл, какая.)
Мой совет — теперь изолировать все ваши вызовы Matlab в один поток. Если вы сделаете это, вам не придется делать явный вызов CoInitialize, и вы избежите любых последующих многопоточных проблем COM. Вы могли бы заставить свою программу работать сегодня, вызвав CoInitialize во втором потоке, но однажды вы будете укушены другой проблемой COM.
[Я потратил около десяти лет на COM, и он полон медвежьих ловушек. Вы могли бы потратить несколько недель на изучение технологии, которую Microsoft пыталась скрыть / убить с помощью .Net, но сейчас лучше просто пойти по простому (однопоточному) пути и забыть о нем.]Обновить
Я боюсь, что ваши изменения привели вас в пучину моделей потоков COM. COINIT_MULTITHREADED эффективно сообщает COM, что вы позаботитесь обо всех мелких нюансах многопоточности, что почти наверняка не то, что вы хотите сделать. COM работает с несколькими (в прошлый раз я обратил внимание, что это было три) потоковыми моделями, и параметр, который вы передаете CoInitializeEx, объявляет, какую из этих моделей вы хотите использовать.
Приносим извинения всем, если то, что следует немного выключено.
Если вы указываете COINIT_MULTITHREADED, вам нужно либо знать, что вызываемый вами COM-объект является поточно-ориентированным, либо самостоятельно выполнять соответствующую блокировку (и сортировку интерфейсов и данных между потоками).
COINIT_APARTMENTTHREADED, который, вероятно, использует engOpen, поскольку, по моему опыту, он наиболее распространенный, позволяет библиотеке COM работать с многопоточностью для вас. Библиотека может, например, создавать прокси-объекты и объекты-заглушки для передачи вызовов между потоками (или границами процесса, что и будет, когда вы вызовете Matlab).
engOpen создал прокси-объект Matlab в вашем основном потоке. Этот прокси-объект может быть вызван из потока, в котором он был создан, и, если я правильно помню, из любого другого потока в «квартире» (где CoInitializeEx был вызван с COINIT_APARTMENTTHREADED.) Вы пытались вызвать через прокси из потока в другой модели потоков библиотека COM заметила и выдала упомянутую вами ошибку.
Во многих отношениях COM удивителен, но тонкости — это боль в заднице. Будьте благодарны, что вам никогда не придется использовать Distributed COM, что действительно неприятно!
Обновление 2
Мои древние воспоминания о моделях потоков COM неверны. Эта страница MSDN заявляет, что есть одна нить на квартиру с COINIT_APARTMENTTHREADED. Доступ к COM-объектам можно получить с помощью одного и того же указателя интерфейса из всех потоков в квартире, где они были созданы. Для COINIT_APARTMENTTHREADED это означает только поток, в котором был создан объект. В COINIT_MULTITHREADED это будут все потоки в многопоточной квартире, но (1) вы не можете выбрать, в каком потоке создается движок Matlab, если вы используете engOpen и (2) пытаетесь вызвать COM-объект, который вы не делали писать из многопоточной квартиры рискованно. Исходная модель потоков OLE допускала только вызовы COM из основного потока GUI, BTW.
Других решений пока нет …