【问题标题】:Multithreaded C++ application using Matlab Engine使用 Matlab 引擎的多线程 C++ 应用程序
【发布时间】:2023-03-29 13:38:01
【问题描述】:

我在初始化线程中打开 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() 打开一个到 Matlab 的 COM 通道。 一旦引擎打开,线程就会进入等待事件模式。

然后,在另一个线程中,我这样做:

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

我在engEvalString( MatlabEngine, cPathToScript ); 上收到CoInitialize has not been called 的第一次机会异常,这似乎告诉我Matlab COM 服务器不可用(但Matlab 引擎仍在运行)。

当我将所有内容放在同一个线程中时,它工作得很好,但这不是我想到的那种设计。

我发现 Matlab 引擎文档缺少关于引擎+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()

【问题讨论】:

    标签: c++ multithreading matlab com matlab-engine


    【解决方案1】:

    必须从您使用 COM 对象的每个线程调用 CoInitialize,而不仅仅是主线程。

    自从我上次自动化 Matlab 以来已经有十年了,所以请原谅下面的生锈。您收到 CoInitialize 错误表明 engOpen 调用包装了底层 COM 调用。不幸的是,这会让您不知不觉地接触到 COM 的蠕虫罐头。我猜你是对的,engOpen 包含对 CoInitialize 的调用,它初始化当前线程上的 COM 库。要从线程访问 COM 对象 CoInitialize 必须始终在调用 COM 之前在该线程上调用(除了一个允许的 COM 函数,我忘记了。)

    我的建议是现在将所有 Matlab 调用隔离到一个线程上。如果这样做,您将不必进行显式 CoInitialize 调用,并且可以避免任何以后的多线程 COM 问题。你今天可以通过在第二个线程上调用 CoInitialize 来让你的程序正常工作,但有一天你会被另一个 COM 问题所困扰。

    [我在 COM 上花了大约十年的时间,而且它充满了熊陷阱。你可以花几周时间阅读微软试图用 .Net 隐藏/杀死的技术,但最好现在就走简单的(单线程)路径,然后忘记它。]

    更新 恐怕您的编辑使您陷入了 COM 线程模型的泥潭。 COINIT_MULTITHREADED 有效地告诉 COM 您将处理线程的所有细微差别,这几乎肯定不是您想要做的。 COM 使用多个(上次我注意到它是三个)线程模型进行操作,您传递给 CoInitializeEx 的参数声明您希望使用哪些模型。

    如果后面的内容有点不对,向大家道歉。

    如果您指定 COINIT_MULTITHREADED,您需要知道您正在调用的 COM 对象是线程安全的,或者自己执行适当的锁定(以及线程之间的接口和数据编组)。

    COINIT_APARTMENTTHREADED,这可能是 engOpen 使用的,因为根据我的经验,它是最常见的,让 COM 库为您处理多线程。例如,该库可以创建代理和存根对象来调解跨线程(或进程边界,当您调用 Matlab 时会发生这种情况)的调用。

    engOpen 在您的主线程上创建了一个 Matlab 代理对象。可以从创建它的线程调用此代理对象,如果我没记错的话,可以从“公寓”中的任何其他线程调用(其中 CoInitializeEx 已使用 COINIT_APARTMENTTHREADED 调用。)您尝试通过代理从线程调用在不同的线程模型中,COM 库已经注意到并发出了您提到的错误。

    COM 在很多方面都令人惊叹,但其错综复杂却让人头疼。感谢您永远不必使用分布式 COM,这真的很讨厌!

    更新 2 我对 COM 线程模型的古老记忆是错误的。 This MSDN page 声明每个公寓有一个带有 COINIT_APARTMENTTHREADED 的线程。 COM 对象可以使用相同的接口指针从创建它们的单元中的所有线程访问。对于 COINIT_APARTMENTTHREADED,这意味着只是创建对象的线程。在 COINIT_MULTITHREADED 中,这将是多线程单元中的所有线程,但是(1)如果您使用 engOpen,您将无法选择在哪个线程上创建 Matlab 引擎,并且(2)尝试调用您没有的 COM 对象从多线程单元写入是有风险的。原来的 OLE 线程模型只允许来自主 GUI 线程的 COM 调用,顺便说一句。

    【讨论】:

    • 问题是,我不会在任何地方调用 CoInitialize。猜猜它必须由 engOpen 执行? (这是一个 matlab 引擎函数)此外,我从来没有使用过 CoInitialize,有什么建议可以做到这一点吗?
    • RobH,请查看我上面的编辑。猜猜我最终会按照您的建议将所有内容移到同一线程中:D
    • @CTZStef 我又添加了一点。
    • 我已经尝试了 COINIT_APARTMENTTHREADED,但仍然得到“应用程序调用了一个为不同线程编组的接口。”第一次机会异常。
    • 我添加了另一个更新。抱歉,我弄错了 COINIT_APARTMENTTHREADED 的详细信息。底线是,如果您使用 engOpen 等库调用,则需要从单个线程执行所有 Matlab 调用。另一种方法是直接使用 COM 接口执行所有 Matlab 调用,但我不建议这样做。
    猜你喜欢
    • 2012-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多