【问题标题】:C++ Builder: interface that was marshalled for a different threadC++ Builder:为不同线程编组的接口
【发布时间】:2016-02-16 16:24:56
【问题描述】:

为了使用线程中的 COM 对象,我将 CoInitialize(NULL) 插入到线程 Execute 函数中,并将 CoUninitialize() 插入到 Terminate 函数中。

一切正常,除非用户通过从调用表单调用 Terminate 函数来中止线程。

一个表单调用的 Terminate 函数似乎被视为另一个线程(错误消息:'应用程序调用了一个为不同线程编组的接口')。

另一方面,我无法将代码放入特定函数中以使用 Synchronize 进行调用。这样程序就会一直静止,直到被调用函数的COM进程结束。

我知道存在重新寻址 COM 编组的函数。但不知道具体要做什么。我也没有在 C++ 中找到示例。

在寻求帮助之前,我尝试了各种方法来解决这个问题。不幸的是我在这里。

这是我的代码:

class TThreadCamera : public TThread
{
private:
  Variant Camera;

protected:
  void __fastcall Execute();

public:
  void __fastcall Terminate(TObject *Sender);

public:
  __fastcall TThreadCamera();

};

-

__fastcall TThreadCamera::TThreadCamera()
  : TThread(false)
{
}
//---------------------------------------------------------------------------

void __fastcall TThreadCamera::Execute()
{
  FreeOnTerminate = true;
  OnTerminate = &Terminate;
  CoInitialize(NULL);
  Camera = Variant::CreateObject("ASCOM.Simulator.Camera");
  Camera.OlePropertySet("Connected", true);
  Camera.OleProcedure("StartExposure", 60, true);
  while ((! (bool) Camera.OlePropertyGet("ImageReady")))
    Sleep 100;
}
//---------------------------------------------------------------------------

void __fastcall TThreadCamera::Terminate(TObject *Sender)
{
  if (Camera.OlePropertyGet("CameraState") == 2) // Exposure currently in progress
    Camera.OleProcedure("AbortExposure");
  CoUninitialize();
}
//---------------------------------------------------------------------------

【问题讨论】:

    标签: c++ delphi com c++builder vcl


    【解决方案1】:

    您需要在同一个线程上调用CoInitializeCoUninitialize,因为它们作用于调用线程。 OnTerminate 事件总是在主线程上执行。

    因此,删除您的 OnTerminate 事件处理程序,将该代码移动到线程中,然后从线程中调用 CoUninitialize

    void __fastcall TThreadCamera::Execute()
    {
      FreeOnTerminate = true;
      CoInitialize(NULL);
    
      Camera = Variant::CreateObject("ASCOM.Simulator.Camera");
      // code to operate on the camera goes here
    
      CoUninitialize();
    }
    

    在 finally 块中保护未初始化可能是谨慎的。

    【讨论】:

    • 谢谢,不幸的是这种方式没有帮助,因为如果用户在 StartExposure 正在进行(60 秒长)时调用 Terminate,则 Couninitialize() 尚未执行。
    • 这是你需要做的。所有代码都需要在线程中执行。从 COM 初始化一直到最终确定。关于对Terminate 的调用,您的代码不会测试Terminated,并且由于该线程在终止线程上是空闲的,因此您无论如何都不能持有对它的引用。
    • 所以你需要决定如何在终止时免费处理。如果要保留对线程的引用,则不能在终止时使用 free 或至少需要清除 OnTerminate 处理程序中的引用。如果您希望能够使用 Terminate,您需要检查 Terminated 属性。因此,您的代码可能存在很多问题。但是关于 COM uninit 的问题的答案正如我所说的那样。
    • 您提到的“在相机上操作的代码”需要很长时间(在天文摄影中从几秒到 10 分钟或更长时间)。如果用户决定通过调用 Terminate 来停止进程,而 CoUninitialize 尚未处理,问题仍然存在:“应用程序调用了为不同线程编组的接口”。还有其他解决方案吗?
    • 你没有在听。除了在调用 CoInitialize 的线程上调用 CoUninitialize 之外别无选择。最终你会接受的。但是,由于我解释的原因,Terminate 不适用于您的线程。
    【解决方案2】:

    在 Delphi 中,如果需要在线程上下文中调用线程终止代码,则应覆盖受保护的 TThread.DoTerminate 方法,而不是编写 OnTerminate 事件处理程序。

    【讨论】:

    • 确实如此,但以这种方式将 init 和 finit 分开会很奇怪
    • 也许;但似乎覆盖 DoTerminate 是对 OP 代码的直接修复..
    • 我不会那样做的。你愿意吗?
    • 这取决于我现在的心情和月相。 :)
    • 重要的是DoTerminate 将始终被调用,即使Execute 未被调用。所以是的,最好将CoInitializeCoUninitialize 调用到Execute
    【解决方案3】:

    TThread.OnTerminate 事件在主 UI 线程的上下文中调用。工作线程在Execute()退出后调用的虚拟TThread.DoSynchronize()方法使用TThread.Synchronize()调用OnTerminateDoTerminate() 总是被调用,即使 Execute() 由于未捕获的异常而退出,所以覆盖 DoTerminate() 是执行特定线程清理的好方法。

    CoInitialize()CoUninitialize() 必须在同一个线程中调用。因此,您必须在Execute() 内部调用CoUninitialize(),或覆盖DoTerminate()。我更喜欢后者,因为它减少了在Execute() 中使用try/catchtry/__finally 块的需要(RAII 解决方案,例如utilscls.h 中的TInitOle,甚至更好)。

    只能在创建它的线程的上下文中访问单元线程 COM 对象。所以你必须在Execute()中调用相机的CameraStateproperty和AbortExposure()过程,或者覆盖DoTerminate()

    TThread.Terminate() 方法只是将TThread.Terminated 属性设置为 true,它什么也不做。 Execute() 有责任定期检查Terminated 属性并尽快退出。等待相机的 ImageReady 属性为 true 的 while 可以而且应该检查线程的 Terminated 属性,以便它可以在请求时停止等待。

    试试这样的:

    class TThreadCamera : public TThread
    {
    private:
      bool init;
    
    protected:
      void __fastcall Execute();
      void __fastcall DoTerminate();
    
    public:
      __fastcall TThreadCamera();
    };
    

    __fastcall TThreadCamera::TThreadCamera()
      : TThread(false)
    {
      FreeOnTerminate = true;
    }
    
    void __fastcall TThreadCamera::Execute()
    {
      init = SUCCEEDED(CoInitialize(NULL));
      if (!init) return;
      Variant Camera = Variant::CreateObject("ASCOM.Simulator.Camera");
      Camera.OlePropertySet("Connected", true);
      Camera.OleProcedure("StartExposure", 60, true);
      while (!Terminated)
      {
        if ((bool) Camera.OlePropertyGet("ImageReady"))
          return;
        Sleep(100);
      }
      if (Camera.OlePropertyGet("CameraState") == 2) // Exposure currently in progress
        Camera.OleProcedure("AbortExposure");
    }
    
    void __fastcall TThreadCamera::DoTerminate()
    {
      if (init) CoUninitialize();
      TThread::DoTerminated();
    }
    

    或者:

    class TThreadCamera : public TThread
    {
    protected:
      void __fastcall Execute();
    
    public:
      __fastcall TThreadCamera();
    };
    

    #include <utilcls.h>
    
    __fastcall TThreadCamera::TThreadCamera()
      : TThread(false)
    {
      FreeOnTerminate = true;
    }
    
    void __fastcall TThreadCamera::Execute()
    {
      TInitOle oleInit;
      Variant Camera = Variant::CreateObject("ASCOM.Simulator.Camera");
      Camera.OlePropertySet("Connected", true);
      Camera.OleProcedure("StartExposure", 60, true);
      while (!Terminated)
      {
        if ((bool) Camera.OlePropertyGet("ImageReady"))
          return;
        Sleep(100);
      }
      if (Camera.OlePropertyGet("CameraState") == 2) // Exposure currently in progress
        Camera.OleProcedure("AbortExposure");
    }
    

    【讨论】:

    • CoUninitialize 在匹配 try finally 或 RAII 方面显然更好,为了清楚起见并避免处理 Execute 不运行的情况。将其搁置到DoTerminate 是一种非常不透明且令人困惑的处理方式。
    • @user 您对我的回答发表了评论,说这里所说的对您没有好处。发生了什么变化?
    • 您的解释总体上对我非常有用。谢谢!
    • @user1125432 你应该接受这些答案之一。由于这三个基本上都说相同的话,请选择您认为最有用的一个。
    猜你喜欢
    • 1970-01-01
    • 2023-03-20
    • 2011-04-05
    • 1970-01-01
    • 2018-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多