`

delphi 四种创建线程的方式及对比

阅读更多

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.

1
2
分享到:
评论

相关推荐

    Delphi 创建线程监视目录是否改变.rar

    Delphi 目录监视,指定需要监视的目录,创建监视线程,然后可以监控目标文件夹内文件变动的情况,会弹出提示告诉用户文件夹内容已改变,这个用途比较广了,这个例子也是很基础的类型,适合Delphi初学者参考学习。...

    Delphi创建一个计数器线程.rar

    Delphi创建一个计数器线程,代码文件中,cpThread是这个线程单元文件名,你可以学习下线程的定义,重载Execute方法、计数器、设置线程终止许可、用GiveAnswer来显示线程运行结果等基础知识,比较适合Delphi新手朋友...

    Delphi6分布式开发

    统,从数据库的链接讲起,由浅入深地讲了MIDAS的概念,它的DCOM及CORBA的实现方式,最后深入剖析了它的结构;第五 篇讲了分布式Web技术,包括现在流行的 Web技术及 Internet Express的应用。本书的各个部分,都辅之...

    Delphi技术手册

    本书还用一章的篇幅讨论了Delphi中的并发编程,以及多线程应用程序的创建。 本书的主要内容是Delphi语言以字母顺序排列的完整参考。每项参考的内容都包括: 语法,使用标准编码惯例 说明参数列表,如果函数或...

    Delphi5开发人员指南

    11.1.2 在Delphi程序中使用多线程 304 11.1.3 关于线程的滥用 305 11.2 TThread对象 305 11.2.1 TThread基础 305 11.2.2 TThread实例 307 11.2.3 线程的终止 307 11.2.4 与VCL同步 308 11.2.5 一个演示程序 310 ...

    Delphi 5 经典教程

    4.2.5 项目选项及桌面设置文件 81 4.2.6 备份文件 81 4.2.7 包文件 82 4.3 项目管理提示 82 4.3.1 一个项目一个目录 82 4.3.2 共享代码的单元 82 4.3.3 多项目管理 84 4.4 Delphi 5项目的框架类 84 4.4.1 TForm类 84...

    DELPHI技巧集(集合各种开发源码)

    59 Link.htm Delphi3.0中连接数据库的三种方式 5K 60 Linkcom.htm Delphi中串行通信的实现 7K 61 Listmp3.htm 用Delphi 3.0编制MP3音乐点歌台 3K 62 Loadfunc.htm Delphi3.0函数调用模式 3K 63 Look_pm.htm ...

    Delphi7 编程 100 实例

    ToolBar工具栏控件的使用 动态建立主菜单选项 窗口界面的动态分隔...对比度 实现图像的灰度级处理效果 3种像素历遍方法的比较和实现 实现屏幕拷贝 实现图像漫游 4种幕布式图像显示技巧 盘旋法...

    Delphi编程100例

    3种像素历遍方法的比较和实现 实现屏幕拷贝 实现图像漫游 4种幕布式图像显示技巧 盘旋法实现9种滤镜效果 图形朦胧叠合显示技巧 给MDI主窗体增加背景 实现图像的淡入淡出显示 多媒体播放器 播放AVI文件 根据客户端IP...

    Delphi7编程100例

    3种像素历遍方法的比较和实现 实现屏幕拷贝 实现图像漫游 4种幕布式图像显示技巧 盘旋法实现9种滤镜效果 图形朦胧叠合显示技巧 给MDI主窗体增加背景 实现图像的淡入淡出显示 多媒体播放器 播放...

    《Delphi7编程100例》代码

    ToolBar工具栏控件的使用动态建立主菜单选项窗口界面的动态分隔条动态设置...对比度实现图像的灰度级处理效果3种像素历遍方法的比较和实现实现屏幕拷贝实现图像漫游4种幕布式图像显示技巧盘旋法实现9种滤镜效果图形朦胧...

    《Delphi高级辅助工具精解》PDF版

    简介: Delphi作为一个面向对象程序设计的系统构建的集成工具,已经拥有了比较稳定的第三方工具,借助于这些工具可以最大限度地提高相应的应用程序开发效率。本书针对这一特点, 重点对人秋IDE(集成开发环境)增强...

    Delphi开发范例宝典目录

    实例244 两种信息发送方式 310 实例245 使用线程添加数据 312 实例246 功能快捷键 314 第7章 注册表 317 7.1 操作注册表 318 实例247 怎样存取注册表信息 318 实例248 注册表保存注册信息 319 实例...

    delphi7编程百例

    ToolBar工具栏控件的使用 动态建立主菜单选项 窗口界面的...对比度 实现图像的灰度级处理效果 3种像素历遍方法的比较和实现 实现屏幕拷贝 实现图像漫游 4种幕布式图像显示技巧 ...

    delphi 开发经验技巧宝典源码

    0220 使用赋值方式保存数据 147 0221 当ADO循环删除数据时需要注意的问题 147 0222 把Excel中的数据保存到数据库中 147 0223 怎样弹出ConnectionString设置页 148 0224 利用ADO获取DELETE后所影响的记录数...

    delphi 开发经验技巧宝典源码06

    0220 使用赋值方式保存数据 147 0221 当ADO循环删除数据时需要注意的问题 147 0222 把Excel中的数据保存到数据库中 147 0223 怎样弹出ConnectionString设置页 148 0224 利用ADO获取DELETE后所影响的记录数...

    Direct Oracle Access v4.1.3 bcb6

    Direct Oracle Access 组件有两种版本,Direct Oracle Access Standard version 和 Direct Oracle Access Object version Object版允许你通过TOracleObject和TOracleReference 对象使用 Oracle8的对象扩展。 如果...

    window32 API大全 win32编程

    如果指定的窗口是由不同线程创建的,则系统切换到该线程并调用恰当的窗口程序。线程间的消息只有在线程执行消息检索代码时才被处理。发送线程被阻塞直到接收线程处理完消息为止。 Windows CE:Windows CE不支持...

Global site tag (gtag.js) - Google Analytics