Delphi中的DLL、窗体和线程(全部合并)问题

3
有一个非常复杂的应用程序我正在尝试构建。
有一个DLL库,我在其中创建了一个表单并放置了一个线程。
在DLL中,我有一个函数:
procedure ShowForm; stdcall;
var
Form1 : TFormSNVFL7;
begin
  Form1 := TFormSNVFL7.Create(nil);
  Form1.Show;
end;

我创建了一个表单并显示它。这里没有问题。 我向这个dll添加了一个线程。 我在表单上放置了一个计时器。几秒钟后,我创建了一个线程并运行它。一切都正常,但当我尝试更改表单的任何内容时,什么也不会发生。

在同步函数中,我尝试更改标签,但没有任何反应。

以下是文件:

DLL pas:

library uploader;

uses
  SysUtils,
  Classes,
  Forms,
  UploaderForm in 'UploaderForm.pas' {FormUploader},
  ThreadUpload in 'ThreadUpload.pas';

{$R *.res}

procedure ShowForm; stdcall;
var
  upForm: TFormUploader;
begin
  upForm := TFormUploader.Create(nil);
  upForm.Show;
end;

exports
ShowForm;

begin
end.

表格密码:
unit UploaderForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, acPNG, ExtCtrls, JvExExtCtrls, JvImage, JvExControls, JvLabel,
  JvAnimatedImage, JvGIFCtrl, ComCtrls, JvExComCtrls, JvProgressBar, StdCtrls,
  FileCtrl, JvDriveCtrls;

type
  TFormUploader = class(TForm)
    imgRunning: TJvImage;
    imgReady: TJvImage;
    imgUpdate: TJvImage;
    JvLabel1: TJvLabel;
    JvLabel2: TJvLabel;
    imgConnect: TJvImage;
    imgUpload: TJvImage;
    imgCheck: TJvImage;
    JvLabel3: TJvLabel;
    JvLabel4: TJvLabel;
    JvLabel5: TJvLabel;
    JvLabel6: TJvLabel;
    imgRun: TJvImage;
    imgOK: TJvImage;
    imgDone: TJvImage;
    JvProgressBar1: TJvProgressBar;
    JvLabel7: TJvLabel;
    fileList: TJvFileListBox;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  FormUploader: TFormUploader;

implementation

{$R *.dfm}

Uses ThreadUpload;

procedure TFormUploader.FormCreate(Sender: TObject);
begin
imgUpdate.Picture := imgReady.Picture;
imgConnect.Picture := imgReady.Picture;
imgUpload.Picture := imgReady.Picture;
imgCheck.Picture := imgReady.Picture;
imgRun.Picture := imgReady.Picture;
imgOK.Picture := imgReady.Picture;
fileList.Directory := ExtractFilePath(Application.ExeName) + 'csvexport/';
end;

procedure TFormUploader.Timer1Timer(Sender: TObject);
var
UpThread: TThread;
begin
Timer1.Enabled := False;

UpThread := UploadThread.Create(true);
UpThread.Create;
UpThread.Resume;

end;

end.

线程通行证:

unit ThreadUpload;

interface

uses
  Classes, UploaderForm;

type
  UploadThread = class(TThread)
  private
    { Private declarations }
  protected
    procedure Execute; override;
  end;

implementation

{ UploadThread }

procedure UploadThread.Execute;
begin
  With FormUploader do
  begin
    imgUpdate.Picture := imgRunning.Picture;
  end;
end;

end.

我无法解决这个问题。

为什么在创建线程对象后要调用Upload.Create?我没有在你的代码中看到imgRunning声明。为什么要使用线程来执行这样的任务? - Linas
imgRunning是一个窗体对象。它在窗体创建时被创建。我在我的窗体上放置了一个jvImage组件并将其重命名为imgRunning,但这并不重要。当我尝试执行以下操作时,出现了相同的问题:Label1.Caption := 'anything'; 什么也没有改变... - mrtakdnz
顺便说一下,这是任务的开始。我将使用IndyFTP上传数据库文件。我将从Interbase服务器获取数据,然后将其转换为CSV文件,上传到服务器并在服务器上运行它们(我的服务器生产商不支持MySQL的远程连接)等。 - mrtakdnz
2
我直言不讳地说,看起来你只是想为了复杂而复杂。这不是一个非常实际的做法。我们编写程序是为了解决问题,而不是使生活更加困难。如果没有目的,就更难帮助你了。这最能说明的是,你的线程除了任意赋值、退出和完成之外什么也没做!你的线程没有目的。首先要决定你想做什么,而不是_你想如何做它_。 - Disillusioned
1
当您有特定的问题需要解决时,我们很可能可以帮助您找到解决方案,而无需为自己创造一堆复杂性。 - Disillusioned
4个回答

8

TThread.Synchronize()默认情况下无法在DLL中使用,因为Synchronize()所使用的同步队列是局限于调用它的可执行文件本身的。换句话说,当应用程序调用Synchronize()时,它会将其发布到本地exe文件的队列中。当DLL调用Synchronize()时,它会将其发布到本地dll文件的队列中。当应用程序在闲置时间内操作其同步队列时,它不会自动操作DLL的队列。您必须从您的DLL中导出一个函数,然后您的应用程序可以在需要时调用该函数,比如在TApplication.OnIdle事件或计时器中。那个导出的函数可以调用RTL的CheckSynchronize()函数来操作DLL的同步队列。


0

简单

您正在从UpThread中的UploaderForm单元更改FormUploader变量的属性。

但是在DLL.pas单元中,您正在创建来自TFormUploader的其他对象。

请尝试在显示表单的过程中执行此操作:

procedure ShowForm; stdcall;
begin
  FormUploader := TFormUploader.Create(nil);
  FormUploader.Show;
end;

做这个,问题就解决了。


这个会起作用,但我建议你在你的dll中进行审查,验证计时器是否真的必要,实现是否需要在其他线程中进行...但如果你只是为了学习多线程而这样做,那就没有问题。 - KelvinS

0

您的问题来自于使用 VCL 和线程。您应该永远不要在没有使用同步机制的情况下从线程中调用与 VCL 相关的代码。

通常,您可以通过使用 TApplication 和 TApplication.Run() 来创建 VCL 应用程序,并创建程序的主循环。主循环处理窗口消息和其他内容,但也会调用 CheckSynchronize(),而 CheckSynchronize() 会查找是否有等待同步的调用(这是通过使用 TThread.Synchronize() 将调用添加到队列中的调用)。因此,当您创建一个线程时,它将并发运行到主循环中,这就是您的问题所在。

您应该将图片分配代码移动到 TFormUploader 中的单独方法中,并通过使用 TThread.Synchronize() 调用该方法,或者使用其他同步机制,如事件对象(TEvent / CreateEvent())。


0

我遇到了类似的问题,尝试从由DLL调用的回调函数更新主EXE中的TToolButton图标。 DLL响应通过DataSnap实现发送到通道消息而调用回调函数,在子线程中执行。

直接从EXE回调函数访问TToolButton会导致TToolBar闪烁并且图标消失。

我创建了一个TThread对象,并使用TThread.Synchronize()函数在主线程中管理与TToolButton的交互:这样解决了我的问题。

interface    
type
  TCallBackThread=class(TThread)

  private
    procedure DoInSync;
  public
    procedure Execute; override;
  end;
var
  CallBackThread: TCallBackThread;

implementation

procedure TCallBackThread.DoInSync;
begin
  // Jobs to be done in main thread
end;

procedure TCallBackThread.Execute;
begin
  inherited;
  Synchronize(DoInSync);
end;

回调函数进入EXE是:

procedure ConnectWf_Callback(s: PAnsiChar); stdcall;
begin
  if not Assigned(CallBackThread) then begin
    CallBackThread := TCallBackThread.Create(true);
    CallBackThread.Resume;
  end else begin
    CallBackThread.Execute;
  end;
end;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接