说在前面
记得我第一次接触 COM 组件编程时,就有这样的疑问:传递数据集有没有好办法。我们知道,Visual FoxPro 是最强大的数据处理系统,它的Cursor 的强大、灵活的支持是无有匹敌的。既然Visual FoxPro 对数据集的处理能力这么强大,但是COM编程时,怎样简单的在各类客户端程序与 Visual FoxPro 编写的 COM 组件之间传递 Cursor ——只有来去容易,用的才方便舒心!
那个时候我们模仿ADO组件,使用 “属性+循环” 的方法解决这个难题,记得 fbilo 翻译的一篇文章中有过介绍:
总的来说,觉得那个时候 “活的” 很累,并且这个方案没有解决客户端往COM组件中传递Cursor 的问题!
在我撰写的《Visual FoxPro 漫谈》一文中,我承设想用XML来解决这个问题,并且在Visual FoxPro 6的时代也做过一些尝试。我编写过一个叫做:CursorToXML的小程序,解决了Cursor 到XML的转变,但对于 XML 到 Cursor 的转变我就很无奈,我们知道微软等一些大公司专门开发了解析XML的组件,按理说只要是用这些东西就能够完成XML 到Cursor的转变,但实际操作起来很是麻烦。不信,你可以试试!这个时期,即使我成功的封装了XML的解析器,意义也不大,因为:所有的开发工具对XML的支持都不好,我的COM组件支持又有何用,所谓孤掌难鸣!
忽如一夜春风来
2000年初听说了XML,2001年初买到了有关XML的参考书,开始了解了这个被称为“可扩展的标记名语言”。本文不是专题的XML讨论、本人亦不是此方面的专家,所以我不会在XML方面浪费笔墨。我对XML的理解就是用一定的规范格式,用文本的形式将数据表现出来。基于文本、基于规范的格式,于是XML天生的就具备了广泛的适应性,这也就是我们说用 XML在各类客户端与Visual FoxPro 开发的COM之间传递数据集的原因!
微软推出的Visual FoxPro 7 全面支持XML,具体体现在三个函数中:
CursorToXML(nWorkArea | cTableAlias, cOutput [, nOutputFormat [, nFlags [, nRecords [, cSchemaName [, cSchemaLocation [, cNameSpace ]]]]]])
XMLTOCURSOR(XMLSource eExpression | cXMLFile [, cCursorName [, nFlags ]])
XMLUPDATEGRAM( [cAliasList [, nFlags]])
真有 “忽如一夜春风来”的感觉,一是感慨:业界技术发展之快;二是感慨:我们的 Visual FoxPro 也迎头赶上,适时推出了这项时髦而有意义的功能!
总的来说,Visual FoxPro 7对XML的支持是通过对 MSXML Parser 的封装来实现的。记得吗——在安装Visual FoxPro 7之前,微软会提示先安装——“MSXML Parser 3.0 ”!
CursorToXML ()与XMLTOCURSOR()
CursorToXML () 将Cursor 转换成XML文件或字符串
CursorToXML(nWorkArea | cTableAlias, cOutput [, nOutputFormat [, nFlags [, nRecords [, cSchemaName [, cSchemaLocation [, cNameSpace ]]]]]])
nWorkArea | cTableAlias--指定被转换光标
cOutput--指定输出文件名称或变量名
nOutputFormat--指定XML文档中数据的格式
这里有三种选择:
A.nOutputFormat=1:ELEMENTS
- <employees>
<employeeid>1</employeeid>
<lastname>Davolio</lastname>
<firstname>Nancy</firstname>
<title>Sales Representative</title>
<titleofcourtesy>Ms.</titleofcourtesy>
<birthdate>1948-12-08T00:00:00</birthdate>
<hiredate>1992-05-01T00:00:00</hiredate>
<address>507 - 20th Ave. E. Apt. 2A</address>
<city>Seattle</city>
<region>WA</region>
<postalcode>98122</postalcode>
<country>USA</country>
<homephone>(206) 555-9857</homephone>
<extension>5467</extension>
<notes />
<reportsto>2</reportsto>
<photopath>bmp/davolio.bmp</photopath>
</employees>
B.nOutputFormat=2:ATTRIBUTES
<employees employeeid="1" lastname="Davolio" firstname="Nancy" title="Sales Representative" titleofcourtesy="Ms." birthdate="1948-12-08T00:00:00" hiredate="1992-05-01T00:00:00" address="507 - 20th Ave. E. Apt. 2A" city="Seattle" region="WA" postalcode="98122" country="USA" homephone="(206) 555-9857" extension="5467" notes="" reportsto="2" photopath="bmp/davolio.bmp" />
C.nOutputFormat=3:ROW
<row employeeid="1" lastname="Davolio" firstname="Nancy" title="Sales Representative" titleofcourtesy="Ms." birthdate="1948-12-08T00:00:00" hiredate="1992-05-01T00:00:00" address="507 - 20th Ave. E. Apt. 2A" city="Seattle" region="WA" postalcode="98122" country="USA" homephone="(206) 555-9857" extension="5467" notes="" reportsto="2" photopath="bmp/davolio.bmp" />
nFlags--指定标记
这个参数最为复杂,这里要提请大家注意的是:如果想将XML输出到文件中,请让 nFlags=512+n(n为其他标记);如果想将XML输出到内存变量,nFlags就不用为512;
nRecords--输出的记录范围
一般我们会输出Cursor 中所有的记录,所以这个参数多用0代入!
举例:
输出实例数据库的Employee的数据到C:Temp1.xml中:
CURSORTOXML("employees","c:\temp1.xml",1,512+16,0,"1")
输出实例数据库的Employee的数据到内存变量ABC中:
CURSORTOXML("employees","ABC",3,512+16,0,"1")
XMLToCursor () 将XML 文件或字符串转换成Cursor
XMLTOCURSOR(XMLSource eExpression | cXMLFile [, cCursorName [, nFlags ]])
XMLSource eExpression | cXMLFile--指定XML的来源,可以是文件也可以是内存变量
cCursorName --新的Cursor的名称
举例:
将c:\temp1.xml 转变成为cursor:T1
XMLToCursor("c:\temp1.xml","T1")
将内存变量转变成为cursor:T2
XMLToCursor("c:\temp2.xml","T2")
XMLUPDATEGRAM() 函数与本文所介绍的内容无关,在此暂不作介绍。
一个使用XML传递的实例
编写COM组件
代码:
#define CL chr(13)+chr(10)
DEFINE CLASS PowerVFP as Session olepublic
datasession=2
PROCEDURE CreateCurosr(cXml as string,cCursorName as string)
XMLTOCURSOR(cXml,cCursorName)
ENDPROC
PROCEDURE ExecFoxCode(cCode as string)
EXECSCRIPT(cCode)
ENDPROC
FUNCTION GetCursor(cCursorName as String) as String
LOCAL cXml as String
CURSORTOXML(cCursorName,"cXml",3,48,0,"")
RETURN cXml
ENDFUNC
PROCEDURE Error(nError as Integer,cMethod as String, nLine as Integer)
LOCAL cText as String
cText=;
'出错时间:'+transform(datetime())+CL+;
'错误代码:'+str(nError,4)+CL+;
'错误提示:'+message()+CL+;
'错误方法:'+cMethod+CL+;
'错误行号:'+transform(nLine)+CL+CL
strtofile(cText,'c:\PowerFox.txt',.t.)
comreturnerror("PowerFox COM Service",cText)
ENDPROC
ENDDEFINE
我的思路
写这个组件是“有感而发”的,前一阵子用Delphi写ERP系统,虽然很努力的将大量的工作交给后台的 SQL Server,多数情况下,Delphi端得到的是很简单的数据集。然而,在一些涉及到复杂应用时,很多从经SQL Server处理的数据只是半成品,必须到客户端加工,这种加工往往是异常复杂的,分组、排序、关联、统计……在Delphi中实现这些功能是非常繁琐的,除了使用一些第三方控件能解决一些问题之外,无一例外的是大量的“循环+条件”,真的是很麻烦,工作量很大。
基于上述思考,加上经SQL Server处理过的数据量已不大,我尝试着编写一个COM来解决这个问题。除了Error方法之外,整个组件只有三个方法: CreateCurosr(cXml as string,cCursorName as string) 是将外部传入的XML字串转换成Visual FoxPro 的Cursor;GetCursor(cCursorName as String) as String是将Visual FoxPro 的Cursor 转换成为XML字符串,向外传递;关键的是ExecFoxCode(cCode as string),它可一次执行多句符合Visual FoxPro 语法的语句,这样就可以在外部任意享用Visuial FoxPro提供的绝大多数功能!
应用实例
编译这个项目以后,我们做两个简单的实验,感受一下这超级的功能!
例一:
LOCAL ox as "PowerFox.PowerVFP"
LOCAL cStr,cFoxCode as String
*建立COM对象实例
ox=CREATEOBJECT("PowerFox.PowerVFP")
*在客户端得到XML的结果集
USE northwind!Customers IN 0 SHARED
CURSORTOXML("Customers","cStr",3,48,0,"")
USE IN Customers
*将XML语句传递到COM中,并建立Cursor
ox.CreateCurosr(cStr,"C1")
*产生符合 Visual FoxPro 语法的语句
cFoxCode="SELECT CustomerID,CompanyName FROM C1 WHERE Country like '%s%' INTO CURSOR T1"
cFoxCode=cFoxCode+CHR(10)+"SELECT CustomerID,CompanyName,ContactName,ContactTitle FROM C1 INTO CURSOR T2"
*传递语句到 COM 中执行
ox.ExecFoxCode(cFoxCode)
*让 COM 组件将结果集以XML的形式传回客户端
XMLTOCURSOR(ox.GetCursor("T1"),"Result1")
XMLTOCURSOR(ox.GetCursor("T2"),"Result2")
例二:
LOCAL ox as "PowerFox.PowerVFP"
LOCAL cStr1,cStr2,cStr3,cFoxCode as String
*建立COM对象实例
ox=CREATEOBJECT("PowerFox.PowerVFP")
*在客户端得到XML的结果集
USE northwind!Employees IN 0 SHARED
USE northwind!Orders IN 0 SHARED
USE northwind!Order_Details IN 0 SHARED
CURSORTOXML("Employees","cStr1",3,48,0,"")
CURSORTOXML("Orders","cStr2",3,48,0,"")
CURSORTOXML("Order_Details","cStr3",3,48,0,"")
USE IN Employees
USE IN Orders
USE IN Order_Details
*将XML语句传递到COM中,并建立Cursor
ox.CreateCurosr(cStr1,"Employees")
ox.CreateCurosr(cStr2,"Orders")
ox.CreateCurosr(cStr3,"Order_Details")
*产生符合 Visual FoxPro 语法的语句
cFoxCode="SELECT a.employeeid,ALLTRIM(LastName)+' '+ALLTRIM(FirstName) as Name,Title,sum(unitprice*Quantity)*(1-discount) as nJe "
cFoxCode=cFoxCode+"from orders a INNER JOIN order_details b ON a.orderid=b.orderid INNER JOIN employees c ON a.employeeid=c.employeeid GROUP BY a.employeeid into cursor T1 noFilter"
cFoxCode=cFoxCode+CHR(10)+"SELECT employeeid,Name,Title,MAX(nJe) as nJe FROM T1 INTO CURSOR T2"
*传递语句到 COM 中执行
ox.ExecFoxCode(cFoxCode)
*让 COM 组件将结果集以XML的形式传回客户端
XMLTOCURSOR(ox.GetCursor("T1"),"Result1")
XMLTOCURSOR(ox.GetCursor("T2"),"Result2")
后记
好了,关于 “XML 在 COM 组件数据集传递中的意义” 已经大致讨论完毕了。不知您有何感受,我是怀着兴奋、骄傲的情绪做这个实验的、写这篇文章的。可能你对整个实验不以为然——神经病一样的将数据集导来导去,自找麻烦!但你设想一下:如果两个Test程序不用Visual FoxPro来实现,情况会怎样?那是一种多么美好的事情啊!将Visual FoxPro 作为一个 Server 运行,这是微软近年来努力的方向,在Visual FoxPro 7里,融入了大量的将关增强技术,这里介绍的就是一个很关键的东西。