二、学问的传承

接下来我们要谈一谈学问的传承,具体地说就是中医这门学问的传承。我们在这里
用”传承”这个词,可以说比较的古典,一门学问要流传下去,它靠的什么呢?靠的就是
这个传承。所以,学问的传承是一个重要的问题。下面我们分两部分来讨论。

1.现代中医教育的模式

传承用现代的词语说就是教育,我们首先来看一看现代的中医教育。什么是现代中
医教育呢?这个”现代”的界限应该是中医高等院校建立以后的这么一个年限。中医高等
院校是1956 年开始筹办的,那么,到现在已经四十多年了。回顾中医所走过的这四十多
年的教育,我们能够看到她的一些利和弊。首先,从形式上来说,大家在这么一个高等
学府里学习,我们所采用的教育形式、教育方法,基本上与医科大学没有什么差别。大
家现在同时学两套,既学中,又学西,从在中医传承形式上大家想想有什么区别呢?没
有什么区别。所以,所谓的现代中医教育,实际上是模仿了现代医学的一种教育。现代
教育有些什么特色呢?教育这个问题是与学科相关的,学科的性质决定了教育应该采用
一种什么样的模式。前面第一部分,杨振宁教授曾谈到现代科学有别于传统文化的一个
很特殊的方面,就是它的数理逻辑体系,它的推演体系。这个逻辑体系是很严密的,而
且公理性很强,透明度很大,所以,在教育的时候就有很容易趋于接受的一面。另外一
个有区别的方面,就是现代科学是一门中介性科学,这一点我们前面已经多次谈到,它
是现代科学的一个非常显著的特点。中介具有储存的功用,具有复制的功用。人类的思
维,人类的智慧都可以聚集在这样一些中介体上,如电脑这样一个中介体。然后再由中
介来认识事物,改造事物,服务人类。所以,我们把现代科学叫做中介科学。有了这个
中介,就有了复制的可能。我们的电脑从”板块”设计出来以后,就可以批量生产。这就
是复制的过程,而不再需要我们一台一台地重新设计,重新制造。所以,它的复制性很
强,复制性就决定了它的规模性。现代教育之所以有这样大的规模,就是与现代科学这
个特性相应的。还有另外一个方面,就是现代科学的分科非常精细,这也决定了现代教
育的分科性极强的特性。近现代科学的这些特征,也充分体现在西方文化的各方面,比
如绘画艺术,我们看到西方的一幅油画,它给我们一种什么感受呢?它给我们一种实实
在在的感觉,比如画人体,它是裸露的,整个人体充分暴露在你的面前,有时甚至每个
毛孔也清晰可见,而反过来看中国画呢?中国画不画人体,展开一幅通常的国画山水画,
总给人一种烟雨蒙蒙、飘飘渺渺的感觉,就像《老子》说的”恍兮、惚兮,其中有象”。

用行家的话说,西洋画重写实,中国画重写意。在中医传承形式上大家想想有什么区别
呢?没有什么区别。所以,所谓的现代中医教育,实际上是模仿了现代医学的一种教育。
学现代教育有些什么特色呢?一目了然,一个朦胧可见。这就构成了中西文化的差别,
亦正是这样一个差别,促使我们去思考,中西文化的教育,中医西医的教育应不应该有
所区别?从现代中医的教育,我们看到她的分科愈趋精细,有的中医传承上甚至尝试将
一本《中基》划分为数个学科,将针灸也分为经的问题。络学、俞穴学、灸刺学。这种
学科的分化是否提高了原本这些学科的教学质量与教学效果呢?从规模上讲,中医教育
确实步入了历史上前所未有的时期,培养出了大批专科生、本科生、研究生。特别是现
在许多中医院校相继升级为大学,—规模上去了,教育的内涵上去了没有呢?这些年都
有大四的学生在实习前请我作讲座,讲什么题目呢?还是讲前面的”如何学好中医”,为
什么呢?因为他们对中医还是觉得困惑,还是觉得不清楚,不知道用什么去对付实习?4
年的时间应不算短,过去学医3 年出师,像蒲辅周那样,15 岁随祖父学习,3 年后即独
立开业行医。我们现在4 年了,还困惑,还糊涂,问题出在什么上面呢?是不是出在教
育的路子上?这是我们很自然就想到的原因。前面我们提到学科的性质决定教育的模式,
我们是否充分考虑了这个问题。

2.形而上与形而下

以下我们从内涵结构的角度继续上面的问题,现代文化明确地将世界划分为两个,
一个是物质世界,一个是精神世界。现代科学研究的范围主要限于物质世界,对精神世
界的东西涉及并不多,所以,唯物主义肯定物质是第一性,精神是第二性。那么,这个
物质世界又是属于一个什么范畴的东西呢?这一点我们可以暂时放下,而先来看一看古
人对这个世界的划分。在古代有这么一个”形而上与形而下”的区别,”形而上者谓之道,
形而下者谓之器”。所以,注意:这里的”形而上”界就分一个形而上,一个形而下,一个
道,一个器。什么是不是所谓的器呢?器就是有形质的东西,有结构的东西,所以,叫
做形而下。很显然,现代科学所探讨的物质世界,就是这个形而下的器世界。所以,现
代科学所探讨的范畴就是这个”形而下”的范畴。那么,什么是形而上呢?有形之上的东
西,那当然就是无形的东西了。这个无形的”形而上”的东西,就称之为道。道世界的东
西是否就是精神世界呢?这个问题还有待三思。但,至少在范畴上有相近的地方。上述
的这个区别,关键在于”形”。《素问》里面对这个”形”有很具体的描述,那就是”气合而
有形”,或者说”气聚而成形”。形是怎么构成的呢?气聚而成形,气合而有形。气聚合以
后就可以构成有形质的东西,形而下的东西,器世界的东西。那么,气还没有聚合以前
呢?这是一个什么状态呢?显然这就是一个无形的,形而上的状态。所以,按照上述的
这个划分,现代科学讨论的领域,实际上是气聚合以后的这个领域。比如物理学,她探
讨物质的结构、物质的组成。因此,就有基本粒子这样一个概念。物质是由什么构成的
呢?由分子,分子又由原子,原子又由原子核、电子,后面又有质子、中子、介子,介
子后面又是什么呢?是夸克!夸克是现代科学目前所发现的物质最微细的结构,尽管它
很微细,但,它仍属于形而下的这个层面。夸克这个名字起得相当幽默,它反映了科学
家们对寻找物质最终结构的一种心态。夸克原本是西方神话中的一种神鸟,这种鸟的叫
声就像”夸克”、”夸克”,这个鸟平常不轻易叫,它一叫太阳就要落山,大地就一片漆黑,
什么也看不见了。科学家们对现今发现的物质的最细微结构赋予了”夸克”的名字,看起
来他们不愿再寻找下去了,再寻找下去又有什么结果呢?太阳落山了,天黑了,什么也
看不见了。在这一点上,大家可以想象,如果按照这样一个模式去寻找物质的最终结构,
我们什么时候能够找到这个结构呢?看来遥遥无期!在这个遥遥无期,难有希望的问题
上,古人没有驻足,他只讲”夫有形者生于无形”,而不去追究这个最有形的东西,最形
而下的东西。所以,《老子》讲:”天下万物生于有,有生于无。”前面我们谈到现代科学
的研究领域大概属于”形而下”的范围,也就是”有”的范围。那么,中医呢?中医属于一
个什么范围呢?很显然,她既有形而上的成分,又有形而下的成分。她是道器合一的学
问。所以,《老子》也好,《内经》也好,都强调要形神合一,形气合一,要形与神俱。
所以,中医所探讨的,既有夸克以前的东西,又有夸克以后的东西。中医是不是一门道
器合一的学问,这一点有太多太多的证明,就以五藏而言,在五藏的心、肝、脾、肺、
肾中,我们不难发现,它有一个很重大的区别,就是肝、肾、脾、肺都有一个月旁结构,
而心没有这个结构。”月”这个部首,《说文》把它归在”肉”部,”肉”当然是有形质的东西。
所以,古人对肝肾脾肺的定位是非常明确的,它属于形而下这个范畴,属于一个形器结
构。那么,心呢?心就不同了,它没有这个􀁥心的定位不”肉”部,也就是说它没有这个”
形器”,它是形而上的东西,但是对中医而非形而下的东西。五藏的这个定位,不是一个
简单的定的定位,也不是一个轻松的定位,实在的,它是对整个中医的定位,是对整个
传是对整个传统文化的定位。这个定位我们也可以从五行的统文化的定联系中去认识,
像金、木、水、土这些都是有形有质的东西。这些东西都是往下走的,因为它有一个重
量,都受万有引力的作用,都属于器的范围。而火呢?惟独这个火,我们很难用形质去
描述;惟独这个火,你放开后它是往上走的,难道它没有重量?难道它不受引力作用?
这就是所谓的”形而上”,这就是道。在这一点上,大家可以想象,如果按照这样一个模
式去寻找物质的最终结构,我们什么时候能够找到这个结构呢?看来遥遥无期!在这个
遥遥无期,难有希望的问题上,古人没有驻足,他只讲”夫有形者生于无形”,而不去追
究这个最有形的东西,最形而下的东西。所以,《老子》讲:”天下万物生于有,有生于
无。”前面我们谈到现代科学的研究领域大概属于”形而下”的范围,也就是”有”的范围。
那么,中医呢?中医属于一个什么范围呢?很显然,她既有形而上的成分,又有形而下
的成分。这就是所谓的”形而上”,这就是道。现在我们知道了,中医光讲肝、肾、脾、
肺行不行呢?不行!还要讲心。所以,中医肯定是一门既讲形而下,又讲形而上的学问。
那么在这两者之间有没有一个轻重的区别呢?这个答案也是很明确的。我们看一看《素
问·灵兰秘典论》就可以知道,《论》中说:”心者君主之官,神明出焉。””君主”意味着
什么呢?我想大家应该很清楚的。而《灵兰秘典论》的另外一段话,也很值得引出来供
大家参考:”凡此十二官者,不得相失也。故主明则下安,以此养生则寿,殁世不殆,以
为天下则大昌。主不明则十二官危,使道闭塞而不通,形乃大伤,以此养生则殃,以为
天下者,其宗大危,戒之戒之!”从这个五藏的关系,从这个十二官的关系中,我们可以
看到,传统文化、传统中医,虽然的确是道器合一的统一体,虽然,它强调要形气相依,
形神合一,但是,总的侧重却在道的一面,神的一面,气的一面。所以,她是一门以道
御器,以神御形,以形而上御形而下的学问。有关上述这个侧重的问题,我们还可以用
一个更实际的例子来说明,就是医生的等级。《内经》里将医生划分为两个等级,即上工
与下工。上工指的是非常高明的医生;下工呢?当然就是非常普通,非常一般的医生了。
上工、下工怎样从更内在的因素去加以区别呢?《灵枢》在这方面给出了一个很具体的
指标,就是”上工守神,下工守形”。神是什么?神是无形的东西,属于道的范畴,属于
形而上的范畴,上工守的就是这个。换句话说,就是能够守持这样一个范畴的东西,能
够从这样一个层面去理解疾病,治疗疾病,那就有可能成为上工。反之,如果守持已经
成形的东西,从形而下的这样一个层面去理解疾病,治疗疾病,那只能成为一个下工。
所以,《素问·四气调神大论》说:”是故圣人不治已病治未病,不治已乱治未乱,此之
谓也。夫病已成而后药之,乱已成而后治之,譬犹渴而穿井,斗而铸锥,不亦晚乎!”
守神就是治未病,未病就是尚未成形的病,在未成形的时候你拿掉它,不是轻而易举的
事吗!等成形了,甚至等它牢不可破了,你再想拿掉它,那就不容易了,那就会吃力不
讨好。任何疾病的发生都是从未病到已病,从未成形到已成形。按照西医的说法,就是
任何一个器质性的病变都是从非器质性的阶段发展而来。在非器质性的阶段治疗是比较
容易的,而一旦进入器质性的阶段,治疗就困难多了。因此,为医者不但要善于治病更
要善于识病。疾病在未病的阶段,在未成形的阶段,你能否发现它,截获它,使它消于
无形。像扁鹊望齐侯之色一样,病还在皮肤就发现了,在皮肤就进行治疗,应该不费吹
灰之力。而张仲景为侍中大夫王仲宣诊病,提前20 年作出诊断,并提出相应的治疗措施。
这就是见微知著的功夫,这就是防微杜渐的功夫。等到晚期癌症了你才发现它,又有多
少意义呢?目前现代医学的诊断技术从总体上来说还是处于诊断已病的水平阶段,也就
是说这个诊断技术再先进,也只是诊断出那些已成形的病,对于未病,对于尚未成形的
病,现代的诊断还无能为力。但是,到了基因诊断,检查婴儿,甚至胎儿的基因,就能
发现将来的疾病,到了这个阶段,就应该是知未病了。所以现代医学从总体上说,还是
向传统中医这样一个方向发展。现在大多数人对中医的认识,都是从已病的这个层次上
去认识,都是从形而下的这个层次去认识。从这个层次上去认识中医,当然觉得中医处
处不如西医。我经常打一个比方,比如一个心梗的病人,心梗发生了,你会往哪个医院
送呢?是往中医院送,还是往西医院送?我看100 个人会有100 个人要往西医院送,也
许就是张仲景再世,他也会建议你送医科大附院,而不送中医学院附院。凭着这个,搞
西医的人个个挺胸抬头,搞中医的人个个垂头丧气,以为中医确实糟糕,自己入错了行。
如果这样比较,那中医确实不怎么样,要甘拜下风。但是,如果我们换一个角度去思考,
我治的这个病人,我治的这个冠心病,根本就不发生心梗,乃至根本就不发生冠心病,
我是使它不发生,你是发生了以后去救治,这两个如何比较呢?对社会,对国家,对家
庭,对患者个人,哪一个更有利益?我想100 个人里,也会有100 个人是赞成我的。如
果我们从这样一个角度去比较,也许中医的认识我们就会有信心。中医讲究治未病,张
仲景在《金匮要略》的开首就指出”上工不治已病治未病”,我们这门医学
的出发点,它的宗旨是治未病,是未渴而穿井,未斗而铸锥的,可现在许多人偏偏要在
已病的行列跟西医较劲,搞什么中医急救医学,这就叫做不自量力,这就叫做以己之短
击人之长。渴而穿井,斗而铸锥,你怎么可能和现代的速度相比真实吗?所
以,上面这个问题是一个十分严重的问题。中医是这样的一门医学,它整个地是偏向于
形而上的一面,是以形而上统形而下,是以治未病统治已病。而我们现在却在完完全全
地用形而下的眼光去看待它,把它当作一门完完全全的形而下的学问,治已病的学问。
我们提倡科研,提倡现代化,提倡现代中医教育,完全就是用现代科学这个”形而下”的
筛孔去对中医进行过滤,滤过去的是”精华”,是可以继承的东西,滤不过去的东西,就
是”糟粕”,就要扬弃掉。大家想一想,这个通不过筛孔的部分是中医的哪一部分呢?必
定是形而上的这部分。对上述问题我们思考清楚了,我们就会发现,原来我们所采用的
现代教育方式,我们所采用的现代中医教育路子,只是一条培养造就下工的路子!大家
也许不会同意我的看法,认为这太偏激。但是,我们需要解释,为什么用这个模式培养
出来的学生对中医没有多少信心?为什么临床医生碰到一点困难不在中医里想办法,而
急着上西药?中医里有许许多多的办法,不是开两剂药就了事。除了时代造成的客观因
素外,我们怎么去解释当前中医的这个现状?我想原因不外两个,一个就是教育上、传
承上出了问题;一个就是中医自身的问题。可是,只要我们回顾历史,看一看这些有成
就的医家,我们就会发现,问题并不出在中医身上。

2.杨振宁教授所认识的中国文化

1999 年12 月3 日,著名物理学家、诺贝尔奖获得者杨振宁教授应香港中文大学之
邀,于新亚书院举办了一个题为”中国文化与科学”的讲座。在这个讲座中,杨教授用了
相当长的篇幅来阐述中国文化的特征。杨教授是公认的20 世纪最伟大的物理学家之一,
在传统文化方面也有相当的造诣。所以,他对传统文化的看法应该具有相当的代表性和
影响力。杨振宁教授对中国传统文化的认识,可以归结为以下几个方面:

第一,传统文化是求理,而近代科学(包括现代科学)是求自然规律。传统文化所
求的理并非自然规律、自然法则,而近代科学追求的是自然规律。这样一种划分就使传
统文化与近(现)代科学泾渭分明了。传统文化求理,不求自然规律,那么,这个理又
是什么呢?杨教授解释这个”理”就是一种”精神”,一种”境界”。那么,这个”精神”,这
个”境界”又是指的什么呢?难道科学没有精神,没有境界吗?

第二,杨教授认为在传统文化里只有归纳的方法,而没有逻辑推演(或称演绎)。大
家知道,在科学体系里进行研究,需要两种方法,一个是归纳,一个就是推演。所谓归
纳,就是把许多现象归纳起来得到一个认识,一个定义,一个理论,把许多事物聚在一
点上,一个认识上。原来现象上看似不同,本质上却是这么相近。所以,归纳实际是由
外向内的一种认识。逻辑推演则是另一个重要的方法,这个过程非常严密,比如由一到
二,由二到三,这个次序只能这样。现代科学既有归纳,又有逻辑推演,而逻辑推演是
它的标志。中国文化里只有归纳却没有逻辑推演,这又将传统与现代区别开来了。

第三,传统文化里缺少实验,缺少自然哲学。在很多场合,许多人都认为中医与其
说是一门自然科学,倒不如说是一门自然哲学。而杨教授在讲演中却以中医为例,认为
传统文化中缺少自然哲学,这显然与许多人的观点相左。在现代科学领域里,实验是非
常重要的,离开实验几乎寸步难行。即便是审视科学的部门也是如此。当年我读博士的
时候,管理博士这一层次的机构就有个不成文的规定,就是除了文献博士外,其余的都
要搞实验研究。所以,我这个博士算是侥幸得的,因为我并没有做实验研究,这要得益
于我的导师。在中医历史里没有实验,我们没有看到黄帝问岐伯,你的阴阳理论是怎么
发现的?是不是通过小白鼠实验发现的?确实没有。所以在中医乃至其他传统科学里没
有现代意义上的实验,这是合乎实际的。以上就是杨教授对中国文化的大体认识。

3.传统理论的构建

这一小节的内容主要是杨教授的上述认识是具有代表性的,但是不是很正确就传统
文化呢?是否真正表述出了传统文化的内涵呢?这一点我有不与中医的问同的看法。传
统文化虽然有许多分支领域,但是,中医是最题跟杨振宁具代表性的。下面就以中医为
例,次第讲述我的观点。教授商榷。

(1)何为理?首先,我们要来认识的问题就是什么是”理”。传统文化孜孜以求的这
个”理”,是不是仅仅是一个精神和境界的问题,还是包括了精神和境界?我们可以先从
文字的角度来考究这个问题。《说文》曰:”理,治玉也。”所谓治玉,也就是雕刻玉。玉
石开采出来以后,经过我们的琢磨,经过我们的精雕细刻,形成我们所需的形状,形成
一个艺术品。所以,理的原意,是指的这样一个过程。在古人眼里,所有的物质中最致
密的东西是什么呢?是玉。为什么玉看起来很冰清、很细腻呢?就是因为玉的纹理非常
细润的缘故。大家知道历史上有一个庖丁解牛的故事,庖丁解牛,目无全牛。为什么呢?
因为他非常熟悉牛的理,每一块肌肉的走向他都非常清楚,顺着这个走向、这个纹理去
解牛,既快捷,又不费刀。那么,玉的理当然就要比牛的致密多了。所以,治玉更要加
倍地细心,更要清楚这个理,顺着这个理去琢磨,去雕刻,就可以弄出我们所喜欢的艺
术品,要是逆着这个理去雕刻,玉就会被损坏。理的原意就是这样。引申出来呢,就是
你要这样走才行得通,那样走就行不通,为什么呢?因为理在这里发生作用。大家想想
看,这样的理不是自然规律又是什么呢?自然规律,自然法则是不能违背的,违背了就
行不通。俗话说:有理走遍天下,无理寸步难行。理的意义就在这里。你要顺着走,路
子才走得通,这就是理。人理也好,天理也好,自然之理也好,都是这样。自然之理就
是我们要顺着这个理与自然相处,才行得通;人理就是我们如何跟人相处才行得通。所
以,理不光是精神和境界的问题。理是一个很实在的东西,是看得见、摸得着的。你这
样走就行,那样走就会碰壁。而精神有时是虚无的、飘渺的,没办法把握的。我们说在
中医里面,更显得上面这个理、这个规律、这个法则的重要,而这个理、这个规律、这
个法则是什么呢?就是阴阳四时!所以,在《素问·四气调神大论》里说:”故阴阳四时
者,万物之终始也,死生之本也,逆之则灾害生,从之则苛疾不起,是谓得道。”这里为
什么要用”得道”这个字眼呢?这是一个很有趣的问题。得道这个词在古人那里用得很多,
得道可以升天,连天都可以升,还有什么不能的呢?那因为什么得道呢?因为你明白了
这个理,顺着这个理走,当然就得道了,当然就步入坦途了。现在的宇宙飞船为什么能
够升天呢?不就是因为我们弄清了相对论这个理吗?所以,这个理,这个道,这个道理
都是很有义趣的词语,古如是,今亦如是。

(2)归纳与推演的结合  传统文化的建立是不是只有一个归纳呢?这一点上我也是不
同意的,在《素问·上古天真论》里明确指出:”上古之人,其知道者,法于阴阳,和于
术数。”这里的知道者,也就是得道者。得道者,当然必须是明理者。这里的理包括两个
方面,一个是阴阳,一个是术数。所以,这就有两个问题,阴阳表示的是归纳,《素问·阴
阳应象大论》说:”阴阳者,天地之道也,万物之纲纪,变化之父母,生杀之本始,神明
之府也。”这里将天地万物,将一切事物的变化、生杀都归结到阴阳里,所以,就归纳的
角度而言,天下没有比阴阳更完美的归纳法。那么,术数呢?术数所表述的显然就是推
演的一面,显然就是传统意义上的逻辑的一面。谈到推演和逻辑就必须联系数学,所以,
杨教授认为在中国古代没有数学产生,只是到16、17 世纪西学传入后,才有了数学的苗
子。而真正意义上的数学则到20 世纪才有,这要以清华、北大于20 世纪初开设数学课
程为标志。那么,中国文化里究竟有没有数学呢?答案是肯定的,术数就是关于数学的
学问。《四库全书总目》在谈到术数的定义时,有下面一段文字:”物生有象,象生有数,
乘除推阐,务究造化之源者,是为数”当然,这并不是现代意义上的数理逻辑系统,但是,
它属于推演的部分却是可以肯定的。所以,要想成为知道者,要想真正把握传统这门学
问,就既要把握阴阳,又要明于术数。因此,传统文化是归纳和推演的结合,二者缺一
不可。

(3)理性思考与内证实验  传统文化里没有实验,这个问题杨振宁教授只说对了一半。
确实,在中国文化里我们看不到像现代的这样一类实验研究。就医学而言,运用人体以
外的东西,如用大白兔、小白鼠或其他动物所进行的一系列实验,这样的实验的确没有。
但是,在传统文化里,存在很细微、很精深的内证实验,却是不可否认的事实。正是因
为这个内证实验和理性思考的结合,才产生了传统文化,才构建了中医理论。当然,内
证实验这样一个问题确实不容易说清楚,为什么呢?任何一个有因为这个内证实验不是
摆在我们面前的小白鼠,你看得见,志于研究中摸得着,它完全是通过自身修炼来实现
的一种能力。一旦医的人所不具备了这一能力,就可以自在地进行各种有别于在机体之
能回避的。外进行的各种实验。所以,这个问题不好谈,但是,不谈不行。如果讲传统
文化回避了这个问题,那么,我们就要按照上面的路子理解传统文化。这就会出现两种
情况,要么,中医是不具备理论结构的经验医学,要么,中医的理论是仅凭思考得出来
的结果。大家可以想想,光凭一个思考得出的理论,值不值得我们完全地去信受?大家
也可以想想,中医的许多理论,中医的许多事实,光凭一个思考行吗?比如经络、穴位
这样一些东西,你能够思考出来吗?比如风池、风府这个问题,你凭什么思考可以得出
这样一个特定的穴位要叫做风池、风府?你凭什么思考出少阳经是这样一个循行,太阳
经又是那样一个循行?我想无论你如何聪明,这些东西也是思考不出来的。不信,你就
思考出来一个看看。显然,如果没有内证实验的参与、没有非常精微实验的参与,是不
可能的。所以,我们完全有理由相信,在传统文化,特别像中医这样的学问,在其理论
的构建过程中,是既有思考,又有实验的。传统文化中没有实验的说法是站不住脚的。
我们只有理由来区分内证实验与现代的外证实验,而根本没有理由来否定内证实验。这
个问题不应该含糊。因此,理性思考和精微实验是传统的基础,在这个基础上建立起来
的理论是完全可以信受的。问题在于为什么现在很多人都不认为传统文化里有试验。因
为我们很难想象内证实验是个什么东西,比如说经络,李时珍曾经说过,经络隧道,若
非内视返观者,是难以说出道道的。内视返观是什么呢?内视返观就是典型的内证实验。
具备这个内证能力,经络穴位都是看得见的东西,可是在现有的科学实验那里看不见,
甚至动用最先进的科技手段也难以看见,那你完全可以不相信,所以,困难就在这里。
要进行上述的内证实验,需要主体具备一定的素养,一定的能力,在我们本身不具备这
种内证实验的条件与能力的情况下,你有没有这样一个直觉?科学也需要直觉。爱因斯
坦在很大程度上就是一个直觉的信奉者。离开直觉,科学研究就少了一条腿。我想在我
们许多人里,也许会有人具备这样一种内证的能力,也许一个也没有。但你相不相信呢?
这是学中医一个很重要的方面。有人问我,学中医需要什么条件?我想就是需要这个条
件,在你做不出来的情况下,你相不相信有这么一个存在?内证实验究竟是什么一个情
况呢?梁启超的一句话说得很好:”心明便是天理。”这也是杨振宁教授在讲座中引用过
的一句话。心明不是普通的心里明白,要获得这样一个心明是很不容易的。心明实在的
就是已经具备了内证实验的这么一种状态。心明就可以内视,就可以返观,经络隧道就
可以一目了然。你就可以进行内证实验的操作。为什么说这是内证实验呢?因为它不是
在人体外部进行的。大家知道,张仲景在《伤寒论》序言中提到过一本书,这本书的名
字叫做《胎胪药录》。过去认为,既然有一本《颅囟经》是讲小儿疾病的,现在又用一个
胎字,所以,《胎胪药录》自然应该是讲小儿用药的书。如采用现代的语言来翻译,或者
可以叫做《儿科用药全书》吧。但是,我们翻开历史就会清楚,东汉以前会不会有一本
专讲小儿用药的书呢?《神农本草经》只分上、下、中三品,而不分内科、外科、妇科、
儿科,就是到明代的《本草纲目》也只分木部、草部、石部、兽部,等等。所以,有这
些常识,就不应该这样来思维《胎胪药录》。那么,《胎胪药录》究竟是一部什么样的书
呢?胎,不是指胎儿,而是讲的胎息,是一种回复到胎儿时期的特殊呼吸状态。人一旦
进入到胎息的状态,心明的状态也就自然产生了,内证的条件也就具备了,这个时候内
证实验室就可以建立起来。此时,你对药物的感受是实实在在的,药物服下去以后,它
的气味如何,它先走哪一经,后走哪一经,在这些部位发生什么作用,这些都是清清楚
楚、明明白白的。所以,古人讲药物的气味,讲药物的归经,并不都是思考出来的,而
是真正试验出来的。所以,《胎胪药录》就是在能够进行内证实验的条件下,对药物在体
内运行作用过程的一个记录。因此,传统文化,特别是中医理论的构建,完全是在理性
思考与内证实验的结合下产生的。所以,光有思考,没有实验这样一种认识是不能接受
的。可以接受的是,中医确实没有像现代一样的外证实验。

(4)理论的运用 中医理论产生以后,它是怎么应用的呢?理论应用就有一个技术
问题。在现代科学领域里,我们可以划分为三大块,就是基础学科、技术学科、应用学
科。技术学科是什么呢?就是基础理论与应用之间的一个桥梁,一个中介。
为什么现代科学往往是科学技术并称呢?就是因为这两者的相互影响太大,有
些时候科学决定技术,有些时候技术决传统科学。比如物质结构的研究,没有
理论不行,但是要突破非中介科理论,没有高速度、高能量的碰撞机也不行。所以,科
学与技术是相辅相成的。但在传统文化里,有一个非常奇怪的现象,就是在理论与应
用之间,缺少一个现代意义上的技术,理论与应用之间没有中介,没有桥梁。我们看现
代医学,理论与应用之间有一个庞大的技术中介,整个现代科学的物理学、化学、生物
学都在为这个中介服务,这使得医学理论的应用变得非常方便。现在,医生很少再用望
触叩听去诊断疾病,而代之的就是上面这个庞大的技术中介,这是一系列的
理化检测手段。而中医呢?我们没有这样一个中介。理论的应用,理论价值
的实现,这一切都得靠我们去心领神会,靠我们自己去把握,这就带来
了很大的困难。所以,要谈传统文化与现代科学的差别,我想最大的差别就在这里。现
代科学里,理论与应用之间有一个技术中介来帮助实现理论的价值,而传统文化,特别
是中医,完全没有这个中介。理论的应用只有靠主体直接来把握,主体能够把握多少呢?
像现代科学这个技术的过程完全可以由科学精英来创造,而技术一旦创造出来,就可以
进行大批的复制,这个过程是可以由普通技术工人来进行的。钱学森搞原子弹,并不需
要他亲自去造原子弹,电脑的专家发明电脑后,也不需要他一台一台地去造电脑,技术
就可以帮助他们完成这个过程。所以,现代技术是很方便的东西,它可以帮助我们,使
再高深的理论都能够变成现实。所以,在现代科学面前,精英是可以复制的。但是,在
传统的领域里就没有这样一个方便。这样一个理论再好,如果你不能把握的话,还是等
于零。就像我们现在拿到相对论,我们能搞出什么明堂?大家可以想想,一个相对论摆
在你面前,你可以搞出些什么东西?我很难想象这个问题。如果你搞不出什么,是否就
能说这个相对论太落后,爱因斯坦太糟糕呢?所以,中医所面临的就是这样一个问题,
它落后就落后在这样一个环节上。并不是说它理论真正的落后了。因为历史上已经有非
常多的精英成功地运用了这个理论,成功地运用它造出了”原子弹”,造出了”计算机”。
所以,我们应该有一个很清醒的头脑,要好好地思考上面的问题。思考清楚以后,我们
就会发现问题出在什么地方,是出在理论的环节上,还是出在其他的环节上。通过上面
这些讨论,大家是不是能建立这样一个认识,中医这门学问,现在并不是理论出了问题,
并不是理论滞后于临床,实际上完全不是这么回事。中医的理论,你一旦进去了,你就
会有感觉,你就会有受用,怎么还会说她滞后呢?现在,如果我们有了这样一个共识:
中医的问题没有出在理论上面。既然没有出在理论上,那为什么会出现我们今天的这个
局面呢?这就需要从我们自身上去找原因。我们对中医理论的领悟如何?我们是否真正
把握了中医理论的临床运用?记得1987 年,我的师父曾经接治过一例血气胸的病人,患
者经过一周的西医保守治疗,病情不见缓解,仍高热不退,呼吸困难,左肺压缩2/3。
在这种情况下,西医只有求诸手术治疗。但,患者本人及家属并不愿放弃保守治疗的希
望,于是转而求治于我的师父。师父诊后,认为这是阳明病,属阳明不降所致,只要设
法恢复阳明之降,血气胸的问题就可以解决。于是处了玉竹120 克、陈皮120 克、白芷
120 克、大枣120 克,共四味药。服药以后出现大量腹泻,自觉症状迅速缓解,第四天,
体温恢复正常,治疗一周血气全部吸收,左肺复原。血气胸与阳明又有什么关系呢?看
来这完全是一个领悟和运用技巧的问题,而不是理论本身的问题。经典的这个理论不但
能够解决20 世纪的问题,而且能够解决21 世纪的问题。

作者:刘力红

对中医的心理。今年五月,我应邀参加一个中医学术研讨会,在会上就作了个”略说
中医的学习与研究”的报告,报告之后,一位与会的博士找我交谈,一方面对我在这样的
年代里还能用如此大的热情来研究经典、宣扬经典表示赞叹,另一方面,则是对我的行
为感到不解。据说在他们一帮中医博士里,已经绝少有人看经典,如果哪一位博士的案
头放上一部《黄帝内经》,那绝对是要被笑话的。博士的案头都是什么书呢?不可以不读
都是分子生物学一类的现代书。博士这个群体,无疑是个高层次的群体。在他们身上肩
负着中医现代化的使命,所以,读些现代的书是理所当然的。但为什么不愿读中医书尤
其不读经典的书呢?我想答案只能有一个,就是在他们的心目中,中医只不过如此,经
典只不过如此,难道还有什么更多的看头吗?我想与上述许多问题相比,这个问题显得
尤其严重。大家知道,博士这个群体,将很快、很自然地要成为中医这个行当的决策者、
领路人,等到这个群体真正当政的时候,中医会成一个什么样子呢?这是不难想象的。
所以,这样一个问题就不得不提出来,就是:我们现在看到的中医,我们现在认识的这
个中医,究竟代不代表真正的中医?我们现在在各类中医医疗机构看到的这些医生的水
平,究竟能不能代表中医的真正水平?中医的真正水平在哪里?中医的制高点在哪里?
在现代,还是在古代?对这个问题的不同回答,会形成对中医截然不同的认识。如果真
正的中医就是我们现在看到的这个样子,那我们值不值得花很多时间来学习她?值不值
得花毕生的精力去钻研她、实践她?我想首先我不会的!何必陷在这个死胡同里呢?花
去许多精力还只能做个配角。所以,我提出”如何正确认识”这样一个问题,就是希望大
家不要被当今的这个局面所迷惑,从而丧失掉对中医的信心。

一、树立正确的认识

1.理论认识的重要性

对中医的信念和感情,自然造就了我对中医有一种责无旁贷的使命,以为中医兴亡,
匹夫有责。这部书的写作,也许正是出于这样一种使命感和责任感。所以,很希望通过
这部书的写作,切实地为中医解决一些问题,特别是认识上的问题。这部书的写作,经
历了近十年的酝酿,应该说准备还是充分的。但是,真正要动笔了,却还是不知从何入
手。总觉得中医的问题千头万绪,哪一个更重要?哪一个更关键呢?在平常人眼里,中
医是治疗慢性病的,或者说西医治标,中医治本。什么是治本呢?实在的就是大病重病,
西医帮助渡过了急、危、重等诸道难关,然后让中医来收尾,让中医来调养。因此,说
到底,中医只能用来治一些死不了的病。而在另一些人眼里,中医只是啼鸣的公鸡。你
啼,天也亮;你不啼,天也亮。中医究竟是不是这么回事呢?我想解决这个认识,应是
一个关键。

(1)中医目前的状况 上述这样一个认识并不是偶然的,也不是没有根据的。在历
届毕业生中,有不少都喜欢到我这里来谈体会。他们很多人都有一个共同的感受,就是
在大学四年的学习里,对中医还是有热情、有信心的,很希望在毕业的一年里能有小试
牛刀的机会。可是一年的实习下来,他们几乎彻底绝望了,对中医的热情也所剩无几。
为什么呢?很重要的一个方面是他们在临床上所看到的中医,并不是他们原来所想象的
中医。中医无论在中医院还是西医院的中医科,都几乎成了一种装饰。搞中医的人对中
医没信心,稍微碰到一点难题,就急着上西药,或是在西医的常规治疗上,加一点中医
做样子。而真正想搞中医的人,在制度上又没有保障。记得我刚毕业的时候,在一家中
医院搞临床,这家中医院就有一条明文规定,发热的病人用中医治疗,如果三日内不退
烧,就一定要上西药。中医院会作出这样的规定,至今我仍不明白。为什么中医院不规
定,用西药退烧,如果三日退不下,就必须上中药呢?中医落到这样一个地步,不能不
叫人生疑。昨天,有一位即将临产的孕妇到我这里拜访,目的是在生产前来面谢我。在
她怀孕7 个月的时候,因为劳累的关系,出现腹痛、阴道流血等先兆流产症状。经过一
周的西医治疗,没有得到改善,又因为患者过去有过流产的历史,所以,心里特别害怕。
经友人介绍到我这里诊治。诊查舌脉之后,我给她开了黄芪建中汤,第一剂药后,出血
就减少了,三剂药下去,腹痛、流血皆止,而且胃口大开。事后,她将经过打电话告诉
在北方的母亲,母亲听说这件事后,第一句话就问:用中医行吗?

(2)中医理论是否滞后于临床 近十年里,中医界提得很多的一个问题,就是中医
理论滞后于临床的问题。对于任何一门科学而言,都是理论走在前面,实际运用慢慢跟
上来。有关这一点,我在后面还要详细谈。这几十年来,中医的局面为什么没有办法突
破?临床疗效为什么老是上不去?遇到高热降不下来,最后还得上青霉素。为什么呢?
为什么会造成这种局面呢?中医的理论已经形成两千余年,在这期间,没有大的突破、
大的变化,会不会是因为理论的落后已经不能为临床提供更多、更有效的指导了呢?中
医理论滞后于临床的问题便顺理成章地提了出来。大家可以思考,今天我们的临床落后,
我们治病的水平上不去,是不是因为理论落后造成的?我的看法完全不是这样。恰恰相
反,理论不但没有落后,在很多领域还大大地超前。这与其他传统学问有类似的地方。
近代著名学者梁漱溟先生提出:中国传统文化,如儒家文化、道家文化、佛家文化,皆
系人类文化之早熟品。我想中医的情况大抵亦如此,正因为其早熟,而且早熟的跨度太
大,乃至现代她仍不落后,甚至还超前。所以,在中医这个体系里,完全不存在理论落
后于临床的问题。你认为理论落后于临床,你认为理论在你那里不能指导临床,那我就
要问你:你真正弄通中医理论没有?对于中医的理论,对于《内经》的理论,你把握了
多少?有十成把握了没有?如果不到十成,二三成呢?如果连二三成都不到,有的甚至
搞了一辈子中医最后竟然还分不清阴阳,那你怎能说理论落后于临床?现在的人把中医
理论看得太简单了、太朴素了。因为太朴素,就有点像山里的农民。其实,朴素有什么
不好呢?朴素才是最高的境界,因为返璞才能归真!如果你还没有真正认识中医的理论
或者最多只是一种相似的认识,你怎么能说中医理论是超前还是落后呢?上述这个问题
是个很严重的问题,如果没有认识好,那导致中医今天这样一个局面的症结就不容易抓
到。我们今天看到的临床水平比较低下的状况是什么原因造成的?如果错误地把这个原
因归结到理论的落后,而去寻找理论方面的原因,那我们可能就会形成真正的倒退,真
正的落后!记得本科毕业后,我在附院搞临床。一次,接治一位女性肺炎患者,患者年
龄60 岁,入院体温39.5C,WBC 近两万,中性98%,右肺大片阴影,按照西医的看法,
这是一例重症肺炎患者。老年人患重症肺炎是很容易出危险的。但是,当初的我,初生
牛犊不畏虎,总想试试中医的疗效,所以,选择了中医治疗。经过辨证,属于肺热所致,
遂投清肺之剂。不料服药之后,不久即泻,始则药后2 小时泻,后渐至药后十余分钟即
泻。所泻皆似药水,入院三天体温丝毫未降,其他症状亦无缓解。按照院规,次日再不
退烧,就必须上西药。此时的我,心情比病人还要着急。遂匆匆赶到师父处求教,师父
听完介绍后,说这是太阴阳明标本同病,阳明热而太阴寒,阳明热需清,然清药太阴不
受,故服之而泻利。此病宜太阴阳明分途而治,方不至互相牵扯。内服仍守前方以清阳
明,外则以理中汤加砂仁,研末调酒加热外敷神厥以温太阴。我赶紧如法炮制,当晚近
9 时敷上,约过1 小时,继服上药,服后竟未再泻。次日晨查房,体温降至正常,一夜
之间,他症亦顿减。此病始终未用一粒西药,周余时间肺部炎症即全部吸收而出院。此
例病人给我的影响极深,使我于长长的十多年中,在遇到临床疗效不如意的时候,从来
没有怀疑过是中医的问题,是理论的问题。所以,对于理论是否滞后于临床这个问题,
我们应该好好地去思考。在遇到障碍的时候,我们会在自身的领悟上找问题,而不会去
归咎于理论。当然,如果问题真正出在理论上,确实是理论滞后了,我们亦不应死抱住
这个理论。但是,根据我的经历和观察,大多数情况下,问题并不出在理论上,而是出
在我们的认识上。

(3)20 世纪物理学发展的启示 有关上述问题,我还想从另外一个方面加以申说。
理论与实际运用,理论与临床的关系是非常明确的。有关这一点,我们只要回顾一下20
世纪物理学的发展,就会很清楚。19 世纪末,经典物理学已经达到了人们想象中的十分
完美的程度。人们也许认为,这就是解释世界的最终极、最和谐的理论。但是,时间一
跨入20 世纪,这种和谐就被打破了。随着1905 年狭义相对论的创立,以及后来的广义
相对论和量子力学的建立,人们对宏观及微观世界的看法产生了根本的改变。在认识上
的这个改变,导致了技术应用上翻天覆地的变化,从宇航技术、原子能技术,到微电子
技术,乃至我们今天所能感受到的一切变化,都无不与新理论的建立相关。在经典的物
理学框架里,宇航技术、原子弹,以及现代通讯技术,这些都是难以想象的。回顾刚刚
过去的这个世纪,我们切实感受到了理论的重要,理论确实制约着技术的应用与发展。
而这样的一种感受和经验,能否作为我们提出中医理论滞后于临床的理由呢?我想这个
理由应是双重的,正因为我们看到了理论的重要性,它制约着实践和技术的发展,所以,
更应该重新来评价我们今天的认识,重新来认识中医的理论。看看经典中医理论的包容
性究竟有多大?它的延伸性、超前性究竟有多大?它究竟还能不能给我们今天的临床带
来指导?而不应光看到她是两千年前的产物。如果这个理论的确落后了,的确不能适应
现代,那就要毫不犹豫地打破她,在中医这个体系里建立起”相对论”。如果这个理论根
本没有落后,如果在这个经典的框架里已然具足”相对论”、”量子力学”,那,我们为什
么一定要打破她呢?现在在中医界有一个怪现象,也是一个可怕的现象,就是对中医经
典的教育逐步在减弱。现在大多数中医院校都已将经典改为选修课,就连成都、南京这
些老牌的、原本非常注重经典的学院亦不例外。这种改变是不是一种进步呢?很值得怀
疑。在我们没有建立起新的理论前,在我们还没有切实地发现传统理论的破绽前,经典
仍然是中医的核心,经典仍然是中医的基础,经典仍然是中医的必修。怎么可以将核心
和基础作为选修呢?有人说《中基》不是从《内经》里来的吗?《内科》不是从《伤寒》、
《金匮》里来的吗?而且比《内经》、《伤寒》、《金匮》更完善了,怎么不可以用它们来
取代经典呢?实在的说,《中基》与《内经》,《内科》与《伤寒》、《金匮》根本就不是一
回事,相差太远太远了,怎么可以同日而语呢?我想这个问题今后会有机会谈到的。好
比新的力学尚未建立,就将经典力学束之高阁,这是一个什么格局呢?大家可以思考。
理论需要实践来检验、来说明,这是普适的,东西方文化都是如此。在现代科学里,由
于许多杰出科学家的工作,理论的价值显而易见,如我们从费米的工作里可以充分体会
到量子理论的魅力。但在我们一般人那里,量子论、相对论又能起到什么作用呢?
所以,理论评价绝不是一件简单的事情。在中医的历史里,出现过许多成功运用经典理论
的人,比如张仲景,比如扁鹊。扁鹊运用经典理论成为起死回生的一代神医,而张仲景则
因为谙熟经典而最终成为医圣。我们是否可从扁鹊、张仲景及历代名医那里,看到经典理论
的价值,就像从费米及许多科学家那里领略到现代物理一样。

void AdjustPrivilege(int pid, BOOL bEnable)
{
    HANDLE    hProcess;
    HANDLE    hToken
=0;
    TOKEN_PRIVILEGES tkp;
    tkp.PrivilegeCount 
= 1;  
    tkp.Privileges[
0].Attributes = 0;
    
if (bEnable)
        tkp.Privileges[
0].Attributes = SE_PRIVILEGE_ENABLED;
    
if (LookupPrivilegeValue(NULL, "SeDebugPrivilege"&tkp.Privileges[0].Luid))
    {
        
if (hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid))
        {
            
if (OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken))
            {
                
if (AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, NULL))
                {
                    CloseHandle(hToken);
                }
            }
            CloseHandle(hProcess);
        }
    }
}

 

历史地看,中西医的产生与发展过程有着本质的不同,这种差异导致了这两种医学的认识论与方法论的根本性区别。

      中医理论是从人对自身乃至宇宙万物的生命及其能量流动的深刻体验出发的,中医治病的原理就是调整人身的能量动态使之归于平衡的常态,中医的一切理论都是围绕这种能量状态的消长变化展开的,理法方药莫不如此。如果偏离了这种观察思考的角度,处方用药必将变得毫无方向。所以中医学是以理论为第一要义的,经验只是在理论指导下的实践的积累。离开了中医的基本理论,即使用中药治病,也只能是简单的经验医学,不能归入中医纯粹的辨证施治体系了。

      西医是随着解剖学与化学的发展产生并发展起来的,由于起点远离了直接的生命体验,导致了其认识论与方法论的机械主义倾向。一般来说,西医是把人体当作一部机器对待的,西医的治疗方法除了作用于诸大系统的内科化学疗法外,外科的方法更像是木匠或裁缝工作。这样就忽略了作为一个生命体的个人的生命力的能量存在状态,及其作用于人的直接或即发性病理状态。由于其认识论与方法论的局限,导致了西医治疗学体系的重大缺陷——无法正确诊断能量状态的非常态客观存在。常见的现实如:病人能够很明显地感觉得到自身的某种不适症状,但经过西医病理检查,却被告知没有病,于是,对于病人来说很现实的客观症状就被歪曲成了一种主观错觉。还有诸如手术后病人已经死亡,却得出了手术非常成功的荒谬结论,等等。

      这些现象从本质上来说都是由于其认识论与方法论的先天缺陷造成的,中医的认识论与方法论的客观实在性与可验证性,证实了中医理论针对疾病治疗的正确性的同时,也从客观上弥补了西医方法论上的这种先天不足。可惜绝大多数自以为很了解西医很相信科学的明白人并不能清醒地认识到这一点,他们熟视无睹或有意回避其局限性,科学在他们的头脑中已经图腾为一种拜物教似的迷信。
  
中医治病是以十全(100%)为最终目的的,但由于客观因素的局限,能够达到十全的上工寥寥无几。西医是以十全六七(60%-70%)为目的的,只要整体上达到这样的有效率就算成功。

中医自古就是以个体行医的,治疗对象也是个体,不会等到凑全人数才开始治疗。西医是以群体行医,治疗是分组进行的,实际上同时是在拿患者作实验。中医的标准是上工,真正达标的医生很少。西医是以下工为标准,只要能够与整体相应就算合格。

中医是在进行治疗,西医是在进行实验。因为大多数中医不能真正代表中医,所以中西医之间其实一直没有机会进行真正的整体比较。个案相比,西医明显不敌,治疗结果相差悬殊。整体对比,中医又似乎处于劣势,统计结果无法验证。

从经营上看,中医是个体经营,西医是集体经营。中医是小农生产方式,西医是工业生产方式。两者的结果表现在规模与效果上(如所造成的污染)存在差异,两种结果的区别在于,前种方式更能够对作为个体的患者有利,后种方式更能够对作为集体的医生有利。所以,从治疗的效果看,中医是胜利者,而从经营的结果上看,西医是胜利者。其区别的本质表现在目的性上是,一个是以服务为根本目的,一个是以盈利为根本目的。一个是福利性质的,一个是商业性质的(至少目前在国内是这样表现的)。

但这些其实还不是最根本的区别,二者最根本的区别在于,真正能代表两种医学的治疗结果(权威的水平)的疗效的性质,即标本的差别,也即预后的情况。是真正的治愈了,还是只是掩盖一时,比如激素的短期效果与后期副作用。这种区别才是两种医学理论所指导下的医学实践的本质区别,而不是人为造成的错误之间的区别。

注:所谓权威的水平,指的是医术,而非资格或资历。当然,作为整体运作的西医之间的个体差异是不大的,就像机器的零部件一样,只存在合格与不合格的差别,是不太分别精品和次品的。

时间过得真快,还没感觉就两个多星期过去了。
在家歇了半年,现在渐渐的找到了以前的感觉。
退步了不少,思维也变慢了。
知识也变弱了。
要更多练习才行。
大家都进步了,感觉好不爽,努力学习,天天向上。
手里有这么多资料,要好好利用,要做国内最好的游戏团队。
加油!!

民主法治?为什么我没有投过票? 为什么贪官这么多?
公平正义?为什么贫富差距越来越大?为什么从没见过见义永为?
诚信友爱?为什么看到都是满街奸商?为什么看不到给老人让座?
充满活力?为什么火车站那么多乞丐? 为什么那么多人没钱看病?
安定有序?为什么杀人抢劫不断?为什么天天堵车?
人与自然和谐相处?为什么绿化面积在减小?



以上是个人观点与博客园无关。
看来要把博客移到国外去了。

本人做软件多年,一直与软件开发行业的各种级别的软件开发人才打交道,很多时候,

  还扮演面视考官的角色(很遗憾,本人还没有被面试过)。

  写下这篇文章,目的是区分各种层次的软件开发人员,也让软件开发人员能够对照自己,看看自己在什么层次。

  软件开发工作,其实是一种很复杂的工作,需要多方面的技能。我认为,尤其以学习能力和创新能力为主。所以,我以下对软件人才的层次划分,也围绕这两个能力展开。

  一、门外汉型:几乎没有学习能力,更没有创新能力。比如,买了一本《一步一步跟我学VB编程》之类的书,对照书上写的,把例子程序给做出来了,还把例子程序的某些窗口标题给修改了一下。然后,就自认为自己可以做软件开发工作了。到处递简历,应聘的职位为软件开发工程师。这类人,以刚毕业的计算机专业的大学生为多(当然,刚毕业的学生中也有非常高级的人才)。读书期间,就以玩游戏为主,考试的时候,就搞点舞弊过关。

  二、入门型:该类型的人员(不叫人才,所以叫人员),可能入门某一种到两种开发语言,10年前,我上大学的时候,这类人的典型特点是热衷于DOS命令的n种用法。比如,dir命令的各种参数。学习过basic语言,知道C语言中printf函数的各种参数的用法,到了2005年,这类人是热衷于windows下的注册表,热种于在自己的机器上安装各种开发工具(VB,VC,dephi,asp等)。但是,仅仅停留在编译开发工具中自带的几个例子程序中。(可能还会做点修改)。经过一段时间的学习,可能还自己能够编写个简单的windows应用程序,修改注册表的程序等等。其很多时间还是在玩游戏,上QQ聊天泡MM,看了一篇如何修改某病毒的文章,一定会对照文章上的说明,把病毒给修改了,然后到处发,以显示自己的能力。当然,很多时候,该类人即使对照文章的说明,也不能将病毒修改。那就找那些带配置工具的黑客程序去弄吧,比如。BO等就是他们最常用来炫耀的。中国的破解者与初级黑客,绝大部分是这一类人。懂的不多,还喜欢炫耀(为炫耀目的的破解和修改病毒就是这一类人的最大特点)。该类人员,一般都没有在软件公司从事软件开发工作。

  三、基本型人才:该类型一般是大学毕业,并且从事软件开发工作超过2年的人为多,至少比较熟悉一门语言(以VB,dephi,java,asp等其中的一种)。也有少数人熟悉C或者C++,但是如果是C或者C++,一般对指针等概念还是似懂非懂的状态。哦,对了,该类人员可能还会在自己的机器上安装过linux或者sco unix等。但由于对自己没有信心,大部分人会在半个月之后把linux删除。该类型人才,有一定学习能力。创新能力为零。适合培养成为软件蓝领,如果人际交往能力还可以的话,可以培养成为一个初级营销人员。该类型的人典型的特点是:你要他做个项目,他首先就会问:用什么语言?(因为用他不熟悉的语言对他来说,他就没有信心),该类人员,习惯看中文文档,不得以的情况下,才会看英文文档。另外,该类人员,喜欢购买软件开发类的书籍。该类人员,一般在软件公司从事软件开发工作,待遇在4000元到10000元以下为主。

  四、熟练工:该类型一般是毕业5年并一直从事软件开发工作,至少熟悉 VB,asp ,熟悉数据库,知道什么叫存储过程,什么叫触发器。知道软件工程管理的基本概念,如果做面象对象开发,可能还会用到Rose等工具。有过20人以下软件项目管理的经验。对于linux,至少知道是个开源的项目。由于做过比较大的软件项目,项目中带的小兵一般都不具备unix下的开发经验,所以,项目中难免会出现需要在unix下运行的代码,所以,就自己动手。用c编写过几段Unix下的小程序。学习能力比较强,该类人员,已经习惯看英文文档,有时候看翻译的别扭的中文文档会觉得不爽。干脆就找英文文档。该类人员,是否喜欢买书不得而知,如果喜欢买书,一般以非软件开发类书籍为主了。在技术选型方面具备一定的创新能力,至少,你叫他做一个报表程序,他会考虑用Excel的COM对象来实现。国内软件公司中的项目经理,绝大部分是这一类型的人才。待遇一般在6000到15000元左右。

  五、聪明型:该类人员的工作经历不重要,可以是还没毕业的学生,也可以是工作了10年的老鸟,1周内(甚至一小时)就熟悉了一门语言,并且可以开始用该语言开发,该类人员,由于学习能力很强,短时间内就熟悉了许多语言,即使从来没用过该语言,也敢于在该语言上进行软件开发,选择什么样的语言,不在于学没学过,而在于是否适合解决当前问题。对技术充满好奇与激情,举个例子,如果该类人员接触过linux,马上就会被Linux的魅力所吸引。即使与自己的工作无关,也会一回家就研究linux,可以肯定的是,该类人员的笔记本电脑上,肯定安装有linux ,并且,linux的启动次数和windows的启动次数一样多甚至更多。如果该类人员接触到了人工智能,至少会编写一个推理机程序来用用。另外,该类型人才的典型特点是学习能力超强。英语不一定很厉害,但是,不害怕看英文资料。该类型人才,许多并不是计算机专业毕业,可以是学数学的,物理的,音乐的等等都有可能。我就见过一个学英语的学生属于这种类型。该类型的人才,几乎所有的病毒代码是他们写出来的(不算那些修改病毒代码的人)。爱表现,也是他们的特点。如果该类人员在读书,那么,他们是软件公司青睐的人才,绝对不会出现简历递出三份还没有人要的情况,一旦进入公司,在半年内,其才能一定会得到公司领导的认可,并作为重点培养对象。为了留住这样的人才,软件公司一般会每听说有别的公司要挖他的消息就会给他涨工资一次。薪水的增长速度往往令同事红眼。

  六、技术天才型:该类人才,技术方面一流,如果只从技术方面的学习能力,创新能力来讲,都要超过以上的任何一种类型的人才。上帝造人总是很公平的,他们在技术方面是天才,往往其他方面几乎白痴,不善与人交往,甚至害怕与人交往。另外,某些东西对他们有致命吸引力,比如,有些人就迷恋自己做一个操作系统,有些人就迷恋人工智能。该类人员,不写软件则以,一写,肯定是一流的。全球一流。从语言来讲,因为他们几乎不用微软的开发工具做具体的项目,他们所看的技术资料,全部是英文资料,在网上交流的,全是操英语或者法语的人。即使是中国人,他们也习惯用英语与别人进行技术沟通。该类人才,如果在工作,一般是在某实验室,或者是在某基金的资助下开展研究,如果在软件公司,一定是主持举世瞩目的软件项目。或者,在自己开的小公司既当CEO又当CTO。由于其技术的优势明显,即使他不是一个很称职的CEO,也能让这个公司维持下去。

  七、数学家型:该类型人才,也许根本就不懂具体某种语言的开发(也可以懂),整天就研究算法。建模。一般不属于计算机专业。他们要把自己的成果变成现实,往往习惯找聪明型或者天才型人才帮他们实现。该类人员,因为不学计算机,所以,无法描述他们在学习技术方面的能力,但是,创新能力绝对一流。该类人才,没有在软件公司工作的,当然,如果其成果有一定商业价值,他们会成为某软件公司的顾问。或者干脆在某软件公司的实验室中当个主任什么的。

  八、比尔型:因为比尔的影响力巨大,所以,我们把具有一定软件开发能力,又有很强的商业运作能力的人归到这一类。比尔型人才,学习能力,在聪明型之上,在技术天才型之下。由于起社会知识面非常广泛,所以,知道什么软件能赚钱,怎么样做能赚钱。该类人写软件的目的只有一个,那就是赚钱,而不会太在乎采用什么样的技术。他们写软件,会极力迎合用户,迎合市场。

  对人的划分,有时候是很难的,有的人是跨类型的。但是,缺少创造的人,最多就到达熟练工型,具有超强创造力的人,可以达到技术天才型和数学家型,如果还有商业头脑,成为比尔型也是可能。最后一句话,如果你连足够的学习能力都没有,那么,就请你离开软件开发行业,另谋出路比较合适。

  这篇帖子,我首发在共享软件论坛,我认为,如果你不具备超强的学习能力,基本的创新能力和基本的商业能力,那么,就请你尽早不要做共享软件。

王国维---
古今之成大事业大学问者,必经过三种之境界:
“昨夜西风雕碧树,独上高楼,望尽天涯路。”此第一境也。
(注解:1。看清道路;2。说的是寻找“对象”。)
“衣带渐宽终不悔,为伊消得人憔悴。”此第二境也。
(注解:1。明确目标,重塑自我。执着,一往无前。2。说的是“死缠烂打”(男生追女生的惯用伎俩)。

“众里寻她千百度,回头蓦见,那人正在,灯火阑珊处。”此第三境也。
(注解:1。找回自我,圣人的境界。2。说的是“有情人终成眷属”。:))


以下是我从别处得来,然后按我的认知而思考,按我的经历而感悟,整理的10条信念,
很多道理大家比我更清楚,更有感悟些,只是我整理出来而已,个人见解,自然有片面、浅薄或误谬的地方,:)
有错有对,大家觉得对的,望大家共勉之:)
其实信念说起来容易,做起来也难:),但做时可以尽量往这些信条上靠近:)

事,说起来容易,做起来难
做技术容易,做人难;发展自己,做人尤为重要!
做人 = 丰富的知识
有能力,有做人原则,不看别人脸色;否则,只能微微侍从,处于被动。

理论与实际很有一段距离;至少计算机理论与实际开发应用技术是这样的:)
中国的教育很失败,孩子的成功教育,50%来自家庭教育,20%来自于你身边的人,30%来自学校的照本宣课教育
理想与现实有很大差距;
失败和成功取决于一个人的意志
成功 = 使自己成为最好的自己。当然,这里的“最好”,也是自己的标准。

1.人生的价值,在坚持不懈地努力学习中显现;不要追求完美,人生不可能有完美;

2.只有多读书,才能不断扩大知识面,人的知识面愈广博,视野愈开阔,
人的表达、社交能力也愈强,也愈加自信,人的各方面也愈臻完美。


3.知道如何停止的人才知道如何加快速度。

4.自助者,天助。
在你失败或处于困境时,能够帮助你,使你重新获得希望,重新看到光明的,只有你自己!
天助强者。

5.在任何处境中,都要保持着平静乐观的心态,保持着幽默。
只有热情和激情,才能赢得积极的人生和快乐的生活。
过分的生气或愤怒,不但于事无补,反而可能也会使事情或处境变得更糟


6.任何事情的发生,都有其必然的原因.
任何事情都没有绝对,塞翁失马焉知非福!

7.快乐不是因为你拥有多少,而是你计较多少!
不改变改变不了的,改变必须改变的。
知足者常乐;知足有时,也是懒惰、不努力的借口。
你能找到痛苦的借口,也会找到快乐的理由。


8.男人应该要有宽阔的胸襟,不要为了点点面子、虚荣和得失,生气或愤怒;
大地之博,承载万物,男人应像大地一样,用宽阔的胸襟、坚毅的品格承载一
切.

9.严于纪己,宽于待人。学会欣赏别人。
每个人的处境和经历不一样,人自然有强弱之分,不要小瞧弱者,在强者面前也不要看轻自己;
命,先天因素,无法改变
运,机遇缘分,要靠自己努力和争取

10.生命是一种过程。人生没有彩排,每天都是现场直播。
珍惜你的朋友,以及你身边每个人,珍惜每次聚会,因为你所经历的每次场景都不会重现。
人活着,要珍惜每一天,要快乐,因为每个人将会死得很久很久:)

 

双拳难敌四手,同样,一手难敌两手。玩魔兽,只用右手是没有用,我们还要学会用左手------键盘。
键盘操作相对于鼠标操作,更加快捷、方便、准确。我先从基础操作讲起,然后是进阶操作,再是更精细的操作。其他的就没有了,因为本人的水平只能到这里了,呵呵。

从基础说起:

一:基础操作:用键盘制造单位,释放魔法,使用物品。
  魔兽中的一切东西都可以用快捷键来完成,而鼠标只是起到一个定位的作用。比如,暗夜做小精灵,你可以用鼠标点击基地里精灵的头像,也可以直接按w;或者暗夜做月亮井,你可以选中小精灵,点建造,再点月亮井头像,也可以直接按b,m。具体的快捷键,你可以把鼠标移到具体单位的头像上,鼠标会弹出一些字幕,单位名称后面括号内的黄色字母,就是制造这个单位的快捷键。做到制作单位和建造建筑用键盘来完成,是操作的基础。
  重要的一点,魔兽里面,所有的魔法也同样可以用快捷键来完成。这就意味着你可以用键盘迅速的完成英雄魔法的释放。比如,人族的山岳放锤子,你可以用鼠标点击锤子的图标,再点击对方的英雄,也可以键盘按t,同时鼠标点中对方的英雄。魔法释放的速度也许就相差半秒或者1秒钟。但有时候,这半秒钟或者1秒钟就决定着比赛的胜负。
同样,在商店购买物品,也可以点击商店然后按下物品相应的快捷键。这样可以让你的英雄在靠近商店作战的过程中,迅速的购买物品(回程,血瓶,无敌,群疗或者飞艇),以很好的保护自己以及部队。
  同样,英雄物品的使用也可以用键盘来完成。物品栏对应的是数字键盘上前两竖排的按键。就是说,第一个物品可以按7来使用,第二个物品可以按8来使用,第三个物品按4来使用,以此类推。物品使用的快捷键也许有人觉得使用起来,还不如用鼠标来的快。但用键盘使用物品有些好处:1,使用数量类物品(例如药膏,飓风权仗),鼠标的点击显得烦琐,键盘使用更加快捷。2,在中后期混战中,很可能出现的情况就是你的英雄身上明明有血瓶或者无敌,但是用鼠标使用时出现点偏,误点而导致英雄的阵亡,而用键盘使用失误率更低。用键盘使用物品重要的一点在于,尽量把要使用的物品(例如血瓶,无敌,回程)放在靠物品栏左边一侧,毕竟按7,4,1比按8,5,2要好得多。
  另外,永远记住F1是你的首发英雄,F2是你的次发英雄,F3是你的三发英雄。这3个键可以让你迅速的选中你想要选中的英雄,在混乱的战斗中了解他们的位置。
一些你可能不知道的热键:
1,“~”,按它可以选中正在休息的农民。
2,“backspace”,按它可以迅速的切回主基地,并以主基地为屏幕的中心。这个热键在英雄回程救援的时候极为重要,你选中英雄,使用回程,再按backspace切回基地,选中自己回程的理想地点,回程(别忘了回程可以选择位置,不一定要点到主基地上才能回来)。这样总比在小地图上点击基地再选好位置要快一点。
3,insert和delete可以让屏幕右旋或者左旋,这样你可以点到一些正常视角点击不到的单位,比如人族探路的小动物往往躲在高大建筑的后面。
4,pageup和pagedown可以让视角下移或者上移,鼠标中键也可以做到。这样你可以近距离欣赏到你想看到的东西。
5,O键可以给英雄迅速的学习技能,做法是选中英雄,按O再按相应技能的快捷键,相当于鼠标点击那个“+”号。例如你的死亡骑士在红血的时候恰好升级,可以迅速的按下O、E学习死亡契约,再吃自己的单位,保证死亡骑士不死。
用键盘建造、放魔法、用物品,重要的一点在于摈弃以前用鼠标点击的坏习惯,强迫自己使用键盘。不断的使用着键盘以后,你会发现键盘的使用,已经成为了你的习惯,而且终会尝到使用键盘的甜头。
PS:A键,H键,P键,tab键的运用。
1,A键,攻击键。如果只用右键行军,部队只是移动到目的地,再进行攻击。而使用A键然后点目的地行军,部队行进过程中如果发现敌军,立刻能够停下攻击。另外如果你想杀掉自己或者队友的单位,同样是用A键攻击。
2:H键,保持原位键。例如对手的基地里海了箭塔,而你只能用攻城车攻击他的基地,你的对手往往会用几个单位出来诱引你的单位到他的箭塔群里去。这个时候你可以把你的非攻城单位用H键停留原位保护攻城车。
3:P键,巡逻键。例如你非要让不死的阴影在对手的主基地与分基地之间转来转去,可以先让阴影到主基地,按p再点分基地,你的阴影就会达到你的目的了。
4:TAB键,子组转换键。例如把死亡骑士和巫妖编入一个组队,而我想释放巫妖的霜冻新星。可以按tab,就发现状态栏里巫妖的头像凸现了出来,就可以释放霜冻新星了。

二:进阶的操作:alt、shift、ctrl的使用。
可以说,不使用这3个键的魔兽玩家基本没有入门,不掌握这3个键的魔兽玩家不能算是高手。个人认为,掌握这3个键的过程,是菜鸟成为高手的必由之路。下面介绍一下这3个键的作用。

㈠alt键:
①:按alt键的用途是观察屏幕上所有单位的血量。按下alt之后你可以发现每一个单位头顶上都出现血条,这使得你对敌我双方,哪个单位濒死一目了然。然后你应该做的是把自己濒死的单位向后拖动,然后想办法把对方濒死的单位弄死。记住操作的最终目的,尽可能多的保护自己的每一个单位,尽可能多的杀死对方的每一个单位。据说某高手在战斗的时候,是把一根牙签插在alt键上的。虽然略觉夸张,但这事仍然说明alt键事关重大。
②:alt键可以和其他键组合产生作用,最著名的莫过于alt+ctrl+del了,其次是alt+F4。但这些组合在魔兽过程中是不能乱按的。在魔兽中常用的组合是:
1,alt+G 在2vs2的时候很重要,用于在地图上发出一个警告。可以告诉你的盟友你在哪里需要帮助。
2,alt+A 改变自己、队友、敌人的颜色。尽量改变成模式3,即自己成为蓝色,队友成为绿色,敌人成为红色。以在战斗时能更加分清敌我,以免误伤。
3,alt+F 开关队型移动。在逃跑的时候,记住一定要把队型移动关掉,否者那些远程单位非要等到近战单位逃到前面才肯移动。

㈡shift键:
①:shift键的一个作用其实很简单,说白了就是让一个单位连续的做几件事。但这个作用一旦运用起来,就变得很有用处了。
1,让一个农民做完建筑之后自动回去采矿。做法是选中农民,下达建造命令以后按住shift,再用右键点击木头,或者金矿。
2,让一个农民连续做几样建筑。做法是选中农民,下达第一个建造命令,按住shift,下达第二个建造命令...类推。结合第1种方法,可以让农民做完一大堆建筑以后继续回去采矿。如果连续建造的是相同的建筑,比如人族的农场。你可以先把农民拖出,按住shift不放,按BF(农场快捷键),之后想做哪里就做哪里,往空地按就是,最后别忘了用shift把农民拉回来采矿。
3,连续向几个地方探路。做法是选中探路的单位,先点击第一个探路地点,按住shift不放,再依次点击你想让这个单位去的各个地方,放开shift。
4,让你的单位集中火力,连续攻击N个对方单位。这种做法的对象最好是远程单位。做法是用右键点击对方的一个单位,按shift连续点击你依次想攻击的单位。这样可以集中火力,造成对某几个单位的连续高伤害。但这种做法的弊端在于如果对方把正在被攻击的单位调开,你的单位仍然会继续追击,这样会受到更多对方其他单位的攻击。所以最好只是连续的点射2-3个单位就可以了。因为如果你一下下达点射12个单位的指令,一旦对手把他的单位调开,你又要重新下达新的指令。汗,你累不累啊。
5,让你的部队绕过野生怪物。经常战斗中出现的情况是你的单位集结在英雄身上,而在白天生产出来的单位往往要经过野生怪物的旁边,造成单位的受伤或者死亡。避免这种情况的发生可以使用shift键。做法:点击兵营,按住shift设置集结点,把集结点绕开野生怪物,最后点在英雄身上。之后,从兵营出来的单位会在N个集结点上先后走过,最后走到英雄身上。
6,骚扰之后全身而退。例如石像鬼骚扰,先点杀一个农民(或几个),再shift点击安全的地方。
7,其他用途。具体的很多想不起来。觉得最经典的就是暗夜的熊,先按f变回德鲁伊,shift+e加血,再shift+f变回。这个操作用shift可以在一秒内完成,可是如果你等熊变回德鲁伊,再按e加血,等加血后再按f变回,2-3秒的时间都过去了。
②:在一个队伍里添加或者剔除单位。
1,添加。想要把一个单位编入你正在控制的这个队伍,只要按shift在点击(框选也可以)想添加的单位就可以了。这个做法在编入刚制造出来的单位时尤其重要。
2,剔除。想要把一个单位剔除你正在控制的这个队伍,只要按shift在点击(框选也可以)想剔除的单位就可以了。
具体实例,在losttemple上,你的4个兽兵有1个濒死,可以先把这4个兽兵右键点击生命之泉,再按shift点中濒死兽兵(状态栏的图标也可以),把这个兽兵从组队里剔除,再控制其他3个兽兵做其他的工作(mf,骚扰之类),等这个濒死的兽兵满血以后再把它添加到组队里来。

㈢ctrl键:
①:ctrl键的第一个作用用于组队。例如想把7个火枪手和2个男巫编成一队,只要选中7个火枪手和2个男巫,按ctrl+1,就算是把火枪手和男巫编成一队了。接下来,按一次1是选中1队,按两次1是选中1队,并且以1队做为屏幕中心。魔兽里面最多可以有0、1-9十个编队。把每一个单位编入组队,是操作的基础。另外,建筑也是可以被编队的。例如把兵营编为5队,可以一边在战斗或者mf,一边按5做步兵或者火枪。
②:选中屏幕内相同的单位。例如想选中同个屏幕中的8个食尸鬼,可以按住ctrl再点随便一个食尸鬼,就可以全部选中8个了。但选中的单位,最多只有12个,因为一个组队只能容纳12个单位嘛,呵呵。
③:ctrl键的另外一个作用是传说中的“子组顺序修改键”。进入游戏后,“选项”内的“游戏性”里,把“子组顺序修改键”前面的方框点上勾,就可以用上这个功能了。这个功能的作用可以将同一个组队的不同单位分开工作。举个例子,不死前期使用骷髅权仗+食尸鬼mf,把食尸鬼和骷髅编在一个组队,而mf的时候我想把骷髅顶在前面,就可以先按TAB(因为骷髅的优先度比食尸鬼低)选中骷髅,按住ctrl移动到前面。你会发现只有骷髅在移动而食尸鬼是不会动的。

三:更精细的操作:M键,S键的使用。

㈠M键:本意是move,但是我们可以把它引申为包围。对,它就是传说中的包围键。要成为高手,就要把包围练到神乎其技。各位,努力了。
战场上的第一焦点永远是英雄,所以我们当然先说的是包围英雄。理论上,在没有地形的影响下,4个单位就能够把一个英雄围住。但是这种情况很少出现,所以我们一般用5个或5个以上的单位来围英雄。
围英雄基本的操作步骤(用6食尸鬼为例):1,先用右键移动食尸鬼到对方英雄的另外一边。2,当有2个食尸鬼超过了英雄之后,选中6食尸鬼,M到对方英雄身上。3,再按M,再点到对方英雄身上。4,再按M,再点到对方英雄身上。5,按A攻击对方英雄。包围时候最重要的一点,永远不要认为一次M键就可以把对方围住。[{当然,包围以后要注意再用A建进行攻击,HOHO,别忘记了哦。}]
然而如此明显的包围,对手很容易察觉,从而很轻松的跑掉。所以我们总是想尽办法来相对的减缓对方英雄的速度,来实施更轻松的包围。做法有:
1,固定对方英雄:山岳的风暴锤,牛头人酋长的战争践踏,萨满的净化,狼骑的诱捕,巫医的静止陷阱,丛林守护者的树根缠绕,猛禽德鲁伊的飓风,地穴领主的穿刺,恐惧魔王的睡眠和终极技能,地精修理匠的飞弹。
2,缓慢对方英雄:血法的虚无,女巫的缓慢,影子猎手的巫术,飞龙的浸毒攻击,守望者的暗影突袭(毒镖),树妖的毒素攻击,巫妖的霜冻新星,不死的霜冻塔,骷髅法师的残废,冰龙的攻击,哪伽的冰箭,熊猫的酒雾。当然,还有佩带了闪电球、毒液球或者霜冻球的英雄。
3,加快自己部队:兽族的加速卷轴,牛头人酋长的耐久光环,死亡骑士的邪恶光环(包括拥有天灾骨钟的英雄)。
还有一种做法就是利用地形。论坛上的一张恶魔猎手被一个月亮井和3个小弓箭手包围的图文战报相信大家都看过了,这是利用地形的一次经典包围。同样,我们也可以利用地图上的拐角,楼梯用更少的兵包围英雄。这就要让对手的英雄尽量的走旁边。举个例子,我们可以把自己基地的建筑只留最边上一个口子,假如对方的英雄要来骚扰,一定只能从建筑和树林的缝隙中进来,而我们可以利用建筑和树林,实施包围。
利用召唤单位包围也是一种好方法,具体怎么搞我还真不知道(众人倒:“你个菜鸟,不知道你说什么啊!”)。在战斗的时候,用3个兽兵先包围住你的一半,然后先知躲在一旁一放狼,英雄就正好被包围了。另外死亡之书,一下是召唤出8个单位,围一个英雄足够。这两种情况都是我在和别人对战时候遇到过的尴尬情况。当然我还有在骚扰人族开分基地时候,被民兵+水元素包围的情况,555~~~。
M键还可以用来反包围。当你的对手用固定或者减缓你英雄的方法想对你英雄实施包围,这个时候你可以把自己的单位包围英雄,然后在英雄恢复之后逃离包围。
包围普通的单位和包围英雄基本上是一样的,只是有些单位的体积比英雄小一点,包围的时候需要更多的单位。
一次成功的包围往往可以左右一场战斗的胜负。例如你的对手大军浩浩荡荡的开到你家来,而你家里只有一点点的兵力可守。如果包围对手英雄成功,既可以解决你的燃眉之急,又浪费了对手的一张¥350的回程。
在越来越多的人懂得把濒死的兵拖动以保护单位的现在,好好的练习包围吧。

㈡S键:本意是stop,但是我们可以把它引申为阻挡。
阻挡一个单位的基本做法是:1,让你的单位想办法跑到它移动路线的前面。2,把你的单位斜插到它的移动路线上。3,一旦走到它要移动的路线上面,按S。
接下来你要做的就是不停的推测它的移动路线,不停的占据它的移动路线,一边用鼠标控制你的单位的移动路线,一边狂按S。阻挡时候最重要的一点,永远不要以为你的单位能够恰好跑到它的移动路线上去阻挡它,要用S键控制你的单位在恰当的位置停下来,并用鼠标右键让你的单位继续移动。
有时候我认为,阻挡几乎和包围相同的重要。你的对手在觉得打不赢之后,往往控制大量黄血或者红血的单位逃跑。而你完全可以用一个或者几个单位阻挡住对手一个或几个单位,而等待自己的英雄过来释放魔法或者等待自己的单位过来包围。给我印象最深的两个阻挡,一个是一个人族用农民阻挡一个红血的5级先知,然后这个先知被大法师丢下4个火球而死,还有一个是xiaOt用2条3级隐形狼阻挡一个黄血的6级大法师,然后这个大法师愣是被2道闪电+2个震荡波打倒。
当然,S键的阻挡也可以用来掩护自己部队的撤退。当自己撤退而对手穷追不舍得时候,我们可以让一个速度快一点的单位(这样在掩护这个单位可以迅速的逃走),例如英雄、女猎手、狼骑士之类,阻挡对手部队,让他停止对你的追击,当然最重要的还是意识了。

最后祝大家每次都以一个快乐的心情去面对每一场比赛。

1.求下面函数的返回值(微软)


int func(x)
{
   int countx = 0;
   while(x)
   {
         countx ++;
         x = x&(x-1);
    }
   return countx;
}  

假定x = 9999。 答案:8

思路:将x转化为2进制,看含有的1的个数。

2. 什么是“引用”?申明和使用“引用”要注意哪些问题?

答:引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。

3. 将“引用”作为函数参数有哪些特点?

(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

4. 在什么时候需要使用“常引用”? 

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;

例1

int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确

例2

string foo( );
void bar(string & s);

那么下面的表达式将是非法的:

bar(foo( ));
bar("hello world");

原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。

引用型参数应该在能被定义为const的情况下,尽量定义为const 。

5. 将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?

格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }

好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!

注意事项:

(1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

(2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

(3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

(4)流操作符重载返回值申明为“引用”的作用:

流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。

例3

#i nclude
int &put(int n);
int vals[10];
int error=-1;
void main()
{
put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10;
put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20;
cout<cout<}
int &put(int n)
{
if (n>=0 && n<=9 ) return vals[n];
else { cout<<"subscript error"; return error; }
}

(5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

6. “引用”与多态的关系?

引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。

例4

Class A; Class B : Class A{...};  B b; A& ref = b;

7. “引用”与指针的区别是什么?

指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传ref和pointer的区别。

8. 什么时候需要“引用”?

流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。

以上 2-8 参考:http://blog.csdn.net/wfwd/archive/2006/05/30/763551.aspx

9. 结构与联合有和区别?
1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
2. 对于联合的不同成员赋值, 将会对其它成员重写,  原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。

10. 下面关于“联合”的题目的输出?

a)

#i nclude
union
{
int i;
char x[2];
}a;


void main()
{
a.x[0] = 10;
a.x[1] = 1;
printf("%d",a.i);
}
答案:266 (低位低地址,高位高地址,内存占用情况是Ox010A)

b)

    main()
    {
         union{                   /*定义一个联合*/
              int i;
              struct{             /*在联合中定义一个结构*/
                   char first;
                   char second;
              }half;
         }number;
         number.i=0x4241;         /*联合成员赋值*/
         printf("%c%c\n", number.half.first, mumber.half.second);
         number.half.first='a';   /*联合中结构成员赋值*/
         number.half.second='b';
         printf("%x\n", number.i);
         getch();
    }
答案: AB   (0x41对应'A',是低位;Ox42对应'B',是高位)

      6261 (number.i和number.half共用一块地址空间)

11. 已知strcpy的函数原型:char *strcpy(char *strDest, const char *strSrc)其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数,请编写函数 strcpy。


答案:
char *strcpy(char *strDest, const char *strSrc)
{
if ( strDest == NULL || strSrc == NULL)
return NULL ;
if ( strDest == strSrc)
return strDest ;
char *tempptr = strDest ;
while( (*strDest++ = *strSrc++) != ‘\0’)
;
return tempptr ;
}

12. 已知String类定义如下:

class String
{
public:
String(const char *str = NULL); // 通用构造函数
String(const String &another); // 拷贝构造函数
~ String(); // 析构函数
String & operater =(const String &rhs); // 赋值函数
private:
char *m_data; // 用于保存字符串
};

尝试写出类的成员函数实现。

答案:

String::String(const char *str)
{
  if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断
    {
      m_data = new char[1] ;
      m_data[0] = '\0' ;
    }
  else
   {
      m_data = new char[strlen(str) + 1];
      strcpy(m_data,str);
   }

}

String::String(const String &another)
{
   m_data = new char[strlen(another.m_data) + 1];
   strcpy(m_data,other.m_data);
}


String& String::operator =(const String &rhs)
{
   if ( this == &rhs)
       return *this ;
   delete []m_data; //删除原来的数据,新开一块内存
   m_data = new char[strlen(rhs.m_data) + 1];
   strcpy(m_data,rhs.m_data);
   return *this ;
}


String::~String()
{
   delete []m_data ;
}

13. .h头文件中的ifndef/define/endif 的作用?

答:防止该头文件被重复引用。

14. #i nclude 与 #i nclude "file.h"的区别?

答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。

15.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?

首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。

通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数

extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:

作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:

void foo( int x, int y );
  

该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。

_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。

同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

未加extern "C"声明时的连接方式

假设在C++中,模块A的头文件如下:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif  

在模块B中引用该函数:

// 模块B实现文件 moduleB.cpp
#i nclude "moduleA.h"
foo(2,3);
  

实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!

加extern "C"声明后的编译和连接方式

加extern "C"声明后,模块A的头文件变为:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif  

在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:
(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;

(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。

如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。

所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C++与C及其它语言的混合编程。  

明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧:

extern "C"的惯用法

(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"
{
#i nclude "cExample.h"
}

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

C++引用C函数例子工程中包含的三个文件的源代码如下:

/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif


/* c语言实现文件:cExample.c */
#i nclude "cExample.h"
int add( int x, int y )
{
return x + y;
}


// c++实现文件,调用add:cppFile.cpp
extern "C"
{
#i nclude "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}

如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。


(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。

C引用C++函数例子工程中包含的三个文件的源代码如下:

//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif


//C++实现文件 cppExample.cpp
#i nclude "cppExample.h"
int add( int x, int y )
{
return x + y;
}


/* C实现文件 cFile.c
/* 这样会编译出错:#i nclude "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}

15题目的解答请参考《C++中extern “C”含义深层探索》注解:


16. 关联、聚合(Aggregation)以及组合(Composition)的区别?

涉及到UML中的一些概念:关联是表示两个类的一般性联系,比如“学生”和“老师”就是一种关联关系;聚合表示has-a的关系,是一种相对松散的关系,聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系:

                         

从实现的角度讲,聚合可以表示为:

class A {...}  class B { A* a; .....}

而组合表示contains-a的关系,关联性强于聚合:组合类与被组合类有相同的生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系:

                           

实现的形式是:

class A{...} class B{ A a; ...}

参考文章:http://blog.csdn.net/wfwd/archive/2006/05/30/763753.aspx

         http://blog.csdn.net/wfwd/archive/2006/05/30/763760.aspx

17.面向对象的三个基本特征,并简单叙述之?

1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)

2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。

3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

18. 重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?

常考的题目。从定义上来说:

重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

重写:是指子类重新定义复类虚函数的方法。

从实现原理上来说:

重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!

重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

19. 多态的作用?

主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。

20. Ado与Ado.net的相同与不同?

除了“能够让应用程序处理存储于DBMS 中的数据“这一基本相似点外,两者没有太多共同之处。但是Ado使用OLE DB 接口并基于微软的COM 技术,而ADO.NET 拥有自己的ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于COM 体系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,这也就是说ADO.NET 和ADO是两种数据访问方式。ADO.net 提供对XML 的支持。

21. New delete 与malloc free 的联系与区别?
答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.

22. #define DOUBLE(x) x+x ,i = 5*DOUBLE(5); i 是多少?
答案:i 为30。

23. 有哪几种情况只能用intialization list 而不能用assignment?

答案:当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。

24. C++是不是类型安全的?
答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。

25. main 函数执行以前,还会执行什么代码?
答案:全局对象的构造函数会在main 函数之前执行。

26. 描述内存分配方式以及它们的区别?
1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

27.struct 和 class 的区别

答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和 class 在其他方面是功能相当的。

从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结构仅仅象一堆缺乏封装和功能的开放的内存位,而类就象活的并且可靠的社会成员,它有智能服务,有牢固的封装屏障和一个良好定义的接口。既然大多数人都这么认为,那么只有在你的类有很少的方法并且有公有数据(这种事情在良好设计的系统中是存在的!)时,你也许应该使用 struct 关键字,否则,你应该使用 class 关键字。  

28.当一个类A 中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk)
答案:肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。

29. 在8086 汇编下,逻辑地址和物理地址是怎样转换的?(Intel)
答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器内地址,就得到了真正要访问的地址。

30. 比较C++中的4种类型转换方式?

请参考:http://blog.csdn.net/wfwd/archive/2006/05/30/763785.aspx,重点是static_cast, dynamic_cast和reinterpret_cast的区别和应用。

31.分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。
答案:
BOOL :    if ( !a ) or if(a)
int :     if ( a == 0)
float :   const EXPRESSION EXP = 0.000001
         if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)



32.请说出const与#define 相比,有何优点?
答案:1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
     2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

33.简述数组与指针的区别?
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(1)修改内容上的差别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 字节
cout<< sizeof(p) << endl; // 4 字节
计算数组和指针的内存容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字节而不是100 字节
}

34.类成员函数的重载、覆盖和隐藏区别?
答案:
a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

35. There are two int variables: a and b, don’t use “if”, “? :”, “switch”or other judgement statements, find out the biggest one of the two numbers.答案:( ( a + b ) + abs( a - b ) ) / 2

36. 如何打印出当前源文件的文件名以及源文件的当前行号?
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。

37. main 主函数执行完毕后,是否可能会再执行一段代码,给出说明?
答案:可以,可以用_onexit 注册一个函数,它会在main 之后执行int fn1(void), fn2(void), fn3(void), fn4 (void);
void main( void )
{
String str("zhanglin");
_onexit( fn1 );
_onexit( fn2 );
_onexit( fn3 );
_onexit( fn4 );
printf( "This is executed first.\n" );
}
int fn1()
{
printf( "next.\n" );
return 0;
}
int fn2()
{
printf( "executed " );
return 0;
}
int fn3()
{
printf( "is " );
return 0;
}
int fn4()
{
printf( "This " );
return 0;
}
The _onexit function is passed the address of a function (func) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out) order. The functions passed to _onexit cannot take parameters.


38. 如何判断一段程序是由C 编译程序还是由C++编译程序编译的?
答案:
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif

39.文件中有一组整数,要求排序后输出到另一个文件中
答案:

#i nclude

#i nclude

using namespace std;


void Order(vector& data) //bubble sort
{
int count = data.size() ;
int tag = false ; // 设置是否需要继续冒泡的标志位
for ( int i = 0 ; i < count ; i++)
{
for ( int j = 0 ; j < count - i - 1 ; j++)
{
if ( data[j] > data[j+1])
{
tag = true ;
int temp = data[j] ;
data[j] = data[j+1] ;
data[j+1] = temp ;
}
}
if ( !tag )
break ;
}
}


void main( void )
{
vectordata;
ifstream in("c:\\data.txt");
if ( !in)
{
cout<<"file error!";
exit(1);
}
int temp;
while (!in.eof())
{
in>>temp;
data.push_back(temp);
}
in.close(); //关闭输入文件流
Order(data);
ofstream out("c:\\result.txt");
if ( !out)
{
cout<<"file error!";
exit(1);
}
for ( i = 0 ; i < data.size() ; i++)
out<40. 链表题:一个链表的结点结构
struct Node
{
int data ;
Node *next ;
};
typedef struct Node Node ;


(1)已知链表的头结点head,写一个函数把这个链表逆序 ( Intel)

Node * ReverseList(Node *head) //链表逆序
{
if ( head == NULL || head->next == NULL )
return head;
Node *p1 = head ;
Node *p2 = p1->next ;
Node *p3 = p2->next ;
p1->next = NULL ;
while ( p3 != NULL )
{
p2->next = p1 ;
p1 = p2 ;
p2 = p3 ;
p3 = p3->next ;
}
p2->next = p1 ;
head = p2 ;
return head ;
}
(2)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序。(保留所有结点,即便大小相同)
Node * Merge(Node *head1 , Node *head2)
{
if ( head1 == NULL)
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
Node *p1 = NULL;
Node *p2 = NULL;
if ( head1->data < head2->data )
{
head = head1 ;
p1 = head1->next;
p2 = head2 ;
}
else
{
head = head2 ;
p2 = head2->next ;
p1 = head1 ;
}
Node *pcurrent = head ;
while ( p1 != NULL && p2 != NULL)
{
if ( p1->data <= p2->data )
{
pcurrent->next = p1 ;
pcurrent = p1 ;
p1 = p1->next ;
}
else
{
pcurrent->next = p2 ;
pcurrent = p2 ;
p2 = p2->next ;
}
}
if ( p1 != NULL )
pcurrent->next = p1 ;
if ( p2 != NULL )
pcurrent->next = p2 ;
return head ;
}
(3)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。 (Autodesk)
答案:
Node * MergeRecursive(Node *head1 , Node *head2)
{
if ( head1 == NULL )
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
if ( head1->data < head2->data )
{
head = head1 ;
head->next = MergeRecursive(head1->next,head2);
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2->next);
}
return head ;
}

41. 分析一下这段程序的输出 (Autodesk)
class B
{
public:
B()
{
cout<<"default constructor"<}
~B()
{
cout<<"destructed"<}
B(int i):data(i)    //B(int) works as a converter ( int -> instance of  B)
{
cout<<"constructed by parameter " << data <}
private:
int data;
};


B Play( B b)
{
return b ;
}

(1)                                            results:
int main(int argc, char* argv[])      constructed by parameter 5
{                                     destructed  B(5)形参析构
B t1 = Play(5); B t2 = Play(t1);     destructed  t1形参析构
return 0;               destructed  t2 注意顺序!
}                                     destructed  t1

(2)                                   results:
int main(int argc, char* argv[])      constructed by parameter 5
{                                     destructed  B(5)形参析构
B t1 = Play(5); B t2 = Play(10);     constructed by parameter 10
return 0;               destructed  B(10)形参析构
}                                     destructed  t2 注意顺序!

                                     destructed  t1

42. 写一个函数找出一个整数数组中,第二大的数 (microsoft)
答案:
const int MINNUMBER = -32767 ;
int find_sec_max( int data[] , int count)
{
int maxnumber = data[0] ;
int sec_max = MINNUMBER ;
for ( int i = 1 ; i < count ; i++)
{
if ( data[i] > maxnumber )
{
sec_max = maxnumber ;
maxnumber = data[i] ;
}
else
{
if ( data[i] > sec_max )
sec_max = data[i] ;
}
}
return sec_max ;
}

43. 写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。

KMP算法效率最好,时间复杂度是O(n+m)。

44. 多重继承的内存分配问题:
  比如有class A : public class B, public class C {}
  那么A的内存结构大致是怎么样的?

这个是compiler-dependent的, 不同的实现其细节可能不同。
如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。
可以参考《深入探索C++对象模型》,或者:
http://blog.csdn.net/wfwd/archive/2006/05/30/763797.aspx

45. 如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针)

  struct node { char val; node* next;}

  bool check(const node* head) {} //return false : 无环;true: 有环

一种O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然):
bool check(const node* head)
{
   if(head==NULL)  return false;
   node *low=head, *fast=head->next;
   while(fast!=NULL && fast->next!=NULL)
   {
       low=low->next;
       fast=fast->next->next;
       if(low==fast) return true;
   }
   return false;
}

来源:http://www.azure.com.cn/article.asp?id=325

一、引言

计算机的出现使得很多原本十分繁琐的工作得以大幅度简化,但是也有一些在人们直观看来很容易的问题却需要拿出一套并不简单的通用解决方案,比如几何问题。作为计算机科学的一个分支,计算几何主要研究解决几何问题的算法。在现代工程和数学领域,计算几何在图形学、机器人技术、超大规模集成电路设计和统计等诸多领域有着十分重要的应用。在本文中,我们将对计算几何常用的基本算法做一个全面的介绍,希望对您了解并应用计算几何的知识解决问题起到帮助。

二、目录

  本文整理的计算几何基本概念和常用算法包括如下内容:

  矢量的概念

  矢量加减法

  矢量叉积

  折线段的拐向判断

  判断点是否在线段上

  判断两线段是否相交

  判断线段和直线是否相交

  判断矩形是否包含点

  判断线段、折线、多边形是否在矩形中

  判断矩形是否在矩形中

  判断圆是否在矩形中

  判断点是否在多边形中

  判断线段是否在多边形内

  判断折线是否在多边形内

  判断多边形是否在多边形内

  判断矩形是否在多边形内

  判断圆是否在多边形内

  判断点是否在圆内

  判断线段、折线、矩形、多边形是否在圆内

  判断圆是否在圆内

  计算点到线段的最近点

  计算点到折线、矩形、多边形的最近点

  计算点到圆的最近距离及交点坐标

  计算两条共线的线段的交点

  计算线段或直线与线段的交点

  求线段或直线与折线、矩形、多边形的交点

  求线段或直线与圆的交点

  凸包的概念

  凸包的求法

三、算法介绍

矢量的概念:

如果一条线段的端点是有次序之分的,我们把这种线段成为有向线段(directed segment)。如果有向线段p1p2的起点p1在坐标原点,我们可以把它称为矢量(vector)p2。

矢量叉积:

计算矢量叉积是与直线和线段相关算法的核心部分。设矢量P = (x1,y1) ,Q = (x2,y2),则矢量叉积定义为由(0,0)、p1、p2和p1+p2所组成的平行四边形的带符号的面积,即:P × Q = x1y2 - x2y1,其结果是一个标量。显然有性质 P × Q = - ( Q × P ) 和 P × ( - Q ) = - ( P × Q )。一般在不加说明的情况下,本文下述算法中所有的点都看作矢量,两点的加减法就是矢量相加减,而点的乘法则看作矢量叉积。

  叉积的一个非常重要性质是可以通过它的符号判断两矢量相互之间的顺逆时针关系:

  若 P × Q > 0 , 则P在Q的顺时针方向。
  若 P × Q < 0 , 则P在Q的逆时针方向。
  若 P × Q = 0 , 则P与Q共线,但可能同向也可能反向。

折线段的拐向判断:

  折线段的拐向判断方法可以直接由矢量叉积的性质推出。对于有公共端点的线段p0p1和p1p2,通过计算(p2 - p0) × (p1 - p0)的符号便可以确定折线段的拐向:

  若(p2 - p0) × (p1 - p0) > 0,则p0p1在p1点拐向右侧后得到p1p2。

  若(p2 - p0) × (p1 - p0) < 0,则p0p1在p1点拐向左侧后得到p1p2。

  若(p2 - p0) × (p1 - p0) = 0,则p0、p1、p2三点共线。

  具体情况可参照下图:

uploads/200704/08_110344_guixiang.gif


判断点是否在线段上:

  设点为Q,线段为P1P2 ,判断点Q在该线段上的依据是:( Q - P1 ) × ( P2 - P1 ) = 0 且 Q 在以 P1,P2为对角顶点的矩形内。前者保证Q点在直线P1P2上,后者是保证Q点不在线段P1P2的延长线或反向延长线上,对于这一步骤的判断可以用以下过程实现:

  ON-SEGMENT(pi,pj,pk)

  if min(xi,xj)<=xk<=max(xi,xj) and min(yi,yj)<=yk<=max(yi,yj)

  then return true;

  else return false;

  特别要注意的是,由于需要考虑水平线段和垂直线段两种特殊情况,min(xi,xj)<=xk<=max(xi,xj)和min(yi,yj)<=yk<=max(yi,yj)两个条件必须同时满足才能返回真值。

判断两线段是否相交:

我们分两步确定两条线段是否相交:

  (1)快速排斥试验

  设以线段 P1P2 为对角线的矩形为R, 设以线段 Q1Q2 为对角线的矩形为T,如果R和T不相交,显然两线段不会相交。

  (2)跨立试验

  如果两线段相交,则两线段必然相互跨立对方。若P1P2跨立Q1Q2 ,则矢量 ( P1 - Q1 ) 和( P2 - Q1 )位于矢量( Q2 - Q1 ) 的两侧,即( P1 - Q1 ) × ( Q2 - Q1 ) * ( P2 - Q1 ) × ( Q2 - Q1 ) < 0。上式可改写成( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) > 0。当 ( P1 - Q1 ) × ( Q2 - Q1 ) = 0 时,说明 ( P1 - Q1 ) 和 ( Q2 - Q1 )共线,但是因为已经通过快速排斥试验,所以 P1 一定在线段 Q1Q2上;同理,( Q2 - Q1 ) ×(P2 - Q1 ) = 0 说明 P2 一定在线段 Q1Q2上。所以判断P1P2跨立Q1Q2的依据是:( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) >= 0。同理判断Q1Q2跨立P1P2的依据是:( Q1 - P1 ) × ( P2 - P1 ) * ( P2 - P1 ) × ( Q2 - P1 ) >= 0。具体情况如下图所示:

uploads/200704/08_110648_huchi.gif


  在相同的原理下,对此算法的具体的实现细节可能会与此有所不同,除了这种过程外,大家也可以参考《算法导论》上的实现。

判断线段和直线是否相交:

有了上面的基础,这个算法就很容易了。如果线段P1P2和直线Q1Q2相交,则P1P2跨立Q1Q2,即:( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) >= 0。

判断矩形是否包含点:

只要判断该点的横坐标和纵坐标是否夹在矩形的左右边和上下边之间。

判断线段、折线、多边形是否在矩形中:

因为矩形是个凸集,所以只要判断所有端点是否都在矩形中就可以了。

判断矩形是否在矩形中:

只要比较左右边界和上下边界就可以了。

判断圆是否在矩形中:

很容易证明,圆在矩形中的充要条件是:圆心在矩形中且圆的半径小于等于圆心到矩形四边的距离的最小值。

判断点是否在多边形中:

  判断点P是否在多边形中是计算几何中一个非常基本但是十分重要的算法。以点P为端点,向左方作射线L,由于多边形是有界的,所以射线L的左端一定在多边形外,考虑沿着L从无穷远处开始自左向右移动,遇到和多边形的第一个交点的时候,进入到了多边形的内部,遇到第二个交点的时候,离开了多边形,……所以很容易看出当L和多边形的交点数目C是奇数的时候,P在多边形内,是偶数的话P在多边形外。

  但是有些特殊情况要加以考虑。如图下图(a)(b)(c)(d)所示。在图(a)中,L和多边形的顶点相交,这时候交点只能计算一个;在图(b)中,L和多边形顶点的交点不应被计算;在图(c)和(d) 中,L和多边形的一条边重合,这条边应该被忽略不计。如果L和多边形的一条边重合,这条边应该被忽略不计。

uploads/200704/08_111240_pointinp.gif


  为了统一起见,我们在计算射线L和多边形的交点的时候,1。对于多边形的水平边不作考虑;2。对于多边形的顶点和L相交的情况,如果该顶点是其所属的边上纵坐标较大的顶点,则计数,否则忽略;3。对于P在多边形边上的情形,直接可判断P属于多边行。由此得出算法的伪代码如下:

  count ← 0;
  以P为端点,作从右向左的射线L;
  for 多边形的每条边s
  do if P在边s上
  then return true;
  if s不是水平的
  then if s的一个端点在L上
  if 该端点是s两端点中纵坐标较大的端点
  then count ← count+1
  else if s和L相交
  then count ← count+1;
  if count mod 2 = 1
  then return true;
  else return false;

  其中做射线L的方法是:设P'的纵坐标和P相同,横坐标为正无穷大(很大的一个正数),则P和P'就确定了射线L。

  判断点是否在多边形中的这个算法的时间复杂度为O(n)。

  另外还有一种算法是用带符号的三角形面积之和与多边形面积进行比较,这种算法由于使用浮点数运算所以会带来一定误差,不推荐大家使用。

判断线段是否在多边形内:

  线段在多边形内的一个必要条件是线段的两个端点都在多边形内,但由于多边形可能为凹,所以这不能成为判断的充分条件。如果线段和多边形的某条边内交(两线段内交是指两线段相交且交点不在两线段的端点),因为多边形的边的左右两侧分属多边形内外不同部分,所以线段一定会有一部分在多边形外(见图a)。于是我们得到线段在多边形内的第二个必要条件:线段和多边形的所有边都不内交。

  线段和多边形交于线段的两端点并不会影响线段是否在多边形内;但是如果多边形的某个顶点和线段相交,还必须判断两相邻交点之间的线段是否包含于多边形内部(反例见图b)。

uploads/200704/08_111442_linp.gif


  因此我们可以先求出所有和线段相交的多边形的顶点,然后按照X-Y坐标排序(X坐标小的排在前面,对于X坐标相同的点,Y坐标小的排在前面,这种排序准则也是为了保证水平和垂直情况的判断正确),这样相邻的两个点就是在线段上相邻的两交点,如果任意相邻两点的中点也在多边形内,则该线段一定在多边形内。

  证明如下:

  命题1:

  如果线段和多边形的两相邻交点P1 ,P2的中点P' 也在多边形内,则P1, P2之间的所有点都在多边形内。

  证明:

  假设P1,P2之间含有不在多边形内的点,不妨设该点为Q,在P1, P'之间,因为多边形是闭合曲线,所以其内外部之间有界,而P1属于多边行内部,Q属于多边性外部,P'属于多边性内部,P1-Q-P'完全连续,所以P1Q和QP'一定跨越多边形的边界,因此在P1,P'之间至少还有两个该线段和多边形的交点,这和P1P2是相邻两交点矛盾,故命题成立。证毕。

  由命题1直接可得出推论:

  推论2:

  设多边形和线段PQ的交点依次为P1,P2,……Pn,其中Pi和Pi+1是相邻两交点,线段PQ在多边形内的充要条件是:P,Q在多边形内且对于i =1, 2,……, n-1,Pi ,Pi+1的中点也在多边形内。

  在实际编程中,没有必要计算所有的交点,首先应判断线段和多边形的边是否内交,倘若线段和多边形的某条边内交则线段一定在多边形外;如果线段和多边形的每一条边都不内交,则线段和多边形的交点一定是线段的端点或者多边形的顶点,只要判断点是否在线段上就可以了。

  至此我们得出算法如下:

  if 线端PQ的端点不都在多边形内
  then return false;
  点集pointSet初始化为空;
  for 多边形的每条边s
  do if 线段的某个端点在s上
  then 将该端点加入pointSet;
  else if s的某个端点在线段PQ上
  then 将该端点加入pointSet;
  else if s和线段PQ相交 // 这时候已经可以肯定是内交了
  then return false;
  将pointSet中的点按照X-Y坐标排序;
  for pointSet中每两个相邻点 pointSet[i] , pointSet[ i+1]
  do if pointSet[i] , pointSet[ i+1] 的中点不在多边形中
  then return false;
  return true;

  这个过程中的排序因为交点数目肯定远小于多边形的顶点数目n,所以最多是常数级的复杂度,几乎可以忽略不计。因此算法的时间复杂度也是O(n)。

判断折线是否在多边形内:

  只要判断折线的每条线段是否都在多边形内即可。设折线有m条线段,多边形有n个顶点,则该算法的时间复杂度为O(m*n)。

 判断多边形是否在多边形内:

  只要判断多边形的每条边是否都在多边形内即可。判断一个有m个顶点的多边形是否在一个有n个顶点的多边形内复杂度为O(m*n)。

  判断矩形是否在多边形内:

将矩形转化为多边形,然后再判断是否在多边形内。

判断圆是否在多边形内:

  只要计算圆心到多边形的每条边的最短距离,如果该距离大于等于圆半径则该圆在多边形内。计算圆心到多边形每条边最短距离的算法在后文阐述。

判断点是否在圆内:

计算圆心到该点的距离,如果小于等于半径则该点在圆内。

判断线段、折线、矩形、多边形是否在圆内:

因为圆是凸集,所以只要判断是否每个顶点都在圆内即可。

判断圆是否在圆内:

 设两圆为O1,O2,半径分别为r1, r2,要判断O2是否在O1内。先比较r1,r2的大小,如果r1<r2则O2不可能在O1内;否则如果两圆心的距离大于r1 - r2 ,则O2不在O1内;否则O2在O1内。

计算点到线段的最近点:

  如果该线段平行于X轴(Y轴),则过点point作该线段所在直线的垂线,垂足很容易求得,然后计算出垂足,如果垂足在线段上则返回垂足,否则返回离垂足近的端点;如果该线段不平行于X轴也不平行于Y轴,则斜率存在且不为0。设线段的两端点为pt1和pt2,斜率为:k = ( pt2.y - pt1. y ) / (pt2.x - pt1.x );该直线方程为:y = k* ( x - pt1.x) + pt1.y。其垂线的斜率为 - 1 / k,垂线方程为:y = (-1/k) * (x - point.x) + point.y 。

  联立两直线方程解得:x = ( k^2 * pt1.x + k * (point.y - pt1.y ) + point.x ) / ( k^2 + 1) ,y = k * ( x - pt1.x) + pt1.y;然后再判断垂足是否在线段上,如果在线段上则返回垂足;如果不在则计算两端点到垂足的距离,选择距离垂足较近的端点返回。

计算点到折线、矩形、多边形的最近点:

只要分别计算点到每条线段的最近点,记录最近距离,取其中最近距离最小的点即可。

计算点到圆的最近距离及交点坐标:

  如果该点在圆心,因为圆心到圆周任一点的距离相等,返回UNDEFINED。

  连接点P和圆心O,如果PO平行于X轴,则根据P在O的左边还是右边计算出最近点的横坐标为centerPoint.x - radius 或 centerPoint.x + radius。如果PO平行于Y轴,则根据P在O的上边还是下边计算出最近点的纵坐标为 centerPoint.y -+radius或 centerPoint.y - radius。如果PO不平行于X轴和Y轴,则PO的斜率存在且不为0,这时直线PO斜率为k = ( P.y - O.y )/ ( P.x - O.x )。直线PO的方程为:y = k * ( x - P.x) + P.y。设圆方程为:(x - O.x ) ^2 + ( y - O.y ) ^2 = r ^2,联立两方程组可以解出直线PO和圆的交点,取其中离P点较近的交点即可。

计算两条共线的线段的交点:

  对于两条共线的线段,它们之间的位置关系有下图所示的几种情况。图(a)中两条线段没有交点;图 (b) 和 (d) 中两条线段有无穷焦点;图 (c) 中两条线段有一个交点。设line1是两条线段中较长的一条,line2是较短的一条,如果line1包含了line2的两个端点,则是图(d)的情况,两线段有无穷交点;如果line1只包含line2的一个端点,那么如果line1的某个端点等于被line1包含的line2的那个端点,则是图(c)的情况,这时两线段只有一个交点,否则就是图(b)的情况,两线段也是有无穷的交点;如果line1不包含line2的任何端点,则是图(a)的情况,这时两线段没有交点。

uploads/200704/08_112346_gongxian.gif


计算线段或直线与线段的交点:

  设一条线段为L0 = P1P2,另一条线段或直线为L1 = Q1Q2 ,要计算的就是L0和L1的交点。

  1. 首先判断L0和L1是否相交(方法已在前文讨论过),如果不相交则没有交点,否则说明L0和L1一定有交点,下面就将L0和L1都看作直线来考虑。

  2. 如果P1和P2横坐标相同,即L0平行于Y轴

  a) 若L1也平行于Y轴,

  i. 若P1的纵坐标和Q1的纵坐标相同,说明L0和L1共线,假如L1是直线的话他们有无穷的交点,假如L1是线段的话可用"计算两条共线线段的交点"的算法求他们的交点(该方法在前文已讨论过);

  ii. 否则说明L0和L1平行,他们没有交点;

  b) 若L1不平行于Y轴,则交点横坐标为P1的横坐标,代入到L1的直线方程中可以计算出交点纵坐标;

  3. 如果P1和P2横坐标不同,但是Q1和Q2横坐标相同,即L1平行于Y轴,则交点横坐标为Q1的横坐标,代入到L0的直线方程中可以计算出交点纵坐标;

  4. 如果P1和P2纵坐标相同,即L0平行于X轴

  a) 若L1也平行于X轴,

  i. 若P1的横坐标和Q1的横坐标相同,说明L0和L1共线,假如L1是直线的话他们有无穷的交点,假如L1是线段的话可用"计算两条共线线段的交点"的算法求他们的交点(该方法在前文已讨论过);

  ii. 否则说明L0和L1平行,他们没有交点;

  b) 若L1不平行于X轴,则交点纵坐标为P1的纵坐标,代入到L1的直线方程中可以计算出交点横坐标;

  5. 如果P1和P2纵坐标不同,但是Q1和Q2纵坐标相同,即L1平行于X轴,则交点纵坐标为Q1的纵坐标,代入到L0的直线方程中可以计算出交点横坐标;

  6. 剩下的情况就是L1和L0的斜率均存在且不为0的情况

  a) 计算出L0的斜率K0,L1的斜率K1 ;

  b) 如果K1 = K2

  i. 如果Q1在L0上,则说明L0和L1共线,假如L1是直线的话有无穷交点,假如L1是线段的话可用"计算两条共线线段的交点"的算法求他们的交点(该方法在前文已讨论过);

  ii. 如果Q1不在L0上,则说明L0和L1平行,他们没有交点。

  c) 联立两直线的方程组可以解出交点来

  这个算法并不复杂,但是要分情况讨论清楚,尤其是当两条线段共线的情况需要单独考虑,所以在前文将求两条共线线段的算法单独写出来。另外,一开始就先利用矢量叉乘判断线段与线段(或直线)是否相交,如果结果是相交,那么在后面就可以将线段全部看作直线来考虑。需要注意的是,我们可以将直线或线段方程改写为ax+by+c=0的形式,这样一来上述过程的部分步骤可以合并,缩短了代码长度,但是由于先要求出参数,这种算法将花费更多的时间。

求线段或直线与折线、矩形、多边形的交点:

分别求与每条边的交点即可。

求线段或直线与圆的交点:

  设圆心为O,圆半径为r,直线(或线段)L上的两点为P1,P2。

  1. 如果L是线段且P1,P2都包含在圆O内,则没有交点;否则进行下一步。

  2. 如果L平行于Y轴,

  a) 计算圆心到L的距离dis;
  b) 如果dis > r 则L和圆没有交点;
  c) 利用勾股定理,可以求出两交点坐标,但要注意考虑L和圆的相切情况。

  3. 如果L平行于X轴,做法与L平行于Y轴的情况类似;

  4. 如果L既不平行X轴也不平行Y轴,可以求出L的斜率K,然后列出L的点斜式方程,和圆方程联立即可求解出L和圆的两个交点;

  5. 如果L是线段,对于2,3,4中求出的交点还要分别判断是否属于该线段的范围内。

凸包的概念:

  点集Q的凸包(convex hull)是指一个最小凸多边形,满足Q中的点或者在多边形边上或者在其内。下图中由红色线段表示的多边形就是点集Q={p0,p1,...p12}的凸包。

uploads/200704/08_112649_tubao.gif


凸包的求法:

  现在已经证明了凸包算法的时间复杂度下界是O(n*logn),但是当凸包的顶点数h也被考虑进去的话,Krikpatrick和Seidel的剪枝搜索算法可以达到O(n*logh),在渐进意义下达到最优。最常用的凸包算法是Graham扫描法和Jarvis步进法。本文只简单介绍一下Graham扫描法,其正确性的证明和Jarvis步进法的过程大家可以参考《算法导论》。

  对于一个有三个或以上点的点集Q,Graham扫描法的过程如下:

  令p0为Q中Y-X坐标排序下最小的点

  设<p1,p2,...pm>为对其余点按以p0为中心的极角逆时针排序所得的点集(如果有多个点有相同的极角,除了距p0最远的点外全部移除

  压p0进栈S
  压p1进栈S
  压p2进栈S
  for i ← 3 to m
  do while 由S的栈顶元素的下一个元素、S的栈顶元素以及pi构成的折线段不拐向左侧
  对S弹栈
  压pi进栈S
  return S;

  此过程执行后,栈S由底至顶的元素就是Q的凸包顶点按逆时针排列的点序列。需要注意的是,我们对点按极角逆时针排序时,并不需要真正求出极角,只需要求出任意两点的次序就可以了。而这个步骤可以用前述的矢量叉积性质实现。

四、结语

  尽管人类对几何学的研究从古代起便没有中断过,但是具体到借助计算机来解决几何问题的研究,还只是停留在一个初级阶段,无论从应用领域还是发展前景来看,计算几何学都值得我们认真学习、加以运用,希望这篇文章能带你走进这个丰富多彩的世界。

from:http://www.programfan.com/article/showarticle.asp?id=2776

  有关微软编程技术的书籍可谓多如牛毛,但读来读去感觉还是MSDN比较权威。这里就拿一个例子来说吧,可能让很多刚开始学习Win32 API程序设计、甚至是一些已经有一定Win32 API经验的人感觉大汗淋漓。

  在学习Win32 API程序设计时,“第一课”我想都会学到“事件循环”吧?很多书给出了类似这样的经典示例:

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPCTSTR lpCmdLine, int nCmdShow)
{
 MSG msg;
 
 
while(GetMessage(&msg, NULL, 00))
 
{
  TranslateMessage(
&msg);
  DispatchMessage(
&msg);
 }

 
 
return (int)msg.wParam;
}
 

  没错吧?多么熟悉的事件循环,它可以很好地工作,当收到一个WM_QUIT事件的时候,GetMessage()返回0,我们的程序得以正常退出。因此,几乎任何一本讲述Win32 API程序设计的书籍或文章,不论国内的还是国外的,都会以这样一个程序作为第一章中的示例。

  然而,就在前不久,和往常一样,闲来无事就翻起MSDN来,不知怎么的,就跑来看这个再熟悉不过的GetMessage()函数的参考来了。这一看不要紧,头顶顿时冒出虚汗——原来这么多年我们这么写程序,不能说是错误的,但绝对是有漏洞!来看MSDN上对于GetMessage()函数的讲解(节选):

  注意:下面一段文字节选自MSDN Library Online,原文参见:

http://msdn.microsoft.com/
library/
en-us/
winui/
winui/
windowsuserinterface/
windowing/
messagesandmessagequeues/
messagesandmessagequeuesreference/
messagesandmessagequeuesfunctions/
getmessage.asp

>Return Value

>If the function retrieves a message other than WM_QUIT, the return value is nonzero.

>If the function retrieves the WM_QUIT message, the return value is zero.

>If there is an error, the return value is -1. For example, the function fails if hWnd is an invalid window handle or lpMsg is an invalid pointer. To get extended error information, call GetLastError.

>Warning
>Because the return value can be nonzero, zero, or -1, avoid code like this:

while (GetMessage( lpMsg, hWnd, 0, 0)) ...

>The possibility of a -1 return value means that such code can lead to fatal application errors. Instead, use code like this:

BOOL bRet;

while( (bRet = GetMessage( &msg, NULL, 00 )) != 0)

 
if (bRet == -1)
 
{
  
// handle the error and possibly exit
 }

 
else
 
{
  TranslateMessage(
&msg); 
  DispatchMessage(
&msg); 
 }

}
 

  草译如下,希望更多的朋友能够看清:

  返回值

   如果该函数收到一个除WM_QUIT之外的事件,其返回值为一个非零值。

   如果该函数收到一个WM_QUIT事件,其返回值为零。

   如果该函数发生错误,其返回值为-1。例如,如果hWnd是一个无效的窗口句柄,或者lpMsg是一个无效指针,该函数就会失败。要获得额外的错误信息,请调用GetLastError。

  警告

   由于该函数的返回值可能是非零的、零或者-1,请避免这样做:

while (GetMessage( lpMsg, hWnd, 0, 0)) ...

   返回值-1出现的可能性意味着这样的代码会导致应用程序的致命错误。因此,我们应该编写这样的代码:

BOOL bRet;

while( (bRet = GetMessage( &msg, NULL, 00 )) != 0)

 
if (bRet == -1)
 
{
  
// handle the error and possibly exit
 }

 
else
 
{
  TranslateMessage(
&msg); 
  DispatchMessage(
&msg); 
 }

}
 

  看到了吗?我们这么长时间以来一直书写的代码,却在这个“警告”中被“明令禁止”了!可能有的朋友会想,这样的调用不可能出错啊,我们通常都在启动事件循环之前成功地创建了窗口,并且检查了是否成功,因此传递给GetMessage()函数的窗口句柄肯定是有效的;而且,我们通常在堆栈上分配msg,并通过求址运算符(&)来计算它的地址并传递给GetMessage()函数,也不大可能出现无效指针啊?但是,还记得程序设计的基本原理之一吗——永远不要假设任何事情!因此,看来我们该把过去写的代码拿出来好好审视一遍了。

  这里仅提到了一个这样被我们忽视的技术细节,我想一定还有很多、更多这样的被忽视的东西存在!希望本文抛砖引玉,大家把你们发现的类似东西分享出来,让大家都能够写出更加安全健壮的程序吧!

  P.S. 小感受一则,希望不要挨板砖……

  很多人都骂Windows是如何如何不安全,“缓冲区溢出”甚至变成连小学生都能随口说出的“名词”。其实,很多的Windows API都尽量保证了其执行的成功,并且以各种形式反馈给程序员,同时也在文档中进行了详细的描述。然而,又有多少人真正好好阅读了这些讲解?有多少技术作者、技术作家在下笔之前认真浏览了MSDN Library?

  Windows是安全的,不安全的是我们想当然的作风! 

客户端的处理

客户端并不像服务器端那么复杂,它通常只使用两种消息,即接收数据和终止会话消息,以及需要连接和保持单一连接(服务器端)。另外最主要的就是客户端应用程序必须指定其玩家的位置,以便主机能够检索它们。设置玩家的信息是通过首先将相关数据填入一个DPN_PLAYER_INFO结构体,然后再调用IDirectPlay8Client:: SetClientInfo函数来实现。

Sets the static settings of a client with an application. Call this method before connecting to relay basic player information to the application. Once the client successfully connects with the application, the server can retrieve information obtained through this method by calling the IDirectPlay8Server::GetClientInfo method.

HRESULT SetClientInfo(
const DPN_PLAYER_INFO *const
pdpnPlayerInfo,
PVOID const pvAsyncContext,
DPNHANDLE *const phAsyncHandle,
const DWORD dwFlags
);

Parameters

pdpnPlayerInfo
[in] Pointer to a DPN_PLAYER_INFO structure that contains the client information to set.
pvAsyncContext
[in] Pointer to the user-supplied context, which is returned in the pvUserContext member of the DPN_MSGID_ASYNC_OP_COMPLETE system message.
phAsyncHandle
[in,out] A DPNHANDLE. A value will be returned. However, Microsoft® DirectPlay® 8.1 does not permit cancellation of this operation, so the value cannot be used.
dwFlags
[in] Flag that controls how this method is processed. The following flag can be set for this method.
DPNSETCLIENTINFO_SYNC
Causes the method to process synchronously.

Return Values

Returns S_OK if this method is processed synchronously and is successful. If the request is processed asynchronously, S_OK can return if the method is instantly processed. By default, this method is run asynchronously and generally returns DPNSUCCESS_PENDING or one of the following error values.

DPNERR_NOCONNECTION
DPNERR_INVALIDFLAGS
DPNERR_INVALIDPARAM
 

Remarks

This method can be called at any time during the session.

The dwPlayerFlags member of the DPN_PLAYER_INFO structure must be set to zero.

Transmission of nonstatic information should be handled with the IDirectPlay8Client::Send method because of the high cost of using the IDirectPlay8Client::SetClientInfo method.

You can modify the client information with this method after connecting to the application. Calling this method after connection generates a DPN_MSGID_CLIENT_INFO system message to all players, informing them that data has been updated.


服务器端和客户端都使用相同的应用程序GUID,这样它们才能相互识别。网络应用程序不能连接的主要原因就是没有这样做,所以一定要确保使用相同的应用程序GUID。

设置好客户端的信息后,需要与服务器端建立连接,可以调用 IDirectPlay6Client::Connect来实现。

Establishes the connection to the server. After a connection is established, the communication channel on the interface is open and the application should expect messages to arrive immediately. No messages can be sent by means of the IDirectPlay8Client::Send method until the connection has completed.

Before this method is called, you can obtain an application description by calling IDirectPlay8Client::EnumHosts. When you call EnumHosts, DPN_MSGID_ENUM_HOSTS_RESPONSE messages are sent to your message handler with the IDirectPlay8Address objects and the DPN_APPLICATION_DESC structure for each host found. This information can be passed without modification to the Connect method.

HRESULT Connect(
const DPN_APPLICATION_DESC *const
pdnAppDesc,
IDirectPlay8Address *const pHostAddr,
IDirectPlay8Address *const pDeviceInfo,
const DPN_SECURITY_DESC *const pdnSecurity,
const DPN_SECURITY_CREDENTIALS *const pdnCredentials,
const void *const pvUserConnectData,
const DWORD dwUserConnectDataSize,
void *const pvAsyncContext,
DPNHANDLE *const phAsyncHandle,
const DWORD dwFlags
);

Parameters

pdnAppDesc
[in] Pointer to a DPN_APPLICATION_DESC structure that describes the application. The only member of this structure that you must set is the guidApplication member. Only some of the members of this structure are used by this method. The only member that you must set is guidApplication. You can also set guidInstance, pwszPassword, dwFlags, and dwSize.
pHostAddr
[in] Pointer to an IDirectPlay8Address interface that specifies the addressing information to use to connect to the computer that is hosting. The user can be queried for any missing address information if you set the DPNENUMHOSTS_OKTOQUERYFORADDRESSING flag in the dwFlags parameter.
pDeviceInfo
[in] Pointer to an IDirectPlay8Address object that specifies what network adapter (for example, NIC, modem, and so on) to use to connect to the server. Some service providers allow this parameter to be NULL or be an address object containing only the service provider component. In this case, they will use the most appropriate device to reach the designated host. If you set the DPNCONNECT_OKTOQUERYFORADDRESSING flag in dwFlags, the user can be queried for any missing address information.
pdnSecurity
[in] Reserved. Must be NULL.
pdnCredentials
[in] Reserved. Must be NULL.
pvUserConnectData
[in] Pointer to application-specific data provided to the host or server to further validate the connection. DirectPlay will make a copy of this data when the method is called and therefore you can modify or destroy this data once the connection is complete. This data is sent to the DPN_MSGID_INDICATE_CONNECT message in the pvUserConnectData member. This parameter is optional and you can pass NULL to bypass the connection validation provided by the user code.
dwUserConnectDataSize
[in] Variable of type DWORD that specifies the size of the data contained in pvUserConnectData.
pvAsyncContext
[in] Pointer to the user-supplied context, which is returned in the pvUserContext member of the DPN_MSGID_CONNECT_COMPLETE system message. This parameter is optional and can be set to NULL.
phAsyncHandle
[out] A DPNHANDLE. When the method returns, phAsyncHandle will point to a handle that you can pass to IDirectPlay8Client::CancelAsyncOperation to cancel the operation. This parameter must be set to NULL if you set the DPNCONNECT_SYNC flag in dwFlags.
dwFlags
[in] Flag that describes the connection mode. You can set the following flag.
DPNCONNECT_OKTOQUERYFORADDRESSING
Setting this flag will display a standard Microsoft® DirectPlay® dialog box, which queries the user for more information if not enough information is passed in this method.
DPNCONNECT_SYNC
Process the connection request synchronously.

Return Values

Returns S_OK if this method is processed synchronously and is successful. If the request is processed asynchronously, S_OK will be returned if the method is instantly processed. By default, this method is run asynchronously and generally returns DPNSUCCESS_PENDING or one of the following error values.

DPNERR_HOSTREJECTEDCONNECTION
DPNERR_INVALIDAPPLICATION
DPNERR_INVALIDDEVICEADDRESS
DPNERR_INVALIDFLAGS
DPNERR_INVALIDHOSTADDRESS
DPNERR_INVALIDINSTANCE
DPNERR_INVALIDINTERFACE
DPNERR_INVALIDPASSWORD
DPNERR_NOCONNECTION
DPNERR_NOTHOST
DPNERR_SESSIONFULL
DPNERR_ALREADYCONNECTED
 

Remarks

It is not required to enumerate hosts before calling Connect if you know the appropriate host and device information.

If you do call the IDirectPlay8Client::EnumHosts method and you want to ensure better network address translation (NAT) and proxy support when using the TCP/IP service provider or prevent redialing with the modem service provider, keep the enumeration active when calling Connect. To prevent the enumeration from completing, set the dwEnumCount parameter to INFINITE and do not use the IDirectPlay8Client::CancelAsyncOperation to terminate the enumeration before the connect operation has completed. You should also pass the pAddressSender and pAddressDevice address objects in the DPNMSG_ENUM_HOSTS_RESPONSE message without modification into the pHostAddr and pDeviceInfo parameters of the Connect method. To pass the address objects to Connect outside of the callback function, use IDirectPlay8Address::Duplicate or IDirectPlay8Address::AddRef to prevent the object from being destroyed and store the pointers using thread-safe code. DirectPlay will automatically cancel the enumeration when the connect completes with DPN_OK or when IDirectPlay8Client::Close is called.

Although multiple enumerations can be run concurrently and can be run across the duration of a connection, only one connection is allowed per interface. To establish a connection to more than one application, you must create another interface.

When this method is called, a DPN_MSGID_INDICATE_CONNECT message is posted to the server's message handler. On retrieval of this message, the host can pass back connection reply data to the Connect method. Connection reply data can send a message indicating that the host does not approve the connection. The calling application can then handle this reply appropriately.

If Connect is called synchronously, the following outcomes are possible.

  • Connection Successful. The application will receive a DPN_MSGID_CONNECT_COMPLETE message containing the success code and the Connect method will return with DPN_OK.
  • Connection fails because the server rejects the connection. The application will receive a DPN_MSGID_CONNECT_COMPLETE message containing the DPNERR_HOSTREJECTEDCONNECTION failure code. The Connect method will also return with the error code DPNERR_HOSTREJECTEDCONNECTION. The DPN_MSGID_CONNECT_COMPLETE message provides an opportunity for the client application to inspect any data the server returns with the rejection.
  • Connection fails for any other reason. The application will not receive a DPN_MSGID_CONNECT_COMPLETE message, and the Connect method will return with the appropriate error code.

If Connect is called asynchronously, the method returns immediately with DPNSUCCESS_PENDING. A DPN_MSGID_CONNECT_COMPLETE message will follow after the connection is complete, containing the result of the connection. The only time the method does not return DPNSUCCESS_PENDING is when validation of the supplied parameters fails, in which case the appropriate error code is returned.

When the connection request completes, all outstanding enumerations are canceled with the return of DPNERR_USERCANCEL.

The hResultCode on the completion will indicate S_OK if the Connect() attempt was successful, or an error otherwise. If the Host player returned anything other than S_OK from the DPN_MSGID_INDICATE_CONNECT message, the likely error code in the completion will be DPNERR_HOSTREJECTEDCONNECTION.

To close the connection established with this method, call the IDirectPlay8Client::Close method.

Note  If you set the DPNCONNECT_OKTOQUERYFORADDRESSING flag in dwFlags, the service provider might attempt to display a dialog box to ask the user to complete the address information. You must have a visible window present when the service provider tries to display the dialog box, or your application will lock.


最后一个参数dwFlags告诉DirectPlay采用异步(0)还是同步(DPNCONNECT_SYNC)方式进行连接。使用异步连接方式会立即返回控制权,所以需要等待DPN_MSGID_CONNECT_COMPLETE消息来标识和服务器端的成功连接。采用同步连接方式, Connect函数就只会在出现错误或成功连接时才会返回。

下面这个函数演示了如何设置客户端属性并建立与服务器端的连接。

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "ClientClass";

// application GUID
GUID g_app_guid = { 0xababbe600x1ac00x11d5, { 0x900x890x440x450x530x540x00x1 } };

IDirectPlay8Client
*         g_dp_client;    // DirectPlay Client
DPN_SERVICE_PROVIDER_INFO*  g_adapter_list; // adapters
DWORD                       g_num_adapters; // number of adapters

BOOL g_is_connected;        
// flag indicates whether connection between client and server is build up
DPNHANDLE g_async_handle;   // async connection handle

//--------------------------------------------------------------------------------
// Start connecting to server which GUID specified by adapter_guid and server IP
// specified by ip.
//--------------------------------------------------------------------------------
BOOL Start_Session(GUID* adapter_guid, char* ip)
{
    
// Make sure there are an invalid adapter and an invalid IP address
    if(adapter_guid == NULL || ip == NULL)
        
return FALSE;

    
// Need to re-assign a network handler as quitting a previous session to clears it.
    
// Close the connection first before assigning a new network handler.
    
//
    
// Closes the open connnection to a session.
    g_dp_client->Close(0);

    
// Initialize DirectPlay Client
    if(FAILED(g_dp_client->Initialize(NULL, Net_Msg_Handle, 0)))
        
return FALSE;

    
// Assign player information

    
char player_name[256];
    WCHAR w_player_name[
256];

    GetWindowText(GetDlgItem(g_hwnd, IDC_NAME), player_name, 
256);
    mbstowcs(w_player_name, player_name, strlen(player_name) 
+ 1);

    DPN_PLAYER_INFO dpn_player_info;

    ZeroMemory(
&dpn_player_info, sizeof(DPN_PLAYER_INFO));

    dpn_player_info.dwSize      
= sizeof(DPN_PLAYER_INFO);
    dpn_player_info.dwInfoFlags 
= DPNINFO_NAME | DPNINFO_DATA;
    dpn_player_info.pwszName    
= w_player_name;

    
// set the static settings of a client with an application, process synchronously.
    g_dp_client->SetClientInfo(&dpn_player_info, NULL, NULL, DPNSETCLIENTINFO_SYNC);

    IDirectPlay8Address
* dp_host_address;
    IDirectPlay8Address
* dp_device_address;

    
// Create an address object and fill it with information

    
// create host address object
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC, IID_IDirectPlay8Address, 
                               (
void**)&dp_host_address)))
        
return FALSE;

    
// create device address object
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC, IID_IDirectPlay8Address, 
                               (
void**)&dp_device_address)))
    {
        dp_host_address
->Release();
        
return FALSE;
    }
   
    
// Set the protocol to TCP/IP
    
//
    
// Sets the service provider GUID in the address object.
    
// If a service provider is specified for this address, it is overwrittern by this call.

    
// set the protocol to host address
    if(FAILED(dp_host_address->SetSP(&CLSID_DP8SP_TCPIP)))
        
goto fail;

    
// set the protocol to device address
    if(FAILED(dp_device_address->SetSP(&CLSID_DP8SP_TCPIP)))
        
goto fail;

    
// Set the port
    DWORD port = 21234;

    
// Adds a component to the address.
    
// If the component is part of the address, it is replaced by the new value in this call.

    
// Set port
    if(FAILED(dp_host_address->AddComponent(DPNA_KEY_PORT, &port, sizeof(DWORD), DPNA_DATATYPE_DWORD)))
        
goto fail;

    WCHAR w_ip[
128= {0};

    
// set the host name
    mbstowcs(w_ip, ip, strlen(ip)+1);
    dp_host_address
->AddComponent(DPNA_KEY_HOSTNAME, w_ip, (DWORD)((wcslen(w_ip)+1* sizeof(WCHAR)), 
                                  DPNA_DATATYPE_STRING);

    
// Set the adapter
    dp_host_address->AddComponent(DPNA_KEY_DEVICE, adapter_guid, sizeof(GUID), DPNA_DATATYPE_GUID);
    dp_device_address
->AddComponent(DPNA_KEY_DEVICE, adapter_guid, sizeof(GUID), DPNA_DATATYPE_GUID);
        
    DPN_APPLICATION_DESC app_desc;  
// Describes the settings for a Microsoft DirectPlay application

    
// Setup the application description structure

    ZeroMemory(
&app_desc, sizeof(DPN_APPLICATION_DESC));

    app_desc.dwSize          
= sizeof(DPN_APPLICATION_DESC);
    app_desc.dwFlags         
= DPNSESSION_CLIENT_SERVER;
    app_desc.guidApplication 
= g_app_guid;
    app_desc.pwszSessionName 
= L"SercverSession";

    
// Establishes the connection to the server
    if(FAILED(g_dp_client->Connect(&app_desc, dp_host_address, dp_device_address, NULL, NULL, NULL, 0, NULL, 
                                   
&g_async_handle, 0)))
        
goto fail;    

    
// setup dialog control information  
    EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), FALSE);
    EnableWindow(GetDlgItem(g_hwnd, IDC_IP), FALSE);
    EnableWindow(GetDlgItem(g_hwnd, IDC_NAME), FALSE);
    EnableWindow(GetDlgItem(g_hwnd, IDC_CONNECT), TRUE);

    dp_host_address
->Release();
    dp_device_address
->Release();
    
return TRUE;

fail:
    dp_host_address
->Release();
    dp_device_address
->Release();
    
return FALSE;
}

发送和接收消息

客户端接收消息和服务器端接收消息的处理方法是一样的,所以只需要关心消息处理函数内部的实现,发送消息需要调用IDirectPlay8Server::Send函数。

Transmits data to the server. The message can be sent synchronously or asynchronously.

HRESULT Send(
const DPN_BUFFER_DESC *const
pBufferDesc,
const DWORD cBufferDesc,
const DWORD dwTimeOut,
void *const pvAsyncContext,
DPNHANDLE *const phAsyncHandle,
const DWORD dwFlags
);

Parameters

pBufferDesc
[in] Pointer to a DPN_BUFFER_DESC structure that describes the data to send.
cBufferDesc
[in] Number of DPN_BUFFER_DESC structures pointed to by pBufferDesc. There can only be one buffer in this version of Microsoft® DirectPlay® .
dwTimeOut
[in] Number of milliseconds to wait for the message to send. If the message has not been sent by the dwTimeOut value, it is deleted from the send queue. If you set this parameter to 0, the message remains in the send queue until it is sent or until the link is dropped.
pvAsyncContext
[in] Pointer to the user-supplied context, which is returned in the pvUserContext member of the DPN_MSGID_SEND_COMPLETE system message.
phAsyncHandle
[in,out] A DPNHANDLE. When the method returns, phAsyncHandle will point to a handle that you can pass to IDirectPlay8Client::CancelAsyncOperation to cancel the operation. This parameter must be set to NULL if you set the DPNSEND_SYNC flag in dwFlags.
dwFlags
[in] Flags that describe send behavior. You can set one or more of the following flags.
DPNSEND_SYNC
Process the Send request synchronously.
DPNSEND_NOCOPY
Use the data in the DPN_BUFFER_DESC structure and do not make an internal copy. This may be a more efficient method of sending data to the server. However, it is less robust, because the sender might be able to modify the message before the receiver has processed it. This flag cannot be combined with DPNSEND_NOCOMPLETE.
DPNSEND_NOCOMPLETE
Does not send DPN_MSGID_SEND_COMPLETE to the message handler. This flag may not be used with DPNSEND_NOCOPY or DPNSEND_GUARANTEED. Additionally, when using this flag pvAsyncContext must be NULL.
DPNSEND_COMPLETEONPROCESS
Send DPN_MSGID_SEND_COMPLETE to the message handler when this message has been delivered to the target and the target's message handler returns from indicating its reception. There is additional internal message overhead when this flag is set, and the message transmission process may become significantly slower. If you set this flag, DPNSEND_GUARANTEED must also be set.
DPNSEND_GUARANTEED
Send the message by a guaranteed method of delivery.
DPNSEND_PRIORITY_HIGH
Sets the priority of the message to high. This flag cannot be used with DPNSEND_PRIORITY_LOW.
DPNSEND_PRIORITY_LOW
Sets the priority of the message to low. This flag cannot be used with DPNSEND_PRIORITY_HIGH.
DPNSEND_NOLOOPBACK
Suppress the DPN_MSGID_RECEIVE system message to your message handler if you are sending to yourself.
DPNSEND_NONSEQUENTIAL
If the flag is not set, messages are delivered to the target application in the order that they are sent, which may necessitate buffering out of sequence messages until the missing messages arrive. Messages are simply delivered to the target application in the order that they are received.

Return Values

Returns S_OK if this method is processed synchronously and is successful. By default, this method is run asynchronously and generally returns DPNSUCCESS_PENDING or one of the following error values.

DPNERR_INVALIDFLAGS
DPNERR_TIMEDOUT

Remarks

This method generates a DPN_MSGID_RECEIVE system message in the server's message handler. The data buffer is contained in the pReceiveData member of the associated structure.

Messages can have one of three priorities: low, normal, and high. To specify a low or high priority for the message, set the appropriate flag in dwFlags. If neither of the priority flags is set, the message will have normal priority. See Basic Networking for a discussion of send priorities.

When the Send request is completed, a DPN_MSGID_SEND_COMPLETE system message is posted to the sender's message handler. The success or failure of the request is contained in the hResultCode member of the associate structure. You can suppress the send completion by setting the DPN_NOCOMPLETE flag in dwflags.

If a player joins a game and needs to send multiple messages immediately, the player should first send a message with the DPNSEND_COMPLETEONPROCESS flag set. When the DPN_MSGID_SEND_COMPLETE message is returned, the application can begin sending messages. If the player does not do this, some of the messages might need to be queued on the receiver and, if too much data arrives, the queue can grow faster than the receiver can handle the messages. This might result in lost data. After a player is established in the game, however, throttling in DirectPlay will control the data flow by using message timeouts or the GetSendQueueInfo method. For more information, see Optimizing Network Usage.

Send completions are typically posted on the source computer as soon as the message is sent. In other words, a send completion does not necessarily mean that the message has been processed on the target. It may still be in a queue. If you want to be certain that the message has been processed by the target, set the DPN_COMPLETEONPROCESS flag in dwFlags. This flag ensures that the send completion will not be sent until the target's message handler has processed the message, and returned.

Note  Do not assume that resources such as the data buffer will remain valid until the method has returned. If you call this method asynchronously, the DPN_MSGID_SEND_COMPLETE message may be received and processed by your message handler before the call has returned. If your message handler deallocates or otherwise invalidates a resource such as the data buffer, that resource may become invalid at any time after the method has been called.


下面这两个函数演示了如何发送和接收消息。

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "ClientClass";

// application GUID
GUID g_app_guid = { 0xababbe600x1ac00x11d5, { 0x900x890x440x450x530x540x00x1 } };

IDirectPlay8Client
*         g_dp_client;    // DirectPlay Client
DPN_SERVICE_PROVIDER_INFO*  g_adapter_list; // adapters
DWORD                       g_num_adapters; // number of adapters

BOOL g_is_connected;        
// flag indicates whether connection between client and server is build up
DPNHANDLE g_async_handle;   // async connection handle

//--------------------------------------------------------------------------------
// Send text to all clients.
//--------------------------------------------------------------------------------
void Send_Text_Msg(char* text)
{
    DPNHANDLE async_handle;
    DPN_BUFFER_DESC buffer_desc;
    
char name[64], message[1024];

    
if(g_dp_client == NULL)
        
return;

    
// get the name
    GetWindowText(GetDlgItem(g_hwnd, IDC_NAME), name, 64);

    
// rebuild string based on the name
    sprintf(message, "%s> %s", name, text);

    
// build a data structure
    buffer_desc.dwBufferSize = (DWORD) (strlen(message) + 1);
    buffer_desc.pBufferData  
= (BYTE*) message;

    
// Send message (async method - reason for handle)    
    g_dp_client->Send(&buffer_desc, 10, NULL, &async_handle, DPNSEND_NOLOOPBACK);
}

//----------------------------------------------------------------------------------------
// Callback function that receives all messages from the client, and receives indications 
// of session changes from the IDirectPlay8Client interface. 
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{   

    DPNMSG_RECEIVE
* receive_data;

    
switch(message_id)
    {
    
// Microsoft DirectPlay generates the DPN_MSGID_RECEIVE message when a message has been processed by 
    
// the receiver.
    case DPN_MSGID_RECEIVE:
        receive_data 
= (DPNMSG_RECEIVE*) msg_buffer;

        
// write out text
        SendMessage(GetDlgItem(g_hwnd, IDC_CHATTER), LB_ADDSTRING, 0, (LPARAM) receive_data->pReceiveData);

        
break;
    }

     
// return S_OK to signify the message was handled OK.
    return S_OK;
}

获得玩家的ID号

当连接到服务器端后,在有些情况下客户端需要使用自己的玩家ID号。要获得玩家的ID号,客户端可以解析连接完成消息(DPN_MSGID_CONNECT_COMPLETE),该消息使用以下数据结构来包含有关客户端到服务器端连接的必要信息。

Microsoft® DirectPlay® generates the DPN_MSGID_CONNECT_COMPLETE message when the connection attempt has been completed in a peer-to-peer or client/server session. This message is generated whether or not the connection was successful.

The DPNMSG_CONNECT_COMPLETE structure contains information for the DPN_MSGID_CONNECT_COMPLETE system message.

typedef struct _DPNMSG_CONNECT_COMPLETE{
DWORD dwSize;
DPNHANDLE hAsyncOp;
PVOID pvUserContext;
HRESULT hResultCode;
PVOID pvApplicationReplyData;
DWORD dwApplicationReplyDataSize;
} DPNMSG_CONNECT_COMPLETE, *PDPNMSG_CONNECT_COMPLETE;
dwSize
Size of this structure.
hAsyncOp
Asynchronous operation handle.
pvUserContext
User context supplied when the IDirectPlay8Peer::Connect or IDirectPlay8Client::Connect methods are called.
hResultCode
HRESULT describing the result of the connection attempt. See the Return Values section in the IDirectPlay8Peer::Connect or IDirectPlay8Client::Connect method for more information. Additionally, DPNERR_PLAYERNOTREACHABLE will be returned if a player has tried to join a peer-to-peer session where at least one other existing player in the session cannot connect to the joining player.
pvApplicationReplyData
Connection reply data returned from the host or server.
dwApplicationReplyDataSize
Size of the data, in bytes, of the pvApplicationReplyData member.

Return Values

Return DPN_OK.


当服务器端调用IDirectPlay8Server::DestroyClient来销毁玩家时,客户端会收到消息 DPN_MSGID_TERMINATE_SESSION。


下面这个函数演示了如何处理消息DPN_MSGID_CONNECT_COMPLETE和DPN_MSGID_TERMINATE_SESSION

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "ClientClass";

// application GUID
GUID g_app_guid = { 0xababbe600x1ac00x11d5, { 0x900x890x440x450x530x540x00x1 } };

IDirectPlay8Client
*         g_dp_client;    // DirectPlay Client
DPN_SERVICE_PROVIDER_INFO*  g_adapter_list; // adapters
DWORD                       g_num_adapters; // number of adapters

BOOL g_is_connected;        
// flag indicates whether connection between client and server is build up
DPNHANDLE g_async_handle;   // async connection handle

//----------------------------------------------------------------------------------------
// Callback function that receives all messages from the client, and receives indications 
// of session changes from the IDirectPlay8Client interface. 
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{    
    
// contains information for the DPN_MSGID_CONNECT_COMPLETE system message
    DPNMSG_CONNECT_COMPLETE* connect_complete;

    
// contains information for the DPN_MSGID_TERMINATE_COMPLETE system message
    DPNMSG_TERMINATE_SESSION* terminate_session;

    
switch(message_id)
    {
    
case DPN_MSGID_CONNECT_COMPLETE:
        connect_complete 
= (DPNMSG_CONNECT_COMPLETE*) msg_buffer;

        EnableWindow(GetDlgItem(g_hwnd, IDC_CONNECT), TRUE);

        
// Make sure connection complete
        if(connect_complete->hResultCode == S_OK)
        {
            
// setup the dialog information
            EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), FALSE);
            EnableWindow(GetDlgItem(g_hwnd, IDC_IP), FALSE);
            EnableWindow(GetDlgItem(g_hwnd, IDC_NAME), FALSE);            

            SetWindowText(GetDlgItem(g_hwnd, IDC_CONNECT), 
"Disconnect");

            
// flag as connected
            g_is_connected = TRUE;
        }
        
else
        {
            
switch(connect_complete->hResultCode)
            {
            
case DPNERR_HOSTREJECTEDCONNECTION:
                MessageBox(g_hwnd, 
"Host reject this connection""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDAPPLICATION:
                MessageBox(g_hwnd, 
"The GUID supplied for the application is invalid.""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDDEVICEADDRESS:
                MessageBox(g_hwnd, 
"The address for the local computer or adapter is invalid.""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDFLAGS:
                MessageBox(g_hwnd, 
"The flags passed to this method are invalid. ""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDHOSTADDRESS:
                MessageBox(g_hwnd, 
"The specified remote address is invalid.""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDINSTANCE:
                MessageBox(g_hwnd, 
"The GUID for the application instance is invalid.""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDINTERFACE:
                MessageBox(g_hwnd, 
"The interface parameter is invalid.""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDPASSWORD:
                MessageBox(g_hwnd, 
                    
"An invalid password was supplied when attempting to join a session that requires a password."
                    
"Error", MB_OK);
                
break;
            
case DPNERR_NOCONNECTION:
                MessageBox(g_hwnd, 
"No communication link was established.""Error", MB_OK);
                
break;
            
case DPNERR_NOTHOST:
                MessageBox(g_hwnd, 
"An attempt by the client to connect to a nonhost computer.""Error", MB_OK);
                
break;
            
case DPNERR_SESSIONFULL:
                MessageBox(g_hwnd, 
                    
"The request to connect to the host or server failed because the maximum number of players allotted for the session has been reached. "
                    
"Error", MB_OK);
                
break;
            
case DPNERR_ALREADYCONNECTED:
                MessageBox(g_hwnd, 
"The object is already connected to the session.""Error", MB_OK);
                
break;
            }
            
            
// setup the dialog information
            EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), TRUE);
            EnableWindow(GetDlgItem(g_hwnd, IDC_IP), TRUE);
            EnableWindow(GetDlgItem(g_hwnd, IDC_NAME), TRUE);
            
            SetWindowText(GetDlgItem(g_hwnd, IDC_CONNECT), 
"Connect");

            
// flag as disconnected
            g_is_connected = FALSE;
        }

        
// clear async handle
        g_async_handle = NULL;

        
break;

    
case DPN_MSGID_TERMINATE_SESSION:
        terminate_session 
= (DPNMSG_TERMINATE_SESSION*) msg_buffer;

        
// setup the dialog information
        EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), TRUE);
        EnableWindow(GetDlgItem(g_hwnd, IDC_IP), TRUE);
        EnableWindow(GetDlgItem(g_hwnd, IDC_NAME), TRUE);

        SetWindowText(GetDlgItem(g_hwnd, IDC_CONNECT), 
"Connect");

        
// clears async handle
        g_async_handle = NULL;

        
// flag as disconnected
        g_is_connected = FALSE;

        
// print a disconnect message
        SendMessage(GetDlgItem(g_hwnd, IDC_CHATTER), LB_ADDSTRING, 0, (LPARAM)"Disconnected.");

        
break;
    }
    
     
// return S_OK to signify the message was handled OK.
    return S_OK;
}

终止客户端会话

当客户端需要从一次会话中断开连接时,需要明确向DirectPlay传达该消息,以使其按正确的步骤关闭连接。使用IDirectPlay8Client::Close函数,就能将客户端从一次会话中断开连接。

Closes the open connection to a session. This method must be called on any object that is successfully initialized with a call to the IDirectPlay8Client::Initialize method.

HRESULT Close(
const DWORD
dwFlags
);

Parameters

dwFlags
[in] Reserved. Must be 0.

Return Values

Returns S_OK if successful, or the following error value.

DPNERR_UNINITIALIZED
 

Remarks

Calling Close will cancel all outstanding operations, including data sent as guaranteed. To make sure all messages are sent, wait for all outstanding Send calls to complete before calling Close.

If you do not want the application to wait, the application should call IDirectPlay8Client::CancelAsyncOperation to cancel all outstanding sends prior to calling IDirectPlay8Client::Close or doing a final release call on the IDirectPlay8Client interface. Failing to do so causes unpredictable results.


下面给出一个完整的代码示例:

点击下载源码和工程

/***************************************************************************************
PURPOSE:
    Client Network Demo
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
<dplay8.h>
#include 
<dpaddr.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"dplayx.lib")

#pragma warning(disable : 
4996)

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "ClientClass";

// application GUID
GUID g_app_guid = { 0xababbe600x1ac00x11d5, { 0x900x890x440x450x530x540x00x1 } };

IDirectPlay8Client
*         g_dp_client;    // DirectPlay Client
DPN_SERVICE_PROVIDER_INFO*  g_adapter_list; // adapters
DWORD                       g_num_adapters; // number of adapters

BOOL g_is_connected;        
// flag indicates whether connection between client and server is build up
DPNHANDLE g_async_handle;   // async connection handle

//--------------------------------------------------------------------------------
// Send text to all clients.
//--------------------------------------------------------------------------------
void Send_Text_Msg(char* text)
{
    DPNHANDLE async_handle;
    DPN_BUFFER_DESC buffer_desc;
    
char name[64], message[1024];

    
if(g_dp_client == NULL)
        
return;

    
// get the name
    GetWindowText(GetDlgItem(g_hwnd, IDC_NAME), name, 64);

    
// rebuild string based on the name
    sprintf(message, "%s> %s", name, text);

    
// build a data structure
    buffer_desc.dwBufferSize = (DWORD) (strlen(message) + 1);
    buffer_desc.pBufferData  
= (BYTE*) message;

    
// Send message (async method - reason for handle)    
    g_dp_client->Send(&buffer_desc, 10, NULL, &async_handle, DPNSEND_NOLOOPBACK);
}

//----------------------------------------------------------------------------------------
// Callback function that receives all messages from the client, and receives indications 
// of session changes from the IDirectPlay8Client interface. 
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{    
    
// contains information for the DPN_MSGID_CONNECT_COMPLETE system message
    DPNMSG_CONNECT_COMPLETE* connect_complete;
    
    
// contains information for the DPN_MSGID_TERMINATE_COMPLETE system message
    DPNMSG_TERMINATE_SESSION* terminate_session;

    DPNMSG_RECEIVE
* receive_data;

    
switch(message_id)
    {
    
case DPN_MSGID_CONNECT_COMPLETE:
        connect_complete 
= (DPNMSG_CONNECT_COMPLETE*) msg_buffer;

        EnableWindow(GetDlgItem(g_hwnd, IDC_CONNECT), TRUE);

        
// Make sure connection complete
        if(connect_complete->hResultCode == S_OK)
        {
            
// setup the dialog information
            EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), FALSE);
            EnableWindow(GetDlgItem(g_hwnd, IDC_IP), FALSE);
            EnableWindow(GetDlgItem(g_hwnd, IDC_NAME), FALSE);            

            SetWindowText(GetDlgItem(g_hwnd, IDC_CONNECT), 
"Disconnect");

            
// flag as connected
            g_is_connected = TRUE;
        }
        
else
        {
            
switch(connect_complete->hResultCode)
            {
            
case DPNERR_HOSTREJECTEDCONNECTION:
                MessageBox(g_hwnd, 
"Host reject this connection""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDAPPLICATION:
                MessageBox(g_hwnd, 
"The GUID supplied for the application is invalid.""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDDEVICEADDRESS:
                MessageBox(g_hwnd, 
"The address for the local computer or adapter is invalid.""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDFLAGS:
                MessageBox(g_hwnd, 
"The flags passed to this method are invalid. ""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDHOSTADDRESS:
                MessageBox(g_hwnd, 
"The specified remote address is invalid.""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDINSTANCE:
                MessageBox(g_hwnd, 
"The GUID for the application instance is invalid.""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDINTERFACE:
                MessageBox(g_hwnd, 
"The interface parameter is invalid.""Error", MB_OK);
                
break;
            
case DPNERR_INVALIDPASSWORD:
                MessageBox(g_hwnd, 
                    
"An invalid password was supplied when attempting to join a session that requires a password."
                    
"Error", MB_OK);
                
break;
            
case DPNERR_NOCONNECTION:
                MessageBox(g_hwnd, 
"No communication link was established.""Error", MB_OK);
                
break;
            
case DPNERR_NOTHOST:
                MessageBox(g_hwnd, 
"An attempt by the client to connect to a nonhost computer.""Error", MB_OK);
                
break;
            
case DPNERR_SESSIONFULL:
                MessageBox(g_hwnd, 
                    
"The request to connect to the host or server failed because the maximum number of players allotted for the session has been reached. "
                    
"Error", MB_OK);
                
break;
            
case DPNERR_ALREADYCONNECTED:
                MessageBox(g_hwnd, 
"The object is already connected to the session.""Error", MB_OK);
                
break;
            }
            
            
// setup the dialog information
            EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), TRUE);
            EnableWindow(GetDlgItem(g_hwnd, IDC_IP), TRUE);
            EnableWindow(GetDlgItem(g_hwnd, IDC_NAME), TRUE);
            
            SetWindowText(GetDlgItem(g_hwnd, IDC_CONNECT), 
"Connect");

            
// flag as disconnected
            g_is_connected = FALSE;
        }

        
// clear async handle
        g_async_handle = NULL;

        
break;

    
case DPN_MSGID_TERMINATE_SESSION:
        terminate_session 
= (DPNMSG_TERMINATE_SESSION*) msg_buffer;

        
// setup the dialog information
        EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), TRUE);
        EnableWindow(GetDlgItem(g_hwnd, IDC_IP), TRUE);
        EnableWindow(GetDlgItem(g_hwnd, IDC_NAME), TRUE);

        SetWindowText(GetDlgItem(g_hwnd, IDC_CONNECT), 
"Connect");

        
// clears async handle
        g_async_handle = NULL;

        
// flag as disconnected
        g_is_connected = FALSE;

        
// print a disconnect message
        SendMessage(GetDlgItem(g_hwnd, IDC_CHATTER), LB_ADDSTRING, 0, (LPARAM)"Disconnected.");

        
break;

    
// Microsoft DirectPlay generates the DPN_MSGID_RECEIVE message when a message has been processed by 
    
// the receiver.
    case DPN_MSGID_RECEIVE:
        receive_data 
= (DPNMSG_RECEIVE*) msg_buffer;

        
// write out text
        SendMessage(GetDlgItem(g_hwnd, IDC_CHATTER), LB_ADDSTRING, 0, (LPARAM) receive_data->pReceiveData);

        
break;
    }

    
// return S_OK to signify the message was handled OK.
    return S_OK;
}

//--------------------------------------------------------------------------------
// Create DirectPlay Client and initialize it.
//--------------------------------------------------------------------------------
BOOL Init_DirectPlay_Client()
{
    
// create DirectPlay client component
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Client, NULL, CLSCTX_INPROC, IID_IDirectPlay8Client, 
                               (
void**)&g_dp_client)))
        
return FALSE;

    
// Assign a message handler to network component
    
//
    
// Registers an entry point in the client's code that receives the messages from the IDirectPlay8Client
    
// interface and from the server.
    if(FAILED(g_dp_client->Initialize(NULL, Net_Msg_Handle, 0)))
        
return FALSE;

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Enumerate all TCP/IP adapters.
//--------------------------------------------------------------------------------
void Enum_Adapters()
{
    
// return if no server object or GUID
    if(g_dp_client == NULL)
        
return;

    
// get a handle of the list box
    HWND adapters_ctrl = GetDlgItem(g_hwnd, IDC_ADAPTERS);

    
// clear the combo-box
    SendMessage(adapters_ctrl, CB_RESETCONTENT, 00);

    
// free prior adapter list
    delete[] g_adapter_list;
    g_adapter_list 
= NULL;

    g_num_adapters 
= 0;

    DWORD size 
= 0;

    
// query the required size of the data buffer
    HRESULT rv = g_dp_client->EnumServiceProviders(&CLSID_DP8SP_TCPIP, NULL, g_adapter_list, &size, &g_num_adapters, 0);

    
if(rv != DPNERR_BUFFERTOOSMALL)
        
return;

    
// allocate a buffer
    if((g_adapter_list = (DPN_SERVICE_PROVIDER_INFO*new BYTE[size]) == NULL)
        
return;

    
// enumerate again
    if(SUCCEEDED(g_dp_client->EnumServiceProviders(&CLSID_DP8SP_TCPIP, NULL, g_adapter_list, &size, &g_num_adapters, 0)))
    {
        
char adapter_name[1024];

        
// enumeration is complete, scan through entries.
        DPN_SERVICE_PROVIDER_INFO* adapter_ptr = g_adapter_list;

        
for(DWORD i = 0; i < g_num_adapters; i++)
        {
            
// convert wide string into multi-byte string
            wcstombs(adapter_name, adapter_ptr->pwszName, 1024);

            
// add the adapter name int listbox
            SendMessage(adapters_ctrl, CB_ADDSTRING, 0, (LPARAM)adapter_name);

            
// go to next servicec provider
            adapter_ptr++;
        }
    }

    
// Select first adapter
    
//
    
// An application sends a CB_SETCURSEL message to select a string in the list of a combo box. 
    
// If necessary, the list scrolls the string into view. The text in the edit control of the combo box 
    
// changes to reflect the new selection, and any previous selection in the list is removed. 
    
//
    
// wParam:
    
//    Specifies the zero-based index of the string to select. If this parameter is –1, any current selection 
    
//    in the list is removed and the edit control is cleared. 
    
//
    
// lParam:
    
//    This parameter is not used. 
    SendMessage(adapters_ctrl, CB_SETCURSEL, 00);    
}

//--------------------------------------------------------------------------------
// Release all resource which allocated for DirectPlay.
//--------------------------------------------------------------------------------
void Release_DirectPlay()
{
    
// release client service provider list memory
    delete[] g_adapter_list;
    g_adapter_list 
= NULL;
    
    g_num_adapters 
= 0;

    
// release client component
    if(g_dp_client != NULL)
    {
        
// Closes the open connection to a session. This method must be called on any object that is successfully 
        
// initialized with a call to the IDirectPlay8Client::Initialize method.
        g_dp_client->Close(0);

        g_dp_client
->Release();

        g_dp_client 
= NULL;
    }
}

//--------------------------------------------------------------------------------
// Start connecting to server which GUID specified by adapter_guid and server IP
// specified by ip.
//--------------------------------------------------------------------------------
BOOL Start_Session(GUID* adapter_guid, char* ip)
{
    
// Make sure there are an invalid adapter and an invalid IP address
    if(adapter_guid == NULL || ip == NULL)
        
return FALSE;

    
// Need to re-assign a network handler as quitting a previous session to clears it.
    
// Close the connection first before assigning a new network handler.
    
//
    
// Closes the open connnection to a session.
    g_dp_client->Close(0);

    
// Initialize DirectPlay Client
    if(FAILED(g_dp_client->Initialize(NULL, Net_Msg_Handle, 0)))
        
return FALSE;

    
// Assign player information

    
char player_name[256];
    WCHAR w_player_name[
256];

    GetWindowText(GetDlgItem(g_hwnd, IDC_NAME), player_name, 
256);
    mbstowcs(w_player_name, player_name, strlen(player_name) 
+ 1);

    DPN_PLAYER_INFO dpn_player_info;

    ZeroMemory(
&dpn_player_info, sizeof(DPN_PLAYER_INFO));

    dpn_player_info.dwSize      
= sizeof(DPN_PLAYER_INFO);
    dpn_player_info.dwInfoFlags 
= DPNINFO_NAME | DPNINFO_DATA;
    dpn_player_info.pwszName    
= w_player_name;

    
// set the static settings of a client with an application, process synchronously.
    g_dp_client->SetClientInfo(&dpn_player_info, NULL, NULL, DPNSETCLIENTINFO_SYNC);

    IDirectPlay8Address
* dp_host_address;
    IDirectPlay8Address
* dp_device_address;

    
// Create an address object and fill it with information

    
// create host address object
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC, IID_IDirectPlay8Address, 
                               (
void**)&dp_host_address)))
        
return FALSE;

    
// create device address object
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC, IID_IDirectPlay8Address, 
                               (
void**)&dp_device_address)))
    {
        dp_host_address
->Release();
        
return FALSE;
    }
   
    
// Set the protocol to TCP/IP
    
//
    
// Sets the service provider GUID in the address object.
    
// If a service provider is specified for this address, it is overwrittern by this call.

    
// set the protocol to host address
    if(FAILED(dp_host_address->SetSP(&CLSID_DP8SP_TCPIP)))
        
goto fail;

    
// set the protocol to device address
    if(FAILED(dp_device_address->SetSP(&CLSID_DP8SP_TCPIP)))
        
goto fail;

    
// Set the port
    DWORD port = 21234;

    
// Adds a component to the address.
    
// If the component is part of the address, it is replaced by the new value in this call.

    
// Set port
    if(FAILED(dp_host_address->AddComponent(DPNA_KEY_PORT, &port, sizeof(DWORD), DPNA_DATATYPE_DWORD)))
        
goto fail;

    WCHAR w_ip[
128= {0};

    
// set the host name
    mbstowcs(w_ip, ip, strlen(ip)+1);
    dp_host_address
->AddComponent(DPNA_KEY_HOSTNAME, w_ip, (DWORD)((wcslen(w_ip)+1* sizeof(WCHAR)), 
                                  DPNA_DATATYPE_STRING);

    
// Set the adapter
    dp_host_address->AddComponent(DPNA_KEY_DEVICE, adapter_guid, sizeof(GUID), DPNA_DATATYPE_GUID);
    dp_device_address
->AddComponent(DPNA_KEY_DEVICE, adapter_guid, sizeof(GUID), DPNA_DATATYPE_GUID);
        
    DPN_APPLICATION_DESC app_desc;  
// Describes the settings for a Microsoft DirectPlay application

    
// Setup the application description structure

    ZeroMemory(
&app_desc, sizeof(DPN_APPLICATION_DESC));

    app_desc.dwSize          
= sizeof(DPN_APPLICATION_DESC);
    app_desc.dwFlags         
= DPNSESSION_CLIENT_SERVER;
    app_desc.guidApplication 
= g_app_guid;
    app_desc.pwszSessionName 
= L"SercverSession";

    
// Establishes the connection to the server
    if(FAILED(g_dp_client->Connect(&app_desc, dp_host_address, dp_device_address, NULL, NULL, NULL, 0, NULL, 
                                   
&g_async_handle, 0)))
        
goto fail;    

    
// setup dialog control information  
    EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), FALSE);
    EnableWindow(GetDlgItem(g_hwnd, IDC_IP), FALSE);
    EnableWindow(GetDlgItem(g_hwnd, IDC_NAME), FALSE);
    EnableWindow(GetDlgItem(g_hwnd, IDC_CONNECT), TRUE);

    dp_host_address
->Release();
    dp_device_address
->Release();
    
return TRUE;

fail:
    dp_host_address
->Release();
    dp_device_address
->Release();
    
return FALSE;
}

//--------------------------------------------------------------------------------
// Stoping hosting server.
//--------------------------------------------------------------------------------
void Stop_Session()
{
    
// Close the connection
    if(g_dp_client)
        g_dp_client
->Close(0);

    
// clear async handle
    g_async_handle = NULL;

    
// setup the dialog controls information

    
// Enables combo-box control
    EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), TRUE);
    EnableWindow(GetDlgItem(g_hwnd, IDC_IP), TRUE);
    EnableWindow(GetDlgItem(g_hwnd, IDC_NAME), TRUE);
    
    SetWindowText(GetDlgItem(g_hwnd, IDC_CONNECT), 
"Connect");

    
// flag as disconnected
    g_is_connected = FALSE;
}

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg_id, WPARAM wParam, LPARAM lParam)
{
    DPN_SERVICE_PROVIDER_INFO
*  adapter_ptr;

    unsigned 
int selected;    
    
char msg[256], ip[16];
    
    
switch(msg_id)
    {
    
case WM_COMMAND:
        
// The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a 
        
// notification message to its parent window, or when an accelerator keystroke is translated. 
        
//
        
// wParam:
        
//    The high-order word specifies the notification code if the message is from a control. 
        
//    If the message is from an accelerator, this value is 1. If the message is from a menu, this value is zero. 
        
//    The low-order word specifies the identifier of the menu item, control, or accelerator. 
        
//
        
// lParam:
        
//     Handle to the control sending the message if the message is from a control. 
        
//     Otherwise, this parameter is NULL. 
        switch(LOWORD(wParam))
        {
        
case IDC_CONNECT:
            
if(! g_is_connected)
            {
                
// Get adapter to use 
                if((selected = (int) SendMessage(GetDlgItem(hwnd, IDC_ADAPTERS), CB_GETCURSEL, 00)) == LB_ERR)
                    
// invalid selected item
                    break;

                
// get IP address to use
                GetWindowText(GetDlgItem(g_hwnd, IDC_IP), ip, 16);
               
                
// Make sure it's valid and start session
                if(selected < g_num_adapters)
                {
                    
// pointer adapter pointer to selected position
                    adapter_ptr  = g_adapter_list;
                    adapter_ptr 
+= selected;

                    
if(! Start_Session(&adapter_ptr->guid, ip))
                        MessageBox(hwnd, 
"Unable to start client!""Error", MB_OK | MB_ICONEXCLAMATION);
                }
            }
            
else
                Stop_Session();

            
break;

        
case IDC_SEND:
            GetWindowText(GetDlgItem(hwnd, IDC_MESSAGE), msg, 
256);

            SetWindowText(GetDlgItem(hwnd, IDC_MESSAGE), 
"");

            Send_Text_Msg(msg);

            
break;
        }   
// end - LOWORD(wParam):

        
break;

    
case WM_DESTROY:
        Stop_Session();
        Release_DirectPlay();
        PostQuitMessage(
0);
        
break;

    
default:
        
return (long) DefWindowProc(hwnd, msg_id, wParam, lParam);
    }

    
return 0;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS            win_class;
    MSG                 msg;    

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_CLIENT), 0, NULL);    

    
// initialize COM
    
//
    
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
    
// apartment (STA).
    CoInitialize(0);

    
// Initialzie DirectPlay and enumerate service providers.
    if(! Init_DirectPlay_Client())
    {
        MessageBox(NULL, 
"Error initializing DirectPlay.""ERROR", MB_OK | MB_ICONEXCLAMATION);
        
goto exit;
    }

    
// enumerate all TCP/IP adapters
    Enum_Adapters();

    
// Make sure there's an adapter to use
    if(g_num_adapters == 0)
    {
        MessageBox(g_hwnd, 
"There is no TCP/IP adapters to use!""ERROR", MB_OK | MB_ICONEXCLAMATION);
        
goto exit;
    }

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);            
        }             
    }

exit:
    UnregisterClass(g_class_name, inst);

    
// release COM system
    
//
    
// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other
    
// resources that the thread maintains, and forces all RPC connections on the thread to close.
    CoUninitialize();
    
    
return (int) msg.wParam;
}

该程序必须和使用DirectPlay进行网络互联(3)中提及的服务器端程序配合使用。

运行截图:



销毁玩家

当玩家断开连接时,服务器端会收到消息 DPN_MSGID_DESTROY_PLAYER,这时需要将消息缓冲区转换为DPNMSG_DESTROY_PLAYER类型。

The DPNMSG_DESTROY_PLAYER structure contains information for the DPN_MSGID_DESTROY_PLAYER system message.

typedef struct _DPNMSG_DESTROY_PLAYER{
DWORD dwSize;
DPNID dpnidPlayer;
PVOID pvPlayerContext;
DWORD dwReason;
} DPNMSG_DESTROY_PLAYER, *PDPNMSG_DESTROY_PLAYER;
dwSize
Size of this structure.
dpnidPlayer
DPNID of the player deleted from the session.
pvPlayerContext
Player context value.
dwReason
One of the following flags indicating why the player was destroyed.
DPNDESTROYPLAYERREASON_NORMAL
The player is being deleted for normal reasons.
DPNDESTROYPLAYERREASON_CONNECTIONLOST
The player is being deleted because the connection was lost.
DPNDESTROYPLAYERREASON_SESSIONTERMINATED
The player is being deleted because the session was terminated.
DPNDESTROYPLAYERREASON_HOSTDESTROYEDPLAYER
The player is being deleted because the host called IDirectPlay8Peer::DestroyPeer.

Return Values

Return DPN_OK.

Remarks

In client/server mode, this message is received only by the server. In peer-to-peer mode, all players receive this message.

When the server closes a session, it receives a DPN_MSGID_DESTROY_PLAYER message for all connected players. Because the server knows that it is disconnecting, this is normal behavior, and the dwReason member of the associated structure is set to DPNDESTROYPLAYERREASON_NORMAL. The DPNDESTROYPLAYERREASON_SESSIONTERMINATED value is only set for unexpected disconnections.

You might receive DPN_MSGID_CREATE_PLAYER and DPN_MSGID_DESTROY_PLAYER messages on different threads. However, you will not receive a DPN_MSGID_DESTROY_PLAYER message before your callback function has returned from receiving a DPN_MSGID_CREATE_PLAYER message.

要强制一个玩家断开连接,使用IDirectPlay8Server::DestroyClient函数即可。

Deletes a client from the session.

HRESULT DestroyClient(
const DPNID
dpnidClient,
const VOID *const pDestroyInfo,
const DWORD dwDestroyInfoSize,
const DWORD dwFlags
);

Parameters

dpnidClient
[in] Variable of type DPNID that specifies the identifier of the client to delete.
pDestroyInfo
[in] Pointer that describes additional delete data information.
dwDestroyInfoSize
[in] Variable of type DWORD that specifies the size of the data in the pDestroyInfo parameter.
dwFlags
[in] Reserved. Must be 0.

Return Values

Returns S_OK if successful, or one of the following error values.

DPNERR_INVALIDPARAM
DPNERR_INVALIDPLAYER
DPNERR_NOTHOST

以下代码示例了如何处理消息DPN_MSGID_DESTROY_PLAYER。

// application variables
struct PLAYER_INFO
{
    DPNID   player_id;  
// DirectPlay Player ID
    char    name[26];   // Player Name

    PLAYER_INFO()   { player_id 
= 0; }
};

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "ServerClass";

// application GUID
GUID g_app_guid = { 0xababbe600x1ac00x11d5, { 0x900x890x440x450x530x540x00x1 } };

IDirectPlay8Server
*         g_dp_server;    // DirectPlay Server
DPN_SERVICE_PROVIDER_INFO*  g_adapter_list; // adapters
DWORD                       g_num_adapters; // number of adapters

PLAYER_INFO g_player_info[
256]; // player information
BOOL g_is_hosting;              // flag indicates whether host started or not

//----------------------------------------------------------------------------------------
// Callback function that receives all messages from the client, and receives indications 
// of session changes from the IDirectPlay8Client interface. 
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{

    DPNMSG_DESTROY_PLAYER*  destroy_player; // contains information for the DPN_MSGID_DESTROY_PLAYER system message
    DPN_PLAYER_INFO*        dpn_player_info;// describes static player informaion
    DPN_BUFFER_DESC         buffer_desc;    // used dy DirectPlay for generic buffer information

    DPNHANDLE       async_handle;
    PLAYER_INFO
*    player_info;
    
int             index;
    DWORD           size;
    
char            message[512];
    HRESULT         rv;

    
switch(message_id)
    {
    
// Microsoft DirectPlay generates the DPN_MSGID_DESTROY_PLAYER message when a player leaves a peer-to-peer 
    
// or client/server session.
    case DPN_MSGID_DESTROY_PLAYER:
        destroy_player 
= (DPNMSG_DESTROY_PLAYER *) msg_buffer;

        
// make sure it is not the host
        if((player_info = (PLAYER_INFO*) destroy_player->pvPlayerContext) == NULL)
            
break;

        
// remove the player from list

        player_info
->player_id = 0;

        
// An application sends an LB_FINDSTRING message to find the first string in a list box that begins 
        
// with the specified string. 
        
//
        
// The return value is the index of the matching item, or LB_ERR if the search was unsuccessful. 
        
//
        
// wParam:
        
//    Specifies the zero-based index of the item before the first item to be searched. 
        
//    When the search reaches the bottom of the list box, it continues searching from the top of the 
        
//    list box back to the item specified by the wParam parameter. If wParam is –1, the entire list 
        
//    box is searched from the beginning. 
        
// 
        
// lParam:
        
//    Pointer to the null-terminated string that contains the string for which to search. 
        
//    The search is case independent, so this string can contain any combination of uppercase and 
        
//    lowercase letters.       
        index = (int) SendMessage(GetDlgItem(g_hwnd, IDC_USERS), LB_FINDSTRING, -1, (LPARAM)player_info->name);

        
if(index != LB_ERR)
            
// An application sends an LB_DELETESTRING message to delete a string in a list box. 
            
//
            
// wParam:
            
//      Specifies the zero-based index of the string to be deleted. 
            
// lParam:
            
//      This parameter is not used. 
            SendMessage(GetDlgItem(g_hwnd, IDC_USERS), LB_DELETESTRING, index, 0);

        
// send message to remaining players notifying player left
        sprintf(message, "%s quit.", player_info->name);
        Send_Text_Msg(DPNID_ALL_PLAYERS_GROUP, message);

        
break;
    }

    
// return S_OK to signify the message was handled OK.
    return S_OK;
}

接收数据

实际的游戏数据采取应用程序指定消息的形式,但总是封装在DPN_MSGID_RECEIVE消息类型中,使用DPNMSG_RECEIVE结构体。

The DPNMSG_RECEIVE structure contains information for the DPN_MSGID_RECEIVE system message.

typedef struct {
DWORD dwSize;
DPNID dpnidSender;
PVOID pvPlayerContext;
PBYTE pReceiveData;
DWORD dwReceiveDataSize;
DPNHANDLE hBufferHandle;
} DPNMSG_RECEIVE, *PDPNMSG_RECEIVE;
dwSize
Size of this structure.
dpnidSender
DPNID of the player that sent the message.
pvPlayerContext
Player context value of the player that sent the message.
pReceiveData
PBYTE pointer to the message data buffer. This buffer is normally only valid while the DPN_MSGID_RECEIVE message is being processed by the callback message handler.
dwReceiveDataSize
Size of the data, in bytes, of the pReceiveData member.
hBufferHandle
Buffer handle for the pReceiveData member. If you have returned DPNSUCCESS_PENDING , pass this value to ReturnBuffer to notify Microsoft® DirectPlay® to free the buffer.

Return Values

Return DPNSUCCESS_PENDING to transfer ownership of the data buffer to your application. Otherwise, return DPN_OK.

Remarks

Because you should not spend large amounts of time processing messages, you should copy the data, and process the message. Alternatively, you can return DPNSUCCESS_PENDING from the callback message handler. Doing so transfers ownership of the buffer to the application. If you return DPNSUCCESS_PENDING, you must call IDirectPlay8Peer::ReturnBuffer, IDirectPlay8Client::ReturnBuffer, or IDirectPlay8Server::ReturnBuffer when you are finished with the buffer. Pass the method the value you receive in the hBufferHandle member to identify the buffer. If you fail to call ReturnBuffer, you will create a memory leak.


以下代码示例了如何处理消息DPN_MSGID_RECEIVE。

IDirectPlay8Server*         g_dp_server;    // DirectPlay Server

//----------------------------------------------------------------------------------------
// Callback function that receives all messages from the client, and receives indications 
// of session changes from the IDirectPlay8Client interface. 
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{
    DPNMSG_RECEIVE
*         receive_data;   // contains information for the DPN_MSGID_RECEIVE system message
    DPN_BUFFER_DESC         buffer_desc;    // used dy DirectPlay for generic buffer information

    DPNHANDLE       async_handle;

    
switch(message_id)
    {
    
// Microsoft DirectPlay generates the DPN_MSGID_RECEIVE message when a message has been processed by 
    
// the receiver.
    case DPN_MSGID_RECEIVE:
        receive_data 
= (DPNMSG_RECEIVE*) msg_buffer;

        
// forward message to all player except host
        buffer_desc.dwBufferSize = receive_data->dwReceiveDataSize;
        buffer_desc.pBufferData  
= receive_data->pReceiveData;

        g_dp_server
->SendTo(DPNID_ALL_PLAYERS_GROUP, &buffer_desc, 10, NULL, &async_handle, DPNSEND_NOLOOPBACK);

        
break;
    }

    
// return S_OK to signify the message was handled OK.
    return S_OK;
}

发送服务器端消息

如果网络不传输数据,它就一无是处。要让服务器端对象发送数据到一个已经连接的客户端,需要使用SendTo函数,该函数发送数据到单个玩家,或一次发送到所有玩家,或发送到属于某一特定组的所有玩家。

Transmits data to a client or group within the session. The message can be sent synchronously or asynchronously.

HRESULT SendTo(
const DPNID
dpnid,
const DPN_BUFFER_DESC *const pBufferDesc,
const DWORD cBufferDesc,
const DWORD dwTimeOut,
void *const pvAsyncContext,
DPNHANDLE *const phAsyncHandle,
const DWORD dwFlags
);

Parameters

dpnid
[in] Identifier of the client or group to receive data. Set this parameter to DPNID_ALL_PLAYERS_GROUP to send a message to all players in the session.
pBufferDesc
[in] Pointer to a DPN_BUFFER_DESC structure that describes the data to send.
cBufferDesc
[in] Number of DPN_BUFFER_DESC structures pointed to by pBufferDesc. There can be only one buffer in this version of Microsoft® DirectPlay® .
dwTimeOut
[in] Number of milliseconds to wait for the message to send. If the message has not been sent by the dwTimeOut value, it is deleted from the send queue. If you set this parameter to 0, the message remains in the send queue until it is sent or until the link is dropped.
pvAsyncContext
[in] Pointer to the user-supplied context, which is returned in the pvUserContext member of the DPN_MSGID_SEND_COMPLETE system message.
phAsyncHandle
[out] A DPNHANDLE. When the method returns, phAsyncHandle will point to a handle that you can pass to IDirectPlay8Server::CancelAsyncOperation to cancel the operation. This parameter must be set to NULL if you set the DPNSEND_SYNC flag in dwFlags.
dwFlags
[in] Flags that describe send behavior. You can set one or more of the following flags.
DPNSEND_SYNC
Process the SendTo request synchronously.
DPNSEND_NOCOPY
Use the data in the DPN_BUFFER_DESC structure and do not make an internal copy. This may be a more efficient method of sending data. However, it is less robust because the sender might be able to modify the message before the receiver has processed it. This flag cannot be used with DPNSEND_NOCOMPLETE.
DPNSEND_NOCOMPLETE
Do not send the DPN_MSGID_SEND_COMPLETE structure to the message handler. This flag may not be used with DPNSEND_NOCOPY or DPNSEND_GUARANTEED. Additionally, when using this flag pvAsyncContext must be NULL.
DPNSEND_COMPLETEONPROCESS
Send the DPN_MSGID_SEND_COMPLETE to the message handler when this message has been delivered to the target and the target's message handler returns from indicating its reception. There is additional internal message overhead when this flag is set, and the message transmission process may become significantly slower. If you set this flag, DPNSEND_GUARANTEED must also be set.
DPNSEND_GUARANTEED
Send the message by a guaranteed method of delivery.
DPNSEND_PRIORITY_HIGH
Sets the priority of the message to high. This flag cannot be used with DPNSEND_PRIORITY_LOW.
DPNSEND_PRIORITY_LOW
Sets the priority of the message to low. This flag cannot be used with DPNSEND_PRIORITY_HIGH.
DPNSEND_NOLOOPBACK
Suppress the DPN_MSGID_RECEIVE system message to your message handler when you are sending to a group that includes the local player. For example, this flag is useful if you are broadcasting to the entire session.
DPNSEND_NONSEQUENTIAL
If this flag is set, the target application will receive the messages in the order that they arrive at the user's computer. If this flag is not set, messages are delivered sequentially, and will be received by the target application in the order that they were sent. Doing so may require buffering incoming messages until missing messages arrive.

Return Values

Returns S_OK if this method is processed synchronously and is successful. By default, this method is run asynchronously and generally returns DPNSUCCESS_PENDING or one of the following error values.

DPNERR_CONNECTIONLOST
DPNERR_INVALIDFLAGS
DPNERR_INVALIDPARAM
DPNERR_INVALIDPLAYER
DPNERR_TIMEDOUT

Remarks

This method generates a DPN_MSGID_RECEIVE system message in the receiver's message handler. The data is contained in the pReceiveData member of the associated structure.

Messages can have one of three priorities: low, normal, and high. To specify a low or high priority for the message set the appropriate flag in dwFlags. If neither of the priority flags is set, the message will have normal priority. See Basic Networking for a discussion of send priorities.

When the SendTo request is completed, a DPN_MSGID_SEND_COMPLETE system message is posted to the sender's message handler. The success or failure of the request is contained in the hResultCode member of the associated structure. You can suppress the send completion by setting the DPNSEND_NOCOMPLETE flag in dwflags.

Send completions are typically posted on the source computer as soon as the message is sent. In other words, a send completion does not necessarily mean that the message has been processed on the target. It may still be in a queue. If you want to be certain that the message has been processed by the target, set the DPNSEND_COMPLETEONPROCESS flag in dwFlags. This flag ensures that the send completion will not be sent until the target's message handler has processed the message, and returned.

Note  Do not assume that resources such as the data buffer will remain valid until the method has returned. If you call this method asynchronously, the DPN_MSGID_SEND_COMPLETE message may be received and processed by your message handler before the call has returned. If your message handler deallocates or otherwise invalidates a resource such as the data buffer, that resource may become invalid at any time after the method has been called.


下面这个函数发送消息数据到指定的玩家ID组。

//--------------------------------------------------------------------------------
// Send text to all clients which specified by player_id.
//--------------------------------------------------------------------------------
void Send_Text_Msg(DPNID player_id, char* text)
{
    DPNHANDLE async_handle;
    DPN_BUFFER_DESC buffer_desc;

    
if(g_dp_server == NULL)
        
return;

    
// build a data structure
    buffer_desc.dwBufferSize = (DWORD) (strlen(text) + 1);
    buffer_desc.pBufferData  
= (BYTE*) text;

    
// Send message (async method - reason for handle)
    
//
    
// Transmits data to a client or group within the session. 
    
// The message can be sent synchronously or asynchronously.
    g_dp_server->SendTo(player_id, &buffer_desc, 10, NULL, &async_handle, DPNSEND_NOLOOPBACK);
}

 

结束主持会话

一旦服务器端完成了工作,就是停止会话的时候了,停止会话也就是停止所有传输并销毁所有玩家,这可以通过IDirectPlay8Server::Close来完成。因为该函数采用同步方式工作,所以在所有传输完成以及所有连接关闭之前不会返回,这就保证了无须有任何顾虑,就能关闭应用程序。

Closes the open connection to a session.

HRESULT Close(
const DWORD
dwFlags
);

Parameters

dwFlags
[in] Reserved. Must be 0.

Return Values

Returns S_OK if successful, or the following error value.

DPNERR_UNINITIALIZED

Remarks

This method must be called on any object successfully initialized with IDirectPlay8Server::Initialize.

This method is a counterpart to IDirectPlay8Server::Host. It closes all active network connections hosted by the server. This method is synchronous, and will not return until the server has processed all DPN_MSGID_DESTROY_PLAYER messages. This feature guarantees that when Close returns, you can safely shut down the server application.

Calling Close will cancel all outstanding operations, including data sent as guaranteed. To make sure all messages are sent, wait for all outstanding SendTo calls to complete before calling Close.


以下给出一个完整的服务器端代码示例:

点击下载源码和工程

/***************************************************************************************
PURPOSE:
    Server Network Demo
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
<dplay8.h>
#include 
<dpaddr.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"dplayx.lib")

#pragma warning(disable : 
4996)

#define Safe_Release(p) if((p)) (p)->Release();

// application variables
struct PLAYER_INFO
{
    DPNID   player_id;  
// DirectPlay Player ID
    char    name[26];   // Player Name

    PLAYER_INFO()   { player_id 
= 0; }
};

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "ServerClass";

// application GUID
GUID g_app_guid = { 0xababbe600x1ac00x11d5, { 0x900x890x440x450x530x540x00x1 } };

IDirectPlay8Server
*         g_dp_server;    // DirectPlay Server
DPN_SERVICE_PROVIDER_INFO*  g_adapter_list; // adapters
DWORD                       g_num_adapters; // number of adapters

PLAYER_INFO g_player_info[
256]; // player information
BOOL g_is_hosting;              // flag indicates whether host started or not

//--------------------------------------------------------------------------------
// Send text to all clients which specified by player_id.
//--------------------------------------------------------------------------------
void Send_Text_Msg(DPNID player_id, char* text)
{
    DPNHANDLE async_handle;
    DPN_BUFFER_DESC buffer_desc;

    
if(g_dp_server == NULL)
        
return;

    
// build a data structure
    buffer_desc.dwBufferSize = (DWORD) (strlen(text) + 1);
    buffer_desc.pBufferData  
= (BYTE*) text;

    
// Send message (async method - reason for handle)
    
//
    
// Transmits data to a client or group within the session. 
    
// The message can be sent synchronously or asynchronously.
    g_dp_server->SendTo(player_id, &buffer_desc, 10, NULL, &async_handle, DPNSEND_NOLOOPBACK);
}

//----------------------------------------------------------------------------------------
// Callback function that receives all messages from the client, and receives indications 
// of session changes from the IDirectPlay8Client interface. 
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{
    DPNMSG_CREATE_PLAYER
*   create_player;  // contains information for the DPN_MSGID_CREATE_PLAYER system message
    DPNMSG_DESTROY_PLAYER*  destroy_player; // contains information for the DPN_MSGID_DESTROY_PLAYER system message
    DPNMSG_RECEIVE*         receive_data;   // contains information for the DPN_MSGID_RECEIVE system message
    DPN_PLAYER_INFO*        dpn_player_info;// describes static player informaion
    DPN_BUFFER_DESC         buffer_desc;    // used dy DirectPlay for generic buffer information

    DPNHANDLE       async_handle;
    PLAYER_INFO
*    player_info;
    
int             index;
    DWORD           size;
    
char            message[512];
    HRESULT         rv;

    
switch(message_id)
    {
    
// Microsoft DirectPlay generates the DPN_MSGID_CREATE_PLAYER message when a player is added to a 
    
// peer-to-peer or client/server session.
    case DPN_MSGID_CREATE_PLAYER:
        create_player 
= (DPNMSG_CREATE_PLAYER*) msg_buffer;

        
// get player name and save it

        size 
= 0;
        dpn_player_info 
= NULL;

        
// Retrieves the client information set for the specified client
        rv = g_dp_server->GetClientInfo(create_player->dpnidPlayer, dpn_player_info, &size, 0);

        
if(FAILED(rv) && rv != DPNERR_BUFFERTOOSMALL)
        {
            
// skip this if this is a host player
            if(rv == DPNERR_INVALIDPLAYER)
                
break;

            
return E_FAIL;
        }

        
if((dpn_player_info = (DPN_PLAYER_INFO*new BYTE[size]) == NULL)
            
return E_FAIL;

        ZeroMemory(dpn_player_info, size);
        dpn_player_info
->dwSize = sizeof(DPN_PLAYER_INFO);

        
// retrieves the client information set again
        if(FAILED(g_dp_server->GetClientInfo(create_player->dpnidPlayer, dpn_player_info, &size, 0)))
        {
            delete[] dpn_player_info;
            
return E_FAIL;
        }

        
// Find an empty player structure to use

        index 
= -1;

        
for(int i = 0; i < 256; i++)
        {
            
if(g_player_info[i].player_id == 0)
            {
                index 
= i;
                
break;
            }
        }

        
if(index == -1)
        {
            delete[] dpn_player_info;
            
return E_FAIL;
        }

        
// set player context pointer
        create_player->pvPlayerContext = (void*&g_player_info[index];

        
// save player ID
        g_player_info[index].player_id = create_player->dpnidPlayer;
        
        wcstombs(g_player_info[index].name, dpn_player_info
->pwszName, 256);
        
        
// add player to list
        SendMessage(GetDlgItem(g_hwnd, IDC_USERS), LB_ADDSTRING, 0, (LPARAM) g_player_info[index].name);

        
// send a message to all players notifying someone joined
        sprintf(message, "%s joined!", g_player_info[index].name);
        Send_Text_Msg(DPNID_ALL_PLAYERS_GROUP, message);

        delete[] dpn_player_info;

        
break;

    
// Microsoft DirectPlay generates the DPN_MSGID_DESTROY_PLAYER message when a player leaves a peer-to-peer 
    
// or client/server session.
    case DPN_MSGID_DESTROY_PLAYER:
        destroy_player 
= (DPNMSG_DESTROY_PLAYER *) msg_buffer;

        
// make sure it is not the host
        if((player_info = (PLAYER_INFO*) destroy_player->pvPlayerContext) == NULL)
            
break;

        
// remove the player from list

        player_info
->player_id = 0;

        
// An application sends an LB_FINDSTRING message to find the first string in a list box that begins 
        
// with the specified string. 
        
//
        
// The return value is the index of the matching item, or LB_ERR if the search was unsuccessful. 
        
//
        
// wParam:
        
//    Specifies the zero-based index of the item before the first item to be searched. 
        
//    When the search reaches the bottom of the list box, it continues searching from the top of the 
        
//    list box back to the item specified by the wParam parameter. If wParam is –1, the entire list 
        
//    box is searched from the beginning. 
        
// 
        
// lParam:
        
//    Pointer to the null-terminated string that contains the string for which to search. 
        
//    The search is case independent, so this string can contain any combination of uppercase and 
        
//    lowercase letters.       
        index = (int) SendMessage(GetDlgItem(g_hwnd, IDC_USERS), LB_FINDSTRING, -1, (LPARAM)player_info->name);

        
if(index != LB_ERR)
            
// An application sends an LB_DELETESTRING message to delete a string in a list box. 
            
//
            
// wParam:
            
//      Specifies the zero-based index of the string to be deleted. 
            
// lParam:
            
//      This parameter is not used. 
            SendMessage(GetDlgItem(g_hwnd, IDC_USERS), LB_DELETESTRING, index, 0);

        
// send message to remaining players notifying player left
        sprintf(message, "%s quit.", player_info->name);
        Send_Text_Msg(DPNID_ALL_PLAYERS_GROUP, message);

        
break;

    
// Microsoft DirectPlay generates the DPN_MSGID_RECEIVE message when a message has been processed by 
    
// the receiver.
    case DPN_MSGID_RECEIVE:
        receive_data 
= (DPNMSG_RECEIVE*) msg_buffer;

        
// forward message to all player except host
        buffer_desc.dwBufferSize = receive_data->dwReceiveDataSize;
        buffer_desc.pBufferData  
= receive_data->pReceiveData;

        g_dp_server
->SendTo(DPNID_ALL_PLAYERS_GROUP, &buffer_desc, 10, NULL, &async_handle, DPNSEND_NOLOOPBACK);

        
break;
    }

    
// return S_OK to signify the message was handled OK.
    return S_OK;
}

//--------------------------------------------------------------------------------
// Create DirectPlay Server and initialize it.
//--------------------------------------------------------------------------------
BOOL Init_DirectPlay_Server()
{
    
// create DirectPlay Server component
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Server, NULL, CLSCTX_INPROC, IID_IDirectPlay8Server, 
                               (
void**)&g_dp_server)))
        
return FALSE;

    
// Assign a message handler to network component
    
//
    
// Registers an entry point in the client's code that receives the messages from the IDirectPlay8Client
    
// interface and from the server.
    if(FAILED(g_dp_server->Initialize(NULL, Net_Msg_Handle, 0)))
        
return FALSE;

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Enumerate all TCP/IP adapters.
//--------------------------------------------------------------------------------
void Enum_Adapters()
{
    
// return if no server object or GUID
    if(g_dp_server == NULL)
        
return;

    
// get a handle of the list box
    HWND adapters_ctrl = GetDlgItem(g_hwnd, IDC_ADAPTERS);

    
// clear the list box
    SendMessage(adapters_ctrl, LB_RESETCONTENT, 00);

    
// free prior adapter list
    delete[] g_adapter_list;
    g_adapter_list 
= NULL;

    g_num_adapters 
= 0;

    DWORD adapter_list_size 
= 0;

    
// query the required size of the data buffer
    HRESULT rv = g_dp_server->EnumServiceProviders(&CLSID_DP8SP_TCPIP, NULL, g_adapter_list, &adapter_list_size, 
                                                   
&g_num_adapters, 0);

    
if(rv != DPNERR_BUFFERTOOSMALL)
        
return;

    
// allocate a buffer
    if((g_adapter_list = (DPN_SERVICE_PROVIDER_INFO*new BYTE[adapter_list_size]) == NULL)
        
return;

    
// enumerate again
    if(SUCCEEDED(g_dp_server->EnumServiceProviders(&CLSID_DP8SP_TCPIP, NULL, g_adapter_list, &adapter_list_size, 
                                                   
&g_num_adapters, 0)))
    {
        
char adapter_name[1024];

        
// enumeration is complete, scan through entries.
        DPN_SERVICE_PROVIDER_INFO* adapter_ptr = g_adapter_list;

        
for(DWORD i = 0; i < g_num_adapters; i++)
        {
            
// convert wide string into multi-byte string
            wcstombs(adapter_name, adapter_ptr->pwszName, 1024);

            
// add the adapter name int listbox
            SendMessage(adapters_ctrl, CB_ADDSTRING, 0, (LPARAM)adapter_name);

            
// go to next servicec provider
            adapter_ptr++;
        }
    }

    
// Select first adapter
    
//
    
// An application sends a CB_SETCURSEL message to select a string in the list of a combo box. 
    
// If necessary, the list scrolls the string into view. The text in the edit control of the combo box 
    
// changes to reflect the new selection, and any previous selection in the list is removed. 
    
//
    
// wParam:
    
//    Specifies the zero-based index of the string to select. If this parameter is –1, any current selection 
    
//    in the list is removed and the edit control is cleared. 
    
//
    
// lParam:
    
//    This parameter is not used. 
    SendMessage(adapters_ctrl, CB_SETCURSEL, 00);    
}

//--------------------------------------------------------------------------------
// Release all resource which allocated for DirectPlay.
//--------------------------------------------------------------------------------
void Release_DirectPlay()
{
    
// release client service provider list memory
    delete[] g_adapter_list;
    g_adapter_list 
= NULL;
    
    g_num_adapters 
= 0;

    
// release client component
    if(g_dp_server != NULL)
    {
        
// Closes the open connection to a session. This method must be called on any object that is successfully 
        
// initialized with a call to the IDirectPlay8Client::Initialize method.
        g_dp_server->Close(0);

        g_dp_server
->Release();

        g_dp_server 
= NULL;
    }
}

//--------------------------------------------------------------------------------
// Start hosting a server which GUID specified by adapter_guid.
//--------------------------------------------------------------------------------
BOOL Start_Session(GUID* adapter_guid)
{
    
// Make sure there's an adapter
    if(adapter_guid == NULL)
        
return FALSE;

    
// Need to re-assign a network handler as quitting a previous session to clears it.
    
// Close the connection first before assigning a new network handler.
    
//
    
// Closes the open connnection to a session.
    g_dp_server->Close(0);

    
// Initialize DirectPlay Server
    if(FAILED(g_dp_server->Initialize(NULL, Net_Msg_Handle, 0)))
        
return FALSE;

    IDirectPlay8Address
*    dp_address;

    
// Create an address object and fill it with information
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC, IID_IDirectPlay8Address, 
                               (
void**)&dp_address)))
        
return FALSE;

    
// Set the protocol to TCP/IP
    
//
    
// Sets the service provider GUID in the address object.
    
// If a service provider is specified for this address, it is overwrittern by this call.
    if(FAILED(dp_address->SetSP(&CLSID_DP8SP_TCPIP)))
    {
        dp_address
->Release();
        
return FALSE;
    }

    
// Set the port
    DWORD port = 21234;

    
// Adds a component to the address.
    
// If the component is part of the address, it is replaced by the new value in this call.

    
// Set port
    if(FAILED(dp_address->AddComponent(DPNA_KEY_PORT, &port, sizeof(DWORD), DPNA_DATATYPE_DWORD)))
    {
        dp_address
->Release();
        
return FALSE;
    }

    
// Set the adapter
    if(FAILED(dp_address->AddComponent(DPNA_KEY_DEVICE, adapter_guid, sizeof(GUID), DPNA_DATATYPE_GUID)))
    {
        dp_address
->Release();
        
return FALSE;
    }

    DPN_APPLICATION_DESC app_desc;  
// Describes the settings for a Microsoft DirectPlay application

    
// Setup the application description structure

    ZeroMemory(
&app_desc, sizeof(DPN_APPLICATION_DESC));

    app_desc.dwSize          
= sizeof(DPN_APPLICATION_DESC);
    app_desc.dwFlags         
= DPNSESSION_CLIENT_SERVER;
    app_desc.guidApplication 
= g_app_guid;
    app_desc.pwszSessionName 
= L"SercverSession";
    app_desc.dwMaxPlayers    
= 256;

    
// Start hosting
    
//
    
// Creates a new client/server session, hosted by the local computer.
    if(FAILED(g_dp_server->Host(&app_desc, &dp_address, 1, NULL, NULL, NULL, 0)))
    {
        dp_address
->Release();
        
return FALSE;
    }

    
// Release the address component
    dp_address->Release();

    
// Disables combo-box control.
    
//
    
// Enables or disables mouse and keyboard input to the specified window or control. 
    
// When input is disabled, the window does not receive input such as mouse clicks and key presses. 
    
// When input is enabled, the window receives all input. 
    EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), FALSE);

    
// Setup the dialog information
    SetWindowText(GetDlgItem(g_hwnd, IDC_STARTSTOP), "Stop");

    g_is_hosting 
= TRUE;

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Stoping hosting server.
//--------------------------------------------------------------------------------
void Stop_Session()
{
    
// Close the connection
    if(g_dp_server)
        g_dp_server
->Close(0);

    
// Enables combo-box control
    EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), TRUE);

    
// Setup the dialog information
    SetWindowText(GetDlgItem(g_hwnd, IDC_STARTSTOP), "Start");

    g_is_hosting 
= FALSE;
}

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    unsigned 
int selected;
    
char name[256];
    DPN_SERVICE_PROVIDER_INFO
*  adapter_ptr;

    
switch(msg)
    {
    
case WM_COMMAND:
        
// The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a 
        
// notification message to its parent window, or when an accelerator keystroke is translated. 
        
//
        
// wParam:
        
//    The high-order word specifies the notification code if the message is from a control. 
        
//    If the message is from an accelerator, this value is 1. If the message is from a menu, this value is zero. 
        
//    The low-order word specifies the identifier of the menu item, control, or accelerator. 
        
//
        
// lParam:
        
//     Handle to the control sending the message if the message is from a control. 
        
//     Otherwise, this parameter is NULL. 
        switch(LOWORD(wParam))
        {
        
case IDC_STARTSTOP:
            
if(! g_is_hosting)
            {
                
// Get adapter to use 
                if((selected = (int) SendMessage(GetDlgItem(hwnd, IDC_ADAPTERS), CB_GETCURSEL, 00)) == LB_ERR)
                    
// invalid selected item
                    break;

                
// Make sure it's valid and start session
                if(selected < g_num_adapters)
                {
                    
// pointer adapter pointer to selected position
                    adapter_ptr  = g_adapter_list;
                    adapter_ptr 
+= selected;

                    
if(! Start_Session(&adapter_ptr->guid))
                        MessageBox(hwnd, 
"Unable to start server!""Error", MB_OK | MB_ICONEXCLAMATION);
                }
            }
            
else
                Stop_Session();

            
break;

        
case IDC_DISCONNECT:
            
// Make sure we're hosting
            if(g_is_hosting)
            {
                
// Get user from list to disconnect
                selected = (int) SendMessage(GetDlgItem(hwnd, IDC_USERS), LB_GETCURSEL, 00);

                
if(selected != LB_ERR)
                {
                    
// Find the player in list
                    
//
                    
// An application sends an LB_GETTEXT message to retrieve a string from a list box. 
                    
//
                    
// wParam:
                    
//      Specifies the zero-based index of the string to retrieve. 
                    
//
                    
// lParam:
                    
//      Pointer to the buffer that will receive the string; it is type LPTSTR which is subsequently 
                    
//      cast to an LPARAM. The buffer must have sufficient space for the string and a terminating 
                    
//      null character. An LB_GETTEXTLEN message can be sent before the LB_GETTEXT message to 
                    
//      retrieve the length, in TCHARs, of the string.
                    SendMessage(GetDlgItem(hwnd, IDC_USERS), LB_GETTEXT, selected, (LPARAM)name);

                    
for(unsigned int i = 0; i < 256; i++)
                    {
                        
if(g_player_info[i].player_id != 0 && !strcmp(g_player_info[i].name, name))
                        {
                            
// Disconnect them
                            
//
                            
// Deletes a client from the session
                            g_dp_server->DestroyClient(g_player_info[i].player_id, NULL, 00);

                            
break;
                        }
                    }
                }
            }

            
break;
        }   
// end - case WM_COMMAND:

        
break;

    
case WM_DESTROY:
        Stop_Session();
        Release_DirectPlay();
        PostQuitMessage(
0);
        
break;

    
default:
        
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
    }

    
return 0;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS            win_class;
    MSG                 msg;    

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_SERVER), 0, NULL);    

    
// initialize COM
    
//
    
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
    
// apartment (STA).
    CoInitialize(0);

    
// Initialzie DirectPlay and enumerate service providers.
    if(! Init_DirectPlay_Server())
    {
        MessageBox(NULL, 
"Error initializing DirectPlay.""ERROR", MB_OK | MB_ICONEXCLAMATION);
        
goto exit;
    }

    
// enumerate all TCP/IP adapters
    Enum_Adapters();

    
// Make sure there's an adapter to use
    if(g_num_adapters == 0)
    {
        MessageBox(g_hwnd, 
"There is no TCP/IP adapters to use!""ERROR", MB_OK | MB_ICONEXCLAMATION);
        
goto exit;
    }

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);            
        }             
    }

exit:
    UnregisterClass(g_class_name, inst);

    
// release COM system
    
//
    
// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other
    
// resources that the thread maintains, and forces all RPC connections on the thread to close.
    CoUninitialize();
    
    
return (int) msg.wParam;
}

运行截图:


本篇是使用DirectPlay进行网络互联(1)的续篇。

使用地址

一个网络使用IP地址和端口来传送数据,在DirectPlay中,使用DirectPlay专用对象IDirectPlay8Address来构造地址。常用的 IDirectPlay8Address方法有三个:

IDirectPlay8Address::Clear 清除所有地址数据
IDirectPlay8Address::SetSP 设置服务提供者
IDirectPlay8Address::AddComponent 添加地址组件

使用地址对象之前,需要使用CoCreateInstance函数来创建它:

IDirectPlay8Server*         g_dp_server;    // DirectPlay Server

// create DirectPlay Server component
if(FAILED(CoCreateInstance(CLSID_DirectPlay8Server, NULL, CLSCTX_INPROC, IID_IDirectPlay8Server, (void**)&g_dp_server)))
    
return FALSE;

添加组件

一个地址对象只是包含Unicode文本字符串的简单对象,该文本字符串包含服务提供者、端口号以及其他可选信息。AddComponent函数的惟一用途就是创建该字符串以供其他对象使用。

Adds a component to the address. If the component is part of the address, it is replaced by the new value in this call.

Values are specified in native formats when making this call. Therefore, the lpvData parameter should be a recast pointer to a variable that holds the data in the native format. For example, if the component is a GUID, the lpvData parameter should be a recast pointer to a GUID.

This method validates that the predefined component types are the right format.

HRESULT AddComponent(
const WCHAR *const
pwszName,
const void *const lpvData,
const DWORD dwDataSize,
const DWORD dwDataType
);

Parameters

pwszName
[in] NULL-terminated Unicode string that contains the key for the component.
lpvData
[in] Pointer to a buffer that contains the value associated with the specified key. Data should be specified in its native format.
dwDataSize
[in] Size, in bytes, of the data in the buffer located at lpvData. The size depends on the data type. If the size is not specified correctly, the method returns DPNERR_INVALIDPARAM.
DWORD
Size = sizeof( DWORD )
GUID
Size = sizeof( GUID )
String
Size = size of the string in bytes, including NULL terminator.
dwDataType
[in] Data type of the value associated with this key. The data type can be one of the following:
DPNA_DATATYPE_STRING
Data is a NULL-terminated string.
DPNA_DATATYPE_DWORD
Data is a DWORD.
DPNA_DATATYPE_GUID
Data is a GUID.
DPNA_DATATYPE_BINARY
Data is in raw binary format.

Return Values

Returns S_OK if successful, or one of the following error values.

DPNERR_INVALIDPARAM
DPNERR_INVALIDPOINTER
DPNERR_NOTALLOWED

Remarks

See DirectPlay Addressing for a discussion of various address components and their keys.


设置服务提供者

下一步要做的就是选择服务提供者,调用 IDirectPlay8Address::SetSP函数来设置。

Sets the service provider GUID in the address object. If a service provider is specified for this address, it is overwritten by this call.

HRESULT SetSP(
const GUID *const
pguidSP
);

Parameters

pguidSP
[in] Pointer to the service provider GUID.

Return Values

Returns S_OK if successful, or one of the following error values.

DPNERR_INVALIDPOINTER
DPNERR_NOTALLOWED

选择端口

无论是主持一次会话(作为服务器端或单点)还是使用客户端网络模型连接到一个远程系统,接下来必须要做的就是选择端口,如果要连接到远程系统,就必须知道应用程序所使用的端口,以便能够连接到远程系统以及发送数据到远程系统。端口号可以随意选择,但是不要使用保留端口号(1 - 1024),选择1024以上的端口号会比较安全。可以通过指定端口号0而让DirectPlay来选择一个端口号,但是这样做的弊病在于端口号可能为任意数字,就需要对端口号进行查询,推荐的做法是自己选择一个端口号。

设置端口号使用 IDirectPlay8Address::AddComponent函数。

IDirectPlay8Address*    dp_address;

DWORD port = 21234;

// Set port
if(FAILED(dp_address->AddComponent(DPNA_KEY_PORT, &port, sizeof(DWORD), DPNA_DATATYPE_DWORD)))
{
      dp_address
->Release();
      
return FALSE;
}

使用消息处理函数

以下是一个简单的消息处理函数代码实例:

//----------------------------------------------------------------------------------------
// Callback function that receives all messages from the client, and receives indications 
// of session changes from the IDirectPlay8Client interface. 
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{
    DPNMSG_CREATE_PLAYER
*   create_player;  // contains information for the DPN_MSGID_CREATE_PLAYER system message
    DPNMSG_DESTROY_PLAYER*  destroy_player; // contains information for the DPN_MSGID_DESTROY_PLAYER system message

    
switch(message_id)
    {
    
// Microsoft DirectPlay generates the DPN_MSGID_CREATE_PLAYER message when a player is added to a 
    
// peer-to-peer or client/server session.
    case DPN_MSGID_CREATE_PLAYER:
        create_player 
= (DPNMSG_CREATE_PLAYER*) msg_buffer
        
        
// 处理创建玩家
        
        
return S_OK;

    
// Microsoft DirectPlay generates the DPN_MSGID_DESTROY_PLAYER message when a player leaves a peer-to-peer 
    
// or client/server session.
    case DPN_MSGID_DESTROY_PLAYER:
        destroy_player 
= (DPNMSG_DESTROY_PLAYER *) msg_buffer;

        
// 处理销毁玩家
        
        
return S_OK;       
    }

    
return S_FAIL;
}

通过switch-case语句,可以快速找到要处理的消息,而略过其他消息。如果处理消息成功,就返回S_OK,否则返回E_FAIL表示处理失败。每个消息都带有一个数据缓冲区,这些缓冲区被类型转换为适当的结构体,除了它们是以DPNMSG_而不是DPN_MSGID_开头之外,这些结构体和消息宏几乎具有相同的命名规则。

配置会话信息

每一个网络对象都需要知道一些要主持或要加入的会话信息,这些信息包含在一个结构体中:

Describes the settings for a Microsoft® DirectPlay® application.

typedef struct _DPN_APPLICATION_DESC{
DWORD dwSize;
DWORD dwFlags;
GUID guidInstance;
GUID guidApplication;
DWORD dwMaxPlayers;
DWORD dwCurrentPlayers;
WCHAR* pwszSessionName;
WCHAR* pwszPassword;
PVOID pvReservedData;
DWORD dwReservedDataSize;
PVOID pvApplicationReservedData;
DWORD dwApplicationReservedDataSize;
} DPN_APPLICATION_DESC, *PDPN_APPLICATION_DESC;

Members

dwSize
Size of the DPN_APPLICATION_DESC structure. The application must set this member before it uses the structure.
dwFlags
One of the following flags describing application behavior.
DPNSESSION_CLIENT_SERVER
This type of session is client/server. This flag cannot be combined with DPNSESSION_MIGRATE_HOST.
DPNSESSION_MIGRATE_HOST
Used in peer-to-peer sessions, enables host migration. This flag cannot be combined with DPNSESSION_CLIENT_SERVER.
DPNSESSION_NODPNSVR
Do not forward enumerations to your host from DPNSVR. See Using the DirectPlay DPNSVR Application for details.
DPNSESSION_REQUIREPASSWORD
The session is password protected. If this flag is set, pwszPassword must be a valid string.
guidInstance
Globally unique identifier (GUID) that is generated by DirectPlay at startup, representing the instance of this application. This member is an [out] parameter when calling the GetApplicationDesc method exposed by the IDirectPlay8Peer, IDirectPlay8Client, and IDirectPlay8Server interfaces. It is an optional [in] parameter when calling the Connect method exposed by the IDirectPlay8Peer and IDirectPlay8Client interfaces. It must be set to GUID NULL when you call the SetApplicationDesc method exposed by the IDirectPlay8Server and IDirectPlay8Peer interfaces. You cannot obtain this GUID by calling the IDirectPlay8Server::Host or IDirectPlay8Peer::Host methods. You must obtain the GUID by calling a GetApplicationDesc method.
guidApplication
Application GUID.
dwMaxPlayers
Variable of type DWORD, specifying the maximum number of players allowed in the session. Set this member to 0 to specify an unlimited number of players.
dwCurrentPlayers
Variable of type DWORD specifying the number of players currently connected to the session. This member is an [out] parameter that is set only by the GetApplicationDescription method exposed by IDirectPlay8Peer, IDirectPlay8Client, and IDirectPlay8Server.
pwszSessionName
Pointer to a variable of type WCHAR specifying the Unicode name of the session. This member is set by the host or server only for informational purposes. A client cannot use this name to connect to a host or server.
pwszPassword
Pointer to a variable of type WCHAR specifying the Unicode password that is required to connect to the session. This must be NULL if the DPNSESSION_REQUIREPASSWORD is not set in the dwFlags member.
pvReservedData
Pointer to DirectPlay reserved data. An application should never modify this value.
dwReservedDataSize
Variable of type DWORD specifying the size of data contained in the pvReservedData member. An application should never modify this value.
pvApplicationReservedData
Pointer to application-specific reserved data. This value is optional and may be set to NULL.
dwApplicationReservedDataSize
Variable of type DWORD specifying the size of the data in the pvApplicationReservedData member. This value is optional and may be set to 0.

Remarks

Multiple instances of the application can run simultaneously. "Application" refers to a specific instance of an application.

The dwMaxPlayers, pvApplicationReservedData, dwApplicationReservedDataSize, pwszPassword, and pwszSessionName members can be set when calling the Host or SetApplicationDesc methods exposed by the IDirectPlay8Server and IDirectPlay8Peer interfaces.

When connecting to a password protected session, the data in the pwszPassword member is transmitted in clear text to the host.


会话数据

一个服务器端需要配置允许的最大玩家数(如果需要进行限制)、会话名称和登陆密码、会话标志以及应用程序GUID,如果不想限制最大玩家数,将dwMaxPlayers字段设置为0即可。

// application GUID
GUID g_app_guid = { 0xababbe600x1ac00x11d5, { 0x900x890x440x450x530x540x00x1 } };

DPN_APPLICATION_DESC app_desc;  
// Describes the settings for a Microsoft DirectPlay application

// Setup the application description structure

ZeroMemory(
&app_desc, sizeof(DPN_APPLICATION_DESC));

app_desc.dwSize          
= sizeof(DPN_APPLICATION_DESC);
app_desc.dwFlags         
= DPNSESSION_CLIENT_SERVER;
app_desc.guidApplication 
= g_app_guid;
app_desc.pwszSessionName 
= L"SercverSession";
app_desc.dwMaxPlayers    
= 256;

客户端结构体所需设置的信息包括要加入的会话名称和密码、客户端 / 服务器端会话标志以及应用程序GUID。一定要使用和服务器端相同的应用程序GUID,这样客户端和服务器端才能在网络中找到对方。

服务器端的处理

获得网络的第一步是创建一个服务器端。服务器端扮演了网络游戏的中央处理单元,所有玩家通过客户端应用程序连接到服务器端并在二者之间来回传输数据。服务器端保持游戏数据同步并告知玩家当前的游戏状态,虽然对于小型网络游戏而言,这并不是最快的办法,但对于大型网络游戏而言,则是最好的方法。

要创建服务器端,需要调用IDirectPlay8Server::Host来主持会话。

Creates a new client/server session, hosted by the local computer.

HRESULT Host(
const DPN_APPLICATION_DESC *const
pdnAppDesc,
IDirectPlay8Address **const prgpDeviceInfo,
const DWORD cDeviceInfo,
const DPN_SECURITY_DESC *const pdpSecurity,
const DPN_SECURITY_CREDENTIALS *const pdpCredentials,
VOID *const pvPlayerContext,
const DWORD dwFlags
);

Parameters

pdnAppDesc
[in] Pointer to a DPN_APPLICATION_DESC structure that describes the application.
prgpDeviceInfo
[in] Pointer to an array of IDirectPlay8Address objects containing device addresses that should be used to host the application.
cDeviceInfo
[in] Variable of type DWORD that specifies the number of device address objects in the array pointed to by prgpDeviceInfo.
pdpSecurity
[in] Reserved. Must be set to NULL.
pdpCredentials
[in] Reserved. Must be set to NULL.
pvPlayerContext
[in] Pointer to the context value of the player. This value is preset when the local computer handles the DPN_MSGID_CREATE_PLAYER message. This parameter is optional, and may be set to NULL.
dwFlags
[in] The following flag can be specified.
DPNHOST_OKTOQUERYFORADDRESSING
Setting this flag will display a standard Microsoft® DirectPlay® dialog box, which queries the user for more information if not enough information is passed in this method.

Return Values

Returns S_OK if successful, or the following error value.

DPNERR_DATATOOLARGE
DPNERR_INVALIDPARAM

Remarks

If you set the DPNHOST_OKTOQUERYFORADDRESSING flag in dwFlags, the service provider may attempt to display a dialog box to ask the user to complete the address information. You must have a visible window present when the service provider tries to display the dialog box, or your application will lock.

The maximum size of the application data that you assign to the pvApplicationReservedData member of the DPN_APPLICATION_DESC structure is limited by the service provider's Maximum Transmission Unit. If your application data is too large, the method will fail and return DPNERR_DATATOOLARGE.


以下是创建服务器端会话的代码示例:

HWND g_hwnd;

// application GUID
GUID g_app_guid = { 0xababbe600x1ac00x11d5, { 0x900x890x440x450x530x540x00x1 } };

IDirectPlay8Server
*         g_dp_server;    // DirectPlay Server

BOOL g_is_hosting;              
// flag indicates whether host started or not

//--------------------------------------------------------------------------------
// Start hosting a server which GUID specified by adapter_guid.
//--------------------------------------------------------------------------------
BOOL Start_Session(GUID* adapter_guid)
{
    
// Make sure there's an adapter
    if(adapter_guid == NULL)
        
return FALSE;

    
// Need to re-assign a network handler as quitting a previous session to clears it.
    
// Close the connection first before assigning a new network handler.
    
//
    
// Closes the open connnection to a session.
    g_dp_server->Close(0);

    
// Initialize DirectPlay Server
    if(FAILED(g_dp_server->Initialize(NULL, Net_Msg_Handle, 0)))
        
return FALSE;

    IDirectPlay8Address
*    dp_address;

    
// Create an address object and fill it with information
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC, IID_IDirectPlay8Address, (void**)&dp_address)))
        
return FALSE;

    
// Set the protocol to TCP/IP
    
//
    
// Sets the service provider GUID in the address object.
    
// If a service provider is specified for this address, it is overwrittern by this call.
    if(FAILED(dp_address->SetSP(&CLSID_DP8SP_TCPIP)))
    {
        dp_address
->Release();
        
return FALSE;
    }

    
// Set the port
    DWORD port = 21234;

    
// Adds a component to the address.
    
// If the component is part of the address, it is replaced by the new value in this call.

    
// Set port
    if(FAILED(dp_address->AddComponent(DPNA_KEY_PORT, &port, sizeof(DWORD), DPNA_DATATYPE_DWORD)))
    {
        dp_address
->Release();
        
return FALSE;
    }

    
// Set the adapter
    if(FAILED(dp_address->AddComponent(DPNA_KEY_DEVICE, adapter_guid, sizeof(GUID), DPNA_DATATYPE_GUID)))
    {
        dp_address
->Release();
        
return FALSE;
    }

    DPN_APPLICATION_DESC app_desc;  
// Describes the settings for a Microsoft DirectPlay application

    
// Setup the application description structure

    ZeroMemory(
&app_desc, sizeof(DPN_APPLICATION_DESC));

    app_desc.dwSize          
= sizeof(DPN_APPLICATION_DESC);
    app_desc.dwFlags         
= DPNSESSION_CLIENT_SERVER;
    app_desc.guidApplication 
= g_app_guid;
    app_desc.pwszSessionName 
= L"SercverSession";
    app_desc.dwMaxPlayers    
= 256;

    
// Start hosting
    
//
    
// Creates a new client/server session, hosted by the local computer.
    if(FAILED(g_dp_server->Host(&app_desc, &dp_address, 1, NULL, NULL, NULL, 0)))
    {
        dp_address
->Release();
        
return FALSE;
    }

    
// Release the address component
    dp_address->Release();

    
// Disables combo-box control.
    
//
    
// Enables or disables mouse and keyboard input to the specified window or control. 
    
// When input is disabled, the window does not receive input such as mouse clicks and key presses. 
    
// When input is enabled, the window receives all input. 
    EnableWindow(GetDlgItem(g_hwnd, IDC_ADAPTERS), FALSE);

    
// Setup the dialog information
    SetWindowText(GetDlgItem(g_hwnd, IDC_STARTSTOP), "Stop");

    g_is_hosting 
= TRUE;

    
return TRUE;
}

处理玩家

服务器端创建好之后,接收到的第一个消息就是创建一个玩家。创建的第一个玩家总是主机玩家,然后其他玩家才开始被创建和释放,但是在整个会话过程中,主机玩家始终都存在。创建玩家消息通过DPN_MSGID_CREATE_PLAYER定义,并且消息缓冲区被类型转换成 DPNMSG_CREATE_PLAYER结构体。

Microsoft® DirectPlay® generates the DPN_MSGID_CREATE_PLAYER message when a player is added to a peer-to-peer or client/server session.

The DPNMSG_CREATE_PLAYER structure contains information for the DPN_MSGID_CREATE_PLAYER system message.

typedef struct _DPNMSG_CREATE_PLAYER{
DWORD dwSize;
DPNID dpnidPlayer;
PVOID pvPlayerContext;
} DPNMSG_CREATE_PLAYER, *PDPNMSG_CREATE_PLAYER;
dwSize
Size of this structure.
dpnidPlayer
DPNID of the player that was added to the session.
pvPlayerContext
Player context value.

Return Values

Return DPN_OK.

Remarks

The only method of setting the player context value is through this system message. You can either set the player context value directly, through this message, or indirectly through DPN_MSGID_INDICATE_CONNECT. Once a player context value has been set, it cannot be changed.


玩家相关数据指针pvPlayerContext是用来在应用程序中定义玩家的信息,该数据指针可以指向一个结构体、类实例或包含了玩家名称、健康状态、年龄、当前武器以及护甲等的数据缓冲区。通过把玩家相关数据指针传递给DirectPlay,它就能带来更快的访问速度。因为时间关系,必须很快遍历一个已登录玩家的列表来查找一个匹配的玩家ID时,这样做就能节省时间。

一个玩家有一个与之关联的名称,而且要能够取回玩家名称以便在游戏中使用(因为没有人想使用一个数字作为其名称),这就是IDirectPlay8Server:: GetClientInfo函数所实现的功能。

Retrieves the client information set for the specified client.

HRESULT GetClientInfo(
const DPNID
dpnid,
DPN_PLAYER_INFO *const pdpnPlayerInfo,
DWORD *const pdwSize,
const DWORD dwFlags
);

Parameters

dpnid
[in] Variable of type DPNID that specifies the identifier of the client to retrieve the information for.
pdpnPlayerInfo
[out] Pointer to a DPN_PLAYER_INFO structure that is filled with client information. If pdwSize is not set to NULL, you must set pdpnPlayerInfo.dwSize to an appropriate value.
pdwSize
[in,out] Pointer to a variable of type DWORD that contains the size of the client data, in bytes, returned in the pdpnPlayerInfo parameter. If the buffer is too small, this method returns DPNERR_BUFFERTOOSMALL and this parameter contains the size of the required buffer.
dwFlags
[in] Flags describing the information returned for the client. Currently, both of the following flags are returned.
DPNINFO_NAME
The DPN_PLAYER_INFO structure contains the name set for the client.
DPNINFO_DATA
The DPN_PLAYER_INFO structure contains the data set for the client.

Return Values

Returns S_OK if successful, or one of the following error values.

DPNERR_BUFFERTOOSMALL
DPNERR_INVALIDPARAM
DPNERR_INVALIDPLAYER

Remarks

Call this method after the server receives a DPN_MSGID_CLIENT_INFO message from the application. This message indicates that a client has updated its information.

Microsoft® DirectPlay® returns the DPN_PLAYER_INFO structure, and the pointers assigned to the structure's pwszName and pvData members in a contiguous buffer. If the two pointers were set, you must have allocated enough memory for the structure, plus the two pointers. The most robust way to use this method is to first call it with pdwSize set to NULL. When the method returns, pdwSize will point to the correct value. Use that value to allocate memory for the structure and call the method a second time to retrieve the information.

When the method returns, the dwInfoFlags member of the DPN_PLAYER_INFO structure will always have the DPNINFO_DATA and DPNINFO_NAME flags set, even if the corresponding pointers are set to NULL. These flags are used when calling IDirectPlay8Client::SetClientInfo, to notify DirectPlay of which values have changed.

Transmission of nonstatic information should be handled with the IDirectPlay8Client::Send method because of the high cost of using the IDirectPlay8Client::SetPeerInfo method.

The player sets the information by calling IDirectPlay8Client::SetClientInfo.

pdpnPlayerInfo指向玩家信息结构体。

Describe static player information.
typedef struct _DPN_PLAYER_INFO{ DWORD dwSize; DWORD dwInfoFlags; PWSTR pwszName; PVOID pvData; DWORD dwDataSize; DWORD dwPlayerFlags; } DPN_PLAYER_INFO, *PDPN_PLAYER_INFO;

Members

dwSize
Variable of type DWORD describing the size of this structure.
dwInfoFlags
Variable of type DWORD containing flags that specify the type of information contained in this structure. When a GetPlayerInfo method returns, the dwInfoFlags member of the DPN_PLAYER_INFO will always have both flags set, even if the corresponding pointers are set to NULL. These flags are used when calling IDirectPlay8Peer::SetPeerInfo, to notify Microsoft® DirectPlay® which values have changed.
DPNINFO_NAME
The pwszName member contains valid data.
DPNINFO_DATA
The pvData member contains valid data.
pwszName
Pointer to a variable of type PWSTR specifying the Unicode name of the player.
pvData
Pointer to the data describing the player.
dwDataSize
Variable of type DWORD that specifies the size of the data contained in the pvData member.
dwPlayerFlags
Variable of type DWORD that may contain one of the following flags.
DPNPLAYER_LOCAL
This information is for the local player.
DPNPLAYER_HOST
This player is the host for the application.

Remarks

When using this structure in the IDirectPlay8Peer::GetPeerInfo and IDirectPlay8Server::GetClientInfo methods, dwInfoFlags must be set to 0.

When using this structure in the IDirectPlay8Client::SetClientInfo, IDirectPlay8Peer::SetPeerInfo, or IDirectPlay8Server::SetServerInfo methods, dwPlayerFlags should be set to zero.

以下是处理创建玩家的代码实例:

// application variables
struct PLAYER_INFO
{
    DPNID   player_id;  
// DirectPlay Player ID
    char    name[26];   // Player Name

    PLAYER_INFO()   { player_id 
= 0; }
};

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "ServerClass";

// application GUID
GUID g_app_guid = { 0xababbe600x1ac00x11d5, { 0x900x890x440x450x530x540x00x1 } };

IDirectPlay8Server
*         g_dp_server;    // DirectPlay Server
DPN_SERVICE_PROVIDER_INFO*  g_adapter_list; // adapters
DWORD                       g_num_adapters; // number of adapters

PLAYER_INFO g_player_info[
256]; // player information
BOOL g_is_hosting;              // flag indicates whether host started or not

//----------------------------------------------------------------------------------------
// Callback function that receives all messages from the client, and receives indications 
// of session changes from the IDirectPlay8Client interface. 
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{
    DPNMSG_CREATE_PLAYER
*   create_player;  // contains information for the DPN_MSGID_CREATE_PLAYER system message
    DPNMSG_DESTROY_PLAYER*  destroy_player; // contains information for the DPN_MSGID_DESTROY_PLAYER system message
    DPNMSG_RECEIVE*         receive_data;   // contains information for the DPN_MSGID_RECEIVE system message
    DPN_PLAYER_INFO*        dpn_player_info;// describes static player informaion
    DPN_BUFFER_DESC         buffer_desc;    // used dy DirectPlay for generic buffer information

    DPNHANDLE       async_handle;
    PLAYER_INFO
*    player_info;
    
int             index;
    DWORD           size;
    
char            message[512];
    HRESULT         rv;

    
switch(message_id)
    {
    
// Microsoft DirectPlay generates the DPN_MSGID_CREATE_PLAYER message when a player is added to a 
    
// peer-to-peer or client/server session.
    case DPN_MSGID_CREATE_PLAYER:
        create_player 
= (DPNMSG_CREATE_PLAYER*) msg_buffer;

        
// get player name and save it

        size 
= 0;
        dpn_player_info 
= NULL;

        
// Retrieves the client information set for the specified client
        rv = g_dp_server->GetClientInfo(create_player->dpnidPlayer, dpn_player_info, &size, 0);

        
if(FAILED(rv) && rv != DPNERR_BUFFERTOOSMALL)
        {
            
// skip this if this is a host player
            if(rv == DPNERR_INVALIDPLAYER)
                
break;

            
return E_FAIL;
        }

        
if((dpn_player_info = (DPN_PLAYER_INFO*new BYTE[size]) == NULL)
            
return E_FAIL;

        ZeroMemory(dpn_player_info, size);
        dpn_player_info
->dwSize = sizeof(DPN_PLAYER_INFO);

        
// retrieves the client information set again
        if(FAILED(g_dp_server->GetClientInfo(create_player->dpnidPlayer, dpn_player_info, &size, 0)))
        {
            delete[] dpn_player_info;
            
return E_FAIL;
        }

        
// Find an empty player structure to use

        index 
= -1;

        
for(int i = 0; i < 256; i++)
        {
            
if(g_player_info[i].player_id == 0)
            {
                index 
= i;
                
break;
            }
        }

        
if(index == -1)
        {
            delete[] dpn_player_info;
            
return E_FAIL;
        }

        
// set player context pointer
        create_player->pvPlayerContext = (void*&g_player_info[index];

        
// save player ID
        g_player_info[index].player_id = create_player->dpnidPlayer;
        
        wcstombs(g_player_info[index].name, dpn_player_info
->pwszName, 256);
        
        
// add player to list
        SendMessage(GetDlgItem(g_hwnd, IDC_USERS), LB_ADDSTRING, 0, (LPARAM) g_player_info[index].name);

        
// send a message to all players notifying someone joined
        sprintf(message, "%s joined!", g_player_info[index].name);
        Send_Text_Msg(DPNID_ALL_PLAYERS_GROUP, message);

        delete[] dpn_player_info;

        
break;
    }

    
// return S_OK to signify the message was handled OK.
    return S_OK;
}

写字楼里写字间,写字间里程序员;程序人员写程序,又拿程序换酒钱。
酒醒只在网上坐,酒醉还来网下眠;酒醉酒醒日复日,网上网下年复年。
……
宁愿老死程序间,只要老板多发钱;小车大房不去想,撰个2  k好过年。
若要见识新世面,公务员比程序员;一个在天一在地,而且还比我们闲。
别人看我穿白领,我看别人穿名牌;天生我才写程序,臀大近视肩周炎。

年复一年春光度,度得他人做老板;老板扣我薄酒钱,没有酒钱怎过年。
春光逝去皱纹起,作起程序也委靡;来到水源把水灌,打死不做程序员。
别人笑我忒疯癫,我笑他人命太贱;状元三百六十行,偏偏来做程序员!!

但愿老死电脑间,不愿鞠躬老板前;奔驰宝马贵者趣,公交自行程序员。
若将程员比妓女,一在平地一在天;若将程员比车马,他得驱驰我无闲。
别人笑我忒疯癫,我笑自己命太贱;不见满街漂亮妹,哪个归得程序员。

不想只挣打工钱,那个老板愿发钱;小车大房咱要想,任我享用多悠闲。
比尔能搞个微软,我咋不能捞点钱;一个在天一在地,定有一日乾坤翻。
我在天来他在地,纵横天下山水间;傲视武林豪杰墓,一樽还垒风月山。


电脑面前眼发直,眼镜下面泪茫茫;做梦发财好几亿,从此不用手指忙。
哪知梦醒手空空,老板看到把我训;待到老时眼发花,走路不知哪是家。

各位在此穷抱怨,可知小弟更可怜;俺学编程两年半,至今没赚一分钱。
听说三十是末日,二十三岁在眼前;发誓立志傍微软,渺渺前程对谁言?

 

小农村里小民房,小民房里小民工;小民工人写程序,又拿代码讨赏钱。
钱空只在代码中,钱醉仍在代码间;有钱无钱日复日,码上码下年复年。
但愿老死代码间,不愿鞠躬奥迪前,奥迪奔驰贵者趣,程序代码贫者缘。
若将贫贱比贫者,一在平地一在天;若将贫贱比车马,他得驱驰我得闲。
别人笑我忒疯癫,我笑他人看不穿;不见盖茨两手间,财权富贵世人鉴。


(参考:**  桃花庵歌**         ——唐寅)

桃花坞里桃花庵,桃花庵里桃花仙;桃花仙人种桃树,又摘桃花换酒钱。
酒醒只在花间坐,酒醉还来花下眠;半醒半醉日复日,花开花落年复年。
但愿老死花酒间,不愿鞠躬车马前;车尘马足贵者趣,酒盏花枝贫者缘。
若将贫贱比贫者,一在平地一在天;若将贫贱比车马,他得驱驰我得闲。
别人笑我忒疯癫,我笑他人看不穿;不见五陵豪杰墓,无花无酒锄作田。

了解网络互联

网络是指多台计算机互联以进行数据传输及通信的系统。除了两个或更多的计算机之外,网路还需要有网络互联软件(或一个网路操作系统)、网络适配器以及电缆。网络适配器有各种形状和大小,但是一般都采用调制解调器的形状。实际上,调制解调器就是一个网路适配器,它能够将一台计算机通过世界上最大的网络—互联网连接到数百万台计算机上。

网路模型

网络互联模型有三种基本类型:服务器端、客户端以及点对点。

使用服务器端模型,可以建立一个中央网络互联系统。其他计算机使用客户端模型连接到服务器端后,就可以向服务器端发送数据以及从服务器端接收数据。客户端没有其他客户端的信息,不直接与它们连接,客户端都只知道服务器端的信息,而服务器端则拥有所有客户端的信息以及适合这些客户端之间的路由信息。服务器端和客户端常常成对进行描述,即服务器端/客户端(C/S)模型,但是在使用DirectPlay时,将二者分开是有必要的,因为服务器端和客户端是由两个独立的组件组成的。

点对点(peer-to-peer)这种网络互联模型与服务器端模型或客户端模型正好相反,计算机相互之间直接进行连接。每一台新的计算机加入网络会话中,都会建立一个新的连接,所以每台计算机都能直接连接到其他计算机。连接到网络的时段称为会话,一次会话会有与之相关的属性,如密码、最大连接数等。

游戏厅

可以将游戏厅服务器看作在线玩家的会议大厅,一个游戏厅允许所有玩家登录、通信以及加入他们喜爱的一些游戏。一旦游戏厅服务器连满了玩家,就停止对游戏厅的循环(这样做是为了节省网络带宽)。网络带宽(network bandwidth)指的是一个网络连接能够轻松处理的数据量,高网络带宽连接能比低网路带宽连接更快地处理大量的网络数据。

响应时间和延迟

带宽引出了两个术语:响应时间和延迟。响应时间是完成一个操作所花的时间(越低越好)的量化。延迟是用来描述网络通信的迟滞的术语,即数据从发送到它被接收到所花的时间。

低延迟表示网络数据迅速地被接收。高延迟(最不希望出现的事情)表示网络数据被延迟或者根本没有被发送到。延迟是一个主要问题,特别是使用了互联网时,就必须处理这个问题。

通信协议

网络可以有各种方式进行相互通信,但是要连接到另一个,两个系统都必须采用相同的协议。目前最流行的协议是TCP/IP协议(传输控制协议/Internet协议),它被广泛应用于互联网。通信协议也被称作服务器提供者,无论服务器提供者是一种诸如IPX、TCP/IP的协议,还是一种诸如调制解调器或者串行电缆的设备,都可以把它看作是网络互联的连接体。

TCP/IP协议是一种在网络上传输数据包的方法。它将数据分割成很多小数据包,再加上发送方和接收方的地址以及包的数目,便于重组数据。信息在传输过程中丢失(频繁发生的事情),TCP/IP允许网络重发数据包。当出现延迟时,这些数据包可能以错误的顺序被接收,比如旧的数据包在新的数据包之后到达。好在无需担心,因为TCP/IP会重发丢失的包并且重组无序的包。

寻址

在TCP/IP协议下,一个系统被分配一个由4个数字(0 -255之间)组成的网络地址(IP地址),数字之间用点分割。一个IP地址就像下面这样:

64.120.53.2

IP地址对我们来说不好辨认,但是网络却可以根据每一个数值成功地传递数据。稍加运算就会发现,这4个数字的组合总共可以提供4294967296个可能的地址。为了增加地址数量,网络使用附加的称为端口的地址值,数据被传送到其上。

如果将IP地址比喻成一个邮政室(mailroom),那么该邮政室(IP地址)描述了与网络相连的单个计算机系统,并且该计算机系统只被分配了惟一的IP地址。在邮政室中有很多箱柜(端口),邮件被分类到其中。每一个箱柜(端口)属于一个特定的工作人员(一个特定的应用程序)。一些应用程序可以拥有多个端口,数据只能被一个同相应IP地址以及相应端口所匹配的系统接收。一种称为数据路由器(data router)的设备将接收到的网络数据传送到它所知道的系统,或者将数据传送到另一个网络连接。

DirectPlay概述

要在工程中使用DirectPlay,必须包含DPlay8.h和DPAddr.h,还要链接DPlayX.lib和 DXGuid.lib。

网络对象

使用DirectPlay,需要使用前面提到的网络模型:客户端、服务器端和点对点。每一种网络模型都有自己的接口对象,如下所示:

IDirectPlay8Client:客户端网络对象,连接到一个服务器端。
IDirectPlay8Server:服务器端网络对象,连接多个客户端。
IDirectPlay8Peer:   点对点网络对象,连接客户端于其他客户端。
IDirectPlay8Address:包含(以及构造)网络地址的对象。

要连接到远程网络系统(或主持一次会话),需要使用 IDirectPlay8Address构造一个网络地址,IDirectPlay8Address的惟一用途就是构造并包含单个网络地址。会话指的是主持或加入一个可连接网路系统的时段,终止连接时,会话也就终止了。要主持游戏会话或连接到远程系统,首先需要创建网络对象并给它分配地址。要主持游戏会话,只需要等待其他系统(也就是使用这些系统的人)连接上来即可。连接建立之后,系统就可以和远程系统开始相互传输游戏相关的网络消息, DirectPlay把这些远程系统称作玩家。

将玩家进行分组

在DirectPlay术语中,“玩家”是指通过网络连接到其他计算机的单个连接(通常是一个游戏玩家)。一台计算机可以有多个玩家,但一般只有一个。实际上,服务器端也被标识为玩家,以便于识别。每一个玩家都会接收到一个标识号码(玩家ID),系统使用这个标识号码来将消息传递到每个玩家。这些号码是追踪玩家的惟一可信任方法,所以需要让程序对它们进行相应地处理。

对于大型游戏,可能有成千上万已连接的玩家。为了在游戏中更好地处理这些玩家,可以将一些或所有玩家编制到一些组中。采用组的概念可以减少编程的繁琐,主要原因在于可以将一个游戏区域(比如一张地图或某个级别)中的多个玩家划分为一组,并且一次向整个组发送网络数据,而不是单独发送给各个玩家。使用组还有很多其他原因,但这个原因是最主要的。一个组中包含多少玩家以及创建多少个组完全没有限制,组也可以属于其他组。

带消息的网络互联

消息是分了类的数据包,其中包含简单的结构。每一个消息都有特定的含义,且都有一个与之对应的宏,而且还和所使用的网络模型有关。要接收消息,网络对象必须给自己指定一个回调函数,以便于每次消息到达时进行调用。为了确保得到平滑的数据流,该函数根据数据类型进行分析并尽快地返回。发送消息,需要使用各个网络对象的发送函数。这些函数易于使用,并且提供了许多发送选项,包括保证发送(guaranteed delivery)、安全编码(secure encryption)以及同步或异步发送。

同步与异步

DirectPlay提供的第一个发送选项是同步或异步发送消息的功能,也就是说系统在发出发送数据指定之后交回控制权(异步),还是直到所有的数据都成功发送之后再交回控制权(同步)。很多时候使用异步方法,因为它不会像同步方法那样阻塞系统。

安全性

要引起注意的是任何时候都可能有人在截取并记录游戏的网络数据。因此,就需要采用一种安全的方式编码消息数据,使得那些解码的黑客非常难读懂先前的信息。使用安全网络发送的不好之处在于它会稍稍减慢系统的系统,因为必须在数据发送之前先编码信息,然后在接收到之后进行解码。

保证发送

就像一些快递公司保证送到包裹一样, DirectPlay也能保证送到消息。可以将一组信息标识为保证的,其余确定的就是DirectPlay将一直执行发送操作直到发送成功,以保证将其发送到目标地址(除非断线),使用保证发送是通过在调用函数时指定一个惟一的标志来实现的。保证发送的不好之处在于速度,保证发送在实际的游戏状况下非常慢,游戏采用UDP(用户数据报协议)发送方法,就不用关心数据是否被接收到(和TCP发送方法相反,它保证数据的发送)。

节流

有时系统会因为试图处理流数据而过载,尽管这样, DirectPlay有一个内置的消息节流器,它丢弃了发送队列中低优先级的信息。

使用GUID识别应用程序

如何从众多的网络应用程序中区分出自己的网络应用程序呢?解决这个问题的方法就是给自己的应用程序指定一个惟一的号码,并且只允许使用相同号码的应用程序相互进行连接。这个特殊的号码就是 Windows程序员熟悉的GUID(全局惟一标识符)。创建应用程序之前,花一点时间给它设置一个惟一的GUID,并且保证所有通过网络进行连接的该应用程序使用相同的GUID。

初始化网络对象

无论是服务器端、客户端还是单点对象,使用DirectPlay的第一步都是创建网络对象。要初始化每一个网络模型接口,必须使用CoCreateInstance函数,可能用到的类ID和引用标识符如下所示:

CLSID_DirectPlay8Address  IID_IDirectPlay8Address
CLSID_DirectPlay8Client IID_IDirectPlay8Client
CLSID_DirectPlay8Peer IID_IDirectPlay8Peer
CLSID_DirectPlay8Server IID_IDirectPlay8Server

无论创建的是什么网络对象(客户端、单点或服务器端),都需要创建与之匹配的网络回调函数,在网络消息被接收到的时候会调用该回调函数。

该回调函数使用说明如下:

PFNDPNMESSAGEHANDLER is an application-defined callback function used by the IDirectPlay8Peer, IDirectPlay8Client, and IDirectPlay8Server IDirectPlay8LobbyClient and IDirectPlay8LobbiedApplication interfaces to process messages.

typedef HRESULT (WINAPI *PFNDPNMESSAGEHANDLER)( 
PVOID pvUserContext,
DWORD dwMessageType,
PVOID pMessage
);

Parameters

pvUserContext
Pointer to the application-defined structure that will be passed to this callback function. This is defined in the pvUserContext parameter of the Initialize method.
dwMessageType
One of the following message types that are generated by the IDirectPlay8Peer, IDirectPlay8Client, and IDirectPlay8Server interfaces. Each interface uses a different subset of the available messages. Refer to the interface documentation for details.
  • DPN_MSGID_ADD_PLAYER_TO_GROUP
  • DPN_MSGID_ASYNC_OP_COMPLETE
  • DPN_MSGID_CLIENT_INFO
  • DPN_MSGID_CONNECT_COMPLETE
  • DPN_MSGID_CREATE_GROUP
  • DPN_MSGID_CREATE_PLAYER
  • DPN_MSGID_DESTROY_GROUP
  • DPN_MSGID_DESTROY_PLAYER
  • DPN_MSGID_ENUM_HOSTS_QUERY
  • DPN_MSGID_ENUM_HOSTS_RESPONSE
  • DPN_MSGID_GROUP_INFO
  • DPN_MSGID_HOST_MIGRATE
  • DPN_MSGID_INDICATE_CONNECT
  • DPN_MSGID_INDICATED_CONNECT_ABORTED
  • DPN_MSGID_PEER_INFO
  • DPN_MSGID_RECEIVE
  • DPN_MSGID_REMOVE_PLAYER_FROM_GROUP
  • DPN_MSGID_RETURN_BUFFER
  • DPN_MSGID_SEND_COMPLETE
  • DPN_MSGID_SERVER_INFO
  • DPN_MSGID_TERMINATE_SESSION

    Additionally, if the application supports Microsoft® DirectPlay® lobby functionality, this parameter can specify one of the following message types that are generated by the IDirectPlay8LobbyClient and IDirectPlay8LobbiedApplication interfaces. Each interface uses a different subset of the available messages. Refer to the interface documentation for details.

  • DPL_MSGID_CONNECT
  • DPL_MSGID_CONNECTION_SETTINGS
  • DPL_MSGID_DISCONNECT
  • DPL_MSGID_RECEIVE
  • DPL_MSGID_SESSION_STATUS
pMessage
Structure containing message information.

Return Values

See the documentation for the individual messages for appropriate return values. Unless otherwise noted, this function should return S_OK.

Remarks

This function must be threadsafe because it might be called reentrantly through multiple threads.

Callback messages from the same player are serialized. Once you receive a message from a player, you will not receive another until you have handled the first message, and the callback function has returned.

The message structures have the same name as the message type except the "DPN_MSGID" is replaces with "DPNMSG". For example, the DPN_MSGID_CONNECTION_TERMINATED message type uses the DPNMSG_CONNECTION_TERMINATED message structure to convey the actual message information.

When implementing this callback function, first look at the message type returned in the dwMessageType parameter and then cast the message structure (pMessage) to that type to obtain message information. Some messages don't have a defined structure because they have no parameters. For these messages, the pMessage parameter is NULL.


要初始化网络对象,需要调用Initialize函数。

Registers an entry point in the client's code that receives the messages from the IDirectPlay8Client interface and from the server. This method must be called before calling any other methods of this interface.

HRESULT Initialize(
PVOID const
pvUserContext,
const PFNDPNMESSAGEHANDLER pfn,
const DWORD dwFlags
);

Parameters

pvUserContext
[in] Pointer to the user-provided context value in calls to the message handler. Providing a user-context value can be useful to differentiate messages coming from multiple interfaces to a common message handler.
pfn
[in] Pointer to a PFNDPNMESSAGEHANDLER callback function that receives all messages from the server, and receives indications of session changes from the IDirectPlay8Client interface.
dwFlags
[in] You may specify the following flag.
DPNINITIALIZE_DISABLEPARAMVAL
Disable parameter validation for the current object.

Return Values

Returns S_OK if successful, or one of the following error values.

DPNERR_INVALIDFLAGS
DPNERR_INVALIDPARAM

Remarks

This is the first method you should call after using CoCreateInstance to obtain the IDirectPlay8Client interface.


下面这个函数创建了DirectPlay Client对象并且初始化该对象。

//--------------------------------------------------------------------------------
// Create DirectPlay Client and initialize it.
//--------------------------------------------------------------------------------
BOOL Init_DirectPlay_Client()
{
    
// create DirectPlay client component
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Client, NULL, CLSCTX_INPROC, IID_IDirectPlay8Client, 
                               (
void**)&g_dp_client)))
        
return FALSE;

    
// Assign a message handler to network component
    
//
    
// Registers an entry point in the client's code that receives the messages from the IDirectPlay8Client
    
// interface and from the server.
    if(FAILED(g_dp_client->Initialize(NULL, Net_Msg_Handle, 0)))
        
return FALSE;

    
return TRUE;
}

指定设备

虽然已经选择了一个服务提供者,但系统中可能有一个以上的设备使用它。当一个网络适配器和调制解调器都连接到互联网,并且都使用TCP/IP协议时,就会出现这种情况,这是就必须对设备进行枚举并进行选择。

枚举函数获取了所有可用的服务提供者,并将它们以可用的方式组织在一起。DirectPlay枚举和典型的 Windwos枚举方法有所不同,在查询过程中找到实例对象并不是每次都调用回调函数,而是从包含了以数组形式存储的每个服务提供者的缓冲区中寻找。

处理枚举可以使用EnumServiceProviders函数。

Enumerates the registered service providers available to the application.

HRESULT EnumServiceProviders(
const GUID *const
pguidServiceProvider,
const GUID *const pguidApplication,
DPN_SERVICE_PROVIDER_INFO *const pSPInfoBuffer,
PDWORD const pcbEnumData,
PDWORD const pcReturned,
const DWORD dwFlags
);

Parameters

pguidServiceProvider
[in] Pointer to a variable of type GUID that specifies a service provider. This optional parameter forces the enumeration of subdevices for the specified service provider. You should normally set this value to NULL, to enumerate all available service providers.
pguidApplication
[in] Pointer to a variable of type GUID that specifies an application. If a pointer is passed in this parameter, only service providers who can be connected to the application are enumerated. You can also pass NULL to enumerate the registered service providers for the system.
pSPInfoBuffer
[out] Pointer to an array of DPN_SERVICE_PROVIDER_INFO structures that will be filled with service provider information.
pcbEnumData
[out] Pointer to DWORD, which is filled with the size of the pSPInfoBuffer array, in bytes, if the buffer is too small.
pcReturned
[out] Pointer to a variable of type DWORD that specifies the number of DPN_SERVICE_PROVIDER_INFO structures returned in the pSPInfoBuffer array.
dwFlags
[in] The following flag can be specified.
DPNENUMSERVICEPROVIDERS_ALL
Enumerates all the registered service providers for the system, including those that are not available to the application or do not have devices installed.

Return Values

Returns S_OK if successful, or one of the following error values.

DPNERR_BUFFERTOOSMALL
DPNERR_INVALIDPARAM

Remarks

Call this method initially by specifying NULL in the pguidServiceProvider parameter to determine the base service providers available to the system. Specific devices for a service provider can be obtained by passing a pointer to a service provider GUID in the pguidServiceProvider. This is useful, for example, when using the Modem Connection for Microsoft® DirectPlay® service provider. You can choose among different modems for dialing out and select specific modems for hosting.

If the pEnumData buffer is not big enough to hold the requested service provider information, the method returns DPNERR_BUFFERTOOSMALL and the cbEnumData parameter contains the required buffer size. Typically, the best strategy is to call the method once with a zero-length buffer to determine the required size. Then call the method again with the appropriate-sized buffer.

Normally, this method will return only those service providers that can be used by the application. For example, if the IPX networking protocol is not installed, DirectPlay will not return the IPX service provider. To have DirectPlay return all service providers, even those that cannot be used by the application, set the DPNENUMSERVICEPROVIDERS_ALL flag in dwFlags.


pSPInfoBuffer是指向DPN_SERVICE_PROVIDER_INFO结构体数组的指针,该函数将用枚举的信息填充该结构体。

Used when enumerating information for a specific service provider.

typedef struct _DPN_SERVICE_PROVIDER_INFO{
DWORD dwFlags;
GUID guid;
WCHAR* pwszName;
PVOID pvReserved;
DWORD dwReserved;
} DPN_SERVICE_PROVIDER_INFO, *PDPN_SERVICE_PROVIDER_INFO;

Members

dwFlags
Reserved. Must be 0.
guid
GUID for the service provider.
pwszName
Name of the service provider.
pvReserved
Reserved. Must be 0.
dwReserved
Reserved. Must be 0.

下面这个函数演示了如何枚举系统中的服务提供者:

HWND g_hwnd;    // window handles

IDirectPlay8Client
* g_dp_client;    // directplay client

// service provider list, used when enumerating information for a specifice service provider.
DPN_SERVICE_PROVIDER_INFO*  g_sp_list;  

DWORD g_sp_number;      
// service provider number

//--------------------------------------------------------------------------------
// Enumerate all available service provider.
//--------------------------------------------------------------------------------
void Enum_Service_Provider()
{
    
// return is no server object
    if(g_dp_client == NULL)
        
return;

    
// get a handler to the list box
    HWND listbox = GetDlgItem(g_hwnd, IDC_SERVICE_PROVIDERS);

    
// clear the list box
    SendMessage(listbox, LB_RESETCONTENT, 00);

    
// release service provider list memory
    delete[] g_sp_list;
    g_sp_list 
= NULL;
    
    
// query the required size of the data buffer
    
    DWORD sp_list_size 
= 0;

    
// enumerates the registerd service providers available to the application
    HRESULT rv = g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0);

    
if(rv != DPNERR_BUFFERTOOSMALL)
        
return;

    
// allocate a buffer
    if((g_sp_list = (DPN_SERVICE_PROVIDER_INFO*new BYTE[sp_list_size]) == NULL)
        
return;

    
// enumerate again
    if(SUCCEEDED(g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0)))
    {
        
// enumeration is complete, scan through entries.

        
char sp_name[1024];

        DPN_SERVICE_PROVIDER_INFO
* sp_ptr = g_sp_list;
        
        
for(DWORD i = 0; i < g_sp_number; i++)
        {
            
// convert wide string into multi-byte string
            wcstombs(sp_name, sp_ptr->pwszName, 1024);

            
// Add the service provider into box
            
//
            
// An application sends an LB_ADDSTRING message to add a string to a list box. 
            
// If the list box does not have the LBS_SORT style, the string is added to the end of the list. 
            
// Otherwise, the string is inserted into the list and the list is sorted.  
            SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM)sp_name);

            
// go to next service provider in buffer
            sp_ptr++;
        }
    }
}

下面给出一个完整实例来运用刚才介绍的知识,由于DirectX9 SDK已不包含DirectPlay,所以需要安装DirectX8 SDK,并在Visual Studio中进行相应设置。

点击下载源码和工程

/***************************************************************************************
PURPOSE:
    Enum Network Adapters Demo
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<dplay8.h>
#include 
<dpaddr.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"dplayx.lib")

#pragma warning(disable : 
4996)

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "EnumClass";

IDirectPlay8Client
* g_dp_client;    // directplay client

// service provider list, used when enumerating information for a specifice service provider.
DPN_SERVICE_PROVIDER_INFO*  g_sp_list;  

DWORD g_sp_number;      
// service provider number

//----------------------------------------------------------------------------------------
// callback function that receives all messages from the server, and receives indications 
// of session changes from the IDirectPlay8Client interface. 
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{
    
// return S_OK to signify the message was handled OK.
    return S_OK;
}

//--------------------------------------------------------------------------------
// Create DirectPlay Client and initialize it.
//--------------------------------------------------------------------------------
BOOL Init_DirectPlay_Client()
{
    
// create DirectPlay client component
    if(FAILED(CoCreateInstance(CLSID_DirectPlay8Client, NULL, CLSCTX_INPROC, IID_IDirectPlay8Client, 
                               (
void**)&g_dp_client)))
        
return FALSE;

    
// Assign a message handler to network component
    
//
    
// Registers an entry point in the client's code that receives the messages from the IDirectPlay8Client
    
// interface and from the server.
    if(FAILED(g_dp_client->Initialize(NULL, Net_Msg_Handle, 0)))
        
return FALSE;

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Enumerate all available service provider.
//--------------------------------------------------------------------------------
void Enum_Service_Provider()
{
    
// return is no server object
    if(g_dp_client == NULL)
        
return;

    
// get a handler to the list box
    HWND listbox = GetDlgItem(g_hwnd, IDC_SERVICE_PROVIDERS);

    
// clear the list box
    SendMessage(listbox, LB_RESETCONTENT, 00);

    
// release service provider list memory
    delete[] g_sp_list;
    g_sp_list 
= NULL;
    
    
// query the required size of the data buffer
    
    DWORD sp_list_size 
= 0;

    
// enumerates the registerd service providers available to the application
    HRESULT rv = g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0);

    
if(rv != DPNERR_BUFFERTOOSMALL)
        
return;

    
// allocate a buffer
    if((g_sp_list = (DPN_SERVICE_PROVIDER_INFO*new BYTE[sp_list_size]) == NULL)
        
return;

    
// enumerate again
    if(SUCCEEDED(g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0)))
    {
        
// enumeration is complete, scan through entries.

        
char sp_name[1024];

        DPN_SERVICE_PROVIDER_INFO
* sp_ptr = g_sp_list;
        
        
for(DWORD i = 0; i < g_sp_number; i++)
        {
            
// convert wide string into multi-byte string
            wcstombs(sp_name, sp_ptr->pwszName, 1024);

            
// Add the service provider into box
            
//
            
// An application sends an LB_ADDSTRING message to add a string to a list box. 
            
// If the list box does not have the LBS_SORT style, the string is added to the end of the list. 
            
// Otherwise, the string is inserted into the list and the list is sorted.  
            SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM)sp_name);

            
// go to next service provider in buffer
            sp_ptr++;
        }
    }
}

//--------------------------------------------------------------------------------
// Enumerate all adapters which specified by sp_guid.
//--------------------------------------------------------------------------------
void Enum_Adapters(GUID* sp_guid)
{
    
// return if no server object or GUID
    if(g_dp_client == NULL || sp_guid == NULL)
        
return;

    
// get a handle of the list box
    HWND listbox = GetDlgItem(g_hwnd, IDC_ADAPTERS);

    
// clear the list box
    SendMessage(listbox, LB_RESETCONTENT, 00);

    DPN_SERVICE_PROVIDER_INFO
* adapter_list = NULL;
    DWORD adapter_number 
= 0;
    DWORD adapter_list_size 
= 0;

    
// query the required size of the data buffer
    HRESULT rv = g_dp_client->EnumServiceProviders(sp_guid, NULL, adapter_list, &adapter_list_size, &adapter_number, 0);

    
if(rv != DPNERR_BUFFERTOOSMALL)
        
return;

    
// allocate a buffer
    if((adapter_list = (DPN_SERVICE_PROVIDER_INFO*new BYTE[adapter_list_size]) == NULL)
        
return;

    
// enumerate again
    if(SUCCEEDED(g_dp_client->EnumServiceProviders(sp_guid, NULL, adapter_list, &adapter_list_size, &adapter_number, 0)))
    {
        
char adapter_name[1024];

        
// enumeration is complete, scan through entries.
        DPN_SERVICE_PROVIDER_INFO* adapter_ptr = adapter_list;

        
for(DWORD i = 0; i < adapter_number; i++)
        {
            
// convert wide string into multi-byte string
            wcstombs(adapter_name, adapter_ptr->pwszName, 1024);

            
// add the adapter name int listbox
            SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM)adapter_name);

            
// go to next servicec provider
            adapter_ptr++;
        }
    }
    
    
// delete the list memory resources
    delete[] adapter_list;
}

//--------------------------------------------------------------------------------
// Release all resource which allocated for DirectPlay.
//--------------------------------------------------------------------------------
void Release_DirectPlay()
{
    
// release client service provider list memory
    delete[] g_sp_list;
    g_sp_list 
= NULL;
    
    g_sp_number 
= 0;

    
// release client component
    if(g_dp_client != NULL)
    {
        
// Closes the open connection to a session. This method must be called on any object that is successfully 
        
// initialized with a call to the IDirectPlay8Client::Initialize method.
        g_dp_client->Close(0);

        g_dp_client
->Release();

        g_dp_client 
= NULL;
    }
}

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_COMMAND:
        
// The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a 
        
// notification message to its parent window, or when an accelerator keystroke is translated. 
        
//
        
// wParam:
        
//    The high-order word specifies the notification code if the message is from a control. 
        
//    If the message is from an accelerator, this value is 1. If the message is from a menu, this value is zero. 
        
//    The low-order word specifies the identifier of the menu item, control, or accelerator. 
        
//
        
// lParam:
        
//     Handle to the control sending the message if the message is from a control. 
        
//     Otherwise, this parameter is NULL. 

        
// An application sends the LBN_SELCHANGE notification message when the selection in a list box is about to 
        
// change. The parent window of the list box receives this notification message through the WM_COMMAND message. 
        if(LOWORD(wParam) == IDC_SERVICE_PROVIDERS && HIWORD(wParam) == LBN_SELCHANGE)
        {
            
// Get the selection from the list
            
//
            
// Send an LB_GETCURSEL message to retrieve the index of the currently selected item, 
            
// if any, in a single-selection list box.         
            unsigned int selected = (unsigned int) SendMessage(GetDlgItem(hwnd, IDC_SERVICE_PROVIDERS), LB_GETCURSEL, 00);

            
// enumerate the adapters
            DPN_SERVICE_PROVIDER_INFO* sp_ptr = g_sp_list;
            sp_ptr 
+= selected;
            
            
// enumerate adapter with specified guid
            Enum_Adapters(&sp_ptr->guid);
        }

        
return 0;

    
case WM_DESTROY:
        Release_DirectPlay();
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS            win_class;
    MSG                 msg;    

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_ENUM), 0, NULL);

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// initialize COM
    
//
    
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
    
// apartment (STA).
    CoInitialize(0);

    
// Initialzie DirectPlay and enumerate service providers.
    if(! Init_DirectPlay_Client())
    {
        MessageBox(NULL, 
"Error initializing DirectPlay.""ERROR", MB_OK | MB_ICONEXCLAMATION);
        
return 0;
    }

    
// enumerate all available service provider
    Enum_Service_Provider();

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);            
        }             
    }

    UnregisterClass(g_class_name, inst);

    
// release COM system
    
//
    
// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other
    
// resources that the thread maintains, and forces all RPC connections on the thread to close.
    CoUninitialize();
    
    
return (int) msg.wParam;
}

运行截图:



前言:本教程面向对DriectX 9.0有一定了解的读者,主要讲解DirectX 9.0的各个部分的功能及用法。希望对广大的游戏初学者有一定帮助,也好让本人对中国游戏事业的发展做出一些微不足道的贡献。作者:Fabric(由于本人是广东人,写文章难免参杂粤语写法,请见谅)

  简介:ID3DXSpriteDriectX 9.0里面的一个简单模块,在DriectX 9.0帮助文档里面对其功能的描术为:向用户提供一套简单的在屏幕上实现精灵渲染的接口。何为精灵渲染,说白了就是渲染2D画面,ID3DXSprite帮助用户透过简单的操作就能运用DriectX 9.0制作2D游戏(渲染2D图形),ID3DXSprite的功能还包括:帮助用户在3D游戏里面实现公告牌技术。下面,将对如何使用ID3DXSprite作详细分折。

  得到一个ID3DXSprite对像:玩过DriectX的人都知道,干什么前都得先取得一个实例对像,其实只要简单调用D3DX为我们提供的一个函数就可完成:

HRESULT D3DXCreateSprite(
  LPDIRECT3DDEVICE9 pDevice,
  LPD3DXSPRITE * ppSprite
)
这个函数如何调用,不用我解释了吧,碰过DriectX的人都应该知道他里面的意思。
 
渲染准备:DirectX 9.0规定,运用ID3DXSprite渲染2D图形前,应先调用ID3DXSprite::Begin做准备工作,在渲染工作完成之后,调用ID3DXSprite::End做善后工作。格式如下:

g_pSprite->Begin(NULL);

//渲染代码部分。。。。

g_pSprite->End();

其中,ID3DXSprite::Begin接收一个参数,该参数将决定精灵以什么方式进行渲染,该参数可以为以下值之一:

·   D3DXSPRITE_ALPHABLEND

·   D3DXSPRITE_BILLBOARD

·   D3DXSPRITE_DONOTMODIFY_RENDERSTATE

·   D3DXSPRITE_DONOTSAVESTATE

·   D3DXSPRITE_OBJECTSPACE

·   D3DXSPRITE_SORT_DEPTH_BACKTOFRONT

·   D3DXSPRITE_SORT_DEPTH_FRONTTOBACK

·   D3DXSPRITE_SORT_TEXTURE

上面的标志可以合并使用,标志意思可以从其名字中略知一二,而具体用法,后面将在运用到的时候加以介绍

 

渲染:运用ID3DXSprite渲染2D图形其实好简单,只需调用ID3DXSprite::Draw接口,该接口原型如下:

HRESULT Draw(

  LPDIRECT3DTEXTURE9 pTexture,

  CONST RECT * pSrcRect,

  CONST D3DXVECTOR3 * pCenter,

  CONST D3DXVECTOR3 * pPosition,

  D3DCOLOR Color

);

其中 参数一为精灵所用到的纹理。

参数二为要渲染的纹理矩形区域,意思上就像DirectDraw中所说的原位图矩形(指明要将纹理的哪一部分渲染到屏幕上)

参数三要求传入纹理的中心点坐标,如果传入NULL则表明使用默认值,默认值为将纹理的左上角设为中心点。中心点的设定将关系到日后对精灵进行位移,旋转的效果

参数四为精灵在屏幕上的渲染位置,如要在屏幕的(100,100)像素位置渲染精灵,则应传入&D3DXVECTOR3(100.0f, 100.0f, 0.0f),其实参数三加参数四可以简单的理解为DirectDraw中所说的目的地矩形

参数五要求传入一个32位颜色值,在渲染时,纹理上的每一个像素的颜色值将与其进行相乘,得到最后的渲染颜色,如传入0x00000000,相乘后颜色值将为0,所以精灵将以全黑色渲染,这个参数还有一个用处,就是控制精灵的透明值:当用户在调用ID3DXSprite::Begin时传入D3DXSPRITE_ALPHABLEND标志,则表明打开精灵透明渲染功能,此时参数五的高8位用于指明渲染的透明度,例如:要完全不透明的渲染图像,应传入0xffffffff,要完全透明的渲染图像,应传入0x00ffffff

 

精灵位移,缩放,旋转等处理:可以通过调用ID3DXSprite::SetTransform实现,此接口要求传入一个变换矩阵,注意:这里的变换矩阵指的是变换2D平面上的坐标,而非我们平时常用的3D变换矩阵,2D平面变换矩阵应该调用函数D3DXMatrixTransformation2D得到,该函数的使用方法请参照DirectX9.0帮助文件。

 

下面附上部分代码,大概实现效果为:在屏幕的(100,100)坐标处渲染一个2D精灵,该精灵被缩小为原来的十分之一,并附带0.5孤度的旋转,透明度为60%左右

 

//初始化精灵对像

D3DXCreateSprite(g_pDevice, &g_pSprite);

 

g_pDevice->BeginScene();

 

     g_pDevice->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL,

         D3DCOLOR_XRGB(0,0,0),1.0f,0L);

 

g_pSprite->Begin(D3DXSPRITE_ALPHABLEND);

 

     //得到2D坐标转换矩阵

     D3DXMatrixTransformation2D(&mat, NULL, 0.0f, &D3DXVECTOR2(0.1f, 0.1f),

         &D3DXVECTOR2(50.0f, 50.0f), 0.5f, &D3DXVECTOR2(100.0f, 100.0f));

     g_pSprite->SetTransform(&mat);

 

     //渲染精灵

     g_pSprite->Draw(g_ptexSprite, NULL, NULL, NULL, 0x99ffffff);

 

g_pSprite->End();

 

g_pDevice->EndScene();

 

g_pDevice->Present(NULL, NULL, NULL, NULL);

加入到MP3的革命中

MP3是一种音频压缩格式,它通过删除或修改音乐中不易被人耳察觉的部分来使音乐更小,占用的存储空间更少。在项目中使用MP3(.MP3文件)需要使用DirectX中的 DirectShow组件,在这个组件的帮助下,只需几行短短的代码,就能使用任意的MP3文件了(DirectShow也支持其他的媒体文件,比如 WMA,AVI,MPG等)。当然要想使用更多的媒体文件,必须已经在操作系统中安装了解码器。

解码器(codec)是一个程序,用于解码或编码一些指定的格式(比如MP3解码器专门解码.MP3文件)。通常可以从发明或者创建这种格式的公司中获取这种格式的解码器。比如,MP3解码器来自于Fraunhofer Insitute。幸运的是,MP3解码器等几种比较流行的解码器已经被集成到操作系统中(比如.mp3,.avi,.mpg等),而无需另外从 internet下载这些格式的解码器了。

要在项目中使用DirectShow,需要包含dshow.h头文件,并且在链接库中加入strmiids.lib。

使用DirectShow

DirectX是一组COM接口组件,DirectShow也不例外,DirectShow中经常使用的组件如下:

IGraphBuilder:  帮助建立滤波图,滤波过滤图是一组对象或接口的集合,用于处理某种媒体文件。
IMediaControl:控制数据在滤波图中的流程,使用该接口控制音乐的回放。
IMediaEvents:   从滤波图中获取事件及通告,当希望知道在滤波图中发生了什么的时候这个对象很有用,比如希望知道一个音乐是否仍然在播放或者已经停止播放。

其中第一个接口IGraphBuilder是比较重要的对象,其他对象都依赖于它,或者靠它创建。它创建滤波器,用于处理媒体问题,另外很多有用的功能也是依靠这个对象。

使用DirectShow播放MP3的第一步是调用 CoCreateInstance函数创建滤波图对象IGraphBuilder。

    // initialize COM
    
//
    
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
    
// apartment (STA).
    CoInitialize(0);

    
// create the DirectMusic performance object
    
//
    
// creates a single uninitialized object of the class associated with a specified CLSID.
    if(FAILED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, 
                               (
void**)&g_graph_builder)))
    {
        MessageBox(NULL, 
"Unable to create DirectShow Graph Builder object.""Error", MB_OK);
        
return FALSE;
    }

一旦创建对象IGraphBuilder成功,就可以请求另两个接口了:

// Query for the media control and event objects
g_graph_builder->QueryInterface(IID_IMediaControl, (void**)&g_media_control);
g_graph_builder
->QueryInterface(IID_IMediaEvent, (void**)&g_media_event);

加载媒体文件
 
实际上, DirectShow并不加载媒体文件,而是创建一个DirectShow滤波器到文件的连接。数据在解码的时候被流化,这样可以减少在播放过程中的内存使用,创建连接的过程叫做渲染(rendering)。渲染一个文件,需要调用IGraphBuilder::RenderFile函数。

Builds a filter graph that renders the specified file.

Syntax

HRESULT RenderFile(
LPCWSTR lpwstrFile,
LPCWSTR lpwstrPlayList
);

Parameters

lpwstrFile
[in] Pointer to the name of the file containing the data to be rendered.
lpwstrPlayList
[in] Pointer to the playlist name. Reserved; must be NULL. (This parameter is currently unimplemented.)

Return Value

Returns an HRESULT value, which can include one of the following:

  • VFW_S_AUDIO_NOT_RENDERED
  • VFW_S_DUPLICATE_NAME
  • VFW_S_PARTIAL_RENDER
  • VFW_S_RPZA
  • VFW_S_VIDEO_NOT_RENDERED

Remarks

If the lpwstrPlayList parameter is NULL, this method would use the default playlist, which typically renders the entire file.

控制音乐的播放

在媒体文件被渲染之后,就可以使用另外两个接口 IMediaControl和IMediaEvent进行播放或者对播放进行控制了。

第一个接口 IMediaControl用于播放和各种播放相关的操作,这个接口有三个函数:IMediaControl::Run,IMediaControl:: Pause,IMediaControl::Stop。

如果要开始播放一段音乐,调用 IMediaControl::Run就可以了。

Switches the entire filter graph into a running state.

Syntax

HRESULT Run(void); 

Return Value

Returns S_OK if the graph is actually running.

Returns S_FALSE if the graph is preparing to run (the graph will run automatically when it's ready). Call GetState to wait for the transition to the running state to complete or to check if the transition has completed. If the method returns S_FALSE, subsequent calls to GetState will return a value of State_Running when the graph is actually running. If the transition to the running state is not complete GetState can return a return code of VFW_S_STATE_INTERMEDIATE.

Returns an HRESULT error code if the graph could not run and is now stopped.

Remarks

In a running state, data is pushed down the filter graph and rendered. The graph remains in a running state until it is stopped by the IMediaControl::Pause or IMediaControl::Stop method. The graph remains in a running state even after notifying the application of completion (that is, the EC_COMPLETE notification is sent to the application). This allows the application to determine whether to pause or stop after completion.

If the filter graph is in the stopped state, this method first pauses the graph before running.

If an error value is returned, some filters within the graph might have successfully entered the running state. In a multistream graph, entire streams might be playing successfully. The application must determine whether to stop running or not.

一旦播放开始,就可以随时暂停播放或者停止播放,如果希望暂停播放,调用IMediaControl::Pause函数。

Pauses all the filters in the filter graph.

Syntax

HRESULT Pause(void);

Return Value

Returns S_OK if the graph is actually paused.

Returns S_FALSE if the graph is in paused state but some filters have not completed the transition to pause. Call GetState to wait for the transition to the paused state to complete or to check if the transition has completed. If the method returns S_FALSE, subsequent calls to GetState will return a value of State_Paused when the graph is paused. If the transition to paused is not complete GetState can return a return code of VFW_S_STATE_INTERMEDIATE.

Returns an HRESULT error code if the graph could not transition to paused state and is now stopped.

Remarks

In the paused state, filters process data but do not render it. Data is pushed down the filter graph and is processed by transform filters as far as buffering permits. No data is rendered (except that media types capable of being rendered statically, such as video, have a static, poster frame rendered in paused mode). Therefore, putting a filter graph into a paused state cues the graph for immediate rendering when put into a running state.

开始播放之后,可以随时调用IMediaContrl::Stop函数来停止播放。

Switches all filters in the filter graph to a stopped state.

Syntax

HRESULT Stop(void); 

Return Value

Returns an HRESULT value.

Remarks

In this mode, filters release resources and no data is processed. If the filters are in a running state, this method pauses them before stopping them. This allows video renderers to make a copy of the current frame for poster frame display while stopped.

以下代码演示了如何播放MP3文件。

//--------------------------------------------------------------------------------
// Play mp3 which specified by filename.
//--------------------------------------------------------------------------------
BOOL Play_MP3(char* filename)
{
    
// convert filename to wide-character string
    WCHAR w_filename[MAX_PATH] = {0};

    mbstowcs(w_filename, filename, MAX_PATH);

    
// render the file
    g_graph_builder->RenderFile(w_filename, NULL);

    
// play the file, switches the entire filter graph into a running state.
    g_media_control->Run();

    
return TRUE;
}

检测播放事件

我们主要感兴趣的IMediaEvent函数大概有三个:GetEvent,FreeEventParams,WaitForCompletion。

第一个函数GetEvent可能是使用最多的函数,它用于找回通知播放状态的事件。

Retrieves the next notification event.

Syntax

HRESULT GetEvent(
long *lEventCode,
long *lParam1,
long *lParam2,
long msTimeout
);

Parameters

IEventCode
[out] Pointer to the next event notification.
lParam1
[out] Pointer to the first parameter of the event.
lParam2
[out] Pointer to the second parameter of the event.
msTimeout
[in] Time, in milliseconds, to wait before assuming that there are no events.

Return Value

Returns an HRESULT value that depends on the implementation of the interface. If the time-out is zero and no event is waiting, or if the time-out elapses before an event appears, this method returns E_ABORT.

Remarks

The application can pass a time-out value of INFINITE to indicate that the method should block until there is an event; however, applications should avoid using INFINITE. Threads cannot process any messages while waiting in GetEvent. If you call GetEvent from the thread that processes Windows messages, specify only small wait times on the call in order to remain responsive to user input. This is most important when streaming data from a source such as the Internet, because state transitions can take significantly more time to complete.

After calling GetEvent, applications should always call FreeEventParams to release any resource associated with the event.

For a list of notification codes and event parameter values, see Event Notification Codes.

一般我们最希望获取到的事件是播放完成,这个事件的值是EC_COMPLETE。如果调用GetEvent函数之后,lEventCode的值是 EC_COMPLETE,说明播放完成了。

当获取并处理了GetEvent产生的事件后,必须把事件所占用的资源释放,释放资源使用IMediaEvent::FreeEventParams函数。

Frees resources associated with the parameters of an event.

Syntax

HRESULT FreeEventParams(
long lEventCode,
long lParam1,
long lParam2
);

Parameters

lEventCode
[in] Next event notification.
lParam1
[in] First parameter of the event.
lParam2
[in] Second parameter of the event.

Return Value

Returns an HRESULT value.

Remarks

Event parameters can be of type LONG or BSTR. If a BSTR is passed as an event, it will have been allocated by the task allocator and should be freed using this method. No reference-counted interfaces are passed to an application using IMediaEvent::GetEvent, because these cannot be overridden by IMediaEvent::CancelDefaultHandling. Therefore, do not use this method to release interfaces.

以下代码演示了如何捕捉事件并释放事件所占用的资源:

        // get th status of the song, it if is done, exit program.

        
long event_code, param1, param2;

        
// retrieves the next notification event
        if(SUCCEEDED(g_media_event->GetEvent(&event_code, &param1, &param2, 1)))
        {
            
if(event_code == EC_COMPLETE)
            {
                
// frees resources associated with the parameters of an events.
                g_media_event->FreeEventParams(event_code, param1, param2);

                
break;
            }                
        }

在需要逐帧连续监视事件的时候,用GetEvent和FreeEventParams组合是很有用的,它能帮助我们获取事件并作恰当的处理。但当我们需要连续播放而不希望连续监视播放过程的时候,另外一个函数就会很有用,即WaitForCompletion函数。它可以一直播放,在播放完成的时候,会把控制权交给我们。

Blocks execution of the application thread until the graph's operation finishes.

Syntax

HRESULT WaitForCompletion(
long msTimeout,
long *pEvCode
);

Parameters

msTimeout
[in] Duration of the time-out, in milliseconds. Pass zero to return immediately. To block indefinitely, pass INFINITE.
pEvCode
[out] Pointer to the event that terminated the wait. This value can be one of the following:
EC_COMPLETE Operation completed.
EC_ERRORABORT Error. Playback can't continue.
EC_USERABORT User terminated the operation.
Zero Operation has not completed.

Return Value

Returns one of the following HRESULT values.

E_ABORT Function timed out before the operation completed. This is equivalent to a zero pEvCode value.
S_OK Operation completed.

Remarks

This method is the equivalent of blocking until the event notification EC_COMPLETE, EC_ERRORABORT, or EC_USERABORT is received, or the time-out occurs.

When this method returns, the filter graph is still running. This method assumes that separate calls to the IMediaEvent interface are not being made. The method fails if the graph is not in, or transitioning into, a running state.

The time-out parameter (msTimeout) specifies the length of time to wait for completion. To test whether the operation completed, specify a zero msTimeout value and check the event code value (pEvCode) for zero, indicating that the operation has not completed.

释放DirectShow资源

一旦播放完成,就要关闭和释放所有占用的DirectShow资源。

    // stop music and relaese DirectShow objects

    
// switches all filters in the filter graph to a stopped state.
    g_media_control->Stop();

    g_media_event
->Release();
    g_media_event 
= NULL;

    g_media_control
->Release();
    g_media_control 
= NULL;

    g_graph_builder
->Release();
    g_graph_builder 
= NULL;

下面给出完整的代码示例,由于DirectX9已经将DirectShow移除,所以需要安装DirectX8 SDK,并在Vistual Studio中设置头文件目录。

点击下载源码和工程

/***************************************************************************************
PURPOSE:
    MP3 Playing Demo
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
<dshow.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"strmiids.lib")

#pragma warning(disable : 
4996)

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "MP3PlayClass";

// DirectShows components
IGraphBuilder*  g_graph_builder = NULL;
IMediaControl
*  g_media_control = NULL;
IMediaEvent
*    g_media_event   = NULL;

//--------------------------------------------------------------------------------
// Play mp3 which specified by filename.
//--------------------------------------------------------------------------------
BOOL Play_MP3(char* filename)
{
    
// convert filename to wide-character string
    WCHAR w_filename[MAX_PATH] = {0};

    mbstowcs(w_filename, filename, MAX_PATH);

    
// render the file
    g_graph_builder->RenderFile(w_filename, NULL);

    
// play the file, switches the entire filter graph into a running state.
    g_media_control->Run();

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS            win_class;
    MSG                 msg;    

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_MP3PLAY), 0, NULL);

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// initialize COM
    
//
    
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
    
// apartment (STA).
    CoInitialize(0);

    
// create the DirectMusic performance object
    
//
    
// creates a single uninitialized object of the class associated with a specified CLSID.
    if(FAILED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, 
                               (
void**)&g_graph_builder)))
    {
        MessageBox(NULL, 
"Unable to create DirectShow Graph Builder object.""Error", MB_OK);
        
return FALSE;
    }

    
// Query for the media control and event objects
    g_graph_builder->QueryInterface(IID_IMediaControl, (void**)&g_media_control);
    g_graph_builder
->QueryInterface(IID_IMediaEvent, (void**)&g_media_event);
    
    
// play mp3
    Play_MP3("escape.mp3");
    
    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);            
        }             

        
// get th status of the song, it if is done, exit program.

        
long event_code, param1, param2;

        
// retrieves the next notification event
        if(SUCCEEDED(g_media_event->GetEvent(&event_code, &param1, &param2, 1)))
        {
            
if(event_code == EC_COMPLETE)
            {
                
// frees resources associated with the parameters of an events.
                g_media_event->FreeEventParams(event_code, param1, param2);

                
break;
            }                
        }

        
// frees resources associated with the parameters of an events.
        g_media_event->FreeEventParams(event_code, param1, param2);
    }

    
// stop music and relaese DirectShow objects

    
// switches all filters in the filter graph to a stopped state.
    g_media_control->Stop();

    g_media_event
->Release();
    g_media_event 
= NULL;

    g_media_control
->Release();
    g_media_control 
= NULL;

    g_graph_builder
->Release();
    g_graph_builder 
= NULL;

    UnregisterClass(g_class_name, inst);

    
// release COM system
    
//
    
// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other
    
// resources that the thread maintains, and forces all RPC connections on the thread to close.
    CoUninitialize();
    
    
return (int) msg.wParam;
}

运行截图:


加载音色库(乐器)

DirectMusic加载器在使用固有文件或者MIDI文件的时候会自动加载默认的音色库。乐器总是被一组一组地使用,很多组乐器音色的集合被称之为DLS音色库(可下载的音乐)。每组乐器使用三个值编号,它们是:最高有效位(most-significant byte,MSB),最低有效位(least-significant byte,LSB)和组编号。

通常播放MIDI文件的乐器组是标准化的,也就是说编号为1的乐器总是钢琴,如果想使用新的钢琴作为乐器,可以从DLS集合中加载。DirectMusic包含了标准的乐器集合,通常称之为GM/GS集合(GM = General MIDI,GS = General Synthesizer),这个集合由日本罗兰(Roland)公司提出,称为MIDI合成器标准。

如果使用新的乐器取代标准MIDI乐器库中的乐器,需要确定该乐器的MSB和LSB为0,否则就需要为乐器库中的每个乐器都尝试新的赋值,以免打乱乐器库乐器排列。如果只想修改音色库中的一对乐器,只需要将它们保存在乐器库中即可。在下次加载DLS的时候,就会自动用新修改的乐器覆盖住内存中的旧乐器。当DLS加载完成的时候,就可以通知DirectMusic使用音色库对音乐进行播放了。

加载DLS音色库,需要从加载器中获取一个IDirectMusicCollection8对象,然后再次使用IDirectMusicLoader8::GetObject加载音色库,但是这一次指定的是音色库对象和音色库文件名。

以下代码演示了如何加载指定的音色库:

//--------------------------------------------------------------------------------
// Load DirectMusic collection object.
//--------------------------------------------------------------------------------
IDirectMusicCollection8* Load_DLS_Collection(char* filename)
{
    DMUS_OBJECTDESC dm_obj_desc;
    IDirectMusicCollection8
* dm_collection;

    
// get the object

    ZeroMemory(
&dm_obj_desc, sizeof(DMUS_OBJECTDESC));

    dm_obj_desc.dwSize      
= sizeof(DMUS_OBJECTDESC);
    dm_obj_desc.guidClass   
= CLSID_DirectMusicCollection;
    dm_obj_desc.dwValidData 
= DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;

    
// Converts a sequence of multibyte characters to a corresponding sequence of wide characters
    mbstowcs(dm_obj_desc.wszFileName, filename, MAX_PATH);

    
// retrieves an object from a file or resource and returns the speficied interface
    if(FAILED(g_dm_loader->GetObject(&dm_obj_desc, IID_IDirectMusicCollection8, (LPVOID*)&dm_collection)))
        
return NULL;

    
return dm_collection;
}

音色库被加载后,还需要给音乐片段指定音色库,这个工作通过设置指定的音乐片段的音轨参数来完成,设置参数使用函数 IDirectMusicSegment8::SetParam。

The SetParam method sets data on a track inside this segment.

Syntax

HRESULT SetParam(
REFGUID
rguidType,
DWORD dwGroupBits,
DWORD dwIndex,
MUSIC_TIME mtTime,
void* pParam
);

Parameters

rguidType

Reference to (C++) or address of (C) the type of data to set. See Standard Track Parameters.

dwGroupBits

Group that the desired track is in. Use 0xFFFFFFFF for all groups. For more information, see Identifying the Track.

dwIndex

Index of the track in the group identified by dwGroupBits in which to set the data, or DMUS_SEG_ALLTRACKS to set the parameter on all tracks in the group that contain the parameter.

mtTime

Time at which to set the data.

pParam

Address of a structure containing the data, or NULL if no data is required for this parameter. The structure must be of the appropriate kind and size for the data type identified by rguidType.

Return Values

If the method succeeds, the return value is S_OK.

If it fails, the method can return one of the error values shown in the following table.

Return code
DMUS_E_SET_UNSUPPORTED
DMUS_E_TRACK_NOT_FOUND
E_POINTER

MIDI的配置

一首歌曲如果已经和音色库一起被完整地加载到了内存中,这首音乐基本上已经可以使用了,唯一存在的问题是因为系统需要进行配置,以便适应一般MIDI文件的配置,所以需要告诉系统加载的文件是否是一个MIDI文件。告诉DirectMusic使用的是MIDI文件,需要再一次调用参数配置函数IDirectMusicSegment8:: SetParam。

以下代码演示了如何使用设置MIDI配置:

    // retrieves an object from a file or resource and returns the speficied interface
    if(FAILED(g_dm_loader->GetObject(&dm_obj_desc, IID_IDirectMusicSegment8, (LPVOID*)&g_dm_segment)))
        
return FALSE;

    
// setup midi playing
    if(strstr(filename, ".mid"))
    {
        
// set data on a track inside the segment
        if(FAILED(g_dm_segment->SetParam(GUID_StandardMIDIFile, 0xFFFFFFFF00, NULL)))
            
return FALSE;
    }

播放音乐的下一个准备工作就是配置音色库,将音色库装载到演奏器中,通过调用IDirectMusicSegment8:: Download函数来完成操作。

The Download method downloads band data to a performance or audiopath.

Syntax

HRESULT Download(
IUnknown*
pAudioPath
);

Parameters

pAudioPath

Pointer to the IUnknown interface of the performance or audiopath that receives the data.

Return Values

If the method succeeds, the return value is S_OK or DMUS_S_PARTIALDOWNLOAD. See Remarks for IDirectMusicBand8::Download.

If it fails, the method may return one of the error values shown in the following table.

Return code
DMUS_E_NOT_FOUND
DMUS_E_TRACK_NOT_FOUND
E_POINTER

Remarks

All bands and waveform data in the segment are downloaded.

Always call IDirectMusicSegment8::Unload before releasing the segment.


仅当音乐文件为MIDI文件的时候,才需要调用这个函数,因为该函数会改变音乐文件的信息,如果在不适当的时候强制调用该函数,会导致主音轨改变或者丢失。如果要改变音色库(比如重新指定一个新的DLS给一个段音乐),必须首先卸载音色库数据,然后再加载新的音色库,并重新播放音乐。

以下代码示例了如何使用Download。

// downloads band data to a performance
if(FAILED(g_dm_segment->Download(g_dm_performance)))
   
return FALSE;

当完成播放或者切换音色库时,必须调用一个卸载函数IDirectMusicSegment8::Unload,用它释放音色库及其他资源。

g_pDMSegment->Unload(g_pDS);

循环和重复

在播放之前最后一个要做的工作是设置重复点和重复次数,设置循环点是IDirectMusicSegment8::SetLoopPoints函数的职责。

The SetLoopPoints method sets the start and end points of the part of the segment that repeats the number of times set by the IDirectMusicSegment8::SetRepeats method.

Syntax

HRESULT SetLoopPoints(
MUSIC_TIME
mtStart,
MUSIC_TIME mtEnd
);

Parameters

mtStart

Point at which to begin the loop.

mtEnd

Point at which to end the loop. A value of 0 loops the entire segment.

Return Values

If the method succeeds, the return value is S_OK.

If it fails, the method can return DMUS_E_OUT_OF_RANGE.

Remarks

When the segment is played, it plays from the segment start time until mtEnd, then loops to mtStart, plays the looped portion the number of times set by IDirectMusicSegment8::SetRepeats, and then plays to the end.

The default values are set to loop the entire segment from beginning to end.

The method fails if mtStart is greater than or equal to the length of the segment, or if mtEnd is greater than the length of the segment. If mtEnd is 0, mtStart must be 0 as well.

This method does not affect any currently playing segment states created from this segment.

The loop points of a cached segment persist even if the segment is released, and then reloaded. To ensure that a segment is not subsequently reloaded from the cache, call IDirectMusicLoader8::ReleaseObject on it before releasing it.


很多情况下,我们希望重复播放整首歌曲,这个时候就不需要使用SetLoopPoints函数。设置完循环点后,就可以设置重复次数了(当然,先设置重复次数也是可以的)。如果希望音乐播放一次后就停止,设置重复次数为0,如果希望将曲目播放两次,需要将重复次数设置为1。设置重复次数使用IDirectMusicSegment8::SetRepeats函数,这个函数只有一个参数,就是曲目重复的次数,如果将这个值设置为 DMUS_SEG_REPEAT_INFINITE,那么播放就会一直持续。

The SetRepeats method sets the number of times the looping portion of the segment is to repeat.

Syntax

HRESULT SetRepeats(
DWORD
dwRepeats
);

Parameters

dwRepeats

Number of times that the looping portion of the segment is to repeat, or DMUS_SEG_REPEAT_INFINITE to repeat until explicitly stopped. A value of 0 specifies a single play with no repeats.

Return Values

The method returns S_OK.


播放和停止播放

如果要让演奏器播放音乐片段,只需要调用 IDirectMusicPerformance8::PlaySegmentEx函数就可以了。

The PlaySegmentEx method begins playback of a segment. The method offers greater functionality than IDirectMusicPerformance8::PlaySegment.

Syntax

HRESULT PlaySegmentEx(
IUnknown*
pSource,
WCHAR *pwzSegmentName,
IUnknown* pTransition,
DWORD dwFlags,
__int64 i64StartTime,
IDirectMusicSegmentState** ppSegmentState,
IUnknown* pFrom,
IUnknown* pAudioPath
);

Parameters

pSource

Address of the IUnknown interface of the object to play.

pwzSegmentName

Reserved. Set to NULL.

pTransition

IUnknown interface pointer of a template segment to use in composing a transition to this segment. Can be NULL. See Remarks.

dwFlags

Flags that modify the method's behavior. See DMUS_SEGF_FLAGS.

i64StartTime

Performance time at which to begin playing the segment, adjusted to any resolution boundary specified in dwFlags. The time is in music time unless the DMUS_SEGF_REFTIME flag is set. A value of zero causes the segment to start playing as soon as possible.

ppSegmentState

Address of a variable that receives an IDirectMusicSegmentState interface pointer for this instance of the playing segment. Use QueryInterface to obtain IDirectMusicSegmentState8. The reference count of the interface is incremented. This parameter can be NULL if no segment state pointer is wanted.

pFrom

IUnknown interface pointer of a segment state or audiopath to stop when the new segment begins playing. If it is an audiopath, all segment states playing on that audiopath are stopped. This value can be NULL. See Remarks.

pAudioPath

IUnknown interface pointer of an object that represents the audiopath on which to play, or NULL to play on the default path.

Return Values

If the method succeeds, the return value is S_OK.

If it fails, the method can return one of the error values shown in the following table.

Return code
DMUS_E_AUDIOPATH_INACTIVE
DMUS_E_AUDIOPATH_NOPORT
DMUS_E_NO_MASTER_CLOCK
DMUS_E_SEGMENT_INIT_FAILED
DMUS_E_TIME_PAST
E_OUTOFMEMORY
E_POINTER

如果希望停止播放,只需要调用IDirectMusicPerformance8::Stop函数就可以了。

The Stop method stops playback of a segment or segment state.

This method has been superseded by IDirectMusicPerformance8::StopEx, which can stop playback of a segment, segment state, or audiopath.

Syntax

HRESULT Stop(
IDirectMusicSegment*
pSegment,
IDirectMusicSegmentState* pSegmentState,
MUSIC_TIME mtTime,
DWORD dwFlags
);

Parameters

pSegment

Segment to stop playing. All segment states based on this segment are stopped at mtTime. See Remarks.

pSegmentState

Segment state to stop playing. See Remarks.

mtTime

Time at which to stop the segment, segment state, or both. If the time is in the past or if 0 is passed in this parameter, the specified segment and segment states stop playing immediately.

dwFlags

Flag that indicates when the stop should occur. Boundaries are in relation to the current primary segment. For a list of values, see IDirectMusicPerformance8::StopEx.

Return Values

If the method succeeds, the return value is S_OK.

If it fails, the method can return E_POINTER.

Remarks

If pSegment and pSegmentState are both NULL, all music stops, and all currently cued segments are released. If either pSegment or pSegmentState is not NULL, only the requested segment states are removed from the performance. If both are non-NULL and DMUS_SEGF_DEFAULT is used, the default resolution from the pSegment is used.

If you set all parameters to NULL or 0, everything stops immediately, and controller reset messages and note-off messages are sent to all mapped performance channels.


卸载音乐数据

使用完音乐之后,第一件要做的事就是卸载音色库数据,可以通过IDirectMusicSegment8::Unload来完成。

The Unload method unloads instrument data from a performance or audiopath.

Syntax

HRESULT Unload(
IUnknown *
pAudioPath
);

Parameters

pAudioPath

Pointer to the IUnknown interface of the performance or audiopath from which to unload the instrument data.

Return Values

If the method succeeds, the return value is S_OK.

If it fails, the method can return one of the error values shown in the following table.

Return code
DMUS_E_TRACK_NOT_FOUND
E_POINTER

Remarks

The method succeeds even if no data was previously downloaded.


还要释放加载器中的高速缓存数据,通过IDirectMusicLoader8::ReleaseObjectByUnknown来实现。

The ReleaseObjectByUnknown method releases the loader's reference to an object. This method is similar to IDirectMusicLoader8::ReleaseObject and is suitable for releasing objects for which the IDirectMusicObject8 interface is not readily available.

Syntax

HRESULT ReleaseObject(
IUnknown *
pObject
);

Parameters

pObject

Address of the IUnknown interface pointer of the object to release.

Return Values

If the method succeeds, the return value is S_OK, or S_FALSE if the object has already been released or cannot be found in the cache.

If it fails, the method can return E_POINTER.


如果开始的时候加载了音色库,现在是时候从加载器对象中将它卸载掉了,就像刚才从加载器中卸载音乐片段对象一样。另外,清空缓存很重要,有一个函数可以强制清空整个缓存区,但是并不需要在卸载加载器前调用它,因为在卸载过程中这个操作是自动完成的。

The ClearCache method removes all saved references to a specified object type.

Syntax

HRESULT ClearCache(
REFGUID
rguidClass
);

Parameters

rguidClass

Reference to (C++) or address of (C) the identifier of the class of objects to clear, or GUID_DirectMusicAllTypes to clear all types. For a list of standard loadable classes, see IDirectMusicLoader8.

Return Values

The method returns S_OK.

Remarks

This method clears all objects that are currently being held, but does not turn off caching. Use the IDirectMusicLoader8::EnableCache method to turn off automatic caching.

To clear a single object from the cache, call the IDirectMusicLoader8::ReleaseObject method.


以下给出一个完整的示例,来演示如何加载和播放MIDI音乐。

点击下载源码和工程

完整源码示例:

/***************************************************************************************
PURPOSE:
    Midi Playing Demo
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
<dsound.h>
#include 
<dmusici.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"dsound.lib")

#pragma warning(disable : 
4996)

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "MidiPlayClass";

IDirectSound8
*              g_ds;               // directsound component
IDirectMusicPerformance8*   g_dm_performance;   // directmusic performance
IDirectMusicLoader8*        g_dm_loader;        // directmusic loader
IDirectMusicSegment8*       g_dm_segment;       // directmusic segment

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Load DirectMusic collection object.
//--------------------------------------------------------------------------------
IDirectMusicCollection8* Load_DLS_Collection(char* filename)
{
    DMUS_OBJECTDESC dm_obj_desc;
    IDirectMusicCollection8
* dm_collection;

    
// get the object

    ZeroMemory(
&dm_obj_desc, sizeof(DMUS_OBJECTDESC));

    dm_obj_desc.dwSize      
= sizeof(DMUS_OBJECTDESC);
    dm_obj_desc.guidClass   
= CLSID_DirectMusicCollection;
    dm_obj_desc.dwValidData 
= DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;

    
// Converts a sequence of multibyte characters to a corresponding sequence of wide characters
    mbstowcs(dm_obj_desc.wszFileName, filename, MAX_PATH);

    
// retrieves an object from a file or resource and returns the speficied interface
    if(FAILED(g_dm_loader->GetObject(&dm_obj_desc, IID_IDirectMusicCollection8, (LPVOID*)&dm_collection)))
        
return NULL;

    
return dm_collection;
}

//--------------------------------------------------------------------------------
// Play midi file which specified with filename.
//--------------------------------------------------------------------------------
BOOL Play_Midi(char* filename)
{
    DMUS_OBJECTDESC dm_obj_desc;

    
// get the object

    ZeroMemory(
&dm_obj_desc, sizeof(DMUS_OBJECTDESC));

    dm_obj_desc.dwSize      
= sizeof(DMUS_OBJECTDESC);
    dm_obj_desc.guidClass   
= CLSID_DirectMusicSegment;
    dm_obj_desc.dwValidData 
= DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;

    
// Converts a sequence of multibyte characters to a corresponding sequence of wide characters
    mbstowcs(dm_obj_desc.wszFileName, filename, MAX_PATH);

    
// retrieves an object from a file or resource and returns the speficied interface
    if(FAILED(g_dm_loader->GetObject(&dm_obj_desc, IID_IDirectMusicSegment8, (LPVOID*)&g_dm_segment)))
        
return FALSE;

    
// setup midi playing
    if(strstr(filename, ".mid"))
    {
        
// set data on a track inside the segment
        if(FAILED(g_dm_segment->SetParam(GUID_StandardMIDIFile, 0xFFFFFFFF00, NULL)))
            
return FALSE;
    }

    
// downloads band data to a performance
    if(FAILED(g_dm_segment->Download(g_dm_performance)))
        
return FALSE;

    
// set to loop forever
    g_dm_segment->SetRepeats(DMUS_SEG_REPEAT_INFINITE);

    
// play on default audio path
    g_dm_performance->PlaySegmentEx(g_dm_segment, NULL, NULL, 00, NULL, NULL, NULL);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS            win_class;
    MSG                 msg;    

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_MIDIPLAY), 0, NULL);

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// initialize and configure directsound

    
// creates and initializes an object that supports the IDirectSound8 interface
    if(FAILED(DirectSoundCreate8(NULL, &g_ds, NULL)))
    {
        MessageBox(NULL, 
"Unable to create DirectSound object""Error", MB_OK);
        
return 0;
    }

    
// set the cooperative level of the application for this sound device
    g_ds->SetCooperativeLevel(g_hwnd, DSSCL_NORMAL);

    
// initialize COM
    
//
    
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
    
// apartment (STA).
    CoInitialize(0);

    
// create the DirectMusic performance object
    
//
    
// creates a single uninitialized object of the class associated with a specified CLSID.
    CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8, 
                     (
void**)&g_dm_performance);

    
// create the DirectMusic loader object
    CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**)&g_dm_loader);

    
// initialize the performance with the standard audio path.
    
// this initialize both directmusic and directsound and sets up the synthesizer.
    g_dm_performance->InitAudio(NULL, NULL, g_hwnd, DMUS_APATH_SHARED_STEREOPLUSREVERB, 128, DMUS_AUDIOF_ALL, NULL);
    
    
// tell directmusic where the default search path is

    
char path[MAX_PATH];
    WCHAR search_path[MAX_PATH];

    GetCurrentDirectory(MAX_PATH, path);

    
// maps a character string to a wide-character (Unicode) string
    MultiByteToWideChar(CP_ACP, 0, path, -1, search_path, MAX_PATH);

    
// set a search path for finding object files
    g_dm_loader->SetSearchDirectory(GUID_DirectMusicAllTypes, search_path, FALSE);

    
// play midi
    Play_Midi("escape.mid");
    
    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }        
    }

    
// release directsound objects

    
if(g_dm_segment)
        g_dm_segment
->Unload(g_ds);

    
if(g_dm_loader)
        g_dm_loader
->ReleaseObjectByUnknown(g_dm_segment);

    
if(g_dm_segment)
        g_dm_segment
->Release();

    
if(g_ds)
        g_ds
->Release();

    UnregisterClass(g_class_name, inst);

    
// release COM system
    
//
    
// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other
    
// resources that the thread maintains, and forces all RPC connections on the thread to close.
    CoUninitialize();
    
    
return (int) msg.wParam;
}

运行截图:


使用DirectMusic

在DirectAudio 中,DirectSound负责数字音频方面的处理,而DirectMusic则负责Midi文件(Musical Instrument Data Interface,数字音乐格式,.mid作为文件扩展名),DirectMusic固有音乐文件(.sgt文件)和数字录音设备录制的波形格式文件(.wav文件)等文件的播放操作。

能体现DirectMusic的强大之处是DirectMusic固有文件格式,一首用DirectMusic固有文件格式制作的音乐包括数个小音乐格式,这些样式还能用不同的乐器组合一个接一个地播放。随机的样式和乐器的选取创造出了随时都在改变的音乐,再加上节拍变化,就形成了一个魅力无穷的音乐系统。DirectMusic的另一个特性是可以使用“基调”,就是在正在播放的音乐片段上叠加一段其他音乐,新加入的音乐可以很平滑的融入到原有的音乐中。这在很多时候都有用,比如一个玩家完成了一个目标,可以马上播放一段“获得荣誉”的音乐提示他。

除了传统的音符之外,Midi音乐中可以包含数字音频作为音符,比如枪声、猴子的尖叫、也或者是其他各种各样你觉得奇怪的东西。比如可以在游戏中使用曾经梦想到的最令你心惊胆颤的音乐,而这些MIDI音乐都能完成。使用数字乐谱还有一个巨大的好处,即音乐在所有的计算机上可以发出一致的声音,这点是通过使用统一的DirectSound合成器完成的,当然DirectMusic允许使用 DirectSound接口或者是个人创建的接口。音乐数据使用的合成器通道称为音频通道(Audio Path),你可以获取这个通道,并在普通的DirectSound音频缓冲中播放。

使用Midi文件和 DirectMusic固有文件有共同的好处,那就是可以修改播放的节拍,这也是很有用的特性。有了这个特性,就可以设计随着屏幕动作而加速或者减速的背景音乐。如果游戏进入紧张的时期,就加速节拍,使音乐具有紧张感,如果高潮的活动结束,可以放慢节拍。数字录音设备所录制下来的音乐也是非常丰富多彩的,尽管这种音乐可以拥有非常高的音乐质量,但是这种歌曲不能被修改以便匹配游戏活动,也就是说这些音乐只能保持最开始录制的那个样子,不能有更多的变化,也不能减少里面的元素。

开始使用DirectMusic

使用 DirectMusic的第一步是创建一个主对象,我们把这个对象叫做演奏器对象(performance object),它表现整个音乐系统,第二步创建一个叫加载器(loader object)的对象,加载器加载所有原始的音乐文件。最后必须加载音乐小节到音乐片段对象(segment object)中,多个小节可以被同时加载,并一个接一个地播放,这可以让我们创建更具动态效果的音乐。

DirectMusic并没有提供函数帮助创建或初始化DirectMusic主接口,所以需要自行初始化COM接口,初始化COM接口所调用的第一个函数是CoInitialize。

Initializes the COM library on the current thread and identifies the concurrency model as single-thread apartment (STA). Applications must initialize the COM library before they can call COM library functions other than CoGetMalloc and memory allocation functions.

New applications should call CoInitializeEx instead of CoInitialize.

HRESULT CoInitialize(
LPVOID
pvReserved //Reserved; must be NULL
);

Parameter

pvReserved
[in] Reserved; must be NULL.

Return Values

This function supports the standard return values E_INVALIDARG, E_OUTOFMEMORY, and E_UNEXPECTED, as well as the following:

S_OK
The COM library was initialized successfully on this thread.
S_FALSE
The COM library is already initialized on this thread.
RPC_E_CHANGED_MODE
A previous call to CoInitializeEx specified the concurrency model for this thread as multithread apartment (MTA).

Remarks

CoInitializeEx provides the same functionality as CoInitialize and also provides a parameter to explicitly specify the thread's concurrency model. CoInitialize calls CoInitializeEx and specifies the concurrency model as single-thread apartment. Applications developed today should call CoInitializeEx rather than CoInitialize.

You need to initialize the COM library on a thread before you call any of the library functions except CoGetMalloc, to get a pointer to the standard allocator, and the memory allocation functions.

Once the concurrency model for a thread is set, it cannot be changed. A call to CoInitialize on an apartment that was previously initialized as multithreaded will fail and return RPC_E_CHANGED_MODE.

Typically, the COM library is initialized on a thread only once. Subsequent calls to CoInitialize or CoInitializeEx on the same thread will succeed, as long as they do not attempt to change the concurrency model, but will return S_FALSE. To close the COM library gracefully, each successful call to CoInitialize or CoInitializeEx, including those that return S_FALSE, must be balanced by a corresponding call to CoUninitialize. However, the first thread in the application that calls CoInitialize(0) or CoInitializeEx(COINIT_APARTMENTTHREADED) must be the last thread to call CoUninitialize(). If the call sequence is not in this order, then subsequent calls to CoInitialize on the STA will fail and the application will not work.

Because there is no way to control the order in which in-process servers are loaded or unloaded, it is not safe to call CoInitialize, CoInitializeEx, or CoUninitialize from the DllMain function.


在开始使用DirectMusic的时候调用这个函数,因为该函数有一个内部的计数器,显示被调用的次数,每调用一次这个初始化函数,就必须调用一次关闭COM的函数CoUninitialize。

Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other resources that the thread maintains, and forces all RPC connections on the thread to close.

void CoUninitialize( );

Remarks

A thread must call CoUninitialize once for each successful call it has made to CoInitialize or CoInitializeEx. Only the CoUninitialize call corresponding to the CoInitialize or CoInitializeEx call that initialized the library can close it.

Calls to OleInitialize must be balanced by calls to OleUninitialize. The OleUninitialize function calls CoUninitialize internally, so applications that call OleUninitialize do not also need to call CoUninitialize.

CoUninitialize should be called on application shutdown, as the last call made to the COM library after the application hides its main windows and falls through its main message loop. If there are open conversations remaining, CoUninitialize starts a modal message loop and dispatches any pending messages from the containers or server for this COM application. By dispatching the messages, CoUninitialize ensures that the application does not quit before receiving all of its pending messages. Non-COM messages are discarded.

Because there is no way to control the order in which in-process servers are loaded or unloaded, it is not safe to call CoInitialize, CoInitializeEx, or CoUninitialize from the DllMain function.


CoUninitialize函数减少COM接口的使用计数器,如果这个计数器减少到0,COM接口就会从系统释放内存,这样做可以提高内存的使用效率,所有的COM对象都遵循这个使用原则。

创建演奏器对象

演奏器对象是最主要的对象,可以创建多个演奏器对象,但是建议只使用一个演奏器。要创建演奏器,首先要声明一个IDirectMusicPerformance8对象,然后调用 CoCreateInstance初始化。

Creates a single uninitialized object of the class associated with a specified CLSID. Call CoCreateInstance when you want to create only one object on the local system. To create a single object on a remote system, call CoCreateInstanceEx. To create multiple objects based on a single CLSID, refer to the CoGetClassObject function.

STDAPI CoCreateInstance(
REFCLSID
rclsid, //Class identifier (CLSID) of the object
LPUNKNOWN pUnkOuter, //Pointer to controlling IUnknown
DWORD dwClsContext, //Context for running executable code
REFIID riid, //Reference to the identifier of the interface
LPVOID * ppv //Address of output variable that receives
// the interface pointer requested in riid
);

Parameters

rclsid
[in] CLSID associated with the data and code that will be used to create the object.
pUnkOuter
[in] If NULL, indicates that the object is not being created as part of an aggregate. If non-NULL, pointer to the aggregate object's IUnknown interface (the controlling IUnknown).
dwClsContext
[in] Context in which the code that manages the newly created object will run. The values are taken from the enumeration CLSCTX.
riid
[in] Reference to the identifier of the interface to be used to communicate with the object.
ppv
[out] Address of pointer variable that receives the interface pointer requested in riid. Upon successful return, *ppv contains the requested interface pointer.

Return Values

S_OK
An instance of the specified object class was successfully created.
REGDB_E_CLASSNOTREG
A specified class is not registered in the registration database. Also can indicate that the type of server you requested in the CLSCTX enumeration is not registered or the values for the server types in the registry are corrupt.
CLASS_E_NOAGGREGATION
This class cannot be created as part of an aggregate.
E_NOINTERFACE
The specified class does not implement the requested interface, or the controlling IUnknown does not expose the requested interface.

Remarks

The CoCreateInstance helper function provides a convenient shortcut by connecting to the class object associated with the specified CLSID, creating an uninitialized instance, and releasing the class object. As such, it encapsulates the following functionality:

CoGetClassObject(rclsid, dwClsContext, NULL, IID_IClassFactory, &pCF); 
hresult = pCF->CreateInstance(pUnkOuter, riid, ppvObj)
pCF->Release();

It is convenient to use CoCreateInstance when you need to create only a single instance of an object on the local machine. If you are creating an instance on remote machine, call CoCreateInstanceEx. When you are creating multiple instances, it is more efficient to obtain a pointer to the class object's IClassFactory interface and use its methods as needed. In the latter case, you should use the CoGetClassObject function.

In the CLSCTX enumeration, you can specify the type of server used to manage the object. The constants can be CLSCTX_INPROC_SERVER, CLSCTX_INPROC_HANDLER, CLSCTX_LOCAL_SERVER, or any combination of these values. The constant CLSCTX_ALL is defined as the combination of all three. For more information about the use of one or a combination of these constants, refer to CLSCTX.


以下代码演示了如何创建演奏器对象:

IDirectMusicPerformance8*   g_dm_performance;   // directmusic performance

// create the DirectMusic performance object
//
// creates a single uninitialized object of the class associated with a specified CLSID.
CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8, 
                     (
void**)&g_dm_performance);

演奏器需要被初始化后才能工作,初始化的过程需要首先创建DirectMusic和DirectSound对象,然后由这两个对象创建音频缓冲区并且设置播放特性,还需要设置的内容是播放音乐所用的音频通道,一般情况的设置使用128种乐器(128乐器通道)并且拥有立体声和混音(回音)效果,可以调用InitAudio来初始化。

The InitAudio method initializes the performance and optionally sets up a default audiopath. This method must be called before the performance can play using audiopaths.

This method should be used in most cases instead of IDirectMusicPerformance8::Init.

Syntax

HRESULT InitAudio(
IDirectMusic**
ppDirectMusic,
IDirectSound** ppDirectSound,
HWND hWnd,
DWORD dwDefaultPathType,
DWORD dwPChannelCount,
DWORD dwFlags,
DMUS_AUDIOPARAMS *pParams
);

Parameters

ppDirectMusic

Address of a variable that specifies or receives an interface pointer to a DirectMusic object.

If the variable pointed to by ppDirectMusic contains a valid IDirectMusic or IDirectMusic8 interface pointer, the existing object is assigned to the performance. The reference count of the interface is incremented.

If the variable pointed to by ppDirectMusic contains NULL, a DirectMusic object is created and the IDirectMusic interface pointer is returned. Use QueryInterface to obtain IDirectMusic8.

If ppDirectMusic is NULL, a DirectMusic object is created and used internally by the performance.

See Remarks.

ppDirectSound

Address of a variable that specifies or receives an IDirectSound interface pointer for a DirectSound device object to use by default for waveform output. If this parameter contains a NULL pointer, DirectMusic creates a private DirectSound device object. If the variable pointed to contains NULL, DirectMusic creates a DirectSound device object and returns the interface pointer. See Remarks.

hWnd

Window handle to use for the creation of DirectSound. This parameter can be NULL, in which case the foreground window is used. See Remarks.

This parameter is ignored if an IDirectSound interface pointer is passed to the method in ppDirectSound. In that case the application is responsible for setting the window handle by using IDirectSound8::SetCooperativeLevel.

dwDefaultPathType

Value that specifies the default audiopath type. Can be zero if no default path type is wanted. For a list of defined values, see IDirectMusicPerformance8::CreateStandardAudioPath.

dwPChannelCount

Value that specifies the number of performance channels to allocate to the path, if dwDefaultPathType is not zero.

dwFlags

Flags that specify requested features. If pParams is not NULL, this value is ignored and the requested features are specified in the dwFeatures member of the DMUS_AUDIOPARAMS structure. The values listed in the following table are defined for use in this parameter.

                                                                   
ValueDescription
DMUS_AUDIOF_3D3-D buffers. This flag is not implemented. Buffers in 3-D audiopaths always have 3-D capabilities.
DMUS_AUDIOF_ALLAll features.
DMUS_AUDIOF_BUFFERSMultiple buffers.
DMUS_AUDIOF_DMOSAdditional DMOs. This flag is not implemented.
DMUS_AUDIOF_ENVIRONEnvironmental modeling. This flag is not implemented.
DMUS_AUDIOF_EAXSupport for Environmental Audio Extensions (EAX). This flag is not implemented.
DMUS_AUDIOF_STREAMINGSupport for streaming waveforms.

pParams

Address of a DMUS_AUDIOPARAMS structure that specifies parameters for the synthesizer and receives information about what parameters were set. Can be NULL if the default parameters are wanted.

Return Values

If the method succeeds, the return value is S_OK.

If it fails, the method can return one of the error values shown in the following table.

                                                   
Return code
DMUS_E_ALREADY_INITED
DSERR_BUFFERLOST
DSERR_PRIOLEVELNEEDED
DSERR_UNINITIALIZED
E_NOINTERFACE
E_OUTOFMEMORY
E_POINTER

Remarks

This method can be called only once. It cannot be used to retrieve an existing IDirectMusic8 interface.

A DirectMusic object can be associated with the performance in the following ways.

     
  • The application allows the performance to create the DirectMusic object and needs a pointer to that object. In this case, *ppDirectMusic is NULL on entry and contains the IDirectMusic pointer on exit.  
  • The application allows the performance to initialize itself and does not need a DirectMusic object pointer. In this case, ppDirectMusic is NULL.  
  • The application creates its own DirectMusic object and gives it to the performance by passing the address of the IDirectMusic8 pointer in   ppDirectMusic. Most applications do not use this technique.

If you specify an interface pointer in ppDirectSound, it must be an interface to an object of class CLSID_DirectSound8. Objects of this class support both IDirectSound and IDirectSound8, but the IDirectSound interface must be passed. The DirectSound device object must be fully initialized before being passed to InitAudio. If the object was created by using CoCreateInstance, call IDirectSound8::Initialize. Set the cooperative level to DSSCL_PRIORITY by using IDirectSound8::SetCooperativeLevel.

You can pass NULL in the hWnd parameter to pass the current foreground window handle to DirectSound. However, do not assume that the application window will be in the foreground during initialization. It is best to pass the top-level application window handle.

The parameters set in dwFlags and pParams apply to the default audiopath and any audiopaths created subsequently.

The method fails with DSERR_BUFFERLOST if a value other than zero is passed in dwDefaultPathType and any application has initialized DirectSound with the write-primary cooperative level.

The performance must be terminated by using the IDirectMusicPerformance8::CloseDown method before being released.


以下代码演示了如何使用InitAudio来初始化演奏器对象:

// initialize the performance with the standard audio path.
// this initialize both directmusic and directsound and sets up the synthesizer. g_dm_performance->InitAudio(NULL, NULL, g_hwnd, DMUS_APATH_SHARED_STEREOPLUSREVERB, 128, DMUS_AUDIOF_ALL, NULL);

创建加载器对象

创建加载器是使用DirectMusic 的第二步,这个对象其实是一个缓冲系统,用于加速数据加载和歌曲支持文件(比如乐器库)的加载。

IDirectMusicLoader8表示加载器对象,以下代码可以创建一个加载器:

// create the DirectMusic loader object
    CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**)&g_dm_loader);

请确定在程序中只有一个加载器对象,这样可以帮助系统控制缓存和经常使用的数据资源。

使用加载器的下一步是告诉加载器在哪些目录搜索文件,一般情况下我们把这个路径叫做默认搜索路径(default search directory)。对于加载单独的MIDI文件,不需要设置默认的搜索路径,但是对于加载DirectMusic固有文件,必须设置搜索路径,以便让 DirectMusic能顺利找到支持文件。

可以通过调用IDirectMusicLoader8:: SetSearchDirectory函数来设置工作路径。

The SetSearchDirectory method sets a search path for finding object files. The search path can be set for one object file type or for all files.

Syntax

HRESULT SetSearchDirectory(
REFGUID
rguidClass,
WCHAR* pwszPath,
BOOL fClear
);

Parameters

rguidClass

Reference to (C++) or address of (C) the identifier of the class of objects that the call pertains to. GUID_DirectMusicAllTypes specifies all objects. For a list of standard loadable classes, see IDirectMusicLoader8.

pwszPath

File path for directory. Must be a valid directory and must be less than MAX_PATH in length. The path, if not fully qualified, is relative to the current directory when IDirectMusicLoader8::ScanDirectory is called.

fClear

If TRUE, clears all information about objects before setting the directory. This prevents the loader from accessing objects in a previous directory when those objects have the same name. However, objects are not removed from the cache.

Return Values

If the method succeeds, the return value is S_OK, or S_FALSE if the search directory is already set to pwszPath.

If it fails, the method can return one of the error values shown in the following table.

Return code
DMUS_E_LOADER_BADPATH
E_OUTOFMEMORY
E_POINTER

Remarks

After a search path is set, the loader does not need a full path every time it is given an object to load by file name. This enables objects that refer to other objects to find them by file name without knowing the full path.

When this method has been called, the loader expects the wszFileName member of the DMUS_OBJECTDESC structure to contain only a file name or a path relative to the search directory, unless the DMUS_OBJ_FULLPATH flag is set in the dwValidData member.


该函数接收的是宽字符串,使用时需要把字符串转换为WCHAR数据类型,可以通过mbstowcs来转换。

Converts a sequence of multibyte characters to a corresponding sequence of wide characters.


size_t mbstowcs(
wchar_t *wcstr,
const char *mbstr,
size_t count
);

Parameters

[out] wcstr

The address of a sequence of wide characters.

[in] mbstr

The address of a sequence of null terminated multibyte characters.

[in] count

The maximum number of multibyte characters to convert.

Return Value

If mbstowcs successfully converts the source string, it returns the number of converted multibyte characters. If the wcstr argument is NULL, the function returns the required size (in wide characters) of the destination string. If mbstowcs encounters an invalid multibyte character, it returns –1. If the return value is count, the wide-character string is not null-terminated.

Security Note

Ensure that wcstr and mbstr do not overlap, and that count correctly reflects the number of multibyte characters to convert.


以下代码演示了如何设置搜索路径:

// tell directmusic where the default search path is

    
char path[MAX_PATH];
    WCHAR search_path[MAX_PATH];

    GetCurrentDirectory(MAX_PATH, path);

    
// maps a character string to a wide-character (Unicode) string
    MultiByteToWideChar(CP_ACP, 0, path, -1, search_path, MAX_PATH);

    
// set a search path for finding object files
    g_dm_loader->SetSearchDirectory(GUID_DirectMusicAllTypes, search_path, FALSE);

使用音乐片段

通过以上步骤,系统已经被初始化,加载器也已经准备就绪,现在该加载歌曲然后播放它们了。完成这个操作是IDirectMusicSegment8对象的工作,DirectMusic加载器的职责是加载音乐和乐器库,并且创建IDirectMusicSegment8对象,整个加载过程有2个步骤,第一个步骤是加载包含所需播放的音乐的音乐片段。

加载音乐片段

加载音乐片段的第一步是设置一个对象描述结构,这个结构被称为DMUS_OBJECTDESC,它描述了要加载的信息。

The DMUS_OBJECTDESC structure is used to describe a loadable object. This structure is passed to the IDirectMusicLoader8::GetObject method to identify the object that the loader should retrieve from storage. Information about an object is retrieved in this structure by the IDirectMusicLoader8::EnumObject and IDirectMusicObject8::GetDescriptor methods.

Syntax

typedef struct _DMUS_OBJECTDESC {
DWORD dwSize;
DWORD dwValidData;
GUID guidObject;
GUID guidClass;
FILETIME ftDate;
DMUS_VERSION vVersion;
WCHAR wszName[DMUS_MAX_NAME];
WCHAR wszCategory[DMUS_MAX_CATEGORY];
WCHAR wszFileName[DMUS_MAX_FILENAME];
LONGLONG llMemLength;
LPBYTE pbMemData;
IStream* pStream
} DMUS_OBJECTDESC, *LPDMUS_OBJECTDESC;

Members

dwSize

Size of the structure, in bytes. This member must be initialized to sizeof(DMUS_OBJECTDESC) before the structure is passed to any method.

dwValidData

Flags describing which members are valid and giving further information about some members. Thefollowing values are defined:

                                                                                                           
FlagDescription
DMUS_OBJ_CATEGORYThe wszCategory member is valid.
DMUS_OBJ_CLASSThe guidClass member is valid.
DMUS_OBJ_DATEThe ftDate member is valid.
DMUS_OBJ_FILENAMEThe wszFileName member is valid. The presence of this flag is assumed if DMUS_OBJ_FULLPATH is set.
DMUS_OBJ_FULLPATHThe wszFileName member contains either the full path of a file or a path relative to the application directory. The directory set by IDirectMusicLoader8::SetSearchDirectory is not searched. If this flag is not set, wszFilename is always assumed to be relative to the application directory, or to the search directory if SetSearchDirectory has been called for this object type.
DMUS_OBJ_LOADEDThe object is currently loaded in memory.
DMUS_OBJ_MEMORYThe object is in memory, and llMemLength and   pbMemData are valid.
DMUS_OBJ_NAMEThe wszName member is valid.
DMUS_OBJ_OBJECTThe guidObject member is valid.
DMUS_OBJ_STREAMThe pStream member contains a pointer to the data stream.
DMUS_OBJ_URLThe wszFileName member contains a URL. URLs are not currently supported by the DirectMusic loader.
DMUS_OBJ_VERSIONThe vVersion member is valid.

guidObject

Unique identifier for this object.

guidClass

Unique identifier for the class of object. See DirectMusic Component GUIDs.

ftDate

Date that the object was last edited.

vVersion

DMUS_VERSION structure containing version information.

wszName

Name of the object.

wszCategory

Category for the object.

wszFileName

File path. If DMUS_OBJ_FULLPATH is set, this is the full path; otherwise, it is the file name. If the IDirectMusicLoader8::SetSearchDirectory method has been called, this member must contain only a file name.

llMemLength

Size of data in memory.

pbMemData

Pointer to data in memory. Do not use this value except when loading from a resource contained in the executable file.

pStream

Address of the IStream interface of a custom stream that can be used to load the object into memory. In most cases this value should be NULL.See Remarks.

Remarks

At least one of wszName, guidObject, and wszFileName must contain valid data to retrieve the object by using the IDirectMusicLoader8::GetObject method.

The name and category strings use 16-bit characters in the WCHAR format, not 8-bit ANSI characters. Be sure to convert as appropriate. You can use the C library mbstowcs function to convert from multibyte to Unicode and the wcstombs function to convert from Unicode back to multibyte.

Instead of passing on object descriptor to IDirectMusicLoader8::GetObject or IDirectMusicLoader8::SetObject with a filename or memory pointer, an application can pass a stream. This is done by setting the DMUS_OBJ_STREAM flag in dwValidData and a pointer to the stream in pStream. When the application calls GetObject, the loader saves the stream's current location, reads the object from the stream, and then restores the saved location. The application can continue reading from the stream without being affected by the call to GetObject.

When SetObject is called with a stream, the loader makes a clone of the stream object, and this clone is used if the object is later loaded. Thus an application can release a stream or continue to read from it after passing it to the loader by using SetObject. The actual data of the stream is not copied, so the application should not change or delete the data.


DMUS_OBJECTDESC结构作为参数被传递到IDirectMusicLoader8::GetObject函数中,这个函数确定所有的数据被加载并且被链接到片段对象中。

The GetObject method retrieves an object from a file or resource and returns the specified interface.

Syntax

HRESULT GetObject(
LPDMUS_OBJECTDESC
pDesc,
REFIID riid,
LPVOID FAR * ppv
);

Parameters

pDesc

Address of a DMUS_OBJECTDESC structure describing the object.

riid

Unique identifier of the interface. See DirectMusic Interface GUIDs.

ppv

Address of a variable that receives a pointer to the desired interface of the object.

Return Values

If the method succeeds, the return value is S_OK or DMUS_S_PARTIALLOAD.

DMUS_S_PARTIALLOAD is returned if any referenced object cannot be found, such as a style referenced in a segment. The loader might fail to find the style if it is referenced by name but IDirectMusicLoader8::ScanDirectory has not been called for styles. DMUS_S_PARTIALLOAD might also mean that the default instrument collection file, Gm.dls, is not available.

If it fails, the method can return one of the error values shown in the following table.

Return code
DMUS_E_LOADER_FAILEDCREATE
DMUS_E_LOADER_FAILEDOPEN
DMUS_E_LOADER_FORMATNOTSUPPORTED
DMUS_E_LOADER_NOCLASSID
E_FAIL
E_INVALIDARG
E_OUTOFMEMORY
E_POINTER
REGDB_E_CLASSNOTREG

Remarks

For file objects, it is simpler to use the IDirectMusicLoader8::LoadObjectFromFile method.

DirectMusic does not support loading from URLs. If the dwValidData member of the DMUS_OBJECTDESC structure contains DMUS_OBJ_URL, the method returns DMUS_E_LOADER_FORMATNOTSUPPORTED.

The method does not require that all valid members of the DMUS_OBJECTDESC structure match before retrieving an object. The dwValidData flags are evaluated in the following order:

  1. DMUS_OBJ_OBJECT
  2. DMUS_OBJ_STREAM
  3. DMUS_OBJ_MEMORY
  4. DMUS_OBJ_FILENAME and DMUS_OBJ_FULLPATH
  5. DMUS_OBJ_NAME and DMUS_OBJ_CATEGORY
  6. DMUS_OBJ_NAME
  7. DMUS_OBJ_FILENAME

In other words, the highest priority goes to a unique GUID, followed by a stream pointer, followed by a resource, followed by the full file path name, followed by an internal name plus category, followed by an internal name, followed by a local file name.

Do not load data from untrusted sources. Loading DirectMusic data files causes objects to be constructed, with the possibility that excessive demand on resources will lead to degradation of performance or system failure.


以下代码演示了如何加载音乐片段:

    DMUS_OBJECTDESC dm_obj_desc;

    
// get the object

    ZeroMemory(
&dm_obj_desc, sizeof(DMUS_OBJECTDESC));

    dm_obj_desc.dwSize      
= sizeof(DMUS_OBJECTDESC);
    dm_obj_desc.guidClass   
= CLSID_DirectMusicSegment;
    dm_obj_desc.dwValidData 
= DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;

    
// Converts a sequence of multibyte characters to a corresponding sequence of wide characters
    mbstowcs(dm_obj_desc.wszFileName, filename, MAX_PATH);

    
// retrieves an object from a file or resource and returns the speficied interface
    if(FAILED(g_dm_loader->GetObject(&dm_obj_desc, IID_IDirectMusicSegment8, (LPVOID*)&g_dm_segment)))
        
return FALSE;

使用通告

“通告”是一种触发机制,当缓存中播放位置达到某个固定的位置时,就会向程序发出通知。有了通告,就可以知道播放什么时候结束,这种机制在比较长的声音中特别有效。通告使用一个叫做 IDirectSoundNotify8的对象,这个程序的作用就是在音频缓存中标记一个位置,然后触发事件通知应用程序,而应用程序可以通过消息循环或者单独的线程进行处理。

标记的位置可以是一个缓存中的偏移值,也可以是由宏指定的停止标记,这个表示停止的宏是 DSBPN_OFFSETSTOP。并不是任何偏移值都可以用来作为通告发生的位置,这个值必须和音频的数据块对齐,并且通告的偏移必须按照从小到大的顺序排列。偏移值是不能够共享的,如果使用 DSBPN_OFFSETSTOP宏,它必须被放在最后。举例来说,对于一个块大小为2的音频(单声道、16位),尝试对偏移为4和5的位置设通告会导致失败,因为偏移量位置4和5都在同一个数据块中。

如果要在缓存中使用通告,必须在创建缓存的时候使用 DSBCAPS_CTRLPOSITIONNOTIFY标志,并且如果在创建缓存的过程中使用了这个标志,就必须使用通告对象。如果希望获取 IDirectSoundNotify8对象,可以在IDirectSoundBuffer8对象中通过请求接口来获得。

 IDirectSound8*          g_ds;           // directsound component

    
// create main sound buffer
    if(FAILED(g_ds->CreateSoundBuffer(&ds_buffer_desc, &ds_buffer_primary, NULL)))
        
return NULL;

    
// get newer interface
    if(FAILED(ds_buffer_primary->QueryInterface(IID_IDirectSoundBuffer8, (void**)&ds_buffer_second)))
    {
        ds_buffer_primary
->Release();
        
return NULL;
    }

通告对象只有一个成员函数SetNotificationPositions。

The SetNotificationPositions method sets the notification positions. During capture or playback, whenever the read or play cursor reaches one of the specified offsets, the associated event is signaled.

HRESULT SetNotificationPositions(
DWORD dwPositionNotifies,
LPCDSBPOSITIONNOTIFY pcPositionNotifies
);

Parameters

dwPositionNotifies
Number of DSBPOSITIONNOTIFY structures.
pcPositionNotifies
Pointer to an array of DSBPOSITIONNOTIFY structures (the maximum array size is DSBNOTIFICATIONS_MAX).

Return Values

If the method succeeds, the return value is DS_OK. If the method fails, the return value may be one of the following error values:

Return code
DSERR_INVALIDPARAM
DSERR_OUTOFMEMORY

Remarks

The value DSBPN_OFFSETSTOP can be specified in the dwOffset member to tell DirectSound to signal the associated event when the Stop or Stop method is called or when the end of the buffer has been reached and the playback is not looping. If it is used, this should be the last item in the position-notify array.

If a position-notify array has already been set, the method replaces the previous array.

The buffer must be stopped when this method is called.


pcPositionNotifies是一个DSBPOSITIONNOTIFY结构的数组。

The DSBPOSITIONNOTIFY structure describes a notification position. It is used by IDirectSoundNotify8::SetNotificationPositions.

typedef struct DSBPOSITIONNOTIFY {
DWORD dwOffset;
HANDLE hEventNotify;
} DSBPOSITIONNOTIFY;

Members

dwOffset
Offset from the beginning of the buffer where the notify event is to be triggered, or DSBPN_OFFSETSTOP.
hEventNotify
Handle to the event to be signaled when the offset has been reached.

Remarks

The DSBPN_OFFSETSTOP value in the dwOffset member causes the event to be signaled when playback or capture stops, either because the end of the buffer has been reached (and playback or capture is not looping) or because the application called the IDirectSoundBuffer8::Stop or IDirectSoundCaptureBuffer8::Stop method.

>When a playback buffer was created with DSBCAPS_LOCDEFER and DSBCAPS_CTRLPOSITIONNOTIFY along with any voice management flag, it is possible that a sound that has notifications set, but not yet reached, will be terminated by the voice manager. In this case, no event is signaled.


在使用通告的时候,一般需要使用事件句柄,事件一般有两种状态存在,已发出信号和未发出信号,可以通过CreateEvent函数来创建事件。

The CreateEvent function creates or opens a named or unnamed event object.
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);

Parameters

lpEventAttributes
[in] Pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpEventAttributes is NULL, the handle cannot be inherited.

The lpSecurityDescriptor member of the structure specifies a security descriptor for the new event. If lpEventAttributes is NULL, the event gets a default security descriptor. The ACLs in the default security descriptor for an event come from the primary or impersonation token of the creator.

bManualReset
[in] If this parameter is TRUE, the function creates a manual-reset event object, which requires the use of the ResetEvent function to set the event state to nonsignaled. If this parameter is FALSE, the function creates an auto-reset event object, and system automatically resets the event state to nonsignaled after a single waiting thread has been released.
bInitialState
[in] If this parameter is TRUE, the initial state of the event object is signaled; otherwise, it is nonsignaled.
lpName
[in] Pointer to a null-terminated string specifying the name of the event object. The name is limited to MAX_PATH characters. Name comparison is case sensitive.

If lpName matches the name of an existing named event object, this function requests the EVENT_ALL_ACCESS access right. In this case, the bManualReset and bInitialState parameters are ignored because they have already been set by the creating process. If the lpEventAttributes parameter is not NULL, it determines whether the handle can be inherited, but its security-descriptor member is ignored.

If lpName is NULL, the event object is created without a name.

If lpName matches the name of an existing semaphore, mutex, waitable timer, job, or file-mapping object, the function fails and the GetLastError function returns ERROR_INVALID_HANDLE. This occurs because these objects share the same name space.

Terminal Services:   The name can have a "Global\" or "Local\" prefix to explicitly create the object in the global or session name space. The remainder of the name can contain any character except the backslash character (\). For more information, see Kernel Object Namespaces.
Windows XP:   Fast user switching is implemented using Terminal Services sessions. The first user to log on uses session 0, the next user to log on uses session 1, and so on. Kernel object names must follow the guidelines outlined for Terminal Services so that applications can support multiple users.
Windows 2000:   If Terminal Services is not running, the "Global\" and "Local\" prefixes are ignored. The remainder of the name can contain any character except the backslash character.
Windows NT:  The name can contain any character except the backslash character.
Windows Me/98/95:   The name can contain any character except the backslash character. The empty string ("") is a valid object name.

Return Values

If the function succeeds, the return value is a handle to the event object. If the named event object existed before the function call, the function returns a handle to the existing object and GetLastError returns ERROR_ALREADY_EXISTS.

If the function fails, the return value is NULL. To get extended error information, call GetLastError.

Remarks

The handle returned by CreateEvent has the EVENT_ALL_ACCESS access right; it can be used in any function that requires a handle to an event object, provided that the caller has been granted access. If an event is created from a service or a thread that is impersonating a different user, you can either apply a security descriptor to the event when you create it, or change the default security descriptor for the creating process by changing its default DACL. For more information, see Synchronization Object Security and Access Rights.

Any thread of the calling process can specify the event-object handle in a call to one of the wait functions. The single-object wait functions return when the state of the specified object is signaled. The multiple-object wait functions can be instructed to return either when any one or when all of the specified objects are signaled. When a wait function returns, the waiting thread is released to continue its execution.

The initial state of the event object is specified by the bInitialState parameter. Use the SetEvent function to set the state of an event object to signaled. Use the ResetEvent function to reset the state of an event object to nonsignaled.

When the state of a manual-reset event object is signaled, it remains signaled until it is explicitly reset to nonsignaled by the ResetEvent function. Any number of waiting threads, or threads that subsequently begin wait operations for the specified event object, can be released while the object's state is signaled.

When the state of an auto-reset event object is signaled, it remains signaled until a single waiting thread is released; the system then automatically resets the state to nonsignaled. If no threads are waiting, the event object's state remains signaled.

Multiple processes can have handles of the same event object, enabling use of the object for interprocess synchronization. The following object-sharing mechanisms are available:

     
  • A child process created by the CreateProcess function can inherit a handle to an event object if the lpEventAttributes parameter of CreateEvent enabled inheritance.
  • A process can specify the event-object handle in a call to the DuplicateHandle function to create a duplicate handle that can be used by another process.
  • A process can specify the name of an event object in a call to the OpenEvent or CreateEvent function.

Use the CloseHandle function to close the handle. The system closes the handle automatically when the process terminates. The event object is destroyed when its last handle has been closed.


以下代码创建了4个事件,并且给pos_notify设置了4个事件触发的位置标记。

HANDLE  g_events[4];        // notification handles
DSBPOSITIONNOTIFY   pos_notify[4];

// create the event handles and set the notifications
    for(long i = 0; i < 4; i++)
    {
        
// create an unnamed event object
        g_events[i] = CreateEvent(NULL, FALSE, FALSE, NULL);

        pos_notify[i].dwOffset     
= 16384 * (i+1- 1;
        pos_notify[i].hEventNotify 
= g_events[i];
    }

    
// set the notification position
    
//
    
// During capture or playback, whenever the read or play cursor reached one of the specified offsets,
    
// the associated event is signaled.
    g_ds_notify->SetNotificationPositions(4, pos_notify);

在缓存已经准备就绪之后,就可以开始播放了,播放时等待通告事件被触发,它是WaitForMultipleObjects函数的工作。

The WaitForMultipleObjects function returns when any one or all of the specified objects are in the signaled state or the time-out interval elapses.

To enter an alertable wait state, use the WaitForMultipleObjectsEx function.

DWORD WaitForMultipleObjects(
DWORD nCount,
const HANDLE* lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
);

Parameters

nCount
[in] Number of object handles in the array pointed to by lpHandles. The maximum number of object handles is MAXIMUM_WAIT_OBJECTS.
lpHandles
[in] Pointer to an array of object handles. For a list of the object types whose handles can be specified, see the following Remarks section. The array can contain handles to objects of different types. It may not contain the multiple copies of the same handle.

If one of these handles is closed while the wait is still pending, the function's behavior is undefined.

The handles must have the SYNCHRONIZE access right. For more information, see Standard Access Rights.

Windows Me/98/95:  No handle may be a duplicate of another handle created using DuplicateHandle.
bWaitAll
[in] If this parameter is TRUE, the function returns when the state of all objects in the lpHandles array is signaled. If FALSE, the function returns when the state of any one of the objects is set to signaled. In the latter case, the return value indicates the object whose state caused the function to return.
dwMilliseconds
[in] Time-out interval, in milliseconds. The function returns if the interval elapses, even if the conditions specified by the bWaitAll parameter are not met. If dwMilliseconds is zero, the function tests the states of the specified objects and returns immediately. If dwMilliseconds is INFINITE, the function's time-out interval never elapses.

Return Values

If the function succeeds, the return value indicates the event that caused the function to return. It can be one of the following values. (Note that WAIT_OBJECT_0 is defined as 0 and WAIT_ABANDONED_0 is defined as 0x00000080L.)

Return code/value Description

WAIT_OBJECT_0 to (WAIT_OBJECT_0 + nCount– 1)
If bWaitAll is TRUE, the return value indicates that the state of all specified objects is signaled.

If bWaitAll is FALSE, the return value minus WAIT_OBJECT_0 indicates the lpHandles array index of the object that satisfied the wait. If more than one object became signaled during the call, this is the array index of the signaled object with the smallest index value of all the signaled objects.


WAIT_ABANDONED_0 to (WAIT_ABANDONED_0 + nCount– 1)
If bWaitAll is TRUE, the return value indicates that the state of all specified objects is signaled and at least one of the objects is an abandoned mutex object.

If bWaitAll is FALSE, the return value minus WAIT_ABANDONED_0 indicates the lpHandles array index of an abandoned mutex object that satisfied the wait.

WAIT_TIMEOUT
0x00000102L
The time-out interval elapsed and the conditions specified by the bWaitAll parameter are not satisfied.

If the function fails, the return value is WAIT_FAILED. To get extended error information, call GetLastError.


WaitForMultipleObjects函数可以同时等待64个对象,请确定自己的对象数目小于这个最大值。在没有接收到任何事件的情况下,函数可能会返回WAIT_FAILED,遇到这种情况,只需要再调用一次这个函数,就能恢复正常。取得 WaitForMultipleObjects函数的返回值就能够获取事件号。获取到事件号后,用这个数字减去 WAIT_OBJECT_0,得到的结果就是触发的事件索引号,这个值的范围从0到事件总数减1。

以下代码示例了如何使用WaitForMultipleObjects,以及如何得到事件索引号。

HANDLE  g_events[4];        // notification handles

// wait for a message
//
// return when any one or all of the specified objects are in the signaled state or the time-out 
 
// interval elapses.
DWORD result = MsgWaitForMultipleObjects(4, g_events, FALSE, INFINITE, QS_ALLEVENTS);

// get notification #
DWORD thread_index = result - WAIT_OBJECT_0;


使用线程控制事件


创建线程可以通过调用CreateThread函数来实现。

The CreateThread function creates a thread to execute within the virtual address space of the calling process.

To create a thread that runs in the virtual address space of another process, use the CreateRemoteThread function.

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

Parameters

lpThreadAttributes
[in] Pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpThreadAttributes is NULL, the handle cannot be inherited.

The lpSecurityDescriptor member of the structure specifies a security descriptor for the new thread. If lpThreadAttributes is NULL, the thread gets a default security descriptor. The ACLs in the default security descriptor for a thread come from the primary token of the creator.

Windows XP/2000/NT:  The ACLs in the default security descriptor for a thread come from the primary or impersonation token of the creator. This behavior changed with Windows XP SP2 and Windows Server 2003.

dwStackSize
[in] Initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is zero, the new thread uses the default size for the executable. For more information, see Thread Stack Size.
lpStartAddress
[in] Pointer to the application-defined function to be executed by the thread and represents the starting address of the thread. For more information on the thread function, see ThreadProc.
lpParameter
[in] Pointer to a variable to be passed to the thread.
dwCreationFlags
[in] Flags that control the creation of the thread. If the CREATE_SUSPENDED flag is specified, the thread is created in a suspended state, and will not run until the ResumeThread function is called. If this value is zero, the thread runs immediately after creation.

If the STACK_SIZE_PARAM_IS_A_RESERVATION flag is specified, the dwStackSize parameter specifies the initial reserve size of the stack. Otherwise, dwStackSize specifies the commit size.

Windows 2000/NT and Windows Me/98/95:  The STACK_SIZE_PARAM_IS_A_RESERVATION flag is not supported.
lpThreadId
[out] Pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned.
Windows Me/98/95:  This parameter may not be NULL.

Return Values

If the function succeeds, the return value is a handle to the new thread.

If the function fails, the return value is NULL. To get extended error information, call GetLastError.

Note that CreateThread may succeed even if lpStartAddress points to data, code, or is not accessible. If the start address is invalid when the thread runs, an exception occurs, and the thread terminates. Thread termination due to a invalid start address is handled as an error exit for the thread's process. This behavior is similar to the asynchronous nature of CreateProcess, where the process is created even if it refers to invalid or missing dynamic-link libraries (DLLs).

Windows Me/98/95:  CreateThread succeeds only when it is called in the context of a 32-bit program. A 32-bit DLL cannot create an additional thread when that DLL is being called by a 16-bit program.

lpStartAddress是一个函数指针,该函数指针的定义如下:

The ThreadProc function is an application-defined function that serves as the starting address for a thread. Specify this address when calling the CreateThread or CreateRemoteThread function. The LPTHREAD_START_ROUTINE type defines a pointer to this callback function. ThreadProc is a placeholder for the application-defined function name.
DWORD WINAPI ThreadProc(
LPVOID lpParameter
);

Parameters

lpParameter
[in] Thread data passed to the function using the lpParameter parameter of the CreateThread or CreateRemoteThread function.

Return Values

The function should return a value that indicates its success or failure.

Remarks

A process can determine when a thread it created has completed by using one of the wait functions. It can also obtain the return value of its ThreadProc by calling the GetExitCodeThread function.

Each thread receives a unique copy of the local variables of this function. Any static or global variables are shared by all threads in the process. To provide unique data to each thread using a global index, use thread local storage.


以下代码演示了如何使用函数CreateThread。

HANDLE  g_thread_handle;    // thread handle
DWORD   g_thread_id;        // thread id

// create a thread for notifications
g_thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Handle_Notifications, NULL, 0&g_thread_id);
......

DWORD Handle_Notifications(LPVOID thread_data)
{
   
// thread function to handle notification event 
   ......
}


音频流

音频流是一种很简单很容易理解的处理方式,就是循环播放一个缓冲区的音频数据,在播放的时候不断更新这个缓冲区的数据,使得播放能无缝播放。在播放音频缓冲区的时候设置一系列的标志位,然后需要一个指示器告诉应该在刚刚播放掉的部分填充一些新数据。在每次缓冲区播放完毕后,只需要从头开始继续播放,就能将音频连续地播放下去。



上面的这段音频缓冲区有4个标志位,当回放到某个标志位时就表示应该填充新数据到刚刚播放的部分中。

使用音频流的第一步是用数据填充整个缓冲区,然后开始播放声音,直到播放到达第一个标志位的时候,再读取一段新的数据,取代刚刚播放完成的数据。然后继续播放到达第二个标志位,再读取新数据取代刚刚播放完成的部分。持续这个过程,直到整段音频数据播放完成,最后一个播放到的标志触发一个停止播放的事件。如果需要循环播放音频,只需不触发停止事件,重新开始刚才的过程。在处理通告事件的线程中,使用加载数据的函数不断更新音频缓冲区的数据,保证缓冲区中拥有完成的音频信息。

下面是整个操作的全部过程:

1、创建音频缓冲区,65536字节大小。
2、设置4个通告位置,每个通告标志都在缓冲区的1/4处。
3、用数据填充整个缓冲区。
4、使用DSBPLAY_LOOP标志播放音频。
5、在每个通告被触发的时候,用新数据填充刚刚播放掉的部分,持续这个过程,直到有通告显示缓冲区中已经没有更多的数据可以放入缓冲区中,最后再播放剩余部分即可。
6、最后判断哪个事件表示已经到达了音频数据末端。

以下是代码示例:

IDirectSound8*          g_ds;           // directsound component
IDirectSoundBuffer8*    g_ds_buffer;    // sound buffer object
IDirectSoundNotify8*    g_ds_notify;    // notification object

HANDLE  g_thread_handle;    
// thread handle
DWORD   g_thread_id;        // thread id
HANDLE  g_events[4];        // notification handles
FILE*   g_fp;               // .WAV file handle
long    g_ds_size;          // directsound data buffer size
long    g_ds_pos;           // current directsound buffer position
long    g_ds_leave;         // leave directsound buffer data needed to be played

//--------------------------------------------------------------------------------
// Handle directsound nofification evenet, just stream load sound buffer.
//--------------------------------------------------------------------------------
DWORD Handle_Notifications(LPVOID thread_data)
{
    
while(1)
    {
        
// wait for a message
        
//
        
// return when any one or all of the specified objects are in the signaled state or the time-out 
        
// interval elapses.
        DWORD result = MsgWaitForMultipleObjects(4, g_events, FALSE, INFINITE, QS_ALLEVENTS);

        
// get notification #
        DWORD thread_index = result - WAIT_OBJECT_0;

        
// check for update #
        if(thread_index >= 0 && thread_index < 4)
        {
            
// stop sound and quit thread if no more
            if(g_ds_leave == 0)
                ExitThread(
0);

            
// seek to read position in file
            fseek(g_fp, g_ds_pos, SEEK_SET);

            
// stream in data based on amount of data left
            if(g_ds_leave <= 16384)
            {
                
// if this is reached, then end of sound is coming up.
                Load_Sound_Data(g_ds_buffer, thread_index * 16384, g_ds_leave, g_fp);

                g_ds_leave 
= 0;
            }
            
else
            {
                Load_Sound_Data(g_ds_buffer, thread_index 
* 1638465536, g_fp);

                
// reset directsound buffer leave and current position
                g_ds_leave -= 16384;
                g_ds_pos   
+= 16384;
            }
        }
    }

    
return 0;
}

//--------------------------------------------------------------------------------
// Play WAVE file sound which specified by filename.
//--------------------------------------------------------------------------------
void Play_Stream_Sound(char* filename)
{
    DSBPOSITIONNOTIFY   pos_notify[
4];
    WAVE_HEADER         wave_header;

    
// open the source file
    if((g_fp = fopen(filename, "rb")) == NULL)
        
return;

    
// create a 2 second buffer to stream in wave
    if((g_ds_buffer = Create_Buffer_From_WAV(g_fp, &wave_header)) == NULL)
    {
        fclose(g_fp);
        
return;
    }

    
// get streaming size and pointer
    g_ds_size   = wave_header.data_size;
    g_ds_pos    
= sizeof(WAVE_HEADER);
    g_ds_leave  
= g_ds_size;

    
// create a thread for notifications
    g_thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Handle_Notifications, NULL, 0&g_thread_id);
    
    
// create failed
    if(g_thread_handle == NULL)    
        
return;
    
    
// create the notification interface
    if(FAILED(g_ds_buffer->QueryInterface(IID_IDirectSoundNotify8, (void**)&g_ds_notify)))
        
return;

    
// create the event handles and set the notifications
    for(long i = 0; i < 4; i++)
    {
        
// create an unnamed event object
        g_events[i] = CreateEvent(NULL, FALSE, FALSE, NULL);

        pos_notify[i].dwOffset     
= 16384 * (i+1- 1;
        pos_notify[i].hEventNotify 
= g_events[i];
    }

    
// set the notification position
    
//
    
// During capture or playback, whenever the read or play cursor reached one of the specified offsets,
    
// the associated event is signaled.
    g_ds_notify->SetNotificationPositions(4, pos_notify);

    
// fill buffer completely with sound

    
// advance file pointer to data buffer's head
    fseek(g_fp, sizeof(WAVE_HEADER), SEEK_SET);

    
// load sound buffer data with 65536 bytes
    Load_Sound_Data(g_ds_buffer, 065536, g_fp);

    
// reset leave sound buffer and current sound buffer position
    g_ds_leave -= 65536;
    g_ds_pos   
+= 65536;

    
// play sound looping
    g_ds_buffer->SetCurrentPosition(0);
    g_ds_buffer
->SetVolume(DSBVOLUME_MAX);
    g_ds_buffer
->Play(00, DSBPLAY_LOOPING);
}

下面用一个例子来演示如何使用刚才介绍的那些知识。

点击下载源码和工程

完整代码示例:

/***************************************************************************************
PURPOSE:
    Streaming Playback Demo
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
<dsound.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"dsound.lib")

#pragma warning(disable : 
4996)

#define Safe_Release(p) if((p)) (p)->Release();

// .WAV file header
struct WAVE_HEADER
{
    
char    riff_sig[4];            // 'RIFF'
    long    waveform_chunk_size;    // 8
    char    wave_sig[4];            // 'WAVE'
    char    format_sig[4];          // 'fmt ' (notice space after)
    long    format_chunk_size;      // 16;
    short   format_tag;             // WAVE_FORMAT_PCM
    short   channels;               // # of channels
    long    sample_rate;            // sampling rate
    long    bytes_per_sec;          // bytes per second
    short   block_align;            // sample block alignment
    short   bits_per_sample;        // bits per second
    char    data_sig[4];            // 'data'
    long    data_size;              // size of waveform data
};

// window handles, class.
HWND g_hwnd;
char g_class_name[] = "StreamClass";

IDirectSound8
*          g_ds;           // directsound component
IDirectSoundBuffer8*    g_ds_buffer;    // sound buffer object
IDirectSoundNotify8*    g_ds_notify;    // notification object

HANDLE  g_thread_handle;    
// thread handle
DWORD   g_thread_id;        // thread id
HANDLE  g_events[4];        // notification handles
FILE*   g_fp;               // .WAV file handle
long    g_ds_size;          // directsound data buffer size
long    g_ds_pos;           // current directsound buffer position
long    g_ds_leave;         // leave directsound buffer data needed to be played

//--------------------------------------------------------------------------------
// Create wave header information from wave file.
//--------------------------------------------------------------------------------
IDirectSoundBuffer8* Create_Buffer_From_WAV(FILE* fp, WAVE_HEADER* wave_header)
{
    IDirectSoundBuffer
*     ds_buffer_primary;
    IDirectSoundBuffer8
*    ds_buffer_second;    
    DSBUFFERDESC            ds_buffer_desc;
    WAVEFORMATEX            wave_format;

    
// read in the header from beginning of file
    fseek(fp, 0, SEEK_SET);
    fread(wave_header, 
1sizeof(WAVE_HEADER), fp);

    
// check the sig fields. returning if an error.
    if(memcmp(wave_header->riff_sig, "RIFF"4|| memcmp(wave_header->wave_sig, "WAVE"4||
       memcmp(wave_header
->format_sig, "fmt "4|| memcmp(wave_header->data_sig, "data"4))
    {
        
return NULL;
    }

    
// setup the playback format
    ZeroMemory(&wave_format, sizeof(WAVEFORMATEX));

    wave_format.wFormatTag      
= WAVE_FORMAT_PCM;
    wave_format.nChannels       
= wave_header->channels;
    wave_format.nSamplesPerSec  
= wave_header->sample_rate;
    wave_format.wBitsPerSample  
= wave_header->bits_per_sample;
    wave_format.nBlockAlign     
= wave_format.wBitsPerSample / 8 * wave_format.nChannels;
    wave_format.nAvgBytesPerSec 
= wave_format.nSamplesPerSec * wave_format.nBlockAlign;

    
// create the sound buffer using the header data
    ZeroMemory(&ds_buffer_desc, sizeof(DSBUFFERDESC));

    ds_buffer_desc.dwSize        
= sizeof(DSBUFFERDESC);
    ds_buffer_desc.dwFlags       
= DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_LOCSOFTWARE;
    ds_buffer_desc.dwBufferBytes 
= 65536;
    ds_buffer_desc.lpwfxFormat   
= &wave_format;

    
// create main sound buffer
    if(FAILED(g_ds->CreateSoundBuffer(&ds_buffer_desc, &ds_buffer_primary, NULL)))
        
return NULL;

    
// get newer interface
    if(FAILED(ds_buffer_primary->QueryInterface(IID_IDirectSoundBuffer8, (void**)&ds_buffer_second)))
    {
        ds_buffer_primary
->Release();
        
return NULL;
    }

    
// return the interface
    return ds_buffer_second;
}

//--------------------------------------------------------------------------------
// Load sound data from second directsound buffer.
//--------------------------------------------------------------------------------
BOOL Load_Sound_Data(IDirectSoundBuffer8* ds_buffer, long lock_pos, long lock_size, FILE* fp)
{
    BYTE
* ptr1;
    BYTE
* ptr2;
    DWORD size1, size2;

    
if(lock_size == 0)
        
return FALSE;

    
// lock the sound buffer at position specified
    if(FAILED(ds_buffer->Lock(lock_pos, lock_size, (void**)&ptr1, &size1, (void**)&ptr2, &size2, 0)))
        
return FALSE;

    
// read in the data
    fread(ptr1, 1, size1, fp);

    
if(ptr2 != NULL)
        fread(ptr2, 
1, size2, fp);

    
// unlock it
    ds_buffer->Unlock(ptr1, size1, ptr2, size2);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Handle directsound nofification evenet, just stream load sound buffer.
//--------------------------------------------------------------------------------
DWORD Handle_Notifications(LPVOID thread_data)
{
    
while(1)
    {
        
// wait for a message
        
//
        
// return when any one or all of the specified objects are in the signaled state or the time-out 
        
// interval elapses.
        DWORD result = MsgWaitForMultipleObjects(4, g_events, FALSE, INFINITE, QS_ALLEVENTS);

        
// get notification #
        DWORD thread_index = result - WAIT_OBJECT_0;

        
// check for update #
        if(thread_index >= 0 && thread_index < 4)
        {
            
// stop sound and quit thread if no more
            if(g_ds_leave == 0)
                ExitThread(
0);

            
// seek to read position in file
            fseek(g_fp, g_ds_pos, SEEK_SET);

            
// stream in data based on amount of data left
            if(g_ds_leave <= 16384)
            {
                
// if this is reached, then end of sound is coming up.
                Load_Sound_Data(g_ds_buffer, thread_index * 16384, g_ds_leave, g_fp);

                g_ds_leave 
= 0;
            }
            
else
            {
                Load_Sound_Data(g_ds_buffer, thread_index 
* 1638465536, g_fp);

                
// reset directsound buffer leave and current position
                g_ds_leave -= 16384;
                g_ds_pos   
+= 16384;
            }
        }
    }

    
return 0;
}

//--------------------------------------------------------------------------------
// Play WAVE file sound which specified by filename.
//--------------------------------------------------------------------------------
void Play_Stream_Sound(char* filename)
{
    DSBPOSITIONNOTIFY   pos_notify[
4];
    WAVE_HEADER         wave_header;

    
// open the source file
    if((g_fp = fopen(filename, "rb")) == NULL)
        
return;

    
// create a 2 second buffer to stream in wave
    if((g_ds_buffer = Create_Buffer_From_WAV(g_fp, &wave_header)) == NULL)
    {
        fclose(g_fp);
        
return;
    }

    
// get streaming size and pointer
    g_ds_size   = wave_header.data_size;
    g_ds_pos    
= sizeof(WAVE_HEADER);
    g_ds_leave  
= g_ds_size;

    
// create a thread for notifications
    g_thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Handle_Notifications, NULL, 0&g_thread_id);
    
    
// create failed
    if(g_thread_handle == NULL)    
        
return;
    
    
// create the notification interface
    if(FAILED(g_ds_buffer->QueryInterface(IID_IDirectSoundNotify8, (void**)&g_ds_notify)))
        
return;

    
// create the event handles and set the notifications
    for(long i = 0; i < 4; i++)
    {
        
// create an unnamed event object
        g_events[i] = CreateEvent(NULL, FALSE, FALSE, NULL);

        pos_notify[i].dwOffset     
= 16384 * (i+1- 1;
        pos_notify[i].hEventNotify 
= g_events[i];
    }

    
// set the notification position
    
//
    
// During capture or playback, whenever the read or play cursor reached one of the specified offsets,
    
// the associated event is signaled.
    g_ds_notify->SetNotificationPositions(4, pos_notify);

    
// fill buffer completely with sound

    
// advance file pointer to data buffer's head
    fseek(g_fp, sizeof(WAVE_HEADER), SEEK_SET);

    
// load sound buffer data with 65536 bytes
    Load_Sound_Data(g_ds_buffer, 065536, g_fp);

    
// reset leave sound buffer and current sound buffer position
    g_ds_leave -= 65536;
    g_ds_pos   
+= 65536;

    
// play sound looping
    g_ds_buffer->SetCurrentPosition(0);
    g_ds_buffer
->SetVolume(DSBVOLUME_MAX);
    g_ds_buffer
->Play(00, DSBPLAY_LOOPING);
}

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS            win_class;
    MSG                 msg;    

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_STREAM), 0, NULL);

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// initialize and configure directsound

    
// creates and initializes an object that supports the IDirectSound8 interface
    if(FAILED(DirectSoundCreate8(NULL, &g_ds, NULL)))
    {
        MessageBox(NULL, 
"Unable to create DirectSound object""Error", MB_OK);
        
return 0;
    }

    
// set the cooperative level of the application for this sound device
    g_ds->SetCooperativeLevel(g_hwnd, DSSCL_NORMAL);

    
// play a streaming sound
    Play_Stream_Sound("test.wav");

    
if(g_ds_buffer)
    {
        
// play sound looping
        g_ds_buffer->SetCurrentPosition(0);
        
// set volume
        g_ds_buffer->SetVolume(DSBVOLUME_MAX);
        
// play sound
        g_ds_buffer->Play(00, DSBPLAY_LOOPING);
    }

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }        
    }

    
// stop sound
    if(g_ds_buffer)
        g_ds_buffer
->Stop();

    
// kill the thread
    if(g_thread_handle != NULL)
    {
        
// terminates thread
        TerminateThread(g_thread_handle, 0);

        
// closes an open object handle
        CloseHandle(g_thread_handle);
    }

    
// release the event handle
    for(int i = 0; i < 4; i++)
        CloseHandle(g_events[i]);

    
// close .WAV file
    if(g_fp)
        fclose(g_fp);

    
// release directsound objects
    Safe_Release(g_ds_buffer);
    Safe_Release(g_ds);

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}

运行截图:


调整声道平衡

所谓声道平衡就是调节左右声道的大小, DirectSound定义了两个宏帮助把声道平衡调节到最左边和最右边,使用DSBPAN_LEFT将声道调整到最左边,使用DSBPAN_RIGHT 将声道调整到最右边。

通过调用IDirectSoundBuffer8::SetPan函数可以调节声道平衡。

The SetPan method sets the relative volume of the left and right channels.

HRESULT SetPan(
LONG lPan
);

Parameters

dwFrequency
Frequency, in hertz (Hz), at which to play the audio samples. A value of DSBFREQUENCY_ORIGINAL resets the frequency to the default value of the buffer format.

Return Values

If the method succeeds, the return value is DS_OK. If the method fails, the return value may be one of the following error values:

Return code
DSERR_CONTROLUNAVAIL
DSERR_GENERIC
DSERR_INVALIDPARAM
DSERR_PRIOLEVELNEEDED

Increasing or decreasing the frequency changes the perceived pitch of the audio data. This method does not affect the format of the buffer.

Before setting the frequency, you should ascertain whether the frequency is supported by checking the dwMinSecondarySampleRate and dwMaxSecondarySampleRate members of the DSCAPS structure for the device. Some operating systems do not support frequencies greater than 100,000 Hz.

This method is not valid for the primary buffer.


失去焦点

在很多情况下,其他程序会和你的程序抢占系统资源,然后把那些修改过配置的资源留给你的程序。这种情况多半发生在音频缓存上,所以需要调用IDirectSoundBuffer8::Restore来还原音频设置。如果缓冲区丢失,可以用这个函数找回。

The Restore method restores the memory allocation for a lost sound buffer.

HRESULT Restore();

Parameters

None.

Return Values

If the method succeeds, the return value is DS_OK. If the method fails, the return value may be one of the following error values:

Return code

  • DSERR_BUFFERLOST
  • DSERR_INVALIDCALL
  • DSERR_PRIOLEVELNEEDED

Remarks

If the application does not have the input focus, IDirectSoundBuffer8::Restore might not succeed. For example, if the application with the input focus has the DSSCL_WRITEPRIMARY cooperative level, no other application will be able to restore its buffers. Similarly, an application with the DSSCL_WRITEPRIMARY cooperative level must have the input focus to restore its primary buffer.

After DirectSound restores the buffer memory, the application must rewrite the buffer with valid sound data. DirectSound cannot restore the contents of the memory, only the memory itself.

The application can receive notification that a buffer is lost when it specifies that buffer in a call to the Lock or Play method. These methods return DSERR_BUFFERLOST to indicate a lost buffer. The GetStatus method can also be used to retrieve the status of the sound buffer and test for the DSBSTATUS_BUFFERLOST flag.


使用这个函数会导致缓存中的音频数据丢失,调用完此函数后需要重新加载。在创建音频缓存的时候使用 DSBCAPS_LOCSOFTWARE标志,这样DirectSound将在系统内存中分配缓冲区,因此数据基本上不可能丢失,也就不必担心丢失资源了。

加载声音到音频缓冲

最简单的方法就是通过Windows 最广泛使用的数字音频文件 ---- 波表文件,这种文件通常以.WAV作为它的扩展名。一个波表文件通常由两部分构成,一部分是文件开头的波表文件头,另外一部分是紧随其后的原始音频数据。这些原始音频数据可能是经过压缩的,也可能是未经压缩的。如果是压缩过的,操作起来会复杂很多,如果没有压缩过,操作起来就很容易。

下面的结构表示一个波表文件的文件头,通过观察能看出波表文件的文件头结构。

// .WAV file header
struct WAVE_HEADER
{
    
char    riff_sig[4];            // 'RIFF'
    long    waveform_chunk_size;    // 8
    char    wave_sig[4];            // 'WAVE'
    char    format_sig[4];          // 'fmt ' (notice space after)
    long    format_chunk_size;      // 16;
    short   format_tag;             // WAVE_FORMAT_PCM
    short   channels;               // # of channels
    long    sample_rate;            // sampling rate
    long    bytes_per_sec;          // bytes per second
    short   block_align;            // sample block alignment
    short   bits_per_sample;        // bits per second
    char    data_sig[4];            // 'data'
    long    data_size;              // size of waveform data
};

处理文件头非常简单,只需要打开文件,读取数据(读取数据的大小和WAVE_HEADER结构的大小一致)、填充 WAVE_HEADER结构就可以了。这个结构包含了我们所需要的所有关于音频文件的信息。你可以通过签名段来判断一个文件是否是波形文件,签名段在WAVE_HEADER中是"WaveSig"。请仔细查看 WAVE_HEADER中每个段的特征,如果不符合特征,说明所读取的不是一个波形文件。尤其是要检查签名段,如果签名段不是'WAVE'则说明加载了错误的音频文件。

有了必要的音频数据的结构信息后,就可以基于这些信息创建音频缓存,把音频数据放入其中,然后执行各种各样的操作。
可以编写两个函数来实现这样的功能,Create_Buffer_From_WAV读取并解析波表文件头,并且创建单独的音频缓冲区 ,Load_Sound_Data读取音频数据到缓冲区。

IDirectSound8*          g_ds;           // directsound component
IDirectSoundBuffer8*    g_ds_buffer;    // sound buffer object

//--------------------------------------------------------------------------------
// Create wave header information from wave file.
//--------------------------------------------------------------------------------
IDirectSoundBuffer8* Create_Buffer_From_WAV(FILE* fp, WAVE_HEADER* wave_header)
{
    IDirectSoundBuffer
*     ds_buffer_main;
    IDirectSoundBuffer8
*    ds_buffer_second;    
    DSBUFFERDESC            ds_buffer_desc;
    WAVEFORMATEX            wave_format;

    
// read in the header from beginning of file
    fseek(fp, 0, SEEK_SET);
    fread(wave_header, 
1sizeof(WAVE_HEADER), fp);

    
// check the sig fields. returning if an error.
    if(memcmp(wave_header->riff_sig, "RIFF"4|| memcmp(wave_header->wave_sig, "WAVE"4||
       memcmp(wave_header
->format_sig, "fmt "4|| memcmp(wave_header->data_sig, "data"4))
    {
        
return NULL;
    }

    
// setup the playback format
    ZeroMemory(&wave_format, sizeof(WAVEFORMATEX));

    wave_format.wFormatTag      
= WAVE_FORMAT_PCM;
    wave_format.nChannels       
= wave_header->channels;
    wave_format.nSamplesPerSec  
= wave_header->sample_rate;
    wave_format.wBitsPerSample  
= wave_header->bits_per_sample;
    wave_format.nBlockAlign     
= wave_format.wBitsPerSample / 8 * wave_format.nChannels;
    wave_format.nAvgBytesPerSec 
= wave_format.nSamplesPerSec * wave_format.nBlockAlign;

    
// create the sound buffer using the header data
    ZeroMemory(&ds_buffer_desc, sizeof(DSBUFFERDESC));

    ds_buffer_desc.dwSize        
= sizeof(DSBUFFERDESC);
    ds_buffer_desc.dwFlags       
= DSBCAPS_CTRLVOLUME;
    ds_buffer_desc.dwBufferBytes 
= wave_header->data_size;
    ds_buffer_desc.lpwfxFormat   
= &wave_format;

    
// create main sound buffer
    if(FAILED(g_ds->CreateSoundBuffer(&ds_buffer_desc, &ds_buffer_main, NULL)))
        
return NULL;

    
// get newer interface
    if(FAILED(ds_buffer_main->QueryInterface(IID_IDirectSoundBuffer8, (void**)&ds_buffer_second)))
    {
        ds_buffer_main
->Release();
        
return NULL;
    }

    
// return the interface
    return ds_buffer_second;
}

//--------------------------------------------------------------------------------
// Load sound data from second directsound buffer.
//--------------------------------------------------------------------------------
BOOL Load_Sound_Data(IDirectSoundBuffer8* ds_buffer, long lock_pos, long lock_size, FILE* fp)
{
    BYTE
* ptr1;
    BYTE
* ptr2;
    DWORD size1, size2;

    
if(lock_size == 0)
        
return FALSE;

    
// lock the sound buffer at position specified
    if(FAILED(ds_buffer->Lock(lock_pos, lock_size, (void**)&ptr1, &size1, (void**)&ptr2, &size2, 0)))
        
return FALSE;

    
// read in the data
    fread(ptr1, 1, size1, fp);

    
if(ptr2 != NULL)
        fread(ptr2, 
1, size2, fp);

    
// unlock it
    ds_buffer->Unlock(ptr1, size1, ptr2, size2);

    
return TRUE;
}

接着编写一个函数Load_WAV封装刚才那两个函数,从文件名加载波形文件信息。

//--------------------------------------------------------------------------------
// Load wave file.
//--------------------------------------------------------------------------------
IDirectSoundBuffer8* Load_WAV(char* filename)
{
    IDirectSoundBuffer8
* ds_buffer;
    WAVE_HEADER wave_header 
= {0};
    FILE
* fp;

    
// open the source file
    if((fp = fopen(filename, "rb")) == NULL)
        
return NULL;

    
// create the sound buffer
    if((ds_buffer = Create_Buffer_From_WAV(fp, &wave_header)) == NULL)
    {
        fclose(fp);
        
return NULL;
    }

    
// read in the data
    fseek(fp, sizeof(WAVE_HEADER), SEEK_SET);

    
// load sound data
    Load_Sound_Data(ds_buffer, 0, wave_header.data_size, fp);

    
// close the source file
    fclose(fp);

    
// return the new sound buffer fully loaded with sound
    return ds_buffer;
}


以下给出完整示例:

点击下载源码和工程

/***************************************************************************************
PURPOSE:
    Wave Playing Demo
 **************************************************************************************
*/

#include 
<windows.h>
#include 
<stdio.h>
#include 
<dsound.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"dsound.lib")

#pragma warning(disable : 
4996)

#define Safe_Release(p) if((p)) (p)->Release();

// .WAV file header
struct WAVE_HEADER
{
    
char    riff_sig[4];            // 'RIFF'
    long    waveform_chunk_size;    // 8
    char    wave_sig[4];            // 'WAVE'
    char    format_sig[4];          // 'fmt ' (notice space after)
    long    format_chunk_size;      // 16;
    short   format_tag;             // WAVE_FORMAT_PCM
    short   channels;               // # of channels
    long    sample_rate;            // sampling rate
    long    bytes_per_sec;          // bytes per second
    short   block_align;            // sample block alignment
    short   bits_per_sample;        // bits per second
    char    data_sig[4];            // 'data'
    long    data_size;              // size of waveform data
};

// window handles, class and caption text.
HWND g_hwnd;
char g_class_name[] = "WavPlayClass";

IDirectSound8
*          g_ds;           // directsound component
IDirectSoundBuffer8*    g_ds_buffer;    // sound buffer object

//--------------------------------------------------------------------------------
// Create wave header information from wave file.
//--------------------------------------------------------------------------------
IDirectSoundBuffer8* Create_Buffer_From_WAV(FILE* fp, WAVE_HEADER* wave_header)
{
    IDirectSoundBuffer
*     ds_buffer_main;
    IDirectSoundBuffer8
*    ds_buffer_second;    
    DSBUFFERDESC            ds_buffer_desc;
    WAVEFORMATEX            wave_format;

    
// read in the header from beginning of file
    fseek(fp, 0, SEEK_SET);
    fread(wave_header, 
1sizeof(WAVE_HEADER), fp);

    
// check the sig fields. returning if an error.
    if(memcmp(wave_header->riff_sig, "RIFF"4|| memcmp(wave_header->wave_sig, "WAVE"4||
       memcmp(wave_header
->format_sig, "fmt "4|| memcmp(wave_header->data_sig, "data"4))
    {
        
return NULL;
    }

    
// setup the playback format
    ZeroMemory(&wave_format, sizeof(WAVEFORMATEX));

    wave_format.wFormatTag      
= WAVE_FORMAT_PCM;
    wave_format.nChannels       
= wave_header->channels;
    wave_format.nSamplesPerSec  
= wave_header->sample_rate;
    wave_format.wBitsPerSample  
= wave_header->bits_per_sample;
    wave_format.nBlockAlign     
= wave_format.wBitsPerSample / 8 * wave_format.nChannels;
    wave_format.nAvgBytesPerSec 
= wave_format.nSamplesPerSec * wave_format.nBlockAlign;

    
// create the sound buffer using the header data
    ZeroMemory(&ds_buffer_desc, sizeof(DSBUFFERDESC));

    ds_buffer_desc.dwSize        
= sizeof(DSBUFFERDESC);
    ds_buffer_desc.dwFlags       
= DSBCAPS_CTRLVOLUME;
    ds_buffer_desc.dwBufferBytes 
= wave_header->data_size;
    ds_buffer_desc.lpwfxFormat   
= &wave_format;

    
// create main sound buffer
    if(FAILED(g_ds->CreateSoundBuffer(&ds_buffer_desc, &ds_buffer_main, NULL)))
        
return NULL;

    
// get newer interface
    if(FAILED(ds_buffer_main->QueryInterface(IID_IDirectSoundBuffer8, (void**)&ds_buffer_second)))
    {
        ds_buffer_main
->Release();
        
return NULL;
    }

    
// return the interface
    return ds_buffer_second;
}

//--------------------------------------------------------------------------------
// Load sound data from second directsound buffer.
//--------------------------------------------------------------------------------
BOOL Load_Sound_Data(IDirectSoundBuffer8* ds_buffer, long lock_pos, long lock_size, FILE* fp)
{
    BYTE
* ptr1;
    BYTE
* ptr2;
    DWORD size1, size2;

    
if(lock_size == 0)
        
return FALSE;

    
// lock the sound buffer at position specified
    if(FAILED(ds_buffer->Lock(lock_pos, lock_size, (void**)&ptr1, &size1, (void**)&ptr2, &size2, 0)))
        
return FALSE;

    
// read in the data
    fread(ptr1, 1, size1, fp);

    
if(ptr2 != NULL)
        fread(ptr2, 
1, size2, fp);

    
// unlock it
    ds_buffer->Unlock(ptr1, size1, ptr2, size2);

    
return TRUE;
}

//--------------------------------------------------------------------------------
// Load wave file.
//--------------------------------------------------------------------------------
IDirectSoundBuffer8* Load_WAV(char* filename)
{
    IDirectSoundBuffer8
* ds_buffer;
    WAVE_HEADER wave_header 
= {0};
    FILE
* fp;

    
// open the source file
    if((fp = fopen(filename, "rb")) == NULL)
        
return NULL;

    
// create the sound buffer
    if((ds_buffer = Create_Buffer_From_WAV(fp, &wave_header)) == NULL)
    {
        fclose(fp);
        
return NULL;
    }

    
// read in the data
    fseek(fp, sizeof(WAVE_HEADER), SEEK_SET);

    
// load sound data
    Load_Sound_Data(ds_buffer, 0, wave_header.data_size, fp);

    
// close the source file
    fclose(fp);

    
// return the new sound buffer fully loaded with sound
    return ds_buffer;
}

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS            win_class;
    MSG                 msg;    

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_WAVPLAY), 0, NULL);

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// initialize and configure directsound

    
// creates and initializes an object that supports the IDirectSound8 interface
    if(FAILED(DirectSoundCreate8(NULL, &g_ds, NULL)))
    {
        MessageBox(NULL, 
"Unable to create DirectSound object""Error", MB_OK);
        
return 0;
    }

    
// set the cooperative level of the application for this sound device
    g_ds->SetCooperativeLevel(g_hwnd, DSSCL_NORMAL);

    
// load a sound to play
    g_ds_buffer = Load_WAV("test.wav");

    
if(g_ds_buffer)
    {
        
// play sound looping
        g_ds_buffer->SetCurrentPosition(0);
        
// set volume
        g_ds_buffer->SetVolume(DSBVOLUME_MAX);
        
// play sound
        g_ds_buffer->Play(00, DSBPLAY_LOOPING);
    }

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }        
    }

    
// release directsound objects
    g_ds->Release();

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}

运行截图:



开始使用主音频缓存

让缓存在程序启动的时候开始播放可以节省不少处理器时间。因为内存资源是有限的,特别是在硬件设备中,而你使用的数据缓存可能需要任意大小,因此主音频缓冲区和辅助缓冲区使用环形缓存。因为数据缓冲是一个一维数组,所以可以让这个缓冲区头尾相接。这是一个十分强大的技术,利用这个技术我们可以节省大量的内存。

声音在进行混音处理后,被送入环形主音频缓存。一旦播放位置到达主音频缓存的终点,声音又从头开始播放,这样声音就被无间隙地连续播放。如果想要使用缓存的这种循环特性,需要指定启用循环播放的特性,若不然当播放到缓冲区终点时,播放就停止了。

为了播放缓存中的音频数据(在开启循环选项的情况下播放),需要调用Play函数。

The Play method causes the sound buffer to play, starting at the play cursor.
HRESULT Play(
DWORD dwReserved1,
DWORD dwPriority,
DWORD dwFlags
);

Parameters

dwReserved1
Reserved. Must be 0.
dwPriority
Priority for the sound, used by the voice manager when assigning hardware mixing resources. The lowest priority is 0, and the highest priority is 0xFFFFFFFF. If the buffer was not created with the DSBCAPS_LOCDEFER flag, this value must be 0.
dwFlags
Flags specifying how to play the buffer. The following flags are defined:

Looping flag

Value Description
DSBPLAY_LOOPING After the end of the audio buffer is reached, play restarts at the beginning of the buffer. Play continues until explicitly stopped. This flag must be set when playing a primary buffer.

Voice allocation flags

The voice allocation flags are valid only for buffers created with the DSBCAPS_LOCDEFER flag. One of the following flags can be used to force the processing of the sound into hardware or software. If neither DBSPLAY_LOCHARDWARE nor DBSPLAY_LOCSOFTWARE is set, the sound is played in either software or hardware, depending on the availability of resources at the time the method is called. See Remarks.

Value Description
DSBPLAY_LOCHARDWARE Play this voice in a hardware buffer only. If the hardware has no available voices and no voice management flags are set, the call to IDirectSoundBuffer8::Play fails. This flag cannot be combined with DSBPLAY_LOCSOFTWARE.
DSBPLAY_LOCSOFTWARE Play this voice in a software buffer only. This flag cannot be combined with DSBPLAY_LOCHARDWARE or any voice management flag.

Voice management flags

The voice management flags are valid only for buffers created with the DSBCAPS_LOCDEFER flag, and are used for sounds that are to play in hardware. These flags enable hardware resources that are already in use to be yielded to the current sound. Only buffers created with the DSBCAPS_LOCDEFER flag are candidates for premature termination. See Remarks.

Value Description
DSBPLAY_TERMINATEBY_TIME If the hardware has no available voices, a currently playing nonlooping buffer will be stopped to make room for the new buffer. The buffer prematurely terminated is the one with the least time left to play.
DSBPLAY_TERMINATEBY_DISTANCE If the hardware has no available voices, a currently playing buffer will be stopped to make room for the new buffer. The buffer prematurely terminated will be selected from buffers that have the buffer's DSBCAPS_ MUTE3DATMAXDISTANCE flag set and are beyond their maximum distance. If there are no such buffers, the method fails.
DSBPLAY_TERMINATEBY_PRIORITY If the hardware has no available voices, a currently playing buffer will be stopped to make room for the new buffer. The buffer prematurely terminated will be the one with the lowest priority as set by the dwPriority parameter passed to IDirectSoundBuffer8::Play for the buffer.

Return Values

If the method succeeds, the return value is DS_OK. If the method fails, the return value may be one of the following error values:

Return code
DSERR_BUFFERLOST
DSERR_INVALIDCALL
DSERR_INVALIDPARAM
DSERR_PRIOLEVELNEEDED

当完成主音频缓存设置后(以及整个音响系统),如果想停止它,需要调用IDirectSoundBuffer::Stop。

The Stop method causes the sound buffer to stop playing.

HRESULT Stop();

Parameters

None.

Return Values

If the method succeeds, the return value is DS_OK. If the method fails, the return value may be one of the following error values:

Remarks

For secondary sound buffers, IDirectSoundBuffer8::Stop sets the play cursor to the sample that follows the last sample played. This means that when the Play method is next called on the buffer, it will continue playing where it left off.

For the primary buffer, if an application has the DSSCL_WRITEPRIMARY level, this method will stop the buffer and reset the play cursor to 0 (the beginning of the buffer). This is necessary because the primary buffers on most sound cards can play only from the beginning of the buffer.

However, if IDirectSoundBuffer8::Stop is called on a primary buffer and the application has a cooperative level other than DSSCL_WRITEPRIMARY, this method simply reverses the effects of IDirectSoundBuffer8::Play. It configures the primary buffer to stop if no secondary buffers are playing. If other buffers are playing in this or other applications, the primary buffer will not actually stop until they are stopped. This method is useful because playing the primary buffer consumes processing overhead even if the buffer is playing sound data with the amplitude of 0 decibels.


使用辅助音频缓存

想要使用辅助音频缓存的数据,需要将辅助音频缓存中包含的数据填充到主音频缓存,如在主音频缓存的同一区域写入两段音频数据,这两段声音就会被同时播放。辅助音频缓存使用 IDirectSoundBuffer8接口,这个接口和IDirectSoundBuffer十分类似,实际上如果想要创建 IDirectSoundBuffer8接口,必须先创建IDirectSoundBuffer接口,然后通过请求新接口来获取 IDirectSoundBuffer8接口。

创建辅助音频缓存和创建主音频缓存时主要的不同是必须在初始化时设置回放格式,这意味着缓冲区只能使用一种格式。如果希望使用另外的格式,必须释放当前的缓存,重新创建另外一个。

代码示例:

     // initialize and configure directsound

    
// creates and initializes an object that supports the IDirectSound8 interface
    if(FAILED(DirectSoundCreate8(NULL, &g_ds, NULL)))
    {
        MessageBox(NULL, 
"Unable to create DirectSound object""Error", MB_OK);
        
return 0;
    }

    
// set the cooperative level of the application for this sound device
    g_ds->SetCooperativeLevel(g_hwnd, DSSCL_NORMAL);

    
// create a sound buffer

    
// setup the WAVEFORMATEX structure
    WAVEFORMATEX wave_format;

    ZeroMemory(
&wave_format, sizeof(WAVEFORMATEX));

    wave_format.wFormatTag      
= WAVE_FORMAT_PCM;
    wave_format.nChannels       
= 1;        // mono
    wave_format.nSamplesPerSec  = 11025;    
    wave_format.wBitsPerSample  
= 16;
    wave_format.nBlockAlign     
= (wave_format.wBitsPerSample / 8* wave_format.nChannels;
    wave_format.nAvgBytesPerSec 
= wave_format.nSamplesPerSec * wave_format.nBlockAlign;

    
// setup the DSBUFFERDESC structure
    DSBUFFERDESC ds_buffer_desc;

    
// zero out strcutre
    ZeroMemory(&ds_buffer_desc, sizeof(DSBUFFERDESC));

    ds_buffer_desc.dwSize        
= sizeof(DSBUFFERDESC); 
    ds_buffer_desc.dwFlags       
= DSBCAPS_CTRLVOLUME;
    ds_buffer_desc.dwBufferBytes 
= wave_format.nAvgBytesPerSec * 2;  // 2 seconds
    ds_buffer_desc.lpwfxFormat   = &wave_format;

    
// create the fist version object
    if(FAILED(g_ds->CreateSoundBuffer(&ds_buffer_desc, &ds, NULL)))
    {
        
// error ocuurred
        MessageBox(NULL, "Unable to create sound buffer""Error", MB_OK);
    }
    
else
    {
        
// get the version 8 interface
        ds->QueryInterface(IID_IDirectSoundBuffer8, (void**)&g_ds_buffer);

        
// release the original interface
        ds->Release();
}

锁定和加载 --- 在缓冲区中加载数据

缓存接口有一对处理数据加载的函数。 IDirectSoundBuffer8::Lock函数锁定音频缓冲数据,并且找回指向缓冲区数据的数据指针; IDirectSoundBuffer8::Unlock释放在锁定操作中使用的资源。

当锁定缓存以后,就能对缓冲区进行写入操作。先告诉缓冲写入偏移量的字节数、写入位置、要写入数据的字节数,写入完成后返回两个指向数据的指针以及写入数据的字节数。为什么会有两个指针和两个数据大小呢?因为音频数据缓冲区是环形的,写入的数据也需要跨过起始位置。返回的第一个指针是请求的位置,第一个值是从请求的位置到结束所写入的字节数,第二个指针通常是缓存区的起始位置,第二个值是从起点开始写入的字节数。

这里有一个长度是 65536字节大小的缓冲区,这个缓冲区被锁定,以便于写入62000字节的数据。第一个指针处写入了60000字节,第二个指针处还剩下2000字节。



Lock函数使用信息如下:

The Lock method readies all or part of the buffer for a data write and returns pointers to which data can be written.

HRESULT Lock(
DWORD dwOffset,
DWORD dwBytes,
LPVOID * ppvAudioPtr1,
LPDWORD pdwAudioBytes1,
LPVOID * ppvAudioPtr2,
LPDWORD pdwAudioBytes2,
DWORD dwFlags
);

Parameters

dwOffset
Offset, in bytes, from the start of the buffer to the point where the lock begins. This parameter is ignored if DSBLOCK_FROMWRITECURSOR is specified in the dwFlags parameter.
dwBytes
Size, in bytes, of the portion of the buffer to lock. The buffer is conceptually circular, so this number can exceed the number of bytes between dwOffset and the end of the buffer.
ppvAudioPtr1
Address of a variable that receives a pointer to the first locked part of the buffer.
pdwAudioBytes1
Address of a variable that receives the number of bytes in the block at ppvAudioPtr1. If this value is less than dwBytes, the lock has wrapped and ppvAudioPtr2 points to a second block of data at the beginning of the buffer .
ppvAudioPtr2
Address of a variable that receives a pointer to the second locked part of the capture buffer. If NULL is returned, the ppvAudioPtr1 parameter points to the entire locked portion of the capture buffer.
pdwAudioBytes2
Address of a variable that receives the number of bytes in the block at ppvAudioPtr2. If ppvAudioPtr2 is NULL, this value is zero.
dwFlags
Flags modifying the lock event. The following flags are defined:

Value Description
DSBLOCK_FROMWRITECURSOR Start the lock at the write cursor. The dwOffset parameter is ignored.
DSBLOCK_ENTIREBUFFER Lock the entire buffer. The dwBytes parameter is ignored.

Return Values

If the method succeeds, the return value is DS_OK. If the method fails, the return value may be one of the following error values:

Return code
DSERR_BUFFERLOST
DSERR_INVALIDCALL
DSERR_INVALIDPARAM
DSERR_PRIOLEVELNEEDED

Remarks

This method accepts an offset and a byte count, and returns two write pointers and their associated sizes. If the locked portion does not extend to the end of the buffer and wrap to the beginning, the second pointer, ppvAudioBytes2, receives NULL. If the lock does wrap, ppvAudioBytes2 points to the beginning of the buffer.

If the application passes NULL for the ppvAudioPtr2 and pdwAudioBytes2 parameters, the lock extends no further than the end of the buffer and does not wrap.

After writing data to the pointers returned by this method, the application must immediately call Unlock to notify DirectSound that the data is ready for playback. Failure to do so can cause audio breakup or silence on some sound device configurations.

This method returns write pointers only. The application should not try to read sound data from this pointer, because the data might not be valid. For example, if the buffer is located in on-card memory, the pointer might be an address to a temporary buffer in system memory. When IDirectSoundBuffer8::Unlock is called, the contents of this temporary buffer are transferred to the on-card memory.


以下这段代码锁定辅助音频缓冲区,并且填充随机数据。

       // lock buffer, fill with random values, and unlock.

        
char* ptr;
        DWORD size;

        
// readies all or part of the buffer for a data write and returns pointers to which data can be written
        if(SUCCEEDED(g_ds_buffer->Lock(00, (void**)&ptr, &size, NULL, 0, DSBLOCK_ENTIREBUFFER)))
        {
            
for(DWORD i = 0; i < size; i++)
                ptr[i] 
= rand() % 256;
        }

填充完数据后,应该给缓冲区解锁,调用IDirectSoundBuffer8::Unlock解锁。

The Unlock method releases a locked sound buffer.

HRESULT Unlock(
LPVOID pvAudioPtr1,
DWORD dwAudioBytes1,
LPVOID pvAudioPtr2,
DWORD dwAudioBytes2
);

Parameters

pvAudioPtr1
Address of the value retrieved in the ppvAudioPtr1 parameter of the Lock method.
dwAudioBytes1
Number of bytes written to the portion of the buffer at pvAudioPtr1. See Remarks.
pvAudioPtr2
Address of the value retrieved in the ppvAudioPtr2 parameter of the IDirectSoundBuffer8::Lock method.
dwAudioBytes2
Number of bytes written to the portion of the buffer at pvAudioPtr2. See Remarks.

Return Values

If the method succeeds, the return value is DS_OK. If the method fails, the return value may be one of the following error values:

Return code
DSERR_INVALIDCALL
DSERR_INVALIDPARAM
DSERR_PRIOLEVELNEEDED

Remarks

An application must pass both pointers, pvAudioPtr1 and pvAudioPtr2, returned by the IDirectSoundBuffer8::Lock method to ensure the correct pairing of IDirectSoundBuffer8::Lock and IDirectSoundBuffer8::Unlock. The second pointer is needed even if nothing was written to the second pointer.

The values in dwAudioBytes1 and dwAudioBytes2 must specify the number of bytes actually written to each part of the buffer, which might be less than the size of the lock. DirectSound uses these values to determine how much data to commit to the device.

以下代码解锁刚才锁定的辅助音频缓冲区:


// releases a locked sound buffer
g_ds_buffer->Unlock(ptr, size, NULL, 0);

播放缓冲区中的声音

在辅助音频缓冲区中播放音频和在主音频缓冲区中播放是一样的,惟一不同的是这次需要设置播放的起始位置,通过IDirectSoundBuffer8:: SetCurrentPosition来设置。一般情况下,会选择从缓冲的开头播放,需要注意的是,停止播放一段音频并不会重置播放位置,所以可以通过停止播放来达到暂停的目的,只需要再次调用播放函数来恢复播放。

The SetCurrentPosition method sets the position of the play cursor, which is the point at which the next byte of data is read from the buffer.

HRESULT SetCurrentPosition(
DWORD dwNewPosition
);

Parameters

dwNewPosition
Offset of the play cursor, in bytes, from the beginning of the buffer.

Return Values

If the method succeeds, the return value is DS_OK. If the method fails, the return value may be one of the following error values:

Return code
DSERR_INVALIDCALL
DSERR_INVALIDPARAM
DSERR_PRIOLEVELNEEDED

Remarks

This method cannot be called on the primary buffer.

If the buffer is playing, the cursor immediately moves to the new position and play continues from that point. If it is not playing, playback will begin from the new position the next time the Play method is called.


改变音量

DirectSound在播放声音时按照采样时的最大音量播放,加之受限于音频硬件,所以没有办法让音量变得更大,DirectSound只能让音量变得更小些。这是通过减小声音级别来完成的,这个级别的单位是百分之一分贝。范围从0(最大音量)-- 10000(无声),利用这个特性可以在程序中加入“淡入”或“淡出“的效果。

要调整音量,可以使用 IDirectSoundBuffer8::SetVolume函数。

The SetVolume method sets the attenuation of the sound.

HRESULT SetVolume(
LONG lVolume
);

Parameters

lVolume
Attenuation, in hundredths of a decibel (dB).

Return Values

If the method succeeds, the return value is DS_OK. If the method fails, the return value may be one of the following error values:

Return code
DSERR_CONTROLUNAVAIL
DSERR_GENERIC
DSERR_INVALIDPARAM
DSERR_PRIOLEVELNEEDED

Remarks

Allowable values are between DSBVOLUME_MAX (no attenuation) and DSBVOLUME_MIN (silence). These values are defined in Dsound.h as 0 and ?10,000 respectively. The value DSBVOLUME_MAX represents the original, unadjusted volume of the stream. The value DSBVOLUME_MIN indicates an audio volume attenuated by 100 dB, which, for all practical purposes, is silence. DirectSound does not support amplification.


以下给出一个完整示例来运用刚才学到的知识。

点击下载源码和工程

源码示例:

/***************************************************************************************
PURPOSE:
    Lock Load Playing Demo
 **************************************************************************************
*/

#define DIRECTINPUT_VERSION 0x0800

#include 
<windows.h>
#include 
<stdio.h>
#include 
<dsound.h>
#include 
"resource.h"

#pragma comment(lib, 
"dxguid.lib")
#pragma comment(lib, 
"dsound.lib")

#pragma warning(disable : 
4996)

#define Safe_Release(p) if((p)) (p)->Release();

// window handles, class and caption text.
HWND g_hwnd;
char g_class_name[] = "LockLoadClass";

IDirectSound8
*          g_ds;           // directsound component
IDirectSoundBuffer8*    g_ds_buffer;    // sound buffer object

//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    
switch(msg)
    {
    
case WM_DESTROY:
        PostQuitMessage(
0);
        
return 0;
    }

    
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}

//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    WNDCLASS            win_class;
    MSG                 msg;
    IDirectSoundBuffer
* ds = NULL;

    
// create window class and register it
    win_class.style         = CS_HREDRAW | CS_VREDRAW;
    win_class.lpfnWndProc   
= Window_Proc;
    win_class.cbClsExtra    
= 0;
    win_class.cbWndExtra    
= DLGWINDOWEXTRA;
    win_class.hInstance     
= inst;
    win_class.hIcon         
= LoadIcon(inst, IDI_APPLICATION);
    win_class.hCursor       
= LoadCursor(NULL, IDC_ARROW);
    win_class.hbrBackground 
= (HBRUSH) (COLOR_BTNFACE + 1);
    win_class.lpszMenuName  
= NULL;
    win_class.lpszClassName 
= g_class_name;    

    
if(! RegisterClass(&win_class))
        
return FALSE;

    
// create the main window
    g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_LOCKLOAD), 0, NULL);

    ShowWindow(g_hwnd, cmd_show);
    UpdateWindow(g_hwnd);

    
// initialize and configure directsound

    
// creates and initializes an object that supports the IDirectSound8 interface
    if(FAILED(DirectSoundCreate8(NULL, &g_ds, NULL)))
    {
        MessageBox(NULL, 
"Unable to create DirectSound object""Error", MB_OK);
        
return 0;
    }

    
// set the cooperative level of the application for this sound device
    g_ds->SetCooperativeLevel(g_hwnd, DSSCL_NORMAL);

    
// create a sound buffer

    
// setup the WAVEFORMATEX structure
    WAVEFORMATEX wave_format;

    ZeroMemory(
&wave_format, sizeof(WAVEFORMATEX));

    wave_format.wFormatTag      
= WAVE_FORMAT_PCM;
    wave_format.nChannels       
= 1;        // mono
    wave_format.nSamplesPerSec  = 11025;    
    wave_format.wBitsPerSample  
= 16;
    wave_format.nBlockAlign     
= (wave_format.wBitsPerSample / 8* wave_format.nChannels;
    wave_format.nAvgBytesPerSec 
= wave_format.nSamplesPerSec * wave_format.nBlockAlign;

    
// setup the DSBUFFERDESC structure
    DSBUFFERDESC ds_buffer_desc;

    
// zero out strcutre
    ZeroMemory(&ds_buffer_desc, sizeof(DSBUFFERDESC));

    ds_buffer_desc.dwSize        
= sizeof(DSBUFFERDESC); 
    ds_buffer_desc.dwFlags       
= DSBCAPS_CTRLVOLUME;
    ds_buffer_desc.dwBufferBytes 
= wave_format.nAvgBytesPerSec * 2;  // 2 seconds
    ds_buffer_desc.lpwfxFormat   = &wave_format;

    
// create the fist version object
    if(FAILED(g_ds->CreateSoundBuffer(&ds_buffer_desc, &ds, NULL)))
    {
        
// error ocuurred
        MessageBox(NULL, "Unable to create sound buffer""Error", MB_OK);
    }
    
else
    {
        
// get the version 8 interface
        ds->QueryInterface(IID_IDirectSoundBuffer8, (void**)&g_ds_buffer);

        
// release the original interface
        ds->Release();

        
// lock buffer, fill with random values, and unlock.

        
char* ptr;
        DWORD size;

        
// readies all or part of the buffer for a data write and returns pointers to which data can be written
        if(SUCCEEDED(g_ds_buffer->Lock(00, (void**)&ptr, &size, NULL, 0, DSBLOCK_ENTIREBUFFER)))
        {
            
for(DWORD i = 0; i < size; i++)
                ptr[i] 
= rand() % 256;
        }

        
// releases a locked sound buffer
        g_ds_buffer->Unlock(ptr, size, NULL, 0);

        
// play sound looping

        
// sets the position of the play cursor, 
        
// which is the point at which the next byte of data is read from the buffer.
        g_ds_buffer->SetCurrentPosition(0);

        
// set the attenuation of the sound
        g_ds_buffer->SetVolume(DSBVOLUME_MAX);

        
// causes the sound buffer to play, starting at the play cursor.
        g_ds_buffer->Play(00, DSBPLAY_LOOPING);
    }

    
// start message pump, waiting for signal to quit.
    ZeroMemory(&msg, sizeof(MSG));

    
while(msg.message != WM_QUIT)
    {
        
if(PeekMessage(&msg, NULL, 00, PM_REMOVE))
        {
            TranslateMessage(
&msg);
            DispatchMessage(
&msg);
        }        
    }

    
// release directsound objects
    g_ds->Release();

    UnregisterClass(g_class_name, inst);
    
    
return (int) msg.wParam;
}

运行截图: