CG橙子精彩导航: CG橙子搜索引擎 | 博客 | 动画视频
广告|项目|培训|竞价排名  
86CG > CG教程 > 虚拟现实 > Multigen > 基于Vega的视景驱动软件的分析与设计

基于Vega的视景驱动软件的分析与设计

类型:整理 | 来自:86CG | 时间:2007-11-29 |  点击:
导读】针对在 Windows NT 平台上开发基于 Vega 的视景驱动软件中所遇到的问题,该文从面向对象的软件分析与设计、可复用面向对象的软件技术——设计模式以及 Windows 的核心机制等诸多方面进行了逐步的分析与设计,并最终给出了一种妥善的通用解决方案。
引 言1

  Vega 系统作为一种视景驱动软件平台,大量运用于虚拟现实及实时仿真的应用中。由于 Windows 平台的易用性及应用的需求,Vega 系统由 SGI UNIX 平台移植到 Windows
NT 平台上,这大大方便了 Windows 开发人员开发出基于Vega 的各种应用。而另一方面也不可避免的导致在新的Windows NT 平台上开发基于 Vega 的视景驱动软件的过程中会遇到这样那样的问题,本文就在 Windows NT 平台上开发一个实时虚拟仿真系统中的视景驱动软件部分的过程中遇到的一些问题,从面向对象的软件分析与设计、可复用面向对象的软件技术——设计模式以及 Windows 的核心机制等诸多方面进行了逐步的分析与设计,并最终给出了一种妥善的解决方案。

 1 实时虚拟仿真系统 FVSS 的总体结构

  本文中所涉及的实时虚拟仿真系统FVSS是一个飞机飞行模拟的实时仿真系统,它的总体结构如图 1 所示。该系统将用户的输入通过仿真建模运算后,实时地在输出端给出三维可视化的仿真结果。



图 1 实时虚拟仿真系统 FVSS 的总体结构其中系统各个模块的主要功能为:
  数据采集模块主要对用户的输入进行实时采集并进行数据格式转换,然后将转换后的数据传给系统建模模块作为输入。
  系统建模部分是整个仿真系统的核心部分,它主要是对真实系统中内在关系的模型构造,它能根据用户的输入数据产生与真实系统等效功能或几乎等效的输出。该模块最后将运算得出的仿真结果传给视景驱动模块。
  视景驱动模块则主要是将得到的仿真结果数据进行三维可视化的显示,它主要是在基于 Windows NT 的 Vega 系统平台上实现。本文以下将详细讨论的则仅限于这一部分的软件系统的分析与设计。

2 Vega 系统

2.1 Vega 简介

  Vega 是一种用于实时仿真及虚拟现实应用的高性能软件环境和工具。它主要包括两个部分:一个是被称为 LynX的图形用户界面的工具箱,另一个则是基于 C 语言的 Vega函数调用库。LynX 的主要功能是通过可视化操作建立起三维场景模型,并将其存在一个应用定义文件(.ADF)中,而后应用程序就可以通过调用 Vega 的 C 语言函数库来对已建好的三维场景进行渲染驱动。由于 Vega 起初是作为在 SGIUNIX 平台上的一个产品,后来才移植到 Windows NT 平台户在 Windows 平台上进行应用开发时会遇到一些难以预料的问题,而 Vega 的开发指南上对这些问题也未有提及,本文以下部分对这些问题作了分析,并一步一步改进至最终得到一个妥善合理的方案。
2.2 Vega 应用程序主框架

  对于 Windows NT 平台上的 Vega 应用,主要有三种类型:控制台程序、传统的 Windows 应用程序和基于MFC(Microsoft Foundation Classes)的应用。但无论是哪一种应用,建立 Vega 应用的三个必需的步骤为: 
  1) 初始化:这一步初始化 Vega 系统并创建共享内存以及信号量等;
  2) 定义:通过.ADF 应用定义文件创建三维模型或是通过显式的函数调用来创建三维模型;
  3) 配置:通过调用配置函数来完成配置设置完 Vega 系统后,就开始了 Vega 应用的主循环,主循环的作用是对三维视景进行渲染驱动。它主要分两步:
1) 对于给定的帧速进行帧同步
2) 对当前的显示帧进行必要的处理
以下显示了一个最小的 Vega 应用程序[1]:
main()
{
vgInitSys(); //初始化
vgDefineSys(“myapp.adf”); //定义
vgConfigSys(); //配置
while(bContinuing)
{
vgSyncFrame(); //同步帧
vgFrame(); //帧内处理
//application specific code
}
}
  这就是一个控制台的应用程序。既然是在 Windows 平台上开发,那么具有良好图形用户界面的应用程序将更受到欢迎。对于基于窗口的应用来说,Vega 系统通过提供一个窗口初始化函数调用来完成,即只要把上述的初始化函数vgInitSys()替换成 vgInitWinSys()即可。该函数通过获得窗口句柄来初始化 Vega 的显示窗口。3 基于 MFC 的 Vega 应用3.1 基于 MFC 的 Vega 应用的程序结构 考虑到 Vega函数是用 C++语言写的以及在 Windows 平台上进行开发,所以 VC++作为开发工具成为首选。在 VC++中的 MFC 类库[2]已是一个相当成熟的类库,特别是其基于文档/视结构的应用程序框架,已成为开发 Windows 应用程序的主流框架结构。

该框架结构能够将程序中的数据和显示部分进行有效的隔离,并能将一个文档与多个视进行对应。这种设计方法在设计模式[3]中被称为观察者模式(Observer),它的结构如图2 所示。它是一种对象行为型模式,定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并自动更新。 为了便于开发者能较容易的开发出基于 MFC 的 Vega应用程序,Vega 通过继承 MFC 中的 CView 类而派生出一个子类 zsVegaView。这个zsVegaView 类提供了启动一个 Vega线程最基本的功能,还以虚函数的形式定义了特定的应用要进行操作的通用接口,因此用户的应用程序只需从zsVegaView派生出新类并根据需要重载必要的虚函数即可。从设计模式的观点来看,它是采用了模板方法模式(TemplateMethod)。模板方法模式是一种类行为模式,其意图是定义一个操作算法的骨架,而将一些具体步骤延迟到子类中去,这是代码重用的一种基本技术。模板方法导致了一种反向的控制结构,即指的是一个父类调用一个子类的操作,而不是相反,它的实现依赖于对象特性中的多态性。
3.2 基于 MFC 的 Vega 应用的线程分析

  通过采用模板方法将用户开发一个基于 MFC 应用的工作量减少到最低,这是 Vega 系统提供的方法,其初衷是好的,但由于 MFC 类库并不是一个支持多线程访问的类库。从图 3 可以看出,用户派生的子类通过在 OnInitialUpdate()函数中调用基类的 runVega()来启动 Vega 线程,并将派生类的指针作为参数传给新启动的子线程,这恰是问题的所在。
  由于源代码中的类空间和线程运行时的线程空间是可以相互交迭的,也就是说不同的线程在运行时可以访问相同的类的实例——对象,而 MFC 类库本身设计时并未考虑到多线程访问,因此在线程间传递视类CView(根据“ISA”规则[4],它的子类对象也是一个 CView 对象)将是危险的,实践也证明了这一点。在单文档多视中在 Vega 线程中改变文档数据后通过调用函数 UpdateAllViews()来更新所有相应的视图时旋即出现了访问保护异常的错误。

  回头看一下前面的设计,用类 CView 的派生类来嵌入与 Vega 功能有关的功能代码,而这些功能代码中主要是启动 Vega 的三个主要步骤,模型渲染主循环和便于用户扩展的几个虚函数接口,其对于 CView 的关联仅仅是使用了CView 类所拥有的视窗口的句柄而已。这种设计其实违反了软件设计的基本准则:高内聚,低耦合[5-6]。为了既能保持由模板方法设计带来的好处,又能达到高内聚、低耦合的目标,最好的方法就是进行切割分离。将所有与 Vega 系统有关的数据和操作单独作为一个基类 CVega 来实现,并保持用户的扩展借口。这样用户只需从 CVega 类派生出自己的类并将视口的句柄作为参数进行传递就可以了。那么当 Vega 线程改变数据后如何通知各个视口及时的进行更新呢?好在Windows 平台的消息驱动机制,使得可以利用视口的句柄发送消息来实现。

3.3 基于 MFC 的 Vega 线程中的问题

  前面已经提到,Vega 应用程序可以通过应用定义文件(.ADF)来载入三维场景模型。在实际开发的应用中,当用户打开应用程序时,在不退出该应用的前提下进行三维场景切换是十分必要的。从 Vega 的应用程序框架中我们可以看出,框架主要由它的配置三步曲和渲染主循环构成。当用户已经打开一个场景后需要切换到另一个场景时,程序正处于渲染主循环中,本应只需用配置三步曲中的第二步导入另一个.ADF 文件再进行第三步设置或三步曲皆重新作过再进入渲染主循环即可,遗憾的是由于某些未知的具体原因(并非程序结构的实现技术问题)而行不通。由于 Vega 系统提供的是链接库形式的函数调用,没有源码,从设计软件系统的角度来看,这种为了保证整个系统的完整性,系统外部接口的一致性,系统内部更新的独立性以及系统内部实现技术的保密性等而进行的封装是完全有必要的。然而在保证以上特性的前提下,给开发用户提供必要的诊错手段也是必不可少的。在这方面 Windows 的 API 调用以及 MFC 类库等做的十分好,它们在许多操作的结果通常都返回一个值,以告诉用户是否成功或是失败还是出现异常,更完善的是 Windows系统提供了一个全局的函数调用 GetLastError()操作来获取每一步操作后的结果是否成功。从设计模式的观点来看,这是使用了单件模式(Singleton),它使得在程序的任何位置都能直接访问到该函数。而在 Vega 系统里却没有提供如此完善的诊错手段,它的大部分操作没有返回结果,因此用户只能认为此次操作成功了。然而一旦程序出现了问题,用户将会不知所措。而且,由于 Vega 将类的接口定义都“封装”起来了,用户所能获取的仅仅是一个可用来充当句柄的类的指针而已,一些与面向对象有关的继承、多态、消息操作[4]等皆无明显体现,这大概与 Vega 旨在最大限度的兼容现有程序的移植目标有关吧。

  若在 Vega 线程未退出时进行重新配置将导致失败,那么在切换场景时让正在运行的线程自然终止而重新开启另一个线程应该不会有错了。然而结果是令人沮丧的,出现一个地址访问保护的错误。导致这种错误的主要原因是引用的无效的指针,该指针所指的空间要么是分配空间后已被释放,要么是指向其它线程的局部地址空间。为什么会出现这种错误呢?在第二次初始化 Vega 系统时,前一个 Vega 线程不是已经终止了吗?所以一定存在遗留问题。经调试跟踪显示,问题就出在 Vega 配置三步曲的第一步 vgInitWinSys()上,该函数的作用主要是初始化 Vega 系统并创建共享内存以及信号量等,除此之外它还在后台做了一件重要的事情——就是又开启了一个 Vega 窗口子线程,该子线程根据传送的窗口句柄参数创建一个与该句柄对应窗口一相同大小的窗口二,并将它蒙(覆盖)在窗口一上,这是 Vega 开发指南上未有提及的,这样 Vega 系统的渲染窗口就可以嵌入到基于 MFC 的视口上了。该子线程的创建是必不可少的,但却没有终止它的办法——它是 Vega 函数内部创建的。又因为线程隶属于进程,只有进程结束了,所有的线程才全部退出。所以即使创建它的父线程已死,它却还“活”着,不过因此而保留了一些无用的参数如无效的指针等。而更糟的是当第二个线程启动时,函数 vgInitWinSys()却不再启动新的子线程而是继续保留原来没死的窗口线程,因此错误的发生是注定的。

3.4 基于 MFC 的 Vega 应用的进程解决方案

  由 Windows 线程的一些特性可以得出解决问题的有效方法就是进一步隔离——用一个单独的 Vega 进程来实现。从模式的观点来看,这可以作为一个新的设计模式应用于软件的设计当中去,不妨称之为栅栏模式(Fence)。它通过将同一个应用中的不同部分进行相互黑箱化,彼此之间仅通过定义好的接口进行访问,这使得各部分之间的相互影响减少到最低限度。从操作系统平台考虑,Windows 不是一个实时操作系统,但却是一个较稳定的系统。由于 FVSS 系统是一个实时的虚拟仿真系统,在它的视景驱动模块中将处理大量的实时仿真结果数据,如何保证该模块中的两个进程之间能快速有效地进行通信将成为问题。

3.4.1 Windows NT 上的进程间通信

  在 Windows NT 平台上,进程间通信的主要方式有:动态数据交换(DDE),网络动态数据交换(NetDDE),Windows套接字(Windows Sockets),命名管道(Named Pipes),内存映射文件(Memory-Mapped Files),NetBIOS,远程过程调用(RPC)以及磁盘文件等。在特定的场合下,应选用适当的通信方式以最佳地满足应用性能上的要求和便于功能上的实现及扩展。在视景驱动模块中,由于 Vega 进程和 MFC 主进程将运行在同一台 PC 上,所以主要应用于网络环境中 PC间的进程间通信的 NetDDE、Windows Sockets、NetBIOS 以及 RPC 皆不予考虑。而通过磁盘文件来进行数据交换显然是行不通的,这使得直接在内存中进行通信成为必要。因为在Vega进程和MFC主进程间除了要传递大量的视景驱动数据外,还要进行一些控制信息的传递,鉴于实现的快捷性、进程通信速度上的性能要求以及便与功能的扩展等方面的综合考虑,采用内存映射文件的方式将是最佳的。

3.4.2 内存映射文件

  在Windows NT中的虚拟内存管理器为每一个进程都提供了它自己的虚拟地址空间,而且各个进程间的虚拟地址空间是相互独立的。这使得每个进程只能在它自己的进程空间内运行,而不能直接访问其它的进程空间,这也是 WindowsNT 比较稳定的一个重要原因。但是进程间如何进行内存共享呢?Windows NT 通过提供一种称为文件映射对象[7-8]的内核对象来实现。在 Windows 的内存管理器中用于执行共享的基元叫做“区域对象”,在 Win32 API中它们被称为“文件映射对象”。区域对象在内存管理器中被用来把虚拟地址映射到磁盘页面上,不管它是在页面的文件中还是在应用程序想访问的其它文件中,它就像在内存中一样。一个区域可以被一个进程打开也可以被多个进程打开。区域对象可以被连接到在磁盘上已打开的文件(称为映射文件)或一串页面调度文件(以提供共享内存)上。由于区域对象可用来提供多个进程的共享内存,这样使得进程间交换数据能够在内存中进行,在速度上能够匹配应用。内存映射文件的结构[9]如图 4所示,其中的磁盘文件在进程间仅共享内存时无需存在。
3.4.3 基于 MFC 的 Vega 应用的进程实现

  在 Windows NT 平台上采用文件映射对象,使得 Vega进程与 MFC 界面主进程之间能够进行有效的数据交换。图5 显示了视景驱动模块中两个进程实现的流程图,图中略去了 MFC 主进程中的用户界面控制部分。 因为分离出来的 Vega 进程是一个控制台程序(尽管它还创建了自己的渲染窗口),所以它没有消息泵,不能通过消息机制响应应用要求。而 Vega 应用框架中的渲染主循环恰恰可以插入一些操作用来模拟一个消息泵,以完成 MFC的虚拟内存

的主进程的要求操作,这个被传递的消息同样可以放在文件映射对象中。反之,由于 MFC 主进程是一个窗口应用,它本身具有接收消息的功能,因此只需直接向其发送 Windows消息即可。图 6 显示了实现两进程间主要功能的内存映射文件中的数据结构。至此,通过 Windows 内存管理中的文件映射对象,不仅解决了两个进程间的共享数据和传输数据的速率问题,还提供了 Vega 进程一个虚拟的消息泵来完成主进程的要求操作。
4 结论
   Vega 系统作为一种视景驱动软件平台,大量用于系统仿真和虚拟现实中。由于它本来是工作在SGI UNIX 平台上的,后来因为 Windows 系统的流行以及应用的需求而移植到 Windows NT 平台上的,这样 Windows 开发人员也能根据

需要开发出基于 Vega 系统的应用了。但与此同时由于 Vega的移植目标是最大限度的便于现有的 UNIX 程序的移植,而不是重新构架整个系统,所以在所难免的会出现一些问题。本文从面向对象的软件分析与设计,软件系统的分析与设计,可复用面向对象的软件技术——设计模式以及 Windows的核心机制等几个主要方面对基于 Windows NT 平台的Vega 应用软件的开发作了较详尽透彻的分析与设计,并最终根据提出的栅栏模式,采用 Windows NT 中的内存映射文件,给出了基于多进程的 Vega 应用的设计方案。虽然该设计方案是在开发FVSS系统中的视景驱动模块逐步分析和设计的结果,但由于它是在对 Vega 系统的透彻分析下得出的,所以具有广泛的通用性和实用性。
(完)
可打印版本 | 文章评论 | 我来纠错

|网友评论

    笔名:

    内容:

        

    |热点关注

    新歌 白狐 左边 拉拉爱 校园网 自由飞翔 感恩的心 边做边爱 为你写诗 北京欢迎你 范跑跑之歌 遇上你是我的缘 坏女人 N81 星星 火花 放生 不值得 手机网 分手那天 忘不掉的伤 电子杂志 网上展会 天使 城府 爱死了昨天 BT电影下载 N70 最后一次的温柔 小小 承诺 有没有人告诉你 N73 光荣 葬爱 大海 日不落 有缘人 躲避的爱 香水有毒 一定要爱你 求佛 爱在离别时 北极星的眼泪 假如 flash 歌曲 启示录 那滋味 独家记忆 放手去爱 丁香花 会呼吸的痛 音乐排行榜 爱上别人的人 对不起我爱你 N70 6300 会有天使替我爱你 DJ 爱上你是我的错 不要在我寂寞的时候说爱我 爱上你是一个错 爱你爱的好疲惫 怎么会狠心伤害我 MP3 音乐手机 拍照手机 智能手机 CDMA手机 怒放的生命 老人与海 等爱的玫瑰 爱情里没有谁对谁错 做你的爱人 感动天感动地 做我老婆好不好 你的承诺 王子 心碎 舍不得 摇啊摇 泪的告白 寂寞才说爱 擦肩而过 阿里阿里 中国基金网 新不了情 小情歌 离歌

    关于我们 - 版权隐私 - 友情链接 - 广告服务 - 项目合作 - 网站地图 - 联系方式

    ©Copyright by 86CG.COM, 2006-2008. All rights reserved 京ICP备06059503号