
C++是标准化的计算机语言,不属于任何人,而属于一个标准委员会。STL是支持数据结构和算法的C++扩展。ATL是微软拥有和维护的模板库,使得COM编程更容易。综合这些技术形成了创建COM组件的一种有效方法,这些COM组件用于ASP页面。
下面用所有这些技术创建一个COM对象,你将看到VC++ 6.0的向导如何提供大量代码,因此,可以把注意力集中在解决问题上,而不是担心具体的编程细节。
17.3.1 问题
表现数据的最普通方法是表,列代表字段的类型,每一行是一条记录,拥有字段的值。在文本文件中,表通常由用逗号分开的值(comma-separated values,CSV)组成。
我们将要创建的COM组件以CSV数据作为输入,高效地存储它,并提供访问函数去检索它。这些数据在COM组件中以STL数据结构表示。在以后部分中,我们会看到怎样用STL算法去处理这些数据。另外,在下一章,将介绍怎样在数据库中存储存这些数据。
为了便于说明,假设数据在一个稀疏表中。第一行的字段是列标题,接下来的是一条条数据记录,记录的每个字段对齐于列标题。逗号隔离字段,换行符(/n)隔离行,空的字段用两个逗号表示,即“,,”。
表17-1是一个展开的表的例子。导出时,逗号会隔离每一个字段。
17.3.2 设计
这个组件的设计目的是使数据的存储空间和访问时间最小。由于数据有可能是稀疏的,即许多字段是空的,这就有可能使数据的存储空间最小化。可以通过数值(基于零的索引)访问数据的行,可以通过字段名访问数据的列。例如,要得到表17-1中Keith Moon的Instrument,可以调用GetField (1,"Instrument")。
完成以上工作的工具是STL的vector和map数据结构,这些数据结构是容器,就是说,它们是包含其他对象的一个集合的对象。为了访问集合中的对象,使用STL遍历器。
17.3.3 实现
现在你对这个组件的功能已经有了概念,我们将按下面的步骤实现它:
? 创建包含组件的DLL。
? 创建组件。
? 增加属性。
? 增加方法。
1. 创建DLL和一个组件
选择ATLCOMAppWizard,创建一个新的VC++项目,然后命名为ASPCOMponents。使用默认的服务器类型(DLL),不选中复选框,如图17-1所示。
当完成后,这个向导会自动产生一个组件的外壳。这时没有组件存在,只有DLL根据COM规范所要求的函数存在,可以在ASPCOMponents.cpp文件中看到这些函数,但不用关心文件的细节,也不用改变它。当向DLL中添加组件时,向导会修改该文件。
现在将组件放入DLL。在工作区窗口选择ClassView选项卡,右击ASPComponents Classes,选择New ATL Object。然后再选Simple Object,并且在Short Name框中键入Tablestorage,如图17 - 2所示。
除非你打算更深入地研究ATL,否则在Attributes选项卡保持默认状态,不必改变任何选项。对所有这些选项的意义的详细描述已超出本书的范围。Attributes选项卡如图17-3所示。
点击OK后,就有了一个用来工作的对象。
到目前为止,VC++向导已经完成了所有的工作,如果查看TableStorage.h,你会看到向导产生的ATL代码。在大多数情况下,不必改变这些代码。实际上,可以只依靠这些代码,不用了解ATL,继续进行编程。然而,需要对默认的设置做一点改变,使得生成的代码能够编译。
为了减小组件的大小,当AppWizard创建一个组件时,自动关闭C++的异常处理。因为如果启用这一功能的话,则需要C运行期库。STL使用异常处理,所以需要启用它。在ProjectSettings对话框的C/C++选项卡中,下拉Category框选择C++ Language,确保Settings for框设置为All Configurations,选择Enable exception handing复选框,如图1 7 - 4所示。
如果用Release模式编译可能会得到一个链接错误。当编译一个发行版本的ATL项目时,如果异常处理没有关闭,会出现这样的问题。使用异常处理时,需要C运行期启动代码;但是在默认方式下,Release模式的ATL项目定义了_ ATL _MIN_CRT符号,它拒绝启动代码。为了解决这个问题,在C/C++预处理器定义中删除_ ATL_MIN_CRT。详细资料请看微软基础知识库中的文章Q165259。
我们增加的第一段代码用于建立STL数据结构,把下列代码加到TableStorage.h的开头:
这里,声明了两个映射和一个矢量。这个矢量实际是两个映射之一的矢量。使用C++的typedef命令定义映射和矢量主要是为了使得代码更易读,对数据类型描述得更好。
COLUMN_INDEX_MAP将一个字符串(列的名称)映射到一个索引中。INDEX_FIELD_MAP表示数据表中的每一行的数据。由于他可能是一个稀疏行,用一个映射实现是高效的,空字段不占用任何空间。INDEX_FIELD_MAP映射COLUMN_INDEX_MAP提供的索引到字段值。最后,ROW_VECTOR包含代表行的每一个映射。
现在已说明了内部数据结构,可将它们用于使用属性的外部世界。
2. 增加属性
我们将增加两个属性:行数属性和列数属性。
在项目工作区选择ClassView选项卡,右击ITableStorage接口。在菜单中选择Add Property,按图1 7 - 5填写对话框。选择Get Function复选框,而不选择Put Function复选框,使得它为只读属性。
产生返回这一属性的get_numRows方法。这就是说可有产生属性值的逻辑。在这种情况下,可通过调用行矢量的size()方法设置属性:
现在有了获得所存储数据的行数和列数的方法,还需让一些数据输入到组件中,这是下一步要做的工作。
3. 增加方法
到目前为止,还无法把任何数据输入到内部数据结构中,也无法读取它们。下面增加四个方法完成下列任务:
? 在数据结构中插入数据。
? 从数据结构中获取一个字段。
? 获取列名称。
? 对列进行排序。
(1) 分析数据
第一个方法将获得一个以逗号隔离的字符串,进行分析,再将数据输入数据结构中。
在项目工作区中选择ClassView选项卡,右击ITableStorage接口。在菜单中选择AddMethod,按图17 - 6填写对话框。
然后用下列代码填写ParseCSV方法的主体部分:
CSV数据作为一个字符串参数传递,并清除两个STL成员变量,删除以前调用这个方法时输入的数据。
要特别注意第一行, 因为它包含列的名称,每一列的名称作为一个键存储在m_columnIndexMap中,用映射的当前大小作为索引。当一行处理完后,可以利用列名称映射的索引也就是列的数值索引这一事实。如果被分析的字段有数据,就将其存储在INDEX_FIELD_MAP中。
一旦所有行都处理完后, COM组件中的CSV数据的存储空间已经最小化,并且由于使用映射可以加快访问速度。因为只存储了有实际值的字段,所以存储空间最小。由于映射在内部组织数据,可快速检索,访问速度很快。