6

我正在用它创建的多个线程在 Delphi 中编写一个 dll 库。让我一步一步描述问题。很抱歉提前进行了冗长的描述:-(。

让我们暂时忘记图书馆。我创建了一个 Windows 应用程序,它将呈现来自多个摄像机的视图。我创建了一个窗口,用于显示单个相机的视图,它包含一个 TImage 控件。有一个线程(一个 TThread 后代)每隔几毫秒从相机下载当前图像并将其分配给该窗口的 TImage 控件(使用 Synchronize() 方法)。该应用程序在启动时会创建该窗口的多个实例(每个实例都有一个单独的线程),因此您可以同时查看多个摄像机的实时视图。更重要的是,所有这些查看窗口都是主应用程序窗口的父级,因此它们出现在其中。

一切正常,直到我决定将这两个窗口放入一个 dll 库中。我只是因为某些原因发现它是必要的,但它们现在并不重要。所以我创建了一个新的 dll 库,将现有的主窗口和摄像头视图窗口添加到项目中,并导出了一个创建并返回主窗口实例的函数。创建主窗口时,它会创建多个摄像机视图窗口,使其自身成为父窗口。

然后,出于测试目的,我创建了一个应用程序,该应用程序从库中导入上述 dll 函数并在启动时调用它以获取主窗口的实例;然后将其显示在屏幕上(处于非模态状态)。

当我启动该应用程序时,我发现当时我无法从任何相机中获取单张图像。当我调试它时,我注意到当线程调用 Synchronize() 方法时,它会永远挂起。在将这两个窗口都放入 dll 之前并没有发生这种情况。

这是我的问题。老实说,这是我第一次接触图书馆的方法,到目前为止我不得不解决许多其他问题。你可能想知道为什么我使用窗口而不是框架......所以每当我在 dll 中创建 TFrame 的实例时,我都会收到一个异常,说“控件 xxx 没有父窗口”。我不知道该怎么做,所以我改用 windows :-(。

你能告诉我如何处理同步问题吗?当应用程序启动时,主线程似乎没有以任何方式被阻塞,因为它接受点击按钮等。那么问题是什么?

请帮忙!

先感谢您!!

4

3 回答 3

13

当你调用TThread.Synchronize的线程和方法指针被添加到一个全局SyncList: TList的 Classes.pas 中。在主 exe 的TApplication.Idle例程调用CheckSynchronize中,它检查 . SyncList,但它将检查 exe 中的版本而不是 DLL 中的版本。最终结果,您的同步方法永远不会被调用。

最简单的解决方法是从 DLL 切换到包,这样可以消除重复的SyncList.

另一种方法是覆盖 exe 的Application.OnIdle回调,并CheckSynchronize手动调用您的 DLL。不过,您需要应用程序的一些帮助,因为您的 DLL 也会有一个Application对象,而那个对象将无法工作。

于 2010-09-22T18:22:18.863 回答
3

使用 Synchronize 是个坏主意,因为它往往会导致这样的竞争条件。我不知道您的代码中具体发生了什么——如果没有看到任何代码就很难判断——但这种问题实际上很常见。

使用队列更好地完成线程间通信。如果你有最新的版本,Delphi XE,有一个TThreadedQueue<T>Generics.Collections非常适合这种事情。将 0 传递给构造函数中的 PopTimeout 参数,让您的相机线程推送图像,并让您的主线程使用第三个 PopItem 重载轮询队列,如下所示:

var
  CurrentItem: TImage;
begin
  if ThreadQueue.PopItem(CurrentItem) = wrSignaled then
    UpdateImage(CurrentItem); //or however you do it
end;

(如果队列中没有任何内容,PopItem 将返回 wrTimeout。)

如果您没有 Delphi XE,则需要构建自己的线程安全队列,或者从第三方来源(例如Primoz Gabrielcic 的OmniThreadLibrary)中找到一个。

于 2010-09-22T18:26:35.437 回答
0

我找到了两种解决Synchronize()挂起线程的方法(在 Delphi 7 中):

  1. TTimer在 Dll 表单和onTimer事件调用上放置一个CheckSynchronize

procedure TPluginForm.Timer1Timer(Sender: TObject); begin CheckSynchronize; end;

  1. 将此模块添加到 Dll 表单的使用部分
于 2016-01-22T14:40:50.830 回答