我怎样才能强制我的 window 保持自我更新?

How can I force my window to keep itself updated?

最终更新

解决方案是创建一个 TTimer,将其 Interval 设置为大于 0 的任何值,并将其 OnTimer 属性 分配给一个空函数。


我有一个 TThread 通过 Queue() 定期向主窗体添加新控件。但是排队的函数永远不会执行,直到表单接收到用户输入,或者光标移到它上面,然后它会立即执行所有排队的函数。

通过仔细的记录,我最终确定函数正在按照线程的预期进行排队。在表单获得用户交互之前,它们不会被主 VCL 循环执行。

当没有用户交互时,就好像主应用程序循环没有 运行。

如何强制表单立即执行排队的函数?

如果重要的话,表单和 TThread 由一个 .dll 创建,另一个 .dll 调用它,而另一个 .dll 本身由控制台应用程序调用。

像这样:

console application -> dll -> dll created by C++ Builder

编辑

void __fastcall GirkovArpa::Execute() {
    while (!Terminated) {
        if (GlobalMessageQueue.size() > 0) {
            EnterCriticalSection(&myCritSect);
            std::cout << "";  // this line is required, else thread won't execute
            std::string GlobalMessage = GlobalMessageQueue.at(0);
            std::string copy;
            copy.assign(GlobalMessage);
            GlobalMessageQueue.erase(GlobalMessageQueue.begin());
            LeaveCriticalSection(&myCritSect);
            Queue([&, copy]() {
                // do stuff
            });
        }
    }
}

编辑 2

Node.JS console app => Node DLL addon => C++Builder GUI DLL

我的控制台应用程序专门是 NodeJS。它加载一个"NodeJS addon"(一种DLL),它加载用C++ Builder创建的DLL,导出这个函数:

void myExportedFunction(const char *str) {
    EnterCriticalSection(&myCritSect);
    GlobalMessageQueue.push_back(std::string(str));
    // CheckSynchronize();
    LeaveCriticalSection(&myCritSect);
}

如果 CheckSynchronize() 没有被注释掉,我会得到一个 Segmentation Fault 错误。

我的 TThread 运行 无限循环,检查 GlobalMessageQueue,如果它发现它不为空,它会将一个 lambda 放入队列,该 lambda 在主窗体。

但是在用户与 window 交互之前不会执行排队的 lambda(只需将光标移到 window 上就足够了)。

编辑 3

这是我的完整 lambda:

Queue([&, copy]() {
                std::vector<std::string> words;
                boost::split(words, copy, boost::is_any_of(" "));

                // CREATE $TControl $Name $Text $Parent
                if (words.at(0) == "CREATE") {
                    if (words.at(1) == "TEXTBOX") {
                        String formName = stringToString(words.at(4));
                        TForm *form = getFormByName(formName);
                        TEdit *textbox = new TEdit(form);
                        textbox->Parent = form;
                        textbox->Name = words.at(2).c_str();
                        textbox->Text = words.at(3).c_str();
                        textbox->Show();
                        textbox->OnClick = MyForm->OnClick;
                    }
                    if (words.at(1) == "RADIO") {
                        String formName = stringToString(words.at(4));
                        TForm *form = getFormByName(formName);
                        TRadioButton *radio = new TRadioButton(form);
                        radio->Parent = form;
                        radio->Name = words.at(2).c_str();
                        radio->Caption = words.at(3).c_str();
                        radio->Show();
                        radio->OnClick = MyForm->OnClick;
                    }
                    if (words.at(1) == "BUTTON") {
                        String formName = stringToString(words.at(4));
                        TForm *form = getFormByName(formName);
                        TButton *button = new TButton(form);
                        button->Parent = form;
                        button->Name = words.at(2).c_str();
                        button->Caption = words.at(3).c_str();
                        button->Show();
                        button->OnClick = MyForm->OnClick;
                    }
                    if (words.at(1) == "FORM") {
                        createDialog(words.at(2).c_str(), words.at(3).c_str());
                    }
                }

                if (words.at(0) == "CHANGE") {
                    for (int j = 0; j < Screen->FormCount; j++) {
                        TForm *form = Screen->Forms[j];
                        if (form->Name == words.at(1).c_str()) {
                            TRttiContext ctx;
                            TRttiType *type = ctx.GetType(form->ClassInfo());
                            TRttiProperty *prop = type->GetProperty(words.at(2).c_str());
                            TValue value;

                            if (prop->PropertyType->TypeKind == tkUString) {
                                value = TValue::From<UnicodeString>(words.at(3).c_str());
                            } else if (prop->PropertyType->TypeKind == tkInteger) {
                                value = TValue::From<Integer>(StrToInt(words.at(3).c_str()));
                            } else {
                                std::cout << "ERROR" << std::endl;
                            }
                            prop->SetValue(form, value);
                        }

                        for (int i = 0; i < form->ControlCount; i++) {
                            TControl *control = form->Controls[i];
                            if (control->Name == words.at(1).c_str()) {
                                TRttiContext ctx;
                                TRttiType *type = ctx.GetType(control->ClassInfo());
                                TRttiProperty *prop = type->GetProperty(words.at(2).c_str());
                                TValue value;

                                if (prop->PropertyType->TypeKind == tkUString) {
                                    value = TValue::From<UnicodeString>(words.at(3).c_str());
                                } else if (prop->PropertyType->TypeKind == tkInteger) {
                                    value = TValue::From<Integer>(StrToInt(words.at(3).c_str()));
                                } else {
                                    std::cout << "ERROR" << std::endl;
                                }
                                prop->SetValue(control, value);
                            }
                        }
                    }
                }

                // GET NAME PROP
                if (words.at(0) == "GET") {
                    for (int j = 0; j < Screen->FormCount; j++) {
                        TForm *form = Screen->Forms[j];
                        if (form->Name == words.at(1).c_str()) {
                            TRttiContext ctx;
                            TRttiType *type = ctx.GetType(form->ClassInfo());
                            TRttiProperty *prop = type->GetProperty(words.at(2).c_str());

                            TValue result = prop->GetValue(form);
                            if (result.Kind == tkUString) {
                                String leString = result.AsString();
                                std::wstring w(std::wstring(leString.t_str()));
                                std::string STR(w.begin(), w.end());
                                std::string output = words.at(1) + " " + words.at(2);
                                String o = output.c_str();
                                tellJavaScript(AnsiString(o + ": " + leString).c_str());
                            } else if (result.Kind == tkInteger) {
                                int result_int = result.AsInteger();
                                String result_String = IntToStr(result_int);
                                String name = words.at(1).c_str();
                                String prop = words.at(2).c_str();
                                tellJavaScript(AnsiString(name + " " + prop + ": " + result_String).c_str());
                            } else {
                                // assume boolean
                                String result_String = BoolToStr(result.AsBoolean());
                                String name = words.at(1).c_str();
                                String prop = words.at(2).c_str();
                                tellJavaScript(AnsiString(name + " " + prop + ": " + result_String).c_str());
                            }
                        }

                        for (int i = 0; i < form->ControlCount; i++) {
                            TControl *control = form->Controls[i];
                            if (control->Name == words.at(1).c_str()) {
                                TRttiContext ctx;
                                TRttiType *type = ctx.GetType(control->ClassInfo());
                                TRttiProperty *prop = type->GetProperty(words.at(2).c_str());

                                TValue result = prop->GetValue(control);
                                if (result.Kind == tkUString) {
                                    String leString = result.AsString();
                                    std::wstring w(std::wstring(leString.t_str()));
                                    std::string STR(w.begin(), w.end());
                                    std::string output = words.at(1) + " " + words.at(2);
                                    String o = output.c_str();
                                    tellJavaScript(AnsiString(o + ": " + leString).c_str());
                                } else if (result.Kind == tkInteger) {
                                    int result_int = result.AsInteger();
                                    String result_String = IntToStr(result_int);
                                    String name = words.at(1).c_str();
                                    String prop = words.at(2).c_str();
                                    tellJavaScript(AnsiString(name + " " + prop + ": " + result_String).c_str());
                                } else {
                                    // assume boolean
                                    String result_String = BoolToStr(result.AsBoolean());
                                    String name = words.at(1).c_str();
                                    String prop = words.at(2).c_str();
                                    tellJavaScript(AnsiString(name + " " + prop + ": " + result_String).c_str());
                                }
                            }
                        }
                    }
                }

                if (words.at(0) == "DELETE") {
                    for (int j = 0; j < Screen->FormCount; j++) {
                        TForm *form = Screen->Forms[j];
                        if (form->Name == words.at(1).c_str()) {
                            form->Close();
                        }
                        for (int i = 0; i < form->ControlCount; i++) {
                            TControl *control = form->Controls[i];
                            if (control->Name == words.at(1).c_str()) {
                                control->Free();
                            }
                        }
                    }
                }

                if (words.at(0) == "EXECUTE") {
                    for (int j = 0; j < Screen->FormCount; j++) {
                        TForm *form = Screen->Forms[j];
                        if (form->Name == words.at(1).c_str()) {
                            std::cout << "EXECUTE <<" << words.at(2) << ">>" << std::endl;

                            TRttiContext context;
                            TRttiType *rttiType = context.GetType(form->ClassType());
                            TRttiMethod *method = rttiType->GetMethod(words.at(2).c_str());

                            DynamicArray<TRttiParameter *> parameters = method->GetParameters();
                            TValue args[10];
                            if (parameters.Length) {
                                for (int y = parameters.Low; y <= parameters.High; y++) {
                                    String paramType = parameters[y]->ParamType->ToString();
                                    if (paramType == "UnicodeString") {
                                        args[y] = TValue::From<UnicodeString>(stringToString(words.at(y + 3)));
                                    } else if (paramType == "Integer") {
                                        args[y] = TValue::From<Integer>(StrToInt(stringToString(words.at(y + 3))));
                                    }
                                }
                                TValue value = method->Invoke(form, args, parameters.High);
                            } else {
                                TValue value = method->Invoke(form, NULL, -1);
                            }
                        }

                        for (int i = 0; i < form->ControlCount; i++) {
                            TControl *control = form->Controls[i];
                            if (control->Name == words.at(1).c_str()) {
                                std::cout << "EXECUTE <<" << words.at(2) << ">>" << std::endl;

                                TRttiContext context;
                                TRttiType *rttiType = context.GetType(control->ClassType());
                                TRttiMethod *method = rttiType->GetMethod(words.at(2).c_str());

                                DynamicArray<TRttiParameter *> parameters = method->GetParameters();
                                TValue args[10];
                                if (parameters.Length) {
                                    for (int y = parameters.Low; y <= parameters.High; y++) {
                                        String paramType = parameters[y]->ParamType->ToString();
                                        if (paramType == "UnicodeString") {
                                            args[y] = TValue::From<UnicodeString>(stringToString(words.at(y + 3)));
                                        } else if (paramType == "Integer") {
                                            args[y] = TValue::From<Integer>(StrToInt(stringToString(words.at(y + 3))));
                                        }
                                    }
                                    TValue value = method->Invoke(control, args, parameters.High);
                                } else {
                                    TValue value = method->Invoke(control, NULL, -1);
                                }
                            }
                        }
                    }
                }
            });

你是对的,典型的 Windows 程序的主循环不会 运行 直到有某种输入(通常是用户输入,但也有其他类型)。

我不熟悉 C++ Builder 框架。

如果您可以控制构成主循环的代码,则可以修改它以处理其他信息源,例如监视另一个线程向同步对象发出信号。

其他选项:

  • 让正在向队列中添加项目的线程 post 向主线程的 window 发送一条自定义消息(或只是一条线程消息),每当它执行其中一项常规操作时更新。

  • 在主线程上设置一个计时器。它会定期 "wake up" 主循环,就像用户输入一样。

It's as if the main application loop doesn't run when there is no user interaction.

实际上并没有。好吧,更准确地说,当根本没有待处理的 window 消息时。一旦主线程的消息队列清空,VCL 调用 Win32 WaitMessage() 函数,该函数阻塞调用线程,直到消息队列中出现新消息。即使是传统的非 VCL 消息循环也会在没有消息要处理时阻塞调用线程。

How can I force the form to execute queued functions immediately?

你不能强制它。

If it matters, the form and TThread are created by a .dll, which is called by another .dll, which itself is called by a console application.

这很重要,因为 TThread::Queue()TThread:::Synchronize() 在 DLL 中不能很好地工作。

TThread::Queue()TThread::Synchronize() 将他们的请求放入 RTL 内的内部队列,设置一个信号以指示队列有待处理的请求,然后 post 向 TApplication window 到 "wake up" 主线程(以防消息循环 "sleeping" 等待新消息到达)。该请求队列在主线程方便时尽早处理。

默认情况下,VCL 消息循环仅在以下情况下处理 TThread 队列:

  • 消息循环进入空闲状态,在处理完所有未决消息并且消息队列变空后。

  • TApplicationwindow收到"wake up"消息。

TThread 队列在 DLL 中,并且 DLL 不与主 EXE 共享同一个 RTL 实例时,EXE 中的主消息循环不知道 TThread 在 DLL 中排队,因此它无法在空闲时间处理挂起的请求。这只会留下 "wake up" 消息,DLL 将 post 发送到它自己的 TApplication 实例,而不是主 EXE 的 TApplication。主线程消息循环仍会将 window 消息分派给 DLL 的 TApplication window.

要解决这个问题,您必须:

  • 在DLL和主EXE中启用Runtime Packages,甚至将DLL改为Package,这样它们就可以共享RTL和VCL的公共实例。不过,这确实意味着您必须在您的应用程序中部署 RTL 和 VCL .bpl 文件。

  • 从您的 DLL 中导出调用 RTL 的 CheckSynchronize() 函数的函数,然后在您的 EXE 代码中定期调用该 DLL 函数,例如在 UI 计时器中,或者在TApplication.OnIdle事件等