现在的位置: 首页 > 综合 > 正文

手把手教delphi:写你的dll文件

2013年09月18日 ⁄ 综合 ⁄ 共 30571字 ⁄ 字号 评论关闭

http://www.cnblogs.com/lovejsw2000/archive/2008/11/7.html

 

在Delphi中静态调用DLL top

  调用一个DLL比写一个DLL要容易一些。首先给大家介绍的是静态调用方法,稍后将介绍动态调用方法,并就两种方法做一个比较。同样的,我们先举一个静态调用的例子。

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

//本行以下代码为我们真正动手写的代码

function TestDll(i:integer):integer;stdcall;
external ’Delphi.dll’;

procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.Text:=IntToStr(TestDll(1));
end;

end.

  上面的例子中我们在窗体上放置了一个编辑框(Edit)和一个按钮(Button),并且书写了很少的代码来测试我们刚刚编写的Delphi.dll。大家可以看到我们唯一做的工作是将TestDll函数的说明部分放在了implementation中,并且用external语句指定了Delphi.dll的位置。(本例中调用程序和Delphi.dll在同一个目录中。)让人兴奋的是,我们自己编写的TestDll函数很快被Delphi认出来了。您可做这样一个实验:输入“TestDll(”,很快Delphi就会用fly-by提示条提示您应该输入的参数是什么,就像我们使用Delphi中定义的其他函数一样简单。注意事项有以下一些:
一、调用参数用stdcall
  和前面提到的一样,当引用DLL中的函数和过程时也要使用stdcall参数,原因和前面提到的一样。

二、用external语句指定被调用的DLL文件的路径和名称
  正如大家看到的,我们在external语句中指定了所要调用的DLL文件的名称。没有写路径是因为该DLL文件和调用它的主程序在同一目录下。如果该DLL文件在C:,则我们可将上面的引用语句写为external ’C:Delphi.dll’。注意文件的后缀.dll必须写上。

三、不能从DLL中调用全局变量
  如果我们在DLL中声明了某种全局变量,如:var s:byte 。这样在DLL中s这个全局变量是可以正常使用的,但s不能被调用程序使用,既s不能作为全局变量传递给调用程序。不过在调用程序中声明的变量可以作为参数传递给DLL。

四、被调用的DLL必须存在
  这一点很重要,使用静态调用方法时要求所调用的DLL文件以及要调用的函数或过程等等必须存在。如果不存在或指定的路径和文件名不正确的话,运行主程序时系统会提示“启动程序时出错”或“找不到*.dll文件”等运行错误。

在Delphi中动态调用DLL top

  动态调用DLL相对复杂很多,但非常灵活。为了全面的说明该问题,这次我们举一个调用由C++编写的DLL的例子。首先在C++中编译下面的DLL源程序。

#include

extern ”C” _declspec(dllexport)
int WINAPI TestC(int i)
{
return i;
}

编译后生成一个DLL文件,在这里我们称该文件为Cpp.dll,该DLL中只有一个返回整数类型的函数TestC。为了方便说明,我们仍然引用上面的调用程序,只是将原来的Button1Click过程中的语句用下面的代码替换掉了。
procedure TForm1.Button1Click(Sender: TObject);
type
TIntFunc=function(i:integer):integer;stdcall;
var
Th:Thandle;
Tf:TIntFunc;
Tp:TFarProc;
begin
Th:=LoadLibrary(’Cpp.dll’); {装载DLL}
if Th>0 then
try
Tp:=GetProcAddress(Th,PChar(’TestC’));
if Tp<>nil
then begin
Tf:=TIntFunc(Tp);
Edit1.Text:=IntToStr(Tf(1)); {调用TestC函数}
end
else
ShowMessage(’TestC函数没有找到’);
finally
FreeLibrary(Th); {释放DLL}
end
else
ShowMessage(’Cpp.dll没有找到’);
end;

  大家已经看到了,这种动态调用技术很复杂,但只要修改参数,如修改LoadLibrary(’Cpp.dll’)中的DLL名称为’Delphi.dll’就可动态更改所调用的DLL。

一、定义所要调用的函数或过程的类型
  在上面的代码中我们定义了一个TIntFunc类型,这是对应我们将要调用的函数TestC的。在其他调用情况下也要做同样的定义工作。并且也要加上stdcall调用参数。

二、释放所调用的DLL
  我们用LoadLibrary动态的调用了一个DLL,但要记住必须在使用完后手动地用FreeLibrary将该DLL释放掉,否则该DLL将一直占用内存直到您退出Windows或关机为止。

  现在我们来评价一下两种调用DLL的方法的优缺点。静态方法实现简单,易于掌握并且一般来说稍微快一点,也更加安全可靠一些;但是静态方法不能灵活地在运行时装卸所需的DLL,而是在主程序开始运行时就装载指定的DLL直到程序结束时才释放该DLL,另外只有基于编译器和链接器的系统(如Delphi)才可以使用该方法。动态方法较好地解决了静态方法中存在的不足,可以方便地访问DLL中的函数和过程,甚至一些老版本DLL中新添加的函数或过程;但动态方法难以完全掌握,使用时因为不同的函数或过程要定义很多很复杂的类型和调用方法。对于初学者,笔者建议您使用静态方法,待熟练后再使用动态调用方法。

 

使用DLL的实用技巧 top

一、编写技巧
  1 、为了保证DLL的正确性,可先编写成普通的应用程序的一部分,调试无误后再从主程序中分离出来,编译成DLL。

  2 、为了保证DLL的通用性,应该在自己编写的DLL中杜绝出现可视化控件的名称,如:Edit1.Text中的Edit1名称;或者自定义非Windows定义的类型,如某种记录。

  3 、为便于调试,每个函数和过程应该尽可能短小精悍,并配合具体详细的注释。

  4 、应多利用try-finally来处理可能出现的错误和异常,注意这时要引用SysUtils单元。

  5 、尽可能少引用单元以减小DLL的大小,特别是不要引用可视化单元,如Dialogs单元。例如一般情况下,我们可以不引用Classes单元,这样可使编译后的DLL减小大约16Kb。

二、调用技巧
  1 、在用静态方法时,可以给被调用的函数或过程更名。在前面提到的C++编写的DLL例子中,如果去掉extern ”C”语句,C++会编译出一些奇怪的函数名,原来的TestC函数会被命名为@TestC$s等等可笑的怪名字,这是由于C++采用了C++ name mangling技术。这个函数名在Delphi中是非法的,我们可以这样解决这个问题:
改写引用函数为

function TestC(i:integer):integer;stdcall;
external ’Cpp.dll’;name ’@TestC$s’;

其中name的作用就是重命名。

  2 、可把我们编写的DLL放到Windows目录下或者Windowssystem目录下。这样做可以在external语句中或LoadLibrary语句中不写路径而只写DLL的名称。但这样做有些不妥,这两个目录下有大量重要的系统DLL,如果您编的DLL与它们重名的话其后果简直不堪设想,况且您的编程技术还不至于达到将自己编写的DLL放到系统目录中的地步吧!

三、调试技巧
  1 、我们知道DLL在编写时是不能运行和单步调试的。有一个办法可以,那就是在Run|parameters菜单中设置一个宿主程序。在Local页的Host Application栏中添上宿主程序的名字就可进行单步调试、断点观察和运行了。

  2 、添加DLL的版本信息。开场白中提到了版本信息对于DLL是很重要的,如果包含了版本信息,DLL的大小会增加2Kb。增加这么一点空间是值得的。很不幸我们如果直接使用Project|options菜单中Version选项是不行的,这一点Delphi的帮助文件中没有提到,经笔者研究发现,只要加一行代码就可以了。如下例:
library Delphi;

uses
SysUtils,
Classes;

{$R *.RES}
//注意,上面这行代码必须加在这个位置

function TestDll(i:integer):integer;stdcall;
begin
Result:=i;
end;

exports
TestDll;

begin
end.=

  3 、为了避免与别的DLL重名,在给自己编写的DLL起名字的时候最好采用字符数字和下划线混合的方式。如:jl_try16.dll。

  4 、如果您原来在Delphi 1或Delphi 2中已经编译了某些DLL的话,您原来编译的DLL是16位的。只要将源代码在新的Delphi 3或Delphi 4环境下重新编译,就可以得到32位的DLL了。

posted @ 2008-11-07 16:22 YangHe 阅读(17) | 评论 (0)编辑

1引言
Windows Sockets规范以U.C. Berkeley大学BSD UNIX中流行的Socket接口为范例定义了一套Microsoft Windows下网络编程接口。它不仅包含了人们所熟悉的Berkeley Socket风格的库函数;也包含了一组针对Windows的扩展库函数,以使程序员能充分地利用Windows消息驱动机制进行编程。
Windows Sockets 规范本意在于提供给应用程序开发者一套简单的API,并让各家网络软件供应商共同遵守。此外,在一个特定版本Windows的基础上,Windows Sockets 也定义了一个二进制接口(ABI),以此来保证应用Windows Sockets API 的应用程序能够在任何网络软件供应商的符合Windows Sockets协议的实现上工作。因此这份规范定义了应用程序开发者能够使用,并且网络软件供应商能够实现的一套库函数调用和相关语义。我们可以使用WinSock在Internet上传输数据和交换信息,而且可以不需要关心网络连接的细节,因而很受网络编程程序员的欢迎。
2 Delphi中Socket的操作方式
Delphi分别使用TClientSocket元件和TServerSocket元件来操纵客户端 Socket和服务器段Socket的连接和通信。根据连接发起的方式以及本地 Socket要连接的目标,Socket之间的连接可以分为:客户端连接、监听连接以及服务器端连接。
(1)所谓客户端连接,是指由客户端的 Socket提出连接请求,要连接的目标是服务器端的Socket。为此,客户端的Socket首先要描述它要连接的服务器端Socket,主要是服务器端Socket的地址和端口号,然后再定位所要连接的服务器端Socket。找到以后,就向服务器端Socket请求连接。此时,服务器端的Socket未必正好处于准备好状态。不过,服务器端Socket会自动维护一个客户请求队列,通过这个队列的优先顺序,会在适当的时候通过请求响应的方式向客户端Socket发出"允许连接"(Accept)的信号,这样便在客户端和服务器端通过Sockets建立了连接!
(2)所谓监听连接,是指服务器端Socket并不定位具体的客户端Socket,而是处于等待连接状态,当服务器端 Socket监听到或者接收到客户端Socket的连接请求的时候,它就响应客户端Socket的请求建立一个新的Socket句柄并与客户端连接,而服务器端Socket继续处于监听状态,这样可以与多个客户端同时建立连接。
(3)所谓服务器端连接,是指当服务器端Socket接收到客户端Socket的连接请求后,就把服务器端Socket的描述发送给客户端。一旦客户端确认了此描述,就建立了连接!
3 线程控制的提出
一旦服务器与客户端建立了连接之后,就可以通过 Internet 传输数据和文件。但是在WinSock中存在两种传输模式"阻塞"和"非阻塞"的概念。
一般都采用非阻塞方式。在客户端,如果把 ClientType特性设置为ctNonBlocking,表示采用非阻塞方式进行连接。当服务器端 Socket试图进行读/写操作的时候,客户端 Socket就会得到通知,即OnRead或者OnWrite事件。
对于服务器端Socket来说,如果把ServerType特性设置为 StNonBlocking,表示采取非阻塞方式进行连接。当客户端 Socket试图进行读/写的时候,服务器端Socket就会得到通知,即OnClientRead或者OnClientWrite事件。
与非阻塞方式不同的是,在阻塞方式下没有诸如OnRead或者OnWrite等异步事件。Socket必须主动去读或者写数据。在读写操作完成之前,其他代码都无法执行,成为了纯粹的独占使用方式,整个应用程序将处于等待状态,大大降低应用程序的性能。
对于客户端Socket来说,如果把 ClientType特性设置为ctBlocking,表示采取阻塞方式进行连接,为了尽可能的减少阻塞方式的负面影响,可以把所有涉及到读写的操作放在一个单独的线程中,这样可以使其他的线程可以继续得到执行。
对于服务器端 Socket来说,如果把ServerType设置为stThreadBlocking,表示采取阻塞方式进行连接。Delphi 中将为每一个阻塞方式的连接自动分配一个新的线程,这样即使一个客户正在进行读写操作,其他的客户也不必等待。
4 在客户端使用多线程技术
在阻塞模式下,为了尽可能的减少阻塞方式的副作用,可以把所有的涉及到读写操作放在一个单独的线程种。为此,需要创建一个新的线程对象,然后重载它的Execute方法,在线程代码中,我们通过TWinSockStream对象来进行读写操作。
Procedure TClientThread.Execute;
Var sStream: TWinSockStream;
sBuffer: string;
Begin
//建一个TWinSocketStream对象实例,设置连接超时
SSteam: = TWinSockStream.Create (ClientSocket.Socket, 60000);
Try //获取和操作命令,直到连接断开或者线程终止
While (not Terminate) and (ClientSocket.Active) do
begin
try
GetNextRequest (sBuffer);
//将请求写回到Server
sStream.Write (sBuffer, Length (sBuffer) + 1);

Except
if not(Except Object is EAbort) then
//处理一些自定义的异常情况
Synchronize(HandleThreadException);
end;
end;
finally
sStream.Free;
end;
End;
5 在服务器端使用多线程技术
在服务器端,Delphi将自动为每一个阻塞方式的连接分配一个新的线程,并通过TServerClientThread来操纵每一个线程。所以不能通过对象库中的向导来创建线程对象,只能手工建立一个TServerClientThread的派生类,然后重载ClientExcute方法。Procedure TServerThread.ClientExcute;
Var sStream:TWinSocketStream;
sBuffer:array[0..9] of char
Begin
//获取和操作命令,直到连接断开或者线程终止
While (not Terminate) and (ClientSocket.Active) do
Begin
Try
sStream:= TWinSocketStream.Create(ClientSocket.Socket,60000);
try //填充SBuffer数组
FillChar(sBuffer,10,0);
//延迟时间60秒
If sStream.WaitForData(60000) then
Begin
If sStream.Read(sBuffer,10)=0 then
ClientSocket.Close;
……
End
Else ClientSocket.Close;
except
HandleException;
end;
Finally
sStream.Free;
end;
end;
End;
总结:通过客户端和服务器端的多线程控制,当我们需要对大信息量的数据处理的时候,尤为方便,而且能够很大程度上提高网络资源的利用率。目前我们正在研究通过线程控制来对数据库的查询进行优化处理以及数据发送问题

posted @ 2008-11-07 15:26 YangHe 阅读(51) | 评论 (0)编辑

用Delphi编写Socket通信程序 
 
  笔者在工作中遇到对局域网中各工作站与服务器之间进行Socket通信的问题。现在将本人总结出来的TServerSocket和TClientSocket两个组件的基本用法写出来,希望与您分享。

  ClientSocket组件为客户端组件。它是通信的请求方,也就是说,它是主动地与服务器端建立连接。

  ServerSocket组件为服务器端组件。它是通信的响应方,也就是说,它的动作是监听以及被动接受客户端的连接请求,并对请求进行回复。

  ServerSocket组件可以同时接受一个或多个ClientSocket组件的连接请求,并与每个ClientSocket组件建立单独的连接,进行单独的通信。因此,一个服务器端可以为多个客户端服务。

  设计思路

  本例包括一个服务器端程序和一个客户端程序。客户端程序可以放到多个计算机上运行,同时与服务器端进行连接通信。

  本例的重点,一是演示客户端与服务器端如何通信;二是当有多个客户端同时连接到服务器端时,服务器端如何识别每个客户端,并对请求给出相应的回复。为了保证一个客户端断开连接时不影响其它客户端与服务器端的通信,同时保证服务器端能够正确回复客户端的请求,在本例中声明了一个记录类型:

  type
  client_record=record
  CHandle: integer; //客户端套接字句柄
  CSocket:TCustomWinSocket; //客户端套接字
  CName:string; //客户端计算机名称
  CAddress:string; //客户端计算机IP地址
  CUsed: boolean; //客户端联机标志
end;

  利用这个记录类型数据保存客户端的信息,同时保存当前客户端的连接状态。其中,CHandle保存客户端套接字句柄,以便准确定位每个与服务器端保持连接的客户端;Csocket保存客户端套接字,通过它可以对客户端进行回复。Cused记录当前客户端是否与服务器端保持连接。

  下面对组件ServerSocket和ClientSocket的属性设置简单说明。

 

 

ServerSocket的属性:

  · Port,是通信的端口,必须设置。在本例中设置为1025;

  · ServerTypt,服务器端读写信息类型,设置为stNonBlocking表示异步读写信息,本例中采用这种方式。

  · ThreadCacheSize,客户端的最大连接数,就是服务器端最多允许多少客户端同时连接。本例采用默认值10。

  其它属性采用默认设置即可。

  ClientSocket的属性:

  · Port,是通信的端口,必须与服务器端的设置相同。在本例中设置为1025;

  · ClientType,客户端读写信息类型,应该与服务器端的设置相同,为stNonBlocking表示异步读写信息。

  · Host,客户端要连接的服务器的IP地址。必须设置,当然也可以在代码中动态设置。

  其它属性采用默认设置即可。

  程序源代码:

  · 服务器端源码(uServerMain.pas):

  unit uServerMain;
interface
  uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   ScktComp, ToolWin, ComCtrls, ExtCtrls, StdCtrls, Buttons;
  const
   CMax=10; //客户端最大连接数
  type
   client_record=record
   CHandle: integer; //客户端套接字句柄
   CSocket:TCustomWinSocket; //客户端套接字
   CName:string; //客户端计算机名称
   CAddress:string; //客户端计算机IP地址
   CUsed: boolean; //客户端联机标志
  end;
  type
   TfrmServerMain = class(TForm)
   ServerSocket: TServerSocket;
   ControlBar1: TControlBar;
   ToolBar1: TToolBar;
   tbConnect: TToolButton;
   tbClose: TToolButton;
   tbDisconnected: TToolButton;
   Edit1: TEdit;
   Memo1: TMemo;
   StatusBar: TStatusBar;
   procedure tbConnectClick(Sender: TObject);
   procedure tbDisconnectedClick(Sender: TObject);
   procedure ServerSocketClientRead(Sender: TObject;Socket: TCustomWinSocket);
   procedure ServerSocketListen(Sender: TObject;Socket: TCustomWinSocket);
   procedure ServerSocketClientConnect(Sender: TObject;Socket: TCustomWinSocket);
   procedure ServerSocketClientDisconnect(Sender: TObject;Socket: TCustomWinSocket);
   procedure tbCloseClick(Sender: TObject);
   procedure FormCreate(Sender: TObject);
   procedure FormClose(Sender: TObject; var Action: TCloseAction);
   procedure ServerSocketGetSocket(Sender: TObject; Socket: Integer;
   var ClientSocket: TServerClientWinSocket);
   procedure ServerSocketClientError(Sender: TObject;
   Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
   var ErrorCode: Integer);
   private
   {
    Private declarations
   }
   public
   {
    Public declarations
   }
   session: array[0..CMax] of client_record; //客户端连接数组
   Sessions: integer; //客户端连接数
  end;
  var
   frmServerMain: TfrmServerMain;
   implementation
   {$R *.DFM}
   //打开套接字连接,并使套接字进入监听状态
   procedure TfrmServerMain.tbConnectClick(Sender: TObject);
  begin
   ServerSocket.Open ;
  end;
  //关闭套接字连接,不再监听客户端的请求
  procedure TfrmServerMain.tbDisconnectedClick(Sender: TObject);
  begin
   ServerSocket.Close;
   StatusBar.Panels[0].Text :='服务器套接字连接已经关闭,无法接受客户端的连接请求.';
  end;
  //从客户端读取信息
  procedure TfrmServerMain.ServerSocketClientRead(Sender: TObject;Socket: TCustomWinSocket);
  var
   i:integer;
  begin
   //将从客户端读取的信息添加到Memo1中
   Memo1.Lines.Add(Socket.ReceiveText);
   for i:=0 to sessions do
   begin
    //取得匹配的客户端
    if session[i].CHandle = Socket.SocketHandle then
    begin
     session[i].CSocket.SendText('回复客户端'+session[i].CAddress+' ==> '+Edit1.Text);
    end;
   end;
  end;
  //服务器端套接字进入监听状态,以便监听客户端的连接
  procedure TfrmServerMain.ServerSocketListen(Sender: TObject;Socket: TCustomWinSocket);
  begin
   StatusBar.Panels[0].Text :='等待客户端连接...';
  end;
  //当客户端连接到服务器端以后
  procedure TfrmServerMain.ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
  var
   i,j:integer;
  begin
   j:=-1;
   for i:=0 to sessions do
   begin
    //在原有的客户端连接数组中有中断的客户端连接
    if not session[i].CUsed then
    begin
     session[i].CHandle := Socket.SocketHandle ;//客户端套接字句柄
     session[i].CSocket := Socket; //客户端套接字
     session[i].CName := Socket.RemoteHost ; //客户端计算机名称
     session[i].CAddress := Socket.RemoteAddress ;//客户端计算机IP
     session[i].CUsed := True; //连接数组当前位置已经占用
     Break;
    end;
   j:=i;
  end;
  if j=sessions then
  begin
   inc(sessions);
   session[j].CHandle := Socket.SocketHandle ;
   session[j].CSocket := Socket;
   session[j].CName := Socket.RemoteHost ;
   session[j].CAddress := Socket.RemoteAddress ;
   session[j].CUsed := True;
  end;
  StatusBar.Panels[0].Text := '客户端 '+Socket.RemoteHost + ' 已经连接';
end;
//当客户端断开连接时
procedure TfrmServerMain.ServerSocketClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
var
  i:integer;
begin
  for i:=0 to sessions do
  begin
   if session[i].CHandle =Socket.SocketHandle then
   begin
    session[i].CHandle :=0;
    session[i].CUsed := False;
    Break;
   end;
  end;
  StatusBar.Panels[0].Text :='客户端 '+Socket.RemoteHost + ' 已经断开';
end;
//关闭窗口
procedure TfrmServerMain.tbCloseClick(Sender: TObject);
begin
  Close;
end;
procedure TfrmServerMain.FormCreate(Sender: TObject);
begin
  sessions := 0;
end;
procedure TfrmServerMain.FormClose(Sender: TObject;var Action: TCloseAction);
begin
  ServerSocket.Close ;
end;
//当客户端正在与服务器端连接时
procedure TfrmServerMain.ServerSocketGetSocket(Sender: TObject;
Socket: Integer; var ClientSocket: TServerClientWinSocket);
begin
  StatusBar.Panels[0].Text :='客户端正在连接...';
end;
//客户端发生错误
procedure TfrmServerMain.ServerSocketClientError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
  StatusBar.Panels[0].Text :='客户端'+Socket.RemoteHost +'发生错误!';
  ErrorCode := 0;
end;
end.

 

· 客户端源码(uClientMain.pas):

  unit uClientMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp, ComCtrls, ToolWin, ExtCtrls, StdCtrls, Buttons;
const
  SocketHost = '172.16.1.6'; //服务器端地址
type
  TfrmClientMain = class(TForm)
  ControlBar1: TControlBar;
  ToolBar1: TToolBar;
  tbConnected: TToolButton;
  tbSend: TToolButton;
  tbClose: TToolButton;
  tbDisconnected: TToolButton;
  ClientSocket: TClientSocket;
  Edit1: TEdit;
  Memo1: TMemo;
  StatusBar: TStatusBar;
  btnSend: TBitBtn;
  procedure tbConnectedClick(Sender: TObject);
  procedure tbDisconnectedClick(Sender: TObject);
  procedure ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
  procedure tbSendClick(Sender: TObject);
  procedure tbCloseClick(Sender: TObject);
  procedure FormShow(Sender: TObject);
  procedure ClientSocketConnect(Sender: TObject;
  Socket: TCustomWinSocket);
  procedure ClientSocketConnecting(Sender: TObject;
  Socket: TCustomWinSocket);
  procedure ClientSocketDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
  procedure FormClose(Sender: TObject; var Action: TCloseAction);
  procedure ClientSocketError(Sender: TObject; Socket: TCustomWinSocket;
  ErrorEvent: TErrorEvent; var ErrorCode: Integer);
  private
  { Private declarations }
  public
  { Public declarations }
  end;
  var
   frmClientMain: TfrmClientMain;
   implementation
   {$R *.DFM}
   //打开套接字连接
   procedure TfrmClientMain.tbConnectedClick(Sender: TObject);
   begin
    ClientSocket.Open ;
   end;
   //关闭套接字连接
   procedure TfrmClientMain.tbDisconnectedClick(Sender: TObject);
   begin
    ClientSocket.Close;
   end;
   //接受服务器端的回复
   procedure TfrmClientMain.ClientSocketRead(Sender: TObject;Socket: TCustomWinSocket);
   begin
    Memo1.Lines.Add(Socket.ReceiveText);
   end;
   //发送信息到服务器端
   procedure TfrmClientMain.tbSendClick(Sender: TObject);
   begin
    ClientSocket.Socket.SendText(Edit1.Text);
   end;
   procedure TfrmClientMain.tbCloseClick(Sender: TObject);
   begin
    Close;
   end;
   //设置要连接的服务器端地址
   procedure TfrmClientMain.FormShow(Sender: TObject);
   begin
    ClientSocket.Host := SocketHost;
   end;
   //已经连接到服务器端
   procedure TfrmClientMain.ClientSocketConnect(Sender: TObject;Socket: TCustomWinSocket);
   begin
    tbSend.Enabled := True;
    tbDisconnected.Enabled :=True;
    btnSend.Enabled := True;
    StatusBar.Panels[0].Text := '已经连接到 '+ Socket.RemoteHost ;
   end;
   //正在连接到服务器端
   procedure TfrmClientMain.ClientSocketConnecting(Sender: TObject;Socket: TCustomWinSocket);
   begin
    StatusBar.Panels[0].Text := '正在连接到服务器... ' ;
   end;
   //当断开与服务器端的连接时发生
   procedure TfrmClientMain.ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
   begin
    tbSend.Enabled := False;
    btnSend.Enabled := False;
    tbDisconnected.Enabled := False;
    StatusBar.Panels[0].Text := '已经断开与 '+ Socket.RemoteHost +' 的连接';
   end;
   procedure TfrmClientMain.FormClose(Sender: TObject;
var Action: TCloseAction);
   begin
    ClientSocket.Close ;
   end;
   //当与服务器端的连接发生错误时
   procedure TfrmClientMain.ClientSocketError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;var ErrorCode: Integer);
   begin
    StatusBar.Panels[0].Text := '与服务器端的连接发生错误';
    ErrorCode := 0;
   end;
  end.

posted @ 2008-11-07 15:23 YangHe 阅读(63) | 评论 (0)编辑

笔者在工作中遇到对局域网中各工作站与服务器之间进行Socket通信的问题。现在将本人总结出来的TServerSocket和TClientSocket两个组件的基本用法写出来,希望与您分享。

  ClientSocket组件为客户端组件。它是通信的请求方,也就是说,它是主动地与服务器端建立连接。

  ServerSocket组件为服务器端组件。它是通信的响应方,也就是说,它的动作是监听以及被动接受客户端的连接请求,并对请求进行回复。

  ServerSocket组件可以同时接受一个或多个ClientSocket组件的连接请求,并与每个ClientSocket组件建立单独的连接,进行单独的通信。因此,一个服务器端可以为多个客户端服务。

一、设计思路

  本例包括一个服务器端程序和一个客户端程序。客户端程序可以放到多个计算机上运行,同时与服务器端进行连接通信。

  本例的重点,一是演示客户端与服务器端如何通信;二是当有多个客户端同时连接到服务器端时,服务器端如何识别每个客户端,并对请求给出相应的回复。为了保证一个客户端断开连接时不影响其它客户端与服务器端的通信,同时保证服务器端能够正确回复客户端的请求,在本例中声明了一个记录类型:

type
client_record=record
CHandle: integer; //客户端套接字句柄
CSocket:TCustomWinSocket; //客户端套接字
CName:string; //客户端计算机名称
CAddress:string; //客户端计算机IP地址
CUsed: boolean; //客户端联机标志
end;

  利用这个记录类型数据保存客户端的信息,同时保存当前客户端的连接状态。其中,CHandle保存客户端套接字句柄,以便准确定位每个与服务器端保持连接的客户端;Csocket保存客户端套接字,通过它可以对客户端进行回复。Cused记录当前客户端是否与服务器端保持连接

二、属性设置

  下面对组件ServerSocket和ClientSocket的属性设置简单说明。

1、ServerSocket的属性:

· Port,是通信的端口,必须设置。在本例中设置为1025;

· ServerTypt,服务器端读写信息类型,设置为stNonBlocking表示异步读写信息,本例中采用这种方式。

· ThreadCacheSize,客户端的最大连接数,就是服务器端最多允许多少客户端同时连接。本例采用默认值10。

  其它属性采用默认设置即可。

2、ClientSocket的属性:

· Port,是通信的端口,必须与服务器端的设置相同。在本例中设置为1025;

· ClientType,客户端读写信息类型,应该与服务器端的设置相同,为stNonBlocking表示异步读写信息。

· Host,客户端要连接的服务器的IP地址。必须设置,当然也可以在代码中动态设置。

  其它属性采用默认设置即可。

三、程序源代码

1、服务器端源码(uServerMain.pas):

unit uServerMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp, ToolWin, ComCtrls, ExtCtrls, StdCtrls, Buttons;
const
CMax=10; //客户端最大连接数
type
client_record=record
CHandle: integer; //客户端套接字句柄
CSocket:TCustomWinSocket; //客户端套接字
CName:string; //客户端计算机名称
CAddress:string; //客户端计算机IP地址
CUsed: boolean; //客户端联机标志
end;
type
TfrmServerMain = class(TForm)
ServerSocket: TServerSocket;
ControlBar1: TControlBar;
ToolBar1: TToolBar;
tbConnect: TToolButton;
tbClose: TToolButton;
tbDisconnected: TToolButton;
Edit1: TEdit;
Memo1: TMemo;
StatusBar: TStatusBar;
procedure tbConnectClick(Sender: TObject);
procedure tbDisconnectedClick(Sender: TObject);
procedure ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocketListen(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocketClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure tbCloseClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ServerSocketGetSocket(Sender: TObject; Socket: Integer;
var ClientSocket: TServerClientWinSocket);
procedure ServerSocketClientError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
session: array[0..CMax] of client_record; //客户端连接数组
Sessions: integer; //客户端连接数
end;
var
frmServerMain: TfrmServerMain;
implementation
{$R *.DFM}
//打开套接字连接,并使套接字进入监听状态
procedure TfrmServerMain.tbConnectClick(Sender: TObject);
begin
ServerSocket.Open ;
end;
//关闭套接字连接,不再监听客户端的请求
procedure TfrmServerMain.tbDisconnectedClick(Sender: TObject);
begin
ServerSocket.Close;
StatusBar.Panels[0].Text :='服务器套接字连接已经关闭,无法接受客户端的连接请求.';
end;
//从客户端读取信息
procedure TfrmServerMain.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
i:integer;
begin
//将从客户端读取的信息添加到Memo1中
Memo1.Lines.Add(Socket.ReceiveText);
for i:=0 to sessions do
begin
//取得匹配的客户端
if session[i].CHandle = Socket.SocketHandle then
begin
session[i].CSocket.SendText('回复客户端'+session[i].CAddress+' ==> '+Edit1.Text);
end;
end;
end;
//服务器端套接字进入监听状态,以便监听客户端的连接
procedure TfrmServerMain.ServerSocketListen(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar.Panels[0].Text :='等待客户端连接...';
end;
//当客户端连接到服务器端以后
procedure TfrmServerMain.ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
var
i,j:integer;
begin
j:=-1;
for i:=0 to sessions do
begin
//在原有的客户端连接数组中有中断的客户端连接
if not session[i].CUsed then
begin
session[i].CHandle := Socket.SocketHandle ;//客户端套接字句柄
session[i].CSocket := Socket; //客户端套接字
session[i].CName := Socket.RemoteHost ; //客户端计算机名称
session[i].CAddress := Socket.RemoteAddress ;//客户端计算机IP
session[i].CUsed := True; //连接数组当前位置已经占用
Break;
end;
j:=i;
end;
if j=sessions then
begin
inc(sessions);
session[j].CHandle := Socket.SocketHandle ;
session[j].CSocket := Socket;
session[j].CName := Socket.RemoteHost ;
session[j].CAddress := Socket.RemoteAddress ;
session[j].CUsed := True;
end;
StatusBar.Panels[0].Text := '客户端 '+Socket.RemoteHost + ' 已经连接';
end;

posted @ 2008-11-07 15:20 YangHe 阅读(17) | 评论 (0)编辑

//当客户端断开连接时
procedure TfrmServerMain.ServerSocketClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
var
i:integer;
begin
for i:=0 to sessions do
begin
if session[i].CHandle =Socket.SocketHandle then
begin
session[i].CHandle :=0;
session[i].CUsed := False;
Break;
end;
end;
StatusBar.Panels[0].Text :='客户端 '+Socket.RemoteHost + ' 已经断开';
end;
//关闭窗口
procedure TfrmServerMain.tbCloseClick(Sender: TObject);
begin
Close;
end;
procedure TfrmServerMain.FormCreate(Sender: TObject);
begin
sessions := 0;
end;
procedure TfrmServerMain.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
ServerSocket.Close ;
end;
//当客户端正在与服务器端连接时
procedure TfrmServerMain.ServerSocketGetSocket(Sender: TObject;
Socket: Integer; var ClientSocket: TServerClientWinSocket);
begin
StatusBar.Panels[0].Text :='客户端正在连接...';
end;
//客户端发生错误
procedure TfrmServerMain.ServerSocketClientError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
StatusBar.Panels[0].Text :='客户端'+Socket.RemoteHost +'发生错误!';
ErrorCode := 0;
end;
end.

2、客户端源码(uClientMain.pas):

unit uClientMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp, ComCtrls, ToolWin, ExtCtrls, StdCtrls, Buttons;
const
SocketHost = '172.16.1.6'; //服务器端地址
type
TfrmClientMain = class(TForm)
ControlBar1: TControlBar;
ToolBar1: TToolBar;
tbConnected: TToolButton;
tbSend: TToolButton;
tbClose: TToolButton;
tbDisconnected: TToolButton;
ClientSocket: TClientSocket;
Edit1: TEdit;
Memo1: TMemo;
StatusBar: TStatusBar;
btnSend: TBitBtn;
procedure tbConnectedClick(Sender: TObject);
procedure tbDisconnectedClick(Sender: TObject);
procedure ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
procedure tbSendClick(Sender: TObject);
procedure tbCloseClick(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocketConnecting(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ClientSocketError(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmClientMain: TfrmClientMain;
implementation
{$R *.DFM}
//打开套接字连接
procedure TfrmClientMain.tbConnectedClick(Sender: TObject);
begin
ClientSocket.Open ;
end;
//关闭套接字连接
procedure TfrmClientMain.tbDisconnectedClick(Sender: TObject);
begin
ClientSocket.Close;
end;
//接受服务器端的回复
procedure TfrmClientMain.ClientSocketRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo1.Lines.Add(Socket.ReceiveText);
end;
//发送信息到服务器端
procedure TfrmClientMain.tbSendClick(Sender: TObject);
begin
ClientSocket.Socket.SendText(Edit1.Text);
end;
procedure TfrmClientMain.tbCloseClick(Sender: TObject);
begin
Close;
end;
//设置要连接的服务器端地址
procedure TfrmClientMain.FormShow(Sender: TObject);
begin
ClientSocket.Host := SocketHost;
end;
//已经连接到服务器端
procedure TfrmClientMain.ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
tbSend.Enabled := True;
tbDisconnected.Enabled :=True;
btnSend.Enabled := True;
StatusBar.Panels[0].Text := '已经连接到 '+ Socket.RemoteHost ;
end;
//正在连接到服务器端
procedure TfrmClientMain.ClientSocketConnecting(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar.Panels[0].Text := '正在连接到服务器... ' ;
end;
//当断开与服务器端的连接时发生
procedure TfrmClientMain.ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
tbSend.Enabled := False;
btnSend.Enabled := False;
tbDisconnected.Enabled := False;
StatusBar.Panels[0].Text := '已经断开与 '+ Socket.RemoteHost +' 的连接';
end;
procedure TfrmClientMain.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
ClientSocket.Close ;
end;
//当与服务器端的连接发生错误时
procedure TfrmClientMain.ClientSocketError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
StatusBar.Panels[0].Text := '与服务器端的连接发生错误';
ErrorCode := 0;
end;
end.

四、小结

  上述方法是比较简单的实现方法,同时也是相对较容易理解的方法。通过这个方法,笔者成功实现了局域网内多个客户端与服务器端进行Socket通信的功能,同时可以保证一个客户端的连接、通信或是断开都不影响其它客户端的正常通信。

posted @ 2008-11-07 15:20 YangHe 阅读(11) | 评论 (0)编辑
DELPHI中使用DLL的方法以及INSTALLSHIELD中使用DLL方法
 调用DLL的方法无论是DELPHI还是INSTALLSHIELD,使用过程皆一致的,即声明,载入,使用,释放。
 
现在先看DELPHI调用DLL的情况,在此之前先用DELPHI做个DLL,现以一个创建SQL DB的CreateDB.DLL为例:
**********************************************************************

library CreateDB;

{ Important note about DLL memory management: ShareMem must be the
  first unit in your library's USES clause AND your project's (select
  Project-View Source) USES clause if your DLL exports any procedures or
  functions that pass strings as parameters or function results. This
  applies to all strings passed to and from your DLL--even those that
  are nested in records and classes. ShareMem is the interface unit to
  the BORLNDMM.DLL shared memory manager, which must be deployed along
  with your DLL. To avoid using BORLNDMM.DLL, pass string information
  using PChar or ShortString parameters. }

uses
  SysUtils,
  Classes,
  ADODB, DB,
  Windows,
  Registry;


{$R *.res}
function MakeSqlServerCnn(strAddress,strUsername,strPassword,strDatabase:string):string;
begin
  Result:='Provider=SQLOLEDB.1;'+
          'Password='+strPassword+
          ';Persist Security Info=True;'+
          'User ID='+strUsername+
          ';Initial Catalog='+strDatabase+
          ';Data Source='+strAddress+';';
end;

function MakeSqlServerCnn_WN(strAddress,strDatabase,Conn_Str:String):String;
begin
  if Conn_Str='1' then
  Result:='Provider=SQLOLEDB.1;'+
          'Integrated Security=SSPI;Persist Security Info=False;User ID=sa;'+
          'Initial Catalog='+strDatabase+
          ';Data Source='+strAddress+
          ';'
  else
  Result  :='';
end;

Procedure GetValue(var ServerName,User,Mask,DBName,WN,BackupPath  :String);
var
  Reg :TRegistry;
begin
  Reg :=TRegistry.Create;
  Try
    Reg.RootKey :=HKEY_LOCAL_MACHINE;
    if Reg.OpenKey('SOFTWARE/Faceiv/HY1130',False) then
      begin
        ServerName  :=Reg.ReadString('ServerName');
        User        :=Reg.ReadString('User');
        Mask        :=Reg.ReadString('Mask');
        DBName      :=Reg.ReadString('DBName');
        WN          :=Reg.ReadString('WN');
        BackupPath  :=Reg.ReadString('BackupPath');
      end;
  Finally
    Reg.CloseKey;
    Reg.Free;
  End;
end;

Function CreateDataBase() :Boolean;
var
  Step  :integer;
  Conn  :TADOConnection;
  Connc :TADOCommand;
  ServerName,User,Mask,DBName,WN,BackupPath,DBPath  :String;
begin
  Try
    Result  :=False;

    GetValue(ServerName,User,Mask,DBName,WN,BackupPath);
    DBName  :='Master';
    WN:='1';
    Conn  :=TADOConnection.Create(nil);
    Conn.LoginPrompt  :=False;
    //if WN='1' then
      Conn.ConnectionString :=MakeSqlServerCnn_WN(ServerName,DBName,WN);
    //else
    //  Conn.ConnectionString :=MakeSqlServerCnn(ServerName,User,Mask,DBName);
    Conn.Connected  :=True;
    Connc :=TADOCommand.Create(nil);
    Connc.Connection  :=Conn;

    DBPath  :=Copy(BackupPath,1,Length(BackupPath)-6)+'DB/TimeBook';
    Step  :=0;
    Connc.CommandText :='Use Master;Create DataBase TimeBook';
    Connc.Execute;
    Step  :=1;
    Connc.CommandText :='Restore DataBase TimeBook From Disk='''+DBPath+'''';
    Connc.Execute;
    Result  :=True;
  Except
    if Step=0 then
      Raise Exception.Create('创建数据库失败!');
    if Step=1 then
      Raise Exception.Create('恢复数据库失败!');
  end;
end;

Exports
  CreateDataBase;

begin
end.

*************************************************************************

注:由于这个DLL是为了INSTALLSHIELD中使用的,所以有个通过注册表进行信息传递的函数。

下面开始DELPHI中调用CreateDB.Dll的方法:

------------------------------------------------------------------------

1、声明:

Const
  CreateDB  ='CreateDB.dll';

type
  TCreateDataBase =Function:Boolean;stdcall;

-------------------------------------------------------------------------

procedure TForm2.Button1Click(Sender: TObject);
var
  CreateDataBase  :TCreateDataBase;
  Handle  :integer;

begin
  Handle  :=LoadLibrary(CreateDB);      //2、载入
  if Handle >0 then
    @CreateDataBase :=GetProcAddress(Handle,'CreateDataBase');
    if @CreateDataBase<>nil then
      if CreateDataBase then           //3、使用
        ShowMessage('SUCEED');
  FreeLibrary(Handle);                //4、释放

end;

*************************************************************************

动态调用最为常见,故只以动态调用为例,因为未指定此DLL的路径,所以只可与调用的程序放在一个目录下。

下面是INSTALLSHIELD中的DLL调用

 

prototype BOOL CreateDB.CreateDataBase();  //1、声明

function CreateDB() 
 STRING szDll;
 NUMBER nResult;
begin
 szDll =TARGETDIR^"Main//CreateDB.dll";
 
 nResult = UseDLL (szDll);              //2、载入
 if (nResult =0) then
  //MessageBox("UseDLL successful /n/n.DLL file loaded.",INFORMATION);
 else
  MessageBox("UseDLL failed./n/nCouldn't load CreateDB.DLL file.",INFORMATION);
  abort;
 endif;
 
 if CreateDataBase() then                       //3、使用
  //MessageBox("创建成功",INFORMATION);
 else 
  MessageBox("创建失败",SEVERE);
  abort;
 endif;   
 
 if(UnUseDLL(szDll)<0) then                     //4、释放
  MessageBox("UnUseDLL failed./n/nCreateDB.DLL still in memory.",SEVERE);
 else
  //MessageBox("UnUseDLL successful./n/n.DLL file removed from memory.",INFORMATION);
 endif;       
end;

*************************************************************************

 

posted @ 2008-11-07 09:47 YangHe 阅读(87) | 评论 (0)编辑
一种常见的禁止多实例运行的方法
 在DELPHI的工程文件中

program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Windows,Messages,ShellApi;

{$R *.res}

var
  HMutex:Hwnd;
  Ret:Integer;
  Reg :integer;

begin
  Application.Initialize;
  Application.Title :='这是一个防止多个实例运行的程序';
  HMutex  :=CreateMutex(nil,False,Pchar('这是一个防止多个实例运行的程序'));
  Reg :=GetLastError;
  if Reg<>ERROR_ALREADY_EXISTS then
    begin
      Application.CreateForm(TForm1, Form1);
    end
  else
    begin
      MessageBox(0,'实例已经运行了!','错误', MB_OK + MB_ICONERROR);
      ReleaseMutex(hMutex);
    end;
  Application.Run;
end.

 

posted @ 2008-11-07 09:46 YangHe 阅读(19) | 评论 (0)编辑
判断Exe文件是否正在运行的函数
 function TForm1.exe_is_running(const exeName:String) : Boolean;  //exeName:不要扩展名的Exe主文件名
var
  hCurrentWindow:HWnd;
  szText:array[0..254] of char;
begin
  Result := False;
  hCurrentWindow:=Getwindow(Application.Handle,GW_HWNDFIRST);
  while hCurrentWindow <> 0 do
  begin
    if Getwindowtext(hCurrentWindow,@sztext,255)>0 then
    begin
       if LowerCase(pchar(@sztext))=LowerCase(exeName) then
       begin
         Result := true;
         Exit;
       end;
    end;
    hCurrentWindow:=Getwindow(hCurrentwindow,GW_HWndNext);
  end;
end;
posted @ 2008-11-07 09:45 YangHe 阅读(23) | 评论 (0)编辑
Delphi中的文件操作
 1、Delphi中拷贝文件的几种方法
{方法一:用File stream }
Procedure FileCopy( Const sourcefilename, targetfilename: String );
Var
S, T: TFileStream;
Begin
S := TFileStream.Create( sourcefilename, fmOpenRead );
try
T := TFileStream.Create( targetfilename,fmOpenWrite or fmCreate );
try
T.CopyFrom(S, S.Size ) ;
finally
T.Free;
end;
finally
S.Free;
end;
End;
{ 方法二:使用内存块进行读写 }
procedure FileCopy(const FromFile, ToFile: string);
var
FromF, ToF: file;
NumRead, NumWritten: Word;
Buf: array[1..2048] of Char;
begin
AssignFile(FromF, FromFile);
Reset(FromF, 1); 
AssignFile(ToF, ToFile);
Rewrite(ToF, 1); 
repeat
BlockRead(FromF, Buf, SizeOf(Buf), NumRead);
BlockWrite(ToF, Buf, NumRead, NumWritten);
until (NumRead = 0) or (NumWritten <> NumRead); 
CloseFile(FromF);
CloseFile(ToF);
end;
posted @ 2008-11-07 09:44 YangHe 阅读(22) | 评论 (0)编辑
利用消息在窗体之间传递命令