我怎样才能强制我的 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
队列:
消息循环进入空闲状态,在处理完所有未决消息并且消息队列变空后。
TApplication
window收到"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
事件等
最终更新
解决方案是创建一个 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
队列:
消息循环进入空闲状态,在处理完所有未决消息并且消息队列变空后。
TApplication
window收到"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
事件等