[推荐]delphi基础学堂-详细讲解delphi
荐 ★★★★★
delphi基础学堂-详细讲解delphi
第七讲 线程和DLL
一、基础补充
1 常用控件的使用提示
◇label中的多行
label1.caption := 'abcd' + #10#13 + '1234567';
◇label的右对齐输出
label1.Alignment := taRightJustify; label1.autosize := false;
◇memo中设置滚动条
memo1.scrollbars := ssBoth;
◇radiogroup的用法
不能把radio控件放在radioGroup里。radioGroup本身就是“全能”的。
with radioGroup.items do begin
add("yellow"); add("blue");add("green");
add("white");
end; radioGroup.Columns := 2;
radioGroup.itemIndex表示当前哪个按钮被选中。
◇TStringGrid的用法
mouseToCell(x,y,aCol,aRow);用来把一个x,y坐标转换成单元格的行和列号。
◇主菜单和弹出菜单
两种菜单都可以在设计阶段进行定制。caption中的短横线表示分割线。鼠标右键|new submenu 用来创建自菜单。使用弹出式菜单时,popup(x,y)方法需要的参数是关于屏幕的坐标,而我们一般取得的都是关于客户区的坐标,需要一个转换,使用 clientToScreen可也。
◇使用TAction
我们经常遇到同一个动作有多个操作途径的情况。比如使用菜单,快捷按钮,快捷键都是对应了同一个动作。对这个动作可能有许多种状态,比如:禁止,隐藏等。我们希望只在一处写代码,就会使所有使用的地方都跟随这个特性。这实际上是提醒我们使用一种文档-视图的结构。
delphi为我们提供了TAction来实现把多个对象连接在一个动作上。
使用多页
控件很多的时候,无法在一个form上放置,我们可以使用多页控件,把众多的控件放在不同的页上。
2 多窗体的关系
主窗体
第一个创建的窗体是主窗体。主窗体的关闭导致整个程序的关闭。每个窗体在创建的时候,可以指定一个owner,它的任务是负责在自己释放的时候,把这个窗体也一起释放。
菜单 project|options|forms页|main form选项可以指定哪个窗体是主窗体。
动态创建与预创建
在缺省的情况下,我们所选择的多个窗体都是预先创建好的。我们还可以在使用的时候,才去创建窗体。这需要在project|options|forms中把auto create form移到右边。自己创建窗体的语法是: aForm := TForm4.create(self); self,即释放管理者,也可以直接填application。
◇即便指定了owner,我们仍然可以主动地释放窗体: aForm.free;
关闭和隐藏
一般情况下,我们关闭一个窗体的时候,实际的动作是隐藏,窗体并没有真的被从内存中清除。如果我们希望把窗体释放掉,就要显式地调用 aFrom.free;基于这个原因,如果我们把窗体的属性visible设置为false,或调用窗体的方法hide 可以获得同样的效果。
典型的登陆窗口
登陆窗口一般是先要求输入密码,正确才进入主应用。很多人把登陆窗口做成主窗口,这样很不舒服,因为登陆窗口无论从生存期,作用上都很难担当主窗口的“重任”。比较通顺的做法是把主窗口隐藏起来,弹出登陆窗,正确,再把登陆窗释放,主窗显示出来。
看似简单,但我们直接隐藏主窗体是办不到的。这需要在project中填写代码:
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TForm2, Form2);
form1.ShowPasswordWindow; // 添加这一行代码,弹出密码窗口。
Application.Run;
... form1.showPasswordWindow中的代码:
hide; if form2.showModal = password_ok then show else application.terminate;
二、多进程和多线程
进程从用户的观点看是独立的运行实体,拥有独立的内存空间,处理机资源等。不同的进程之间不能直接访问对方的内存。从操作系统的角度看,进程是轮流执行的独立任务。在切换任务时,要保存与该任务有关的资源信息。
从用户的角度看,线程与进程很类似。区别只是线程之间共享同一虚拟内存空间。在操作系统看来,线程的切换不需要保存内存的信息,线程就是轻型的进程。
每一个进程都拥有至少一个线程,这个线程就是主线程。在delphi中,正是主线程在处理日常的事物。在windows操作系统中,在缺省的情况下,主线程的任务就是:循环往复地执行取消息和执行消息的动作。这实际上就是Application.Run的核心内容。在同一时刻,一个程序不可能既去取消息又去执行消息。因而在执行复杂的耗时的操作的时候,窗口会暂时失去反映,给用户一种好象“死机”的感觉。
三、使用delphi的线程对象
通过继承TThread可以很方便地生成自己的线程类,从而创建自己的线程对象。
delphi的线程类是对windows API中线程服务的封装。缺省的线程类创建对象的时候,需要一个参数,就是创建后是否挂起。挂起的意思是创建后并不马上运行,而要等待调用resume才开始运行。对于正在运行的对象也可以指定它挂起,调用suspend即可。如果调用suspend两次,则需要两次调用resume才能解除挂起状态。
可以使用挂起的这个特性来让一个线程等待其他多个线程执行完毕才开始工作。
delphi的控件并非线程
安全控件。也就是说,多个线程在操作一个控件时候,无法保证控件数据的逻辑完整性。一般说来,在主线程中操作控件。如果其他线程也需要操作,则需要一种同步的机制,就是调用 synchronize()方法,把自己操作包含在其中。synchronize的实质是把对控件的访问自动进行串行化。
【例】构造线程,完成自动向主窗体控件中连续写入数据的操作。为了看到效果,使用了延时操作。可以观察到,在写操作的过程中,主窗体仍然可以被拖动,改变大小等等。
◇创建线程对象可以使用delphi提供的向导来完成更方便。new|other ... Thread Object
◇synchronize中的过程不能有参数,因而需要通过线程的私有变量来传递信息。
四、线程的排斥和同步
排斥和同步是多个线程间的常见关系。排斥可以通过TcriticalSection来控制,它相当于临界区对象。在同一时间,只能有一个线程获得该对象。当申请这个对象时,如果对象已经被占用,则申请的线程会进入挂起状态;当释放这个对象时,如果有其他线程是挂起的,则第一个挂起的线程会被唤醒。
【例】TcriticalSection.create; a.Acquire; … a.release;
TcriticalSection实现了线程间的完全排斥。但有些情况下,我们并不希望所有的线程都是排斥的关系,比如最常见的读写相同的数据区,则读的线程间就不排斥。delphi为我们提供了处理这类问题的专门的类:TmultiReadExclusiveWriteSyncronizer。与TCriticalSection稍有区别的是,这个类的方法是:beginRead,endRead;beginWrite,endWrite。
最简单的同步是等待某一个进程的结束。A.waitfor;就可以了。
TEvent类可以构造一个信号灯对象。
T := TEvent.create(
安全描述,手动复位,创建后有信号,对象名字);
安全描述可以填nil,手动复位如果为true,则线程获得信号灯的时候,信号等并不熄灭,要等待一个显式的reset调用才复位。创建后有信号为true,则创建后,该信号灯是打开的。对象的名字只在进程间通信有用,如果是线程,则可以为空串。
五、windows DLL原理
进程间的内存是独立的。若多个进程用了相同的代码,怎样节省空间呢?DLL是为了共享代码而设计的。当第一次被调用时,DLL被装入物理内存,并映射到逻辑内存中。当第二次被调用时,DLL不会被再次装入,只是再次映射到新的逻辑内存。当最后一个使用DLL的进程退出运行,则DLL被自动从物理内存卸载。
delphi提供了一个wizard来帮助我们生成DLL类型应用的基本框架。
【例】用Wizard生成一个DLL工程。其中提供一个加法函数,供调用。
再生成一个调用的例子。声明外部的DLL函数:
function MyAdd(x,y: integer):integer; external 'c:\gyhang\a.dll';
◇注意exports 和export的区别。
exports用来因出一个或多个函数。在DLL中,我们可能定义了许多函数,但并不是每一个函数都要引出的。有些函数仅仅是被其他的引出函数来调用的。如果DLL中的函数很多,一般不放在一个单元中,如果某个需要引出的函数不在主单元中定义(以library开头的单元),必须在该函数的声明的后边加export进行修饰。
◇注意external的使用。在使用DLL的时候,必须用external指出DLL所在的位置。在delphi中,很多Windows DLL都被预先进行了定义,因此,我们使用的时候可以直接调用,不必再去声明位置信息。
◇注意stdcall的含义。如果DLL只是在自己的程序之间调用,不会有什么问题,但当一个DLL函数在跨语言调用的时候,会存在一个“调用约定”问题,即:以怎样的顺序把参数压栈?由谁来恢复栈指针?使不使用寄存器等等。stdcall修饰的函数使用的是与WinAPI相同的处理方式,因而被广泛地采用。
◇注意使用c语言串指针的使用。在调用WinAPI函数的时候,经常遇到参数类型为c格式的串指针。delphi的串格式与c语言不同,所以必须进行转换。
strPCopy用来把pascal串拷贝到一个c格式存储的字符串数组中
strPas从一个c格式串返回等价的pascal串。
六、使用DLL
使用DLL之前必须明确第描述函数的定义,以及存在哪个DLL中。
DLL与我们的应用程序公用栈空间,因此我们要充分估计栈的使用,使不至于
溢出。
在DLL的使用中,最关键是传递的参数必须是在内存中具有相同表示。比如整数,布尔型。DLL的调用错误经常是由于双方的数据表示不一致造成的。
文章录入:cainiaowang 责任编辑:cainiaowang