首页 > Linux操作系统 > Linux操作系统 > Delphi5学习笔记之三
126. 在使用Printer.Abort();终止打印后,不能再调用Printer.Canvas或Printer.EndDoc来对打印机进行打印操作。
127.使用Printer对象打印的详细步骤:
一、确认要打印的对象。
二、确认打印机画布的测量单位。
三、确认每个要插入到目标上的对象的测量单位。
四、确定一个在整个打印中使用的公共测量单位,通常是打印机的测量单位,即像素。
五、把其它各种对象的测量单位转换成公共测量单位。
六、用公共测量单位计算出打印对象的尺寸。在Pascal里,要打印的尺寸由TPoint结构来表示。
七、根据步骤六得到打印对象的尺寸及打印机画布的坐标系统来计算将要输出到画布的各个对象的位置。在Pascal中,使用TRect来表示对象的位置。
八、利用前面几步得到的数据写出打印函数。
[@more@]128.获取打印机信息,例如打印机是否可以分段打印(分段打印:它是一种可以在一定内存限制下提高打印速度和降低硬盘空间的技术)。可以调用API函数来获得该信息。
129.通过调用API函数GetDeviceCaps()可以获得诸如打印机、绘图仪、显示器等设备的信息。还有一点需要清楚,在调用一个打印机不支持的功能时,打印机仍然可以正常工作。例如调用GetDeviceCaps得知打印机不支持BitBlt函数或TrueType字体,但是可以调用这些函数,GDI会模拟这些函数。
130.判断打印机是否支持双面打印方法:
DeviceCapablitiesA(Device,Port,DC_DVPLEX,nil,nil);//该函数在Winspool.pas中定义
注:函数DeviceCapablities专门用于打印机。
131.线程:线程是一种系统对象,它表示在进程中的一条执行路径,一个Win32应用程序至少要有一个线程,它被称为主线程或默认线程。使用线程我认为主要的目的就是利用线程使不同的代码可以同时执行(这里所讲的同时执行实际上只有在多个CPU时才能实现,所谓的同时只是操作系统给人的一种感觉,因为系统把CPU的时间分成了很多时间片,而每一个时间片又非常短,这样在执行每一个时间片时,人是感觉不到系统是在多个线程间来回切换分配CPU给这些线程的)。
132.说一下操作系统,Win32是一种“抢占式的操作系统”,而Win3.1是“协作式多任务环境”。Win32的抢占式操作系统是指:所有的线程都由操作系统负责在什么时候执行。这也就是说,就算某个线程进入了死锁状态,操作系统也会让其它线程继续执行。而在Win3.1中,所谓的“协作式多任务环境”是指什么呢?它是指:所有的应用程序必须能够确保在执行完后,把控制权交给操作系统。这也就是说,如果在Win3.1中如果有一个应用程序进入了死锁状态,那么整个操作系统将无法再将CPU分配给其它进程。16位的Windows依赖于应用程序的运行情况,并且不允许应用程序进入死循环、无穷递归、以及任何封闭状态。
133.Delphi的VCL是非线程安全的,也就是说在同一时间VCL只能有一个线程访问。有少数是线程安全的,如:TCanvas类是线程安全的。(VCL的属性流机制是线程安全的)
线程安全:我认为就是一个东西可以被多线程安全的读写。
134.在Delphi中使用多线程的缺点就是不易于调试。
135.Delphi把几乎所有关于线程的API函数都封装在TThread类中。不过关于线程同步的一些API没有全部被封装。注TThread中Classes.pas单元中定义。TThread类是抽象类,它的方法Execute ()是抽象的,在TThread的子类中必须实现方法Execute ()。注意在创建线程子类的对象时,如果像下面这样,则Execute方法会在Create时执行,
SomeThread := TSomeThread.Create(False);//传递False给Create那么在创建的同时会执行Execute方法。
虽然说传递False给线程的Create会使Execute方法自动执行,但是,如果你想操作Execute执行完后的结果,就要在Create创建完线程后调用Sleep(0)来使主线程暂停,如下所示:
SomeThread := TSomeThread.Create(False);
Sleep(0);
操作Execute执行的结果
……
因为只有调用了Sleep(0)后,主线程才能挂起,系统才能把CPU时间片分给线程SomeThread,让SomeThread来执行它的方法Execute,否则Execute可能会在你想要调用时它的结果时而得不到。
注:一般情况下,在创建完线程对象后,还需要设置一下线程的其它属性,这时最好在创建时传递True给Create。之后调用Resume来唤醒线程。
再说一下Execute方法:在线程执行完Execute方法后,我们就认为线程到此终止了,这时它会调用Delphi的一个标准函数EndThread(),这个函数再调用标准API函数ExitThread(),再由ExitThread来清除线程所占用的栈。一般在Execute方法中设置线程的FreeOnTerminate属性,这样在线程终止时就会触发OnTerminate事件(注意:只有设置了线程的FreeOnTerminate属性后,才会在线程终止时触发事件OnTerminate,属性FreeOnTerminate在Execute方法退出前设置就行),就有机会在事件OnTerminate里清除线程对象了。
136.Win32中线程保存在栈中。
137. 注意某些紧急情况下,可以使用Win32 API函数TerminateThread()来终止一个线程。但是,除非没有别的办法了,否则不要用它。例如,当线程代码陷入死循环时。
TerminateThread()的声明如下:
function TerminateThread(hThread: THandle;dwExitCode: DWORD);
TThread的Handle属性可以作为第一个参数,因此,TerminateThread()常这样调用:TerminateThread(MyThread.Handle,0)
如果选择使用这个函数,应该考虑到它的负面影响。首先,此函数在Windows NT与在
Windows 95/98下并不相同。在Windows 95/98 下,这个函数能够自动清除线程所占用的栈;而在Windows NT下,在进程被终止前栈仍然保留。其次,无论线程代码中是否有try... finally块,这个函数都会使线程立即停止执行。这意味着,被线程打开的文件没有被关闭、由线程申请的内存没有被释放等情况。而且,这个函数在终止线程的时候也不通知DLL,当DLL关闭时,这也容易出现问题。
138.线程中的Synchronize方法用于在主线程中执行一个方法,这个方法是Synchronize的参数,该参数是一个无参数的过程,如下所示:
procedure TCustomThread.Execute;
var
I: integer;
begin
FreeOnTerminate := True;(*设置FreeOnTerminate,
在执行完Execute后就会触发OnTerminate事件,
这样就有机会在OnTerminate事件里清除线程对象了
*)
if not Terminated then begin
for i := 0 to 200000 do begin
Inc(GI,Round(abs(Sqrt(i))));
//SendMessage(MainForm.edtGetThreadValue.Handle,WM_SETTEXT,0,Integer(PChar(GI)));//使用消息同步
Synchronize(GetValue);(*使用Synchronize实现同步,线程的方法Synchronize需要传递一个无参数的过程做为它的参数,这样在主线程中就会调用GetValue过程*)
end;
end;
end;
procedure TCustomThread.GetValue;
begin
MainForm.edtGetThreadValue.Text := IntToStr(GI);
end;
//在实现这段代码过程中我发现,使用消息同步线程要比使用Synchronize同步线程要慢很多。
139.使用函数GetThreadTimes来得到关于线程时间,
function GetThreadTimes(hThread: THandle;var lpCreationTime,lpExitTime,
lpKernelTime,lpUserTime: TFileTime):Boolean;
参数说明:
hThread是线程的句柄。
LpCreationTime是线程创建的时间。
LpExitTime是线程退出的时间。
LpKernelTime是运行系统代码的时间。
LpUserTime是运行应用程序本身代码的时间。
这四个时间都是TfileTime类型的,该类型定义在Windows.pas单元中,
type
TfileTime = record
dwLowDatetime: DWORD;
dwHighDateTime: DWORD;
End;
由dwLowDatetime和dwHighDatetime可以组成64位的时间,它是开始于1601年1月1日以千万分之一秒为单位的计数。
下面两个函数用于把TfileTime类型的时间与Tdatetime类型时间进行相互转换,
function FileTimeToDateTime(FileTime: TFileTime): TdateTime;
var
SysTime: TSystemtime;
begin
if not FileTimeToSystemtime(FileTime,SysTime) then
raise EconvertError.CreateFmt(‘FileTime To System Time Failed, and’+ ‘Error Code is %d’,[GetLastError]);
with SysTime do begin
Result := EncodeDate(wYear,wMonth,wDay) +
EncodeTime(wHour,wMinute,wSecond,wMillisecond);
end;
end;
function DateTimeToFileTime(DateTime: TDateTime): TFileTime;
var
SysTime: TSystemTime;
begin
with SysTime do begin
EncodeDate(wYear,wMonth,wDay);
EncodeTime(wHour,wMinute,wSecond,wMillisecond);
WDayOfWeek := DayOfWeek(DateTime);
end;
if not SystemTimeToFileTime(SysTime,Result) then
raise EConvertError.CreateFmt(‘System Time To FileTime Failed, and’+ ‘Error Code is %d’,[GetLastError]);
end;
注:GetThreadTimes不适用于Windows95/98,在这两个操作系统中调用该函数将只返回False。
140.以前说过Sleep过程,现在再多说点,
procedure Sleep(dwMilliseconds:DWORD); stdcall;
Sleep()过程用来告诉操作系统,当前的线程在参数dwMilliseconds指定的时间内不需要分配任何CPU时间。通常,可以把参数dwMilliseconds设为0。尽管,这并没有使当前的线程真的“睡眠”,但它使操作系统把CPU时间分给了其他优先级相等或更高的线程。
141.线程的局部存储:首先说明一下为什么要使用线程局部存储?这是防止线程间同一时间写同一数据(或是操作同一个任何东西)而造成数据丢失的手段,例如:两个线程都同时写一个全局变量就会导制意想不到的结果。所谓的线程局部存储是指属于每个线程私有的数据存储方式。实现线程局部存储方式有:
第一种:局部变量,由于每一个线程都存储在自己的栈中,各个线程都有一套局部变量的副本,这样线程间就不会相互干扰。
第二种:线程对象,将数据保存在线程对象中,举例说明,
type
TSomeThread = Class(TThread)
Private
//把变量SomeInt和SomeStr保存在线程TSomeThread中
FSomeInt: integer;
FSomeStr: string;
……
End;
注:据说访问线程对象中的数据要比访问线程的局部变量要快10倍。
第三种:使用关键字threadvar来声明变量。使用threadvar声明的是一个线程的局部变量,因此该变量不能在声明时初始化。如果Synchronize的参数(也就是那个过程)使用threadvar声明的变量,会出现一些奇怪现象。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/7416120/viewspace-901168/ ,如需转载,请注明出处,否则将追究法律责任。