中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> 程序开发 > 编程语言 > Delphi > 综合文章
如何用Delphi 6开发ASP上传组件详解
作者:左轻侯  时间:2006-10-05 13:09 出处:ccidnet.com 责编:月夜寒箫
              摘要:如何用Delphi 6开发ASP上传组件详解

文件上传是WEB开发中经常要用到的功能,但ASP本身和内置的组件都不支持文件上传功能。网上流传的一些第三方组件虽然能够解决这个问题,但大多是要收费的,更别说Open Source了。本文将详细剖析WEB文件上传的原理,以及一步步指导读者如何用Delphi6开发一个ASP上传组件。

1 Html文件分析

首先我们来看一个html文件源码,文件名是test.htm,功能是提供用户上传的界面:

<html>
            <body>
            <center>
               <form name="mainForm" enctype="multipart/form-data"
            action="test.asp" method=post>
                <input type=file name=mefile><br>
            <input type=hidden name=a1 value="fdsaf">
            <input type=hidden name=a2 value="fdsaf">
            <input type=hidden name=a3 value="fdsaf">
            <input type=hidden name=a4 value="fsdfsdsaf">
            <input type=hidden name=a5 value="这个是这个">
            <input type=text name=a6 value="fdsaf">
               <input type=submit name=ok value="OK">
               </form>
            </center>
            </body>
            </html>

这个文件里包含了一个名为mainForm的form,以及随手写的一些input域。注意这个form和一般的form有两个不同的地方:一是它有一个type=file的域,没有value。用浏览器打开这个文件时,这个域会表现为一个右侧有“浏览”字样的文件输入框,用户可以通过它来选择本地硬盘上的文件。二是form有一个特殊的属性:enctype="multipart/form-data"。这个属性告诉浏览器要上传二进制文件,并进行相应编码。

这种编码会产生什么样的表单信息呢?让我们来看看test.asp,也就是接受表单的asp文件的源码,它非常简单:

<%
            formsize=request.totalbytes   '获得表单原始信息的长度
            formdata=request.binaryread(formsize)   '读取表单原始信息
            response.binarywrite formdata  '返回表单原始信息
            %>
            如读者在注释中了解的,这段代码的功能是将表单的原始信息返回。让我们来看看它的运行效果。将
            这两个文件置于web目录下,访问test.htm。在文件输入框中,选择一个文件(我选了一个jpg图片,
            不过最大不要太大)。提交,然后可以看到这样一堆乱七八糟的信息:
            -----------------------------7d2227629012e Content-Disposition: form-data;
            name="mefile"; filename="C:\Documents and Settings\aaa\My Documents\My
            Pictures\zzjh.jpg" Content-Type: image/pjpeg (作者注:以下为乱码)
            -----------------------------7d2227629012e Content-Disposition: form-data;
            name="a1" fdsaf -----------------------------7d2227629012e Content-Disposition:
            form-data; name="a2" fdsaf -----------------------------7d2227629012e
            Content-Disposition: form-data; name="a3" fdsaf
            -----------------------------7d2227629012e Content-Disposition: form-data;
            name="a4" fsdfsdsaf -----------------------------7d2227629012e
            Content-Disposition: form-data; name="a5" 这个是这个
            -----------------------------7d2227629012e Content-Disposition: form-data;
            name="a6" fdsaf -----------------------------7d2227629012e Content-Disposition:
            form-data; name="ok" OK -----------------------------7d2227629012e--
            这就是用"multipart/form-data"方式编码的表单原始信息。其中那一段看起来是乱码的部分,就是jpg图片的编码。
            分析一下这段信息的格式:  
            -----------------------------7d2227629012e 这是各个域之间的分隔符。
            Content-Disposition: form-data; 说明这是表单中的域。
            name="mefile"; 域的名称。
            filename="C:\Documents and Settings\aaa\My Documents\My Pictures\zzjh.jpg" 上
            传文件在本地硬盘上的名称。
            Content-Type: image/pjpeg 文件类型。
            后面是文件本身的数据。

其它各个域的信息也可以以此类推。

众所周知,在ASP中,使用request对象,可以访问用户提交表单的各个域。因为request对象

会对原始的表单信息进行解析,提取出表单中每个域的值。但是,request并不能解析这

"multipart/form-data"格式的表单信息。这就是ASP不能直接支持文件上传的原因所在。读者可以

试试,在test.asp中,用request("mefile")这样的格式,是不能读取到正确的信息的。

问题的症结已经找到,解决的思路也很简单:用Delphi开发一个COM组件,接受这种原始表单信息,

将各个域一一提取出来,返回给asp文件。也就是完成request对象没有完成的功能。

2 用Delphi开发组件

Delphi6对开发ASP组件提供了极好的支持,大大简化了我们的开发过程。

启动Delphi 6,选择File-New-Other-ActiveX-ActiveX Library,这样就建立了一个ActiveX库。将此Library改名为myobj,存盘。选择File-New-Other-ActiveX-Active Server Object,在CoClassname中填入upfile,确定。这时会跳出一个标题为myobj.tlb的对话框,这是Delphi特有的以可视化方式编辑COM接口的功能,用Delphi开发过COM的读者应该比较熟悉。

在myobj下的名为Iupfile的Interface下,添加5个属性和一个方法。如果不懂得如何操作,

请参见Delphi参考书的相关部分。按F12可以看到生成的相应的myobj_tlb.pas文件,其中的

Iupfile接口应该是这个样子:

Iupfile = interface(IDispatch)
            ['{5C40D0EB-5A22-4A1E-8808-62207AE04B51}']
            procedure OnStartPage(const AScriptingContext: IUnknown); safecall;
            procedure OnEndPage; safecall;
            function  Get_Form(Formname: OleVariant): OleVariant; safecall;
            function  Get_FileName: OleVariant; safecall;
            function  Get_FileSize: Integer; safecall;
            procedure FileSaveAs(FileName: OleVariant); safecall;
            function  Get_FileData: OleVariant; safecall;
            function  Get_FileType: OleVariant; safecall;
            property Form[Formname: OleVariant]: OleVariant read Get_Form;
            property FileName: OleVariant read Get_FileName;
            property FileSize: Integer read Get_FileSize;
            property FileData: OleVariant read Get_FileData;
            property FileType: OleVariant read Get_FileType;
            end;

其中的OnStartPage方法和OnEndPage方法是Delphi默认生成的,其它的是手动加入的。

切换到unit1.pas(也是Delphi自动生成的),改名为upfile.pas存盘。可以看到存在一个Tupfile类的声明,它是继承自TASPObject类和Iupfile接口的。Delphi 6已经自动生成了相应的代码。接下来的任务就是实现这个接口。

除了完成Iupfile接口中的属性和方法之后,还需要补充一些东西,以便完成我们的任务。最终的

Tupfile类的声明如下:

Tupfile = class(TASPObject, Iupfile)
            public
            protected
            procedure OnEndPage; safecall;  //页面开始
            procedure OnStartPage(const AScriptingContext: IUnknown); safecall;  //页面
            结束
            procedure FileSaveAs(Filename: OleVariant); safecall;  //保存文件
            function Get_Form(Formname: OleVariant): OleVariant; safecall;  //
            function Get_FileName: OleVariant; safecall;
            function Get_FileSize: Integer; safecall;
            function Get_FileData: OleVariant; safecall;
            function Get_FileType: OleVariant; safecall;
            private
            FContentData:string;
            FFileData,FFileName,FFileType:string;
            FFormInfo:TStringList;
            function instr(str1,str2:string;startpos:integer):integer;
            procedure AnalyFormData(content:string);
            end;

下面我们来一一分析这些成员的具体实现。

procedure Tupfile.OnStartPage(const AScriptingContext: IUnknown);
            var
            AOleVariant : OleVariant;
            tmpvar : OleVariant;
            contentlength : integer;
            i,DeliCount,pos1,pos2,lastpos : integer;
            FDelimeter : string;
            begin
            inherited OnStartPage(AScriptingContext);
            FFormInfo := TStringList.Create;
            contentlength := Request.TotalBytes;
            AOleVariant := contentlength;
            tmpvar := Request.BinaryRead(AOleVariant);
            for i := 1 to contentlength -1 do
            begin
            FContentData := FContentData + chr(byte(tmpvar[i]));
            end;
            pos1 := pos(#13#10,FContentData);
            FDelimeter := copy(FContentData,1,pos1+1);
            DeliCount := length(FDelimeter);
            lastpos := 1;
            pos1:=0;
            while pos2>=pos1 do
            begin
            pos1 := instr(FDelimeter,FContentData,lastpos);
            if pos1 = 0 then Break;
            pos1 := pos1 + DeliCount;
            pos2 := instr(FDelimeter,FContentData,pos1)-1;
            AnalyFormData(copy(FContentData,pos1,pos2-pos1-1));
            lastpos := pos2;
            end;
            end;

前面说过,OnStartPage方法是Delphi自动生成的,在装载页面时发生。在这个方法中,我们完成一些初始化的任务:读取表单的原始数据,解析表单中的域,并存入相应的属性中,以备调用。

由于Delphi已经对ASP中的对象进行了很好的封装,所以即使在Delphi环境下,也可以方便地调用它,就象在ASP中一样,例如Request.TotalBytes。首先将原始表单数据读入到一个OleViarians类型的pvar中,然后通过一个循环,将它转换为Delphi中的string格式,并存放在FContentData中。

接下来,通过查找换行符,解析出分隔符的内容和长度。然后在一个循环中,用AnalyFormData成员函数一一解析出每个域。初始化工作就这样完成了。

再看AnalyFormData函数的实现:

procedure Tupfile.AnalyFormData(content: string);
            var
            pos1,pos2:integer;
            FormName,FormValue:string;
            isFile:boolean;
            begin
            isFile := false;
            pos1 := instr('name="',content,1)+6;
            pos2 := instr('"',content,pos1);
            FormName := copy(content,pos1,pos2-pos1);
            //检查是否文件
            pos1 := instr('filename="',content,pos2+1);
            if pos1 <> 0 then
            begin
            isFile := true;
            pos1 := pos1 + 10;
            pos2 := instr('"',content,pos1);
            FFilename := copy(content,pos1,pos2-pos1);
            end;
            pos1 := instr(#13#10#13#10,content,pos2+1)+4;
            FormValue := copy(content,pos1,length(content)-pos1);
            if isfile then
            begin
            FFileData := FormValue;
            //查找文件类型信息
            pos2 := instr('Content-Type: ',content,pos2+1);
            if pos2 <> 0 then
            begin
            pos2 := pos2 + 14;
            FFileType := copy(content,pos2,pos1-4-pos2);
            end;
            end
            else
            begin
            FFormInfo.add(FormName+'='+FormValue);
            end;
            end;

如注释中所表达的,AnalyFormData提取原始数据中的域。如果是域是文件类型,则将文件类型和文件数据分别放入FFileType和FFileData中。如果是其它类型,则将名称和值放入一个TStringlist类型的FFormInfo中。FFormInfo中维护着除文件类型外的所有域的信息,以“名称=值”的格式存放。

  
            function Tupfile.Get_Form(Formname: OleVariant): OleVariant;
            begin
            Result := FFormInfo.Values[Formname];
            end;

这个函数返回域的值。只需要简单地调用FFormInfo的values方法,就可以得到相应的值。这是在Tstringlist类内部实现的。

function Tupfile.Get_FileName: OleVariant;
            begin
            Result := ExtractFileName(FFileName);
            end;
            function Tupfile.Get_FileSize: Integer;
            begin
            Result := length(FFileData);
            end;
            function Tupfile.Get_FileData: OleVariant;
            var
            i:integer;
            begin
            Result := VarArrayCreate( [0,length(FFileData)], varByte );
            for i := 0 to length(FFileData)-1 do
            begin
            Result[i] := Byte(FFileData[i+1]);
            end;
            end;

这三个函数分别返回文件的名称、大小、数据。要注意的是,在返回文件数据时,必须进行相应的转换,将Delphi中的string类型转换为OleVariant类型。

  
            procedure Tupfile.FileSaveAs(Filename: OleVariant);
            var
            fsout:TFileStream;
            i:integer;
            afile:file of byte;
            begin
            fsout := TFileStream.Create(Filename,fmcreate);
            for i := 1 to length(FFileData) do
            begin
            fsout.Write(Byte(FFileData[i]),1)
            end;
            fsout.Free;
            end;

这个方法将文件保存到服务器上的磁盘。

编译myobj这个project,得到一个myobj.dll文件。开发工作就此完成。

3 使用ASP上传组件

在命令行下,输入“regsvr32 myobj.dll”。弹出一个对话框,告诉你组件已经注册。如果找不到regsvr32.exe这个文件,它在windows\system32或winnt\system32目录下。

将本文开头提到的test.asp文件修改为如下内容:

<%'建立对象
            Set upfile = Server.CreateObject("myobj.upfile")
            '获得表单对象
            response.write upfile.form("a1")&"<br>"
            response.write upfile.form("a2")&"<br>"
            response.write upfile.form("a3")&"<br>"
            response.write upfile.form("a4")&"<br>"
            response.write upfile.form("a5")&"<br>"
            response.write upfile.form("a6")&"<br>"
            '获得文件大小
            response.write "文件字节数:"&upfile.filesize&"<br>"
            '获得文件类型
            response.write "文件类型:"&upfile.filetype&"<br>"
            '获得文件名,保存文件
            upfile.filesaveas(Server.MapPath("")+upfile.filename)
            set upfile = nothing
            %>

再次访问test.htm,提交表单。现在你可以看到相关的返回信息,并且在服务器上test.asp所处

的目录下找到上传的文件。

这个组件只能上传单个文件,但根据同样的原理,一次上传多个文件的功能也是不难实现的。有兴趣的读者可以自行尝试。

关闭本页
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有