Last time (in issue #9) I wrote in length about parallel execution and multithreading but I never wrote any code. It was all just talk, talk, talk. Sorry for that, but I felt I had to write a solid introduction before delving into murky waters of multithreading.
Today I intend to fix that. This article will focus more on code than words. We still won't write even a half-serious program, though. Instead of that, I'll show you how complex even a simple task of creating a new thread can be. It is not complex because of the lack of tools, oh no, quite contrary. It is complex because there are many tools that can be used, each with its own set of advantages and disadvantages.
The Delphi Way
In the first place I'll cover the most obvious approach – Delphi's own threading tools.
Creating a thread in Delphi is as simple as declaring a class that descends from the TThread class (which lives in the Classes unit), overriding its Execute method and instantiating an object of this class (in other words, calling TMyThread.Create). Sounds simple, but the devil is, as always, in the details.
TMyThread=class(TThread) protected procedureExecute;override; end; FThread1:=TMyThread1.Create;
Before we start, I should put out a word of warning. TThread has changed quite a lot since Delphi 2. I tried to use only most common functionality, which is still mostly unchanged in Delphi 2010, but it is entirely possible that the code would not work correctly in the oldest Delphi releases.. The code was tested with Delphi 2007 and if you find any problems with the code in some other Delphi, then let me know so I can fix it in one of future instalments of the Threading series.
The code archive for this article includes application TestTThread, which demonstrates two most obvious thread patterns. The first is a thread that does repeating work. Owner starts it, leaves it running for as long as it is needed (often this is as long as the application is running), then stops the thread. The second is a thread that does one unit of work, then notifies the owner of the results and stops.
An example of the first pattern would be a background polling thread. During its execution such thread monitors some resource (often by doing some test followed by a short sleep) and notifies the owner then the resource is updated. Often the thread would also do the actual handling of the resource. The second pattern covers many lengthy operations, that can be executed in the background, for example copying of large file or uploading a file to the web server.
Let's return to the sample code. In the first case, user starts the thread by clicking the „Start thread 1“ button, which executes the following code (slightly simplified; actual code in the project also does some logging so you can see in the GUI what's going on):
procedureTfrmTestTThread.btnStartThread1Click(Sender:TObject); begin FThread1:=TTestThread1.Create(false); btnStartThread1.Enabled:=false; btnStopThread1.Enabled:=true; end;
The code first creates an instance of the TTestThread1 class (which I'll present in a moment). The parameter false instructs the thread constructor that it can immediately start the new thread. The code then disables the Start button and enables the Stop button.
The code for stopping the thread is just a tad more complicated.
procedureTfrmTestTThread.btnStopThread1Click(Sender:TObject); begin FThread1.Terminate; FThread1.WaitFor; FreeAndNil(FThread1); btnStartThread1.Enabled:=true; btnStopThread1.Enabled:=false; end;
First the code calls thread's Terminate method which instructs the thread to terminate (and again we'll ignore the mechanism behind this for a moment). Then it waits on the thread to terminate and destroys the thread object.
Usually, you'll not be using this long version. It's equally well if you just destroy the thread object because the destructor (TThread.Destroy) will automatically execute Terminate and WaitFor for you.
Finally, let's take a look at the TTestThread1 code. As you may expect, the class itself descends from the TThread class and implements overridden Execute method.
type TTestThread1=class(TThread) strictprivate FMsg:string; protected procedureExecute;override; procedureLog; end; procedureTTestThread1.Execute; begin FMsg:=Format('Thread %d started',[ThreadID]); Synchronize(Log); whilenotTerminateddobegin // some real work could be done here Sleep(1000); FMsg:=Format('Thread %d working ...',[ThreadID]); Synchronize(Log); end; FMsg:=Format('Thread %d stopping ...',[ThreadID]); Synchronize(Log); end; procedureTTestThread1.Log; begin frmTestTThread.Log(FMsg); end;
In Execute, thread first signals the owner that is has commenced execution (first two lines of the method) and then enters the thread work cycle: check if owner has requested termination, do some real work, sleep for a short time. During the execution it will also report the current state to the owner.
Although I wanted to skip all dirty details today, I was only partially successful. I wanted threads in the demo code to send the execution state to the owner and that is, believe it or not, always a messy thing. The code above uses a Synchronize approach. This method executes another method (which is its parameter, Log in this case) to be executed in the context of the main VCL thread. In other words, when you call Synchronize, background thread (TTestThread1) will pause and wait for the parameter method (Log) to be executed in the main program. Then the execution of the background thread will resume. As the parameter method cannot have any parameters I had to put the log message into a class field called FMsg.
Let me emphasize two points here. Firstly, the Synchronize is the only way to safely execute VCL code from the background thread! VCL is not thread-safe and expects to be used only from the main thread! Don't call VCL (and that includes all GUI manipulation) directly from the background thread! If you do this, your code may seem to work but you'll introduce hard to find problems that will sometimes crash your program.
Secondly, I disagree with Synchronize deeply. Its use should be severely limited. Heck, it should not be documented at all. It shouldn't even exist! There are better ways to decouple background threads from the GUI and one of them I'll use later in this article.
Let's move to the pattern no. 2. The code to start the thread is similar to the one we've already seen.
procedureTfrmTestTThread.btnStartThread2Click(Sender:TObject); begin FThread2:=TTestThread2.Create(true); FThread2.FreeOnTerminate:=true; FThread2.OnTerminate:=ReportThreadTerminated; FThread2.Resume; btnStartThread2.Enabled:=false; end;
The code again creates the thread object, but this time true is passed for the CreateSuspended parameter and the thread will be created in suspended state. In other words, the thread object will be created, but associated operating system thread will be paused.
The code then instructs the FThread2 to automatically destroy itself when the Execute method completes its work and sets the OnTerminate handler, which will be called when the thread will be terminated. At the end it calls Resume to start the thread.
Another word of warning – this is the only legitimate way of using Resume. Don't ever call Suspend to pause a thread and Resume to resume it! You'll only cause havoc. Actually, you're not supposed to use Resume in Delphi 2010 anymore. Its use has become deprecated and it was replaced with the Start method, which is a Resume with all the evil parts removed; only the code that does good was left.
As this is a one-shot operation, there is no code to stop the thread. Instead, the thread's Execute sleeps a little to indicate some very hard work and then exits.
procedureTTestThread2.Execute; begin FMsg:=Format('Thread %d started',[ThreadID]); Synchronize(Log); FMsg:=Format('Thread %d working ...',[ThreadID]); Synchronize(Log); // some real work could be done here Sleep(5000); FMsg:=Format('Thread %d stopping ...',[ThreadID]); Synchronize(Log); end;
When the Execute completes its work, TThread infrastructure calls the OnTerminate event handler where the main GUI thread can update its status.
procedureTfrmTestTThread.ReportThreadTerminated(Sender:TObject); begin Log(Format('Thread %d terminated',[TThread(Sender).ThreadID])); btnStartThread2.Enabled:=true; end;
Until the next time, that is all I have to say about TThread and threading in VLC. If you want to know more, read theexcellent tutorial by Martin Harvey.
The Windows Way
Surely, the TThread class is not complicated to use but the eternal hacker in all of us wants to know – how? How is TThread implemented? How do threads function at the lowest level. It turns out that the Windows' threading API is not overly complicated and that it can be easily used from Delphi applications.
It's easy to find the appropriate API, just look at the TThread.Create. Besides other things it includes the following code (Delphi 2007):
FHandle:=BeginThread(nil,0,@ThreadProc,Pointer(Self),CREATE_SUSPENDED,FThreadID); ifFHandle=0then raiseEThread.CreateResFmt(@SThreadCreateError,[SysErrorMessage(GetLastError)]);
If we follow this a level deeper, into BeginThread, we can see that it calls CreateThread. A short search points out that this is a Win32 kernel function, and a look into the MSDN confirms that it is indeed a true and proper way to start a new thread.
Let's take a look at the declaration and step through the parameters.
functionCreateThread(lpThreadAttributes:Pointer; dwStackSize:DWORD;lpStartAddress:TFNThreadStartRoutine; lpParameter:Pointer;dwCreationFlags:DWORD;varlpThreadId:DWORD):THandle;stdcall;
-
lpThreadAttributes is a pointer to security attributes structure. You'll probably never have to use it so just use nil.
-
dwStackSize is the initial stack size, in bytes. If you set it to zero, default stack size (1 MB) will be used. This is what Delphi's BeginThread does.
-
lpStartAddress is the address of the method that will start its life in the new thread.
-
lpParameter is arbitrary data that will be passed to the thread. We can use it to pass configuration parameters to the thread code.
-
dwCreationFlags contains flags that govern thread creation and behaviour. Of particular importance here is the CREATE_SUSPENDED flag which will the thread to be created in suspended (not running) state.
-
lpThreadID is output (var) parameter that will receive the thread's ID. Each thread in the system has unique identifier associated with it and we can use this identifier in various API functions.
The result of the CreateThread call is a handle to the thread. This is a value that has no external value (does not give you any knowledge by itself) but can again be passed to various API functions. If the CreateThread fails, the result will be 0.
The testWinAPI program demonstrates the use of Win32 API for thread creation. Again, it contains two test cases – one running a perpetual thread and another a one-shot thread.
In first case, the thread creation code is quite simple – just a call to CreateThread and a safety check. In the lwParameter field it is passing an address of a boolean field which will be set to True to stop the thread.
procedureTfrmTestWinAPI.btnStartThread1Click(Sender:TObject); begin FStopThread1:=false; FThread1:=CreateThread(nil,0,@ThreadProc1,@FStopThread1,0,FThread1ID); ifFThread1=0then RaiseLastOSError;// RaiseLastWin32Error in older Delphis btnStartThread1.Enabled:=false; btnStopThread1.Enabled:=true; end;
Thread method is not terribly complicated either. The most important thing is that it is declared as a function of one pointer parameter (in my case I specifically declared this parameter as a pointer to Boolean but any pointer type would do) returning a DWORD (or cardinal, if you want) and with the stdcall flag attached.
functionThreadProc1(stopFlag:PBoolean):DWORD;stdcall; begin PostMessage(frmTestWinAPI.Handle,WM_THREAD_INFO, MSG_THREAD_START,GetCurrentThreadID); whilenotstopFlag^dobegin // some real work could be done here Sleep(1000); PostMessage(frmTestWinAPI.Handle,WM_THREAD_INFO, MSG_THREAD_WORKING,GetCurrentThreadID); end; PostMessage(frmTestWinAPI.Handle,WM_THREAD_INFO, MSG_THREAD_STOP,GetCurrentThreadID); Result:=0; end;
There are two main differences between this method and the TThread version. Firstly, this thread is stopped by setting a FStopThread1 flag to True. The thread received a pointer to this variable and can constantly check its contents. When the variable is True, the thread procedure exits and that stops the thread.
Secondly, we cannot use Synchronize as it is a method of the TThread class. Instead of that the thread is sending messages to the main form. In my opinion, this is far superior option as it doesn't block the thread. Besides that, it draws a line between the thread and main GUI responsibilities.
The main program declares message method WMThreadInfo to handle these messages. If this is the first time you encountered message-handling methods, just take a look at the code. It is very simple.
To stop the thread, the code first sets the stop flag to True and then waits on the thread handle to become signalled. Big words, I know, but they represent a very simple operation – a call to WaitForSingleObject API. As the second parameter to this call is INFINITE, it will wait until the thread terminates itself by exiting out of the ThreadProc1 function. Then the code calls CloseHandle on the thread handle and with that releases all internal resources held by the Windows. If we would skip this step, a small resource leak would be introduced at this point.
procedureTfrmTestWinAPI.btnStopThread1Click(Sender:TObject); begin FStopThread1:=true; WaitForSingleObject(FThread1,INFINITE); CloseHandle(FThread1); btnStartThread1.Enabled:=true; btnStopThread1.Enabled:=false; end;
The creation code for the second test is similar, with one change – it demonstrates the use of CREATE_SUSPENDED flag.
procedureTfrmTestWinAPI.btnStartThread2Click(Sender:TObject); begin FMainHandle:=Handle; FThread2:=CreateThread(nil,0,@ThreadProc2,@FMainHandle, CREATE_SUSPENDED,FThread2ID); ifFThread2=0then RaiseLastOSError;// RaiseLastWin32Error in older Delphis ResumeThread(FThread2); btnStartThread2.Enabled:=false; end;
As the thread is created in the suspended state, the code has to call ResumeThread API to start its execution. The termination code for the second example is very similar to the first one – just look it up in the code.
One more thing has to be said about the Win32 threads – why to use them at all? Why go down to the Win32 API if the Delphi's TThread is so more comfortable to use? I can think of two possible answers.
Firstly, you would use Win32 threads if working on a multi-language application (built using DLLs compiled with different compilers) where threads objects are passed from one part to another. A rare occasion, I'm sure, but it can happen.
Secondly, you may be creating lots and lots of threads. Although that is not really something that should be recommended, you may have a legitimate reason to do it. As the Delphi's TThread uses 1 MB of stack space for each thread, you can never create more than (approximately) 2000 threads. Using CreateThread you can provide threads with smaller stack and thusly create more threads – or create a program that successfully runs in a memory-tight environment. If you're going that way, be sure to read great blog post by Raymond Chen.
The Lightweight Way
From complicated to simple … There are many people on the Internet who thought that Delphi's approach to threading is overly complicated (from the programmer's viewpoint, that it). Of those, there are some that decided to do something about it. Some wrote components that wrap around TThread, some wrote threading libraries, but there's also a guy that tries to make threading as simple as possible. His name is Andreas Hausladen (aka Andy) and his library (actually it's just one unit) is called AsyncCalls and can be found at http://andy.jgknet.de/blog/?page%5Fid=100.
AsyncCalls is very generic as it supports all Delphis from version 5 onwards. It is licensed under the Mozilla Public License 1.1, which doesn't limit the use of AsyncCalls inside commercial applications. The only downside is that the documentation is scant and it may not be entirely trivial to start using AsyncCalls for your own threaded code. Still, there are some examples on the page linked above. This article should also help you started.
To create and start a thread (there is no support for creating threads in suspended state), just call AsyncCall method and pass it the name of the main thread method. The code below was taken from the demo testAsyncCalls:
procedureTfrmTestAsyncCalls.btnStartThread1Click(Sender:TObject); begin FStopThread1:=false; FThreadCall1:=AsyncCall(ThreadProc1,integer(@FStopThread1)); Log('Started thread');// AsyncCalls threads have no IDs btnStartThread1.Enabled:=false; btnStopThread1.Enabled:=true; end;
As you can see, we can send a parameter to the ThreadProc1. AsyncCalls defines many overloads for the AsyncCall method but none of them supports pointer type so I had to cheat and wrap my pointer into an integer. (Which is a practice that will cause ugly crashes after we get 64-bit Delphi compiler but … c'est la vie.) We can also use methods with variable number of parameters (array of const – as in the built-in Format function).
Thread code, on the other hand, is not as simple as we would expect it. Then main problem here is that I choose to use Andy's asynchronous version of TThread.Synchronize. (Asynchronous means that it just schedules the code to be executed in the main thread in the near future and continues immediately.) The problem here is that this LocalAsyncVclCall supports only local procedures. In other words, even if we have a form method that implements exactly the functionality we need, we cannot call it directly. The only way is to call a local procedure which then calls the desired method of the owning class. In the code below, LocalAsyncVclCall schedules (local) ReportProgress to be executed. ReportProgress then forwards the parameter to form's ReportProgress which shows the message on the screen.
procedureTfrmTestAsyncCalls.ReportProgress(varparam:integer); begin // show progress on screen end; procedureTfrmTestAsyncCalls.ThreadProc1(stopFlagInt:integer); var stopFlag:PBoolean; procedureReportProgress(param:integer); begin frmTestAsyncCalls.ReportProgress(param); end; begin stopFlag:=PBoolean(stopFlagInt); LocalAsyncVclCall(@ReportProgress,MSG_THREAD_START);//async whilenotstopFlag^dobegin // some real work could be done here Sleep(1000); LocalAsyncVclCall(@ReportProgress,MSG_THREAD_WORKING); end; LocalAsyncVclCall(@ReportProgress,MSG_THREAD_STOP); end;
We could also use message passing technique, just like in the Windows example above, but I wanted to show some of the AsyncCalls capabilities.
In the second test (one-shot thread) I've used a similar approach to signalize thread completion. This time the blocking version is used. This call works exactly as Delphi's Synchronize except that it can again call only local procedures.
procedureTfrmTestAsyncCalls.ThreadProc2(handle:integer); procedureFinished; begin frmTestAsyncCalls.Finished; end; begin // some real work could be done here Sleep(5000); LocalVclCall(@Finished);// blocking end;
In the release 2.9 of AsyncCalls Andy added support for new language constructs in Delphi 2009 – generics and anonymous methods. The latter allows us to simplify the code greatly (demo testAsyncCalls2009).
procedureTfrmTestAsyncCalls.ThreadProc1(stopFlagInt:integer); var stopFlag:PBoolean; begin stopFlag:=PBoolean(stopFlagInt); TAsyncCalls.VCLInvoke(procedurebegin ReportProgress(MSG_THREAD_START);end); whilenotstopFlag^dobegin // some real work could be done here Sleep(1000); TAsyncCalls.VCLInvoke(procedurebegin ReportProgress(MSG_THREAD_WORKING);end); end; TAsyncCalls.VCLInvoke(procedurebegin ReportProgress(MSG_THREAD_STOP);end); end;
As you can see in the code above, the new VCLInvoke global method allows execution of anonymous procedure which then in turn calls the logging method.
The same technique can be used to write the thread code. Instead of writing a separate method that executes in a background thread, you can put all this code into an anonymous procedure and pass it to the Invoke.
procedureTfrmTestAsyncCalls.btnStartThread2Click(Sender:TObject); begin TAsyncCalls.Invoke(procedurebegin TAsyncCalls.VCLInvoke(procedurebeginReportProgress(MSG_THREAD_START);end); TAsyncCalls.VCLInvoke(procedurebeginReportProgress(MSG_THREAD_WORKING);end); // some real work could be done here Sleep(5000); TAsyncCalls.VCLInvoke(procedurebeginReportProgress(MSG_THREAD_STOP);end); TAsyncCalls.VCLSync(procedurebeginFinished;end); end); btnStartThread2.Enabled:=false; end;
AsyncCalls is a great solution to many threading problems. As it is actively developed, I can only recommend it.
The No-Fuss Way
I could say that I left the best for the end but that would be bragging. Namely, the last solution I'll describe is of my own making. Yep, it's all mine, my precioussssssss … (Please, don't run away! I'll stop emotional outbursts now. It's a promise.)
OmniThreadLibrary (OTL for short) approaches the threading problem from a different perspective. The main design guideline was: “Enable the programmer to work with threads in as fluent way as possible.” The code should ideally relieve you from all burdens commonly associated with multithreading. I'm the first to admit that the goal was not reached yet, but I'm slowly getting there.
The bad thing is that OTL has to be learned. It is not a simple unit that can be grasped in an afternoon, but a large framework with lots of functions. On the good side, there are many examples (http://otl.17slon.com/tutorials.htm; you'll also find download links there). On the bad side, the documentation is scant. Sorry for that, but you know how it goes – it is always more satisfying to program than to write documentation. Another downside is that it supports only Delphi 2007 and newer. OTL is released under the BSD license which doesn't limit you from using it in commercial applications in any way.
OTL is a message based framework and uses custom, extremely fast messaging system. You can still use any blocking stuff and write TThread-like multithreading code, if you like. Synchronize is, however, not supported. Why? Because I think it's a bad idea, that's why.
In OTL you don't create threads but tasks. A task can be executed in a new thread (as I did in the demo program testOTL) or in a thread pool. As the latter is not really a beginner level topic I won't cover it today.
procedureTfrmTestOTL.btnStartThread1Click(Sender:TObject); begin FThread1:=CreateTask(ThreadProc1).OnMessage(ReportProgress).Run; Assert(assigned(FThread1)); btnStartThread1.Enabled:=false; btnStopThread1.Enabled:=true; end;
A task is created using CreateTask, which takes as a parameter a global procedure, a method, an instance of TOmniWorker class (or, usually, a descendant of that class) or an anonymous procedure (in Delphi 2009 and newer). In this example, a method from class TfrmTestOTL is used. CreateTask returns an interface, which can be used to control the task. As (almost) all methods of this interface return Self, you can chain method calls in a fluent way. The code fragment above uses this approach to declare a message handler (a method that will be called when the task sends a message to the owner) and then starts the task. In OTL, a task is always created in suspended state and you have to call Run to activate it.
The thread procedure uses the fact that OTL infrastructure automatically creates a messaging channel between the background thread and its owner and just sends notifications over this channel. Messages are processed in the main thread by the ReportProgress method.
procedureTfrmTestOTL.ThreadProc1(consttask:IOmniTask); begin task.Comm.Send(MSG_THREAD_START); whilenottask.Terminateddobegin // some real work could be done here Sleep(1000); task.Comm.Send(MSG_THREAD_WORKING); end; task.Comm.Send(MSG_THREAD_STOP); end; procedureTfrmTestOTL.ReportProgress(consttask:IOmniTaskControl;constmsg: TOmniMessage); begin // log the message ... end;
A similar approach is used in the one-shot thread except that it also declares OnTerminate handler which is called just before the background task object is destroyed.
procedureTfrmTestOTL.btnStartThread2Click(Sender:TObject); begin FThread2:=CreateTask(ThreadProc2) .OnMessage(ReportProgress) .OnTerminated(Thread2Terminated); Assert(assigned(FThread2)); FThread2.Run;// just for demo btnStartThread2.Enabled:=false; end;
Similar to AsyncCalls, OTL supports anonymous procedures at various places. You can use one as a main thread procedure, or in the OnMessage and OnTerminated handlers. For example, in demo testOTL2009 an anonymous method is used to report task termination.
procedureTfrmTestOTL.btnStartThread2Click(Sender:TObject); begin FThread2:=CreateTask(ThreadProc2) .OnMessage(ReportProgress) .OnTerminated(procedure(consttask:IOmniTaskControl)begin Log(Format('Thread %d terminated',[task.UniqueID])); FThread2:=nil; btnStartThread2.Enabled:=true; end); Assert(assigned(FThread2)); FThread2.Run;// delayed, just for demo btnStartThread2.Enabled:=false; end;
The OTL is being actively developed. For example, the next release (which will probably be released before this article is printed) will support higher-level control structures such as parallel for statement. Follow my blog if you want to stay informed.
相关推荐
6. **线程池**:线程池是一种优化线程管理的方式,它可以预先创建一组线程,当有任务需要执行时,线程池会从空闲线程中选择一个执行任务,完成后归还线程池。这种方式减少了频繁创建和销毁线程的开销。 7. **异步...
本项目“Delphi多线程查找文件工具”是一个采用Delphi编程语言实现的实用工具,适用于Delphi XE6及更高版本。这个工具的设计目的是利用多线程技术在Windows操作系统中快速地搜索指定磁盘中的文件,并记录下搜索过程...
Delphi 目录监视,指定需要监视的目录,创建监视线程,然后可以监控目标文件夹内文件变动的情况,会弹出提示告诉用户文件夹内容已改变,这个用途比较广了,这个例子也是很基础的类型,适合Delphi初学者参考学习。...
- **创建线程**:Delphi 提供了 TThread 类,它是 VCL (Visual Component Library) 的一部分,用于创建和管理线程。开发者可以通过继承 TThread 类并重写其 Execute 方法来实现自定义线程逻辑。 2. **线程同步与...
Delphi创建一个计数器线程,代码文件中,cpThread是这个线程单元文件名,你可以学习下线程的定义,重载Execute方法、计数器、设置线程终止许可、用GiveAnswer来显示线程运行结果等基础知识,比较适合Delphi新手朋友...
本文将探讨三种常见的定时器方法:系统定时器、SetEvent函数以及多媒体定时器,并进行详细比较。此外,我们还将讨论如何将多媒体定时器封装成类以供重复使用。 首先,让我们了解每种定时器的基本概念: 1. **系统...
【Delphi多线程端口扫描工具】CScanPort是一款基于Delphi编程语言开发的高效端口扫描工具,主要用于网络管理员、安全研究人员以及对网络拓扑感兴趣的用户进行网络端口状态检测。它利用多线程技术,提高了扫描速度,...
在Delphi编程环境中,RemObjects是一种强大的对象序列化和远程通信框架,它允许开发者创建分布式应用程序,使得在不同系统间可以高效地交换数据和调用方法。本文将深入探讨如何在Delphi中利用RemObjects来实现网络...
Midas是Delphi提供的一种用于构建客户端/服务器应用的技术,它基于TClientDataset和TDataSetProvider组件。Midas允许数据在客户端和服务器之间进行透明传输,支持远程数据服务。通过这种方式,开发者可以将数据库...
标题中的“Delphi数据比较器,比较两个Excel文件异同”指的是使用Delphi编程语言开发的一个工具,该工具能够分析并对比两个Excel文件之间的数据差异。这个工具可能对数据库管理员、数据分析人员或软件开发者非常有用...
在IT行业中,Delphi是一种基于Pascal语言的集成开发环境(IDE),用于创建Windows桌面应用程序。这个主题聚焦于如何在Delphi中实现简单的PING操作,这通常涉及到网络通信和系统编程。PING是网络诊断工具,它使用ICMP...
统,从数据库的链接讲起,由浅入深地讲了MIDAS的概念,它的DCOM及CORBA的实现方式,最后深入剖析了它的结构;第五 篇讲了分布式Web技术,包括现在流行的 Web技术及 Internet Express的应用。本书的各个部分,都辅之...
Delphi是一种基于Object Pascal编程语言的集成开发环境(IDE),由Embarcadero Technologies公司维护。它以其高效、跨平台的特性,在开发桌面应用程序方面备受青睐。本篇将深入探讨Delphi如何被用来编写文件浏览器。...
介绍了如何使用Delphi4中的Thread类来创建线程。 - **5.3.2 向线程函数传递一个参数** 解释了如何将参数传递给线程函数。 - **5.3.3 临界区:让多个线程同时工作** 讨论了如何使用临界区来保护共享资源,避免竞...
Delphi是Borland公司(现Embarcadero Technologies)推出的一种集成开发环境(IDE),它以Pascal语言为基础,结合了Visual Component Library (VCL)组件库,提供了一种图形化、面向对象的编程方式。Delphi的编译器...
在 Windows 操作系统中,句柄是操作系统用来标识和访问对象的一种方式,如窗口、图像、文件、线程、进程等。了解和管理句柄对于调试和优化 Delphi 应用程序至关重要。 句柄观察器的主要功能包括: 1. **句柄列表**...
它提供了一种高效、直观的方式来编写Windows应用程序。在Delphi中,我们可以利用VCL(Visual Component Library)框架来构建用户界面和应用程序逻辑。 要实现图片预览功能,我们需要以下几个关键组件: 1. **...
4. 多线程:Delphi内置的TThread类可以帮助创建并发爬虫,提高爬行速度。 学习这份源码,开发者不仅可以了解Delphi的基本编程,还能掌握网络爬虫的基本架构和设计思路,包括如何发起HTTP请求、处理响应、解析HTML、...
Delphi是一种强大的对象 Pascal 编程语言,它提供了丰富的图形库和工具,使得开发者能够创建高效、高性能的图像处理应用程序。本实例集主要关注如何利用Delphi进行图像处理,下面我们将深入探讨相关知识点。 1. **...