快乐的天空

时间来得快,去得也快

 

Redis 事件库

每个cs程序尤其是高并发的网络服务端程序都有自己的网络异步事件处理库,redis不例外。

事件库仅仅包括ae.c、ae.h,还有3个不同的多路复用(本文仅描述epoll)的wrapper文件,事件库封装了框架调用的主循环函数,暴露了时间、文件事件注册和销毁函数,典型的依赖反转模式。
网络操作都在networking.c里,封装了常见的socket操作。

我们从redis启动的main函数开始,从用户发出连接键入命令开始遍历网络事件库所涉及的函数,unix套接口相关函数不表。

首先对几个最常用对象进行解释。

redis_ae

//redis启动的时候(init_server())会创建的一个全局使用的事件循环结构 typedef struct aeEventLoop { int maxfd;		//仅仅是select 使用 long long timeEventNextId; aeFileEvent events[AE_SETSIZE]; //用于保存epoll需要关注的文件事件的fd、触发条件、注册函数。 aeFiredEvent fired[AE_SETSIZE]; //epoll_wait之后获得可读或者可写的fd数组,通过aeFiredEvent->fd再定位到events。 aeTimeEvent *timeEventHead;	//以链表形式保存多个时间事件,每隔一段时间机会触发注册的函数。 int stop; void *apidata;			//每种多路复用方法的使用的私有变量,例如epoll就是epfd和一个事件数组;而select是保存rset、wset。 aeBeforeSleepProc *beforesleep;	// sleep之前调用的函数,有些事情是每次循环必须做的,并非文件、时间事件。 } aeEventLoop;  //文件可读写事件 typedef struct aeFileEvent { int mask; 			//触发条件:读、写 aeFileProc *rfileProc;		//当fd可读时执行的事件(accept,read) aeFileProc *wfileProc;		//当fd可写时执行的事件(write) void *clientData;               //caller 传入的数据指针 } aeFileEvent;  //超时时间事件 typedef struct aeTimeEvent { long long id; /* time event identifier. */ long when_sec; /* seconds */ long when_ms; /* milliseconds */ aeTimeProc *timeProc;		//当出现超时的时候所执行的事件 aeEventFinalizerProc *finalizerProc; void *clientData;                //caller传入的数据指针 struct aeTimeEvent *next;	//单链表指向下一个时间事件 } aeTimeEvent;  //从epoll_wait或者select返回的,已经触发的文件事件 typedef struct aeFiredEvent { int fd; int mask; } aeFiredEvent;

我们来模拟redis server 启动和用户键入命令的过程,先上图。

redis_network_event_arch

好吧,从main函数开始吧。

// src/redis.c 853 server.el = aeCreateEventLoop();

创建一个aeEventLoop结构体,成员apidata指向一个aeApiState对象,如果使用epoll,fd是由epoll_create创建的全局epoll fd ,events[] 用于保存epoll_wait返回的事件组。

// src/redis.c 866 server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr);

创建监听。

//   src/redis.c 903 aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);

注册一个时间事件serverCron,作用以后再讲。

 //   src/redis.c 906 aeCreateFileEvent(server.el,server.ipfd,AE_READABLE,  acceptTcpHandler,NULL)

为监听fd的注册一个文件事件,首先把listenfd和触发条件epoll_ctl加入到全局的epoll fd进行监控。再以fd作为文件事件event数组index定位,mask填入只读,rfileProc填入acceptTcpHandler函数。

// src/redis.c 1571 aeSetBeforeSleepProc(server.el,beforeSleep);

aeEventLoop注册一个beforeSleep函数,这个函数在主循环里每次会被调用,作用以后再讲。

// src/ redis.c 1572 aeMain()

网络事件库的核心循环函数。

// src/ae.c  379 aeCreateLoop->beforesleep()

就是上面注册的beforeSleep函数。

// src/ae.c 275 aeProcessEvents(eventLoop, AE_ALL_EVENTS);

先调度aeApiPoll,用epoll_wait处理文件事件,返回的fd和触发条件先存储在eventLoop->fired[]里,然后根据fired[]每个事件的的fd,定位到events,根据触发条件调用已经注册的事件。

文件事件处理完毕后,接下来就是处理超时时间事件,这里不表。

假如有个用户连接上redis,则从redis servere就从上面的aeApiPoll的poll_wait返回,产生的只读事件会调度上面注册了acceptTcpHandler函数。

// src/networking.c 390 acceptTcpHandler(eventLoop, fd, NULL, AE_READABLE)

accept返回产生了一个新连接的fd(这里省略了很多步骤),需要从新的连接读取数据,于是为这个fd注册可读事件。

// src/networking.c 20 aeCreateFileEvent(server.el,fd,AE_READABLE,  readQueryFromClient, c)

于是调用了aeCreateFileEvent 为只读事件注册, 这里的步骤和上面的listen fd 一样。用epoll_ctl将fd加入到epoll fd里等待下次epoll_wait,再注册触发条件和readQueyFromClient函数,接着aeMain()继续执行等待用户的数据。

假如用户在这个连接键入redis命令例如:set foo bar,redis server端将会从aeApiPoll的epoll_wait返回,和accept一样的处理方式。返回的fd填入到fired[]数组,通过fired[fd]->fd找到是哪个文件事件,找到events[fd]->rfielProc这个函数,就是上面注册的readQueyFromClient 函数。

//  src/networking.c 816 readQueyFromClient(server.el, fd, NULL, AE_READABLE)

这个函数首先会执行read从tcp buffer读取用户键入的命令(非阻塞io),然后处理buffer,找到对应的command(lookupCommand),接下来执行这个command(call(c,cmd)),命令执行完毕需要返回结果的时候,会再次注册一个文件事件

// src/networking.c  71 aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, sendReplyToClient, c)

这样下次循环epoll_wait的时候就发现这个fd可写,于是就会执行sendReplyToClient,讲结果发送给client。

redis的这个网络事件库是比较标准的网络框架的模式,实现的功能不算多但够用。

posted @ 2012-08-01 17:27 探路者 阅读(343) | 评论 (0)编辑 收藏

lua 调用C 函数

  Lua可以调用C函数的能力将极大的提高Lua的可扩展性和可用性。对于有些和操作系统相关的功能,或者是对效率要求较高的模块,我们完全可以通过C函数来实现,之后再通过Lua调用指定的C函数。对于那些可被Lua调用的C函数而言,其接口必须遵循Lua要求的形式,即typedef int (*lua_CFunction)(lua_State* L)。简单说明一下,该函数类型仅仅包含一个表示Lua环境的指针作为其唯一的参数,实现者可以通过该指针进一步获取Lua代码中实际传入的参数。返回值是整型,表示该C函数将返回给Lua代码的返回值数量,如果没有返回值,则return 0即可。需要说明的是,C函数无法直接将真正的返回值返回给Lua代码,而是通过虚拟栈来传递Lua代码和C函数之间的调用参数和返回值的。这里我们将介绍两种Lua调用C函数的规则。
    1. C函数作为应用程序的一部分。
 #include <stdio.h>

 #include <string.h>
 #include <lua.hpp>
 #include <lauxlib.h>
 #include <lualib.h>
 
 //待Lua调用的C注册函数。
 static int add2(lua_State* L)
 {
     //检查栈中的参数是否合法,1表示Lua调用时的第一个参数(从左到右),依此类推。
     
//如果Lua代码在调用时传递的参数不为number,该函数将报错并终止程序的执行。
     double op1 = luaL_checknumber(L,1);
     double op2 = luaL_checknumber(L,2);
     //将函数的结果压入栈中。如果有多个返回值,可以在这里多次压入栈中。
     lua_pushnumber(L,op1 + op2);
     //返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。
     return 1;
 }
 
 //另一个待Lua调用的C注册函数。
 static int sub2(lua_State* L)
 {
     double op1 = luaL_checknumber(L,1);
     double op2 = luaL_checknumber(L,2);
     lua_pushnumber(L,op1 - op2);
     return 1;
 }
 
 const char* testfunc = "print(add2(1.0,2.0)) print(sub2(20.1,19))";
 
 int main()
 {
     lua_State* L = luaL_newstate();
     luaL_openlibs(L);
     //将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码
     
//在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。
     lua_register(L, "add2", add2);
     lua_register(L, "sub2", sub2);
     //在注册完所有的C函数之后,即可在Lua的代码块中使用这些已经注册的C函数了。
     if (luaL_dostring(L,testfunc))
         printf("Failed to invoke.\n");
     lua_close(L);
     return 0;
 }

    2. C函数库成为Lua的模块。
    将包含C函数的代码生成库文件,如Linux的so,或Windows的DLL,同时拷贝到Lua代码所在的当前目录,或者是LUA_CPATH环境变量所指向的目录,以便于Lua解析器可以正确定位到他们。在我当前的Windows系统中,我将其copy到"C:\Program Files\Lua\5.1\clibs\",这里包含了所有Lua可调用的C库。见如下C语言代码和关键性注释:

#include <stdio.h>
 #include <string.h>
 #include <lua.hpp>
 #include <lauxlib.h>
 #include <lualib.h>
 
 //待注册的C函数,该函数的声明形式在上面的例子中已经给出。
 
//需要说明的是,该函数必须以C的形式被导出,因此extern "C"是必须的。
 
//函数代码和上例相同,这里不再赘述。
 extern "C" int add(lua_State* L) 
 {
     double op1 = luaL_checknumber(L,1);
     double op2 = luaL_checknumber(L,2);
     lua_pushnumber(L,op1 + op2);
     return 1;
 }
 
 extern "C" int sub(lua_State* L)
 {
     double op1 = luaL_checknumber(L,1);
     double op2 = luaL_checknumber(L,2);
     lua_pushnumber(L,op1 - op2);
     return 1;
 }
 
 //luaL_Reg结构体的第一个字段为字符串,在注册时用于通知Lua该函数的名字。
 
//第一个字段为C函数指针。
 
//结构体数组中的最后一个元素的两个字段均为NULL,用于提示Lua注册函数已经到达数组的末尾。
 static luaL_Reg mylibs[] = { 
     {"add", add},
     {"sub", sub},
     {NULL, NULL} 
 }; 
 
 //该C库的唯一入口函数。其函数签名等同于上面的注册函数。见如下几点说明:
 
//1. 我们可以将该函数简单的理解为模块的工厂函数。
 
//2. 其函数名必须为luaopen_xxx,其中xxx表示library名称。Lua代码require "xxx"需要与之对应。
 
//3. 在luaL_register的调用中,其第一个字符串参数为模块名"xxx",第二个参数为待注册函数的数组。
 
//4. 需要强调的是,所有需要用到"xxx"的代码,不论C还是Lua,都必须保持一致,这是Lua的约定,
 
//   否则将无法调用。
 extern "C" __declspec(dllexport)
 int luaopen_mytestlib(lua_State* L) 
 {
     const char* libName = "mytestlib";
     luaL_register(L,libName,mylibs);
     return 1;
 }

    见如下Lua代码:

1 require "mytestlib" --指定包名称 2 3 --在调用时,必须是package.function 4 print(mytestlib.add(1.0,2.0))

posted @ 2012-08-01 14:22 探路者 阅读(448) | 评论 (0)编辑 收藏

流媒体协议

流媒体指的是在网络中使用流技术传输的连续时基媒体,其特点是在播放前不需要下载整个文件,而是采用边下载边播放的方式,它是视频会议、IP电话等应用场合的技术基础。RTP是进行实时流媒体传输的标准协议和关键技术,本文介绍如何在Linux下利用JRTPLIB进行实时流媒体编程。

一、流媒体简介
     随着Internet的日益普及,在网络上传输的数据已经不再局限于文字和图形,而是逐渐向声音和视频等多媒体格式过渡。目前在网络上传输音频/视频(Audio/Video,简称A/V)等多媒体文件时,基本上只有下载和流式传输两种选择。通常说来,A/V文件占据的存储空间都比较大,在带宽受限的网络环境中下载可能要耗费数分钟甚至数小时,所以这种处理方法的延迟很大。如果换用流式传输的话,声音、影像、动画等多媒体文件将由专门的流媒体服务器负责向用户连续、实时地发送,这样用户可以不必等到整个文件全部下载完毕,而只需要经过几秒钟的启动延时就可以了,当这些多媒体数据在客户机上播放时,文件的剩余部分将继续从流媒体服务器下载。
       流(Streaming)是近年在Internet上出现的新概念,其定义非常广泛,主要是指通过网络传输多媒体数据的技术总称。流媒体包含广义和狭义两种内涵:广义上的流媒体指的是使音频和视频形成稳定和连续的传输流和回放流的一系列技术、方法和协议的总称,即流媒体技术;狭义上的流媒体是相对于传统的下载-回放方式而言的,指的是一种从Internet上获取音频和视频等多媒体数据的新方法,它能够支持多媒体数据流的实时传输和实时播放。通过运用流媒体技术,服务器能够向客户机发送稳定和连续的多媒体数据流,客户机在接收数据的同时以一个稳定的速率回放,而不用等数据全部下载完之后再进行回放。由于受网络带宽、计算机处理能力和协议规范等方面的限制,要想从Internet上下载大量的音频和视频数据,无论从下载时间和存储空间上来讲都是不太现实的,而流媒体技术的出现则很好地解决了这一难题。目前实现流媒体传输主要有两种方法:顺序流(progressive streaming)传输和实时流(realtime streaming)传输,它们分别适合于不同的应用场合。

顺序流传输 
      顺序流传输采用顺序下载的方式进行传输,在下载的同时用户可以在线回放多媒体数据,但给定时刻只能观看已经下载的部分,不能跳到尚未下载的部分,也不能在传输期间根据网络状况对下载速度进行调整。由于标准的HTTP服务器就可以发送这种形式的流媒体,而不需要其他特殊协议的支持,因此也常常被称作HTTP流式传输。顺序流式传输比较适合于高质量的多媒体片段,如片头、片尾或者广告等。

实时流传输 
      实时流式传输保证媒体信号带宽能够与当前网络状况相匹配,从而使得流媒体数据总是被实时地传送,因此特别适合于现场事件。实时流传输支持随机访问,即用户可以通过快进或者后退操作来观看前面或者后面的内容。从理论上讲,实时流媒体一经播放就不会停顿,但事实上仍有可能发生周期性的暂停现象,尤其是在网络状况恶化时更是如此。与顺序流传输不同的是,实时流传输需要用到特定的流媒体服务器,而且还需要特定网络协议的支持。

之所以以前对这几个有点分不清,是因为CTC标准里没有对RTCP进行要求,因此在标准RTSP的代码中没有看到相关的部分。而在私有RTSP的代码中,有关控制、同步等,是在RTP Header中做扩展定义实现的

另外,RFC3550可以看作是RFC1889的升级文档,只看RFC3550即可。

  • RTP:实时传输协议(Real-time Transport Protocol)
    • RTP/RTCP是实际传输数据的协议
    • RTP传输音频/视频数据,如果是PLAY,Server发送到Client端,如果是RECORD,可以由Client发送到Server
    • 整个RTP协议由两个密切相关的部分组成:RTP数据协议和RTP控制协议(即RTCP)
  • RTSP:实时流协议(Real Time Streaming Protocol,RTSP)
    • RTSP的请求主要有DESCRIBE,SETUP,PLAY,PAUSE,TEARDOWN,OPTIONS等,顾名思义可以知道起对话和控制作用
    • RTSP的对话过程中SETUP可以确定RTP/RTCP使用的端口,PLAY/PAUSE/TEARDOWN可以开始或者停止RTP的发送,等等
  • RTCP:
    • RTP/RTCP是实际传输数据的协议
    • RTCP包括Sender Report和Receiver Report,用来进行音频/视频的同步以及其他用途,是一种控制协议

以下是每个协议的概要介绍:

一、RTP数据协议 
    RTP数据协议负责对流媒体数据进行封包并实现媒体流的实时传输,每一个RTP数据报都由头部(Header)和负载(Payload)两个部分组成,其中头部前12个字节的含义是固定的,而负载则可以是音频或者视频数据。RTP数据报的头部格式如图1所示:


    其中比较重要的几个域及其意义如下: 

  • CSRC记数(CC):表示CSRC标识的数目。CSRC标识紧跟在RTP固定头部之后,用来表示RTP数据报的来源,RTP协议允许在同一个会话中存在多个数据源,它们可以通过RTP混合器合并为一个数据源。例如,可以产生一个CSRC列表来表示一个电话会议,该会议通过一个RTP混合器将所有讲话者的语音数据组合为一个RTP数据源。 
  • 负载类型(PT):标明RTP负载的格式,包括所采用的编码算法、采样频率、承载通道等。例如,类型2表明该RTP数据包中承载的是用ITU G.721算法编码的语音数据,采样频率为8000Hz,并且采用单声道。 
  • 序列号:用来为接收方提供探测数据丢失的方法,但如何处理丢失的数据则是应用程序自己的事情,RTP协议本身并不负责数据的重传。 
  • 时间戳:记录了负载中第一个字节的采样时间,接收方能够时间戳能够确定数据的到达是否受到了延迟抖动的影响,但具体如何来补偿延迟抖动则是应用程序自己的事情。

    从RTP数据报的格式不难看出,它包含了传输媒体的类型、格式、序列号、时间戳以及是否有附加数据等信息,这些都为实时的流媒体传输提供了相应的基础。RTP协议的目的是提供实时数据(如交互式的音频和视频)的端到端传输服务,因此在RTP中没有连接的概念,它可以建立在底层的面向连接或面向非连接的传输协议之上;RTP也不依赖于特别的网络地址格式,而仅仅只需要底层传输协议支持组帧(Framing)和分段(Segmentation)就足够了;另外RTP本身还不提供任何可靠性机制,这些都要由传输协议或者应用程序自己来保证。在典型的应用场合下,RTP一般是在传输协议之上作为应用程序的一部分加以实现的,如图2所示:

二、RTCP控制协议 
    RTCP控制协议需要与RTP数据协议一起配合使用,当应用程序启动一个RTP会话时将同时占用两个端口,分别供RTP和RTCP使用。RTP本身并不能为按序传输数据包提供可靠的保证,也不提供流量控制和拥塞控制,这些都由RTCP来负责完成。通常RTCP会采用与RTP相同的分发机制,向会话中的所有成员周期性地发送控制信息,应用程序通过接收这些数据,从中获取会话参与者的相关资料,以及网络状况、分组丢失概率等反馈信息,从而能够对服务质量进行控制或者对网络状况进行诊断。

    RTCP协议的功能是通过不同的RTCP数据报来实现的,主要有如下几种类型:

  • SR:发送端报告,所谓发送端是指发出RTP数据报的应用程序或者终端,发送端同时也可以是接收端。
  • RR:接收端报告,所谓接收端是指仅接收但不发送RTP数据报的应用程序或者终端。
  • SDES:源描述,主要功能是作为会话成员有关标识信息的载体,如用户名、邮件地址、电话号码等,此外还具有向会话成员传达会话控制信息的功能。
  • BYE:通知离开,主要功能是指示某一个或者几个源不再有效,即通知会话中的其他成员自己将退出会话。
  • APP:由应用程序自己定义,解决了RTCP的扩展性问题,并且为协议的实现者提供了很大的灵活性。

    RTCP数据报携带有服务质量监控的必要信息,能够对服务质量进行动态的调整,并能够对网络拥塞进行有效的控制。由于RTCP数据报采用的是多播方式,因此会话中的所有成员都可以通过RTCP数据报返回的控制信息,来了解其他参与者的当前情况。
    在一个典型的应用场合下,发送媒体流的应用程序将周期性地产生发送端报告SR,该RTCP数据报含有不同媒体流间的同步信息,以及已经发送的数据报和字节的计数,接收端根据这些信息可以估计出实际的数据传输速率。另一方面,接收端会向所有已知的发送端发送接收端报告RR,该RTCP数据报含有已接收数据报的最大序列号、丢失的数据报数目、延时抖动和时间戳等重要信息,发送端应用根据这些信息可以估计出往返时延,并且可以根据数据报丢失概率和时延抖动情况动态调整发送速率,以改善网络拥塞状况,或者根据网络状况平滑地调整应用程序的服务质量。

三、RTSP实时流协议 
    作为一个应用层协议,RTSP提供了一个可供扩展的框架,它的意义在于使得实时流媒体数据的受控和点播变得可能。总的说来,RTSP是一个流媒体表示协议,主要用来控制具有实时特性的数据发送,但它本身并不传输数据,而是必须依赖于下层传输协议所提供的某些服务。RTSP可以对流媒体提供诸如播放、暂停、快进等操作,它负责定义具体的控制消息、操作方法、状态码等,此外还描述了与RTP间的交互操作(RFC2326)

    RTSP在制定时较多地参考了HTTP/1.1协议,甚至许多描述与HTTP/1.1完全相同。RTSP之所以特意使用与HTTP/1.1类似的语法和操作,在很大程度上是为了兼容现有的Web基础结构,正因如此,HTTP/1.1的扩展机制大都可以直接引入到RTSP中。
    由RTSP控制的媒体流集合可以用表示描述(Presentation Description)来定义,所谓表示是指流媒体服务器提供给客户机的一个或者多个媒体流的集合,而表示描述则包含了一个表示中各个媒体流的相关信息,如数据编码/解码算法、网络地址、媒体流的内容等。
    虽然RTSP服务器同样也使用标识符来区别每一流连接会话(Session),但RTSP连接并没有被绑定到传输层连接(如TCP等),也就是说在整个RTSP连接期间,RTSP用户可打开或者关闭多个对RTSP服务器的可靠传输连接以发出RTSP 请求。此外,RTSP连接也可以基于面向无连接的传输协议(如UDP等)。

    RTSP协议目前支持以下操作:

  • 检索媒体:允许用户通过HTTP或者其它方法向媒体服务器提交一个表示描述。如表示是组播的,则表示描述就包含用于该媒体流的组播地址和端口号;如果表示是单播的,为了安全在表示描述中应该只提供目的地址。
  • 邀请加入:媒体服务器可以被邀请参加正在进行的会议,或者在表示中回放媒体,或者在表示中录制全部媒体或其子集,非常适合于分布式教学。
  • 添加媒体:通知用户新加入的可利用媒体流,这对现场讲座来讲显得尤其有用。与HTTP/1.1类似,RTSP请求也可以交由代理、通道或者缓存来进行处理。

posted @ 2012-07-22 13:42 探路者 阅读(563) | 评论 (0)编辑 收藏

游戏UI库

CEGUI是老牌的开源界面库了,最新版本是0.7.1,完全免费,也是Ogre官方推荐使用的界面库,Ogre1.6及以前的版本,都是内置支持的。使用它的商业游戏也非常多,比如天龙八部,火炬之光,仙剑四等。这也就证明CEGUI确实强大,可以完全达到商业应用级别,而且相关资料非常丰富,至少不用担心某个功能无法实现,因为你能碰到的问题,网上基本都有解决方案,经过这些大作的证明,就不要怀疑了:)。
但是功能强大是有代价的,就是它太庞大、复杂了,上手很困难。这些大作没有一个不修改CEGUI的,也就是说要真正用起来,或者说要用的好,还是要做点事的。那需要做多少事呢?不清楚。


此帖售价 10 引擎币,已有 4 人购买 购买人名单zhangjiq1983squallcodefishtonchenghuai11
2. Ogre SDKTray
从Ogre1.7开始就不再内置支持CEGUI了,转而使用Ogre自己的SDKTray,“tray”是在ogre的overlay和material的基础上实现的,使用很容易理解和使用,但目前它还只是个半成品,无法应用到商业游戏中。

3.QuickGUI

最新版本10.1,专为Ogre写的UI库,支持Ogre1.7,比起CEGUI来说,小巧了很多。
但是很遗憾,至今还有没有编辑器(作者的Blog上说正在开发,但是还没有发布),要靠手动编写xml文件,囧啊~~Ogre社区里从来没有人推荐使用这个。我只是简单看了下,感觉像是作者练手的项目(个人观点)。

4.Hikari

Hikari可以让你使用Flash制作界面,最新版本0.3,完全免费,大家知道Flash动画是非常流行的,如果将Flash应用到游戏中,一定很拉风!Hikari就实现了这个功能,通过Flash.ocx将swf渲染成Ogre的Texture,然后你就可以任意操作这个Texture了,正是因为如此,Hikari支持Flash的所有版本,不存在兼容性问题。因为Flash本身就支持多国语言,所以中文显示也没有问题。可以把界面开发的大部分内容放到Flash那边,从而使客户端简洁很多(简单就是美啊)。
但是Hikari最致命的问题的效率太低,它使用的是Flash的ocx,先将动画内容渲染到DC上,然后拷到Texture中,所以很慢,而且慢的是第二步,使得Flash优秀的脏矩形优势无法发挥,根本无法应用到商业游戏中。我做了个动画,30张图片随机飘动,1023x768的窗口,使Flash占满,Release版只能跑到15帧(集成显卡),这还没有显示任何文字呢:(

5.Scaleform

Scaleform跟Hikari的作用是一样的,都是用Flash来做游戏界面,不同的是Scaleform非常牛X,它的效率很高,可以说是最强游戏界面解决方案了!而且对亚洲语言显示和输入都完美支持。目前最新版本是3.2,据说4.0将支持Actionscript 3.0, 并全方位支持3D UI。超过600款游戏使用Scaleform做界面,比如超大作StarCraft II,Crysis,Fable II,Civilization IV,Halo Wars,Princes of Persia,Mess Effect 2,Prototype,Resistance 2,Splinter Cell等。
心动了吧,可惜Scaleform是收费的,而且授权费相当的高,3.X版的目标售价是每一款游戏2.5万美金。如果你不在乎钱的话,这绝对是不二之选,在乎钱的话,这是二B之选。PS:有不少商业引擎已经将Scaleform集成进去了,比如Gamebryo,Unreal3等,如果你不用Ogre,买了商业引擎也爽了~~

6.ogreSwf/vektrix
Hikari效率太低,Scaleform太贵,ogreSwf/vektrix就是出来解决这个矛盾的。ogreSwf跟Scaleform一样都由开源的gameSwf发展起来,Scaleform开始商业化,ogreswf继续开源,后来ogreswf项目停掉了。2010年ogreSwf的作者重起了该项目,在原来的基础上改进并改名为vektrix,2010年4月发布了新的demo,可能是SourceForge的原因,我下的demo文件无法解压,又觉得vektrix目前还无法达到Scaleform的高度,所以后来就没有再试了。

7.Awesomium

Awesomium的功能有点类似Hikari,只不过它是将网页渲染成Texture,而且效率方面也做的比较好。Awesomium 采用了目前业界速度最快的浏览器内核webkit和v8,其实是把Chrome内核嵌入到了里面,同时还很好地支持flash,可以通过javascript使游戏和网页交互。该项目最初也是开源的,后来商业化了,但是不太贵,最便宜的版本只要400多美金。
Awesomium的效率之所以高,估计也是脏矩形方面做的好,我把上面那个动画嵌到网页中,用Awesomium打开,帧数马上就下来了,所以还是不能做UI,但是游戏中的帮助页面则可以考虑用这个。Awesomium也不方便根据网页内的内容做半透明效果,也就是网页中部分半透明很难实现,全透明,也就是镂空效果则可以通过模板实现。

8.MYGUI

MyGUI最新版本3.0.1,是俄罗斯人写的,要么没有注释,要么注释是俄文的,而且相关的资料实在是太缺乏了,虽然小修改一下可以支持中文显示,但是效果太差了,根本不能用,更别说多种文字,多种效果了,目前也没有什么商业游戏是用这个库做的。
但我最终选择了这个UI库。
就像MYGUI的介绍一样“MyGUI - fast, simple and flexible GUI.”这确实是一个,高效,轻便,灵活的库。首先它的设计很好,所以即使没有注释,也不难理解。换肤的设计避免了CEGUI里的很多中间层,使用要简单很多,资源文件的管理也要清爽(清楚+爽)一些。没有使用ogre的overlay,用底层直接画了,效率比CEGUI要高。LayoutEdit,ImageSetEdit等比CEGUI的要好用的多,CEGUI的工具经常当掉- -。中文显示可以模仿CEGUI去做,我已经完全实现,效率并不低,完全可以接受。
载入layout文件后返回一个窗口的vector,你可以自己写一个类似BaseLayout的类去管理,然后像MYGUI一样大量用C++委托做回调函数,就可以把各个Dialog分开去写,就像写MFC的Dialog一样,这一点做的实在太好了。如果再学一点CEGUI,把Lua脚本集成进去,载入layout文件时把窗口注册到lua中,这样就可以在脚本里写逻辑了,客户端-UI-脚本,很清晰。
为什么没有商业游戏使用MYGUI呢?MYGUI比较稳定的版本2.2.3是09年10月份发布的,是少要1年才能有游戏出来,火炬之光是09年11月上市的,在开发的时候MYGUI还很不完善,以后一定会有不少游戏使用MYGUI:)
跟使用CEGUI一样,MYGUI也要做大量修改才能用到游戏中。
9.Libroket


LibRocket开发者为Wandering Monster Studios。LibRocket是一个基于HTML和CSS标准的C++用户界面中间件包。它设计为项目的界面设计提供解决方案。 跨平台支持:Windows、Mac OSX(intel) and Linux。

10.   Menus Master

开发者为法国巴黎的Omegame公司。Omegame公司开发和授权Menus Master,该产品提供一整套用户界面创作工具。帮助设计师和工程师用最短的时间,利用各种资源(2D 位图、矢量图、3D对象,视频,声音),开发出复杂UI,包括2D和3D前端、游戏内UI、Heads Up Display(HUD)。

Menus Master由三个模块组成:
1.Menus Master Studio:由美工使用,提供一个用户友好的可视化界面来创建任何类型的游戏UI。
2.Menus Master Data Generator:是一个处理模块,将game UI转换成Menus Master Development kit可以使用的数据。
3. Menus Master Development kit:是SDK,让程序员将game UI与游戏整合起来。

11.GLO

GLO是一个独立于平台专门用于游戏开发的免费的图形用户界面库。该引擎可以作为一个C / C + +库,整合到大多数游戏引擎中。

posted @ 2012-06-03 16:36 探路者 阅读(4012) | 评论 (1)编辑 收藏

TW的经验

在ThoughtWorks日常的面试中,大致会分为多个回合,每个回合由不同的人采用不同的方式来面试,例如电话,笔试,面对面交流等等。一般能够通过这样的流程的人,大多可以接受,一般的公司也都采用这样流程招聘开发人员。电话面试的主要意图是要考察求职者的个人经历。因此,求职者应多谈自己:自己在团队中做了什么,自己挽回了什么错误,自己的责任是什么,自己带来了什么影响,自己存在什么问题。面试者看中的是这个人的表达能力,他所发挥的作用,他个人经历,他的信心。问问题也多为开放问题,并经常要追着问他某个项目中的某件事情,并要求他举例来说。
笔试一般为写代码或其他题目,对于代码,我一般要先看这个人的简历,根据他的工作年限和经历,判断他的代码应该是什么水平。对于未毕业的学生,不妨有所宽松,对于工作很久的人,如果他好学上进,接受新鲜的事物和新鲜思路多的话,代码应该是什么样子。这样的标准大概在90%的情况下是准确的。
Joe Homs 写了一篇 如何被我面试 很清楚的写出了各种过程的注意事项。对于技术人员的面试,相信每个做过开发人员招聘的人都会有所体会。技术问题很好度量,说一是一,很容易。然而,对于一些技术要求低一点的职位,如何面试就成了很微妙的问题。例如BA,PM等。
很多BA和PM很久没有写代码了,考察他们代码无疑是浪费时间。对于BA和PM,你可以不需要技术有多好,但你应该对技术有sense。至少别人正在用的新技术你知道是怎么回事。很多时候,BA和PM会在项目前期出现在客户处,决定一个项目的初始技术选择。

电话面试一个非技术人员也很麻烦,他们天天看别的PM和BA做事,你怎么知道他说的是他看到的还是他自己实践过的?所以很多时候,要追着一个问题问到底。这时候逻辑分析很重要,有的人前后说话会有矛盾,这就说明了他的说法有虚假之处,要小心。
我公司在面试的时候,有套逻辑题比较特别。对于Developer,当然要要求很好的逻辑才行。对BA和PM逻辑要求也不能低。例如BA,很多时候会面对逻辑比较差的客户,不能被侃晕是不是。
一个很tricky的事情是,许多人,可能觉得BA是要求比较低的工作,在申请公司职位的时候,觉得达不到Developer的要求才申请BA。我可以明确的告诉这样的人,BA不比Developer要求低,甚至在某些方面比对Developer要求更高,例如专注细节,例如交流,表达,组织,管理等等。一个差的BA,比一个差的Developer更能搞砸项目。
对于BA的面试,我经常采用角色扮演的方式来进行。我扮演客户,另一个同事扮演开发人员,我们一起面试一个BA candidate。我描述一些简单的需求,要求这个BA来分析和整理,并最终转述给开发人员。从这个过程中,很容易就能看出这个人是不是真正的做过需求管理的工作,是不是能够顺畅的表达,是不是能用一些巧妙地方法来获取真正的需求。
另一个难以面试的是PM。国内的PM很多时候扮演的更像是“政委”的角色。他负责管人和管理客户关系,主要靠做思想工作来控制团队,搞平衡。很少见有PM 真正的做项目进度、成本、范围、质量控制,或者说,在这些方面做的比较欠缺。而这样的PM,面试时候也很头大。敏捷团队中,人和思想基本上不需要太多控制,更多的是需要项目经理真正的监控项目本身的属性,理解项目并领导项目。
对于一些想要转行的人,例如作了很久Developer想要做BA了,这样公司也支持。面试的时候就主要看潜质了,也就是学习和接受能力。但如果一个人是因为做某方面实在做不下去了才要转行,那还是好好做回原来那份有前途的工作好。公司鼓励每个人多方面发展。一个Developer也应该具备BA或PM技能,BA也应可以兼任PM,QA也可以转行当BA,这种每个人都了解其他知识,每个都负责的团队,才是最好的团队。

posted @ 2012-06-03 12:52 探路者 阅读(231) | 评论 (0)编辑 收藏

项目经理的"势能 培养

我很早之前就听说过,做为一个项目经理,至少要在公司工作两年以上,且年龄不小于三十岁。我当时还年轻,对这两点很不屑。而现在,我却很理解。在公司工作时间短,对人员不熟悉,将很难横向协调资源;年纪过轻则不够沉稳、练达,难以实现快速沟通。 
作为IT企业,很多项目经理都是由基层做起的,技术好、经验丰富、熟悉行业知识。作为项目经理本人,也觉得自己对团队的领导能力勿庸置疑。并不会认为自己沟通上会有问题。在这里,我也不想讲什么大道理,只举例子、讲故事。

举一个例子

去财务报帐,出纳说票贴的不对,公司财务制度上要求餐费与交通费分开贴,退回来重贴。贴完让出纳整好单子,找老总签字,老总出去了,没办法,只好到明天。而到明天,老总回来,自己却要出差了。
好不容易等老总签了字,拿到财务室,出纳说,财务上没有钱,票先放这里,等两天吧。一拖好几天,票压一堆报不了,项目中各项开销还得支出,严重影响工作情绪。这里要说明一下,财务上没有现金是很正常的,当然也不是完全没有,还是要留一些费用应对日常开销的,至于给不给你,就看财务的心情了。
如果换一个场景。
发票拿到财务室,出纳一看不合格,直接开涮:“你怎么搞的呀,刚公布的财务制度都没看吗,整天忙啥哩?算了,算了,放这吧,反正老总不在,等会儿还是我整一下吧,指望你干这活也没指望。你该干啥干啥去吧,下一回再这样我就给你扔出去。”
过两天,财务电话过来:“你的钱还要不要呀?公司有回款了,要拿钱的赶紧啦。”然后项目经理直接到财务签字拿钱,发票老总已经签过字,整个流程出纳都帮做了。
 一个空降的项目经理与一个老员工项目经理,在协调资源时,差别是很明显的。
为什么空降的项目经理不能很好的协调资源。因为他跟同事不熟悉,他的团队会很支持他,其它人员则不一定。人家只给你照章办事,或许不会影响你工作,但同样不会推动你的工作。
而老员工项目经理,与各部门人员都很熟悉,老总也很信任他。这样处理工作就比较顺利,例如前面说的,出纳帮助你贴票,然后替你拿到老总那里,老总出于对项目经理与出纳的信任,就直接把字签了。项目经理出差回来签字就能领钱,效率高多了。

再举一个例子

项目临近结束,各个功能都差不多要做完了,项目完成进度也被标到90%。而项目经理心里很清楚,后面的修修补补,测试、调整会占用大量时间。公司项目管理不够完善,很多隐性的东西都无法显现出来。而客户此时又提出了一些新的要求,急切要完成。
如果告诉老总说,现在还需增加三个月的工作时间,老总肯定不同意,因为既然都完成90%了,剩下的工作还不加把劲在两星期内搞定,竟然还要增加三个月,挨批是难免的。如果给客户说,实在没有精力做新功能,客户要挟说,不做就不验收。
项目经理谁也不敢得罪,心里明白问题关键,却不知道该如何说服别人。只有硬着头皮往下做。结果项目延期,质量下降,勉强验收了,还有一大堆问题。最主要是因为工作强度加大,加班加点,透支团队成员工作激情,项目最终结束后,大批成员离职。
 而如果项目经理换种思路。首先向团队成员灌输“行百里者半九十”,越到后面,繁杂琐事越多,越不能放松冲刺。要尽可能保持成员的工作积极性。一方面,向公司说明情况,通过项目管理的知识来讲解问题的具体原因,尤其是项目收尾管理,并不是代码写完就是项目结束,还有很多事情要做。总之,摆事实讲道理,积极向公司申请资源,尤其是宽限项目时间(此时单独强调增加开发人手并不明智)。
再一方面,与客户方负责人沟通,甚至私人宴请以促进感情。尽可能把一些新功能放到项目运营维护中来实现,或是项目二期中实现。如果客户仍然坚持,则最好说服他降低质量要求,在验收时放自己一马。向其承诺,在运维中提升质量。
整个情况要向团队成员说明,争取成员的理解与支持。也要向公司说明情况,尽可能多申请些额外福利。
 从这个例子中可以看出,项目经理所做的工作,都不是“高科技”的,非技术的却又是重要的。针对不同对象,例如团队成员、公司高层、客户方负责人等,分析利害与关注点,权衡利益,各个击破。
一个年轻的项目经理与一个老成的项目经理,在处理这些事情时,风格会大不相同。就像上个例子,客户方负责人一般也不会太年轻,三十来岁才会担当个负责人,如果项目经理太年轻,阅历浅薄,则不太容易与对方平等交流。即便他明白道理,也很难影响他人支持自己工作。

讲一个故事

很多年前,我还年少的时候,喜欢下象棋,在学校里基本上我能下赢的,我总能下赢,我下不赢的,总也下不赢。不得其解,也慢慢懒得操练了。
后来有次学校搞业余活动,有个老师是省象棋协会的,组织了一节棋课。我去晚了,只听了半节。大致意思是讲,下棋要讲全局观,要有战略,例如中局五种策略,中局成杀、不成杀则优、不占优占先、不占先则多子、不多子则求和。还有什么炮破士、马破相、残局炮归家等等。却没有讲如何下棋,课堂上也没有摆个象棋,或是什么棋谱。
我并没有把这些当回事儿,之后也很少下棋。
又过多年,毕业后同学聚一起,闲来无事,与一个同学下了两盘。刚开始,他问我这两年有没有下棋,我说没有。他调侃我,“那你以前下不赢我,今天你也难赢了”。我也笑着认同,反正只是玩玩,何必认真。
可是一开局,他就傻眼了,一直处在下风,且每局必输。他很吃惊,我也很吃惊。之后我认真思考了这件事,觉得是那节棋课影响了我,人的思考能力、计算能力都差不多,而思维方式不同,结果也会有很大不同。他看到的是“棋”,我看到的是“局”。决定胜败的不是棋艺。
 又过了几年,我已经不再年少,但还算年轻吧。有一次找一个朋友玩,正好他的一个朋友也在,吃完饭没事儿做,恰巧有副象棋,就与他的朋友下了几局。他们都比我年龄大,已经三十多岁了。不过我也没有放在眼里,自以为水平相当可以,三局我两胜,颇为自得。
等他走后,我朋友问我:“他水平怎么样呀?”我带着些“谦虚”,洋洋自得:“他水平挺可以的,我差点就输了,还好我三局两胜,略胜那么一点点。”
我朋友听完哈哈大笑:“你知道他是干吗的吗?他是卖保险的。卖保险的吗,任何人都可能是他的潜在客户,他自然不会去赢你,不光让你赢,还要让你赢得有面子,这才是高手。他原来是在象棋协会的,论象棋,那叫牛×死了。”
我听完之后,惭愧至极。我关注的是“棋局”,人家所关注的,则超脱棋局之外。眼界不一样,看到的也不一样,操控点也不一样,输赢已经不重要,重要的是输与赢,哪个更有利于自己,然后才是“如何去输”与“如何去赢”。棋局只是一个棋子。决定成败的不是棋艺。
 我讲这个故事,是想说明,一个项目经理,对自己操作的项目要有全局观;而且,视角不能仅限于项目本身。明确自身定位,了解外部环境,才能最大成度影响到整个“局”中的各个元素,而这个影响力,就叫“势”。可以这么说,你明确的势力范围,可以只是你的团队,但你的影响力却不能仅限于团队本身。
做到这些,容易吗?当然不容易。如文章开头所说,“做为一个项目经理,至少要在公司工作两年以上,且年龄不小于三十岁。”这样,才有可能会做得好一些。

posted @ 2012-06-02 00:03 探路者 阅读(279) | 评论 (0)编辑 收藏

一个失败软件项目的思考

一、对一个估计撑不了多久的项目的抱怨(原文)
项目概况
甲方:A公司
乙方:本人所在公司 (称B公司)
项目:X项目是A公司外包到B公司的电子商务项目。
人物:A公司M先生,X项目组,主管、G、Q、P成员
项目状况 
      X项目当前状况不太好,可以说后果比较严重。
      1. 几乎处于停滞状态。现在的需求获取方式可以称之为“挤牙膏”;在现有功能完成,而A公司联系人M先生,提出新需求之前,开发人员完全不知道下一步是什么。并不是需求调研没跟上,而是跟上了M先生无话可说(讲不出需求)。另一方面,项目当前实现功能跟合同写的又有很大落差,不可能截止。这个矛盾就导致了项目几乎停滞。
      2. 项目本身质量也堪忧。X项目组,组成非常简单,1个主管,1个高级程序员(下面简称G成员),1个北大毕业的程序员(少“青鸟”2字,简称Q成员),1个美工皆程序员(简称P成员),ZERO测试。X项目组的需求调研、项目管理,程序设计,技术攻坚外加运维全皆在那个可怜的G成员(就是那个高程)身上。
      3. 上个月美工皆程序员撤退了,这无疑又是给项目雪上加霜。 
原因分析
      造成项目如此惨淡的原因,也是多种多样。
      1. A公司的M先生,是在X项目开始的时候才进入A公司的,而M先生原来是写过代码的。G成员心想M先生有程序员的前身,好沟通,还暗暗偷笑。可没料到的是,M先生原先是个半吊子程序员,只写过几个asp页面,而心里却甚是得意,大有编程不过如此之气势。就是这样的程序员前身,给项目带来数不尽的压力。顺便摘几句就可以管中窥豹:“这个简单了吧,只要数据库这样这样,然后读出来就行了”,“这个有这么难吗?不就是加2个页面的事”。如此等等就不列举了。但这其实还是能应付的,最大的问题是啥呢。之前讲了他是项目开始进的A公司,或者说是A公司专门挑的给给X项目组跟A公司牵线的。这是好事啊。可也是坏事,坏哪了?X项目作为一个电子商务外包,现在却要跟一个不熟悉业务,跟X项目组成员一样的现学现卖的M先生调研需求。如此,我想“项目停滞”恐怕也是能够想象的了。
      2. 项目组成员组成问题也很多。G成员身皆多职,但一个人的精力毕竟是有限的,如果这么多的工作要8个小时内完成,几乎是不可能的,就算完成恐怕也是偷工减料了。于是整个项目周期经常出现这样情形:G成员忙的焦头烂额,而其他2个人就聊聊QQ看看新闻。并不是G成员不舍得把工作交给别人,而是另2个成员,非设计好的功能不做,P成员毕业才不过半年也就请有可原,而Q成员,自“北大”毕业,工作也已经一年多了,这就有点说不过去了。Q成员的事,还是事实为依据:有这么个功能,1个主表1个从表,依据主表当前选中记录,显示并修改从表的内容,这功能,就这么简单一个功能,G成员估摸着熟悉需求加表设计加功能也就是2天时间的事,可Q成员愣是给整了一个星期,结果一发布出去,还一堆问题,挨M先生一堆说。这样的组成和水平,我想代码质量不能保证也是在所难免了,更别提还没有测试人员。
      3. 基于2的问题,很多看官肯定要说了 ,这样的情况,还不赶紧跟领导提意见换人嘛?提啊,G成员可没少提。G成员要求也不高,就提这样个要求:请领导给我们再加个美工或者程序员。如果加美工呢,让P成员转程序开发(这个是P成员自己提的,他一直不想做美工,而且人也机灵,转程序员也行),麻烦再把那个Q先生给换下去,你说拿着3000的工资,干着这个活,我也是为公司节省成本啊。如果加个程序员呢,质量要稍微高点的,另外就要把P成员的思想工作好好做做,让他当个全职美工。
      可领导说了,我们这个小公司小项目,这样哪行呢,这成本太高了,我看你们也不是很忙,先这样吧。 G成员听着郁闷,这人家QP成员的确不是很忙... G成员当时就差说:你不换他们那就把我换了吧。毕竟还是饭碗要紧,就吞回去了。得......这一来一切照旧。
      4. 这项目组里都讲到了就差个主管,主管我就不给取外号了,比较是主管,咱得尊重。主管呢,是领导派下来的,其实不负责管理,也不负责开发,人家是搞delphi的牛人,但对C#是一窍不通,可是这人偏偏啥都爱插一手。
就说,项目刚开始,G成员思量着上个ORM,就跟主管打个报告,列了几个ORM的优势。人家主管就是牛:第二天给回话,不要上什么ORM 了,你看这个sql多好。这不看还好,一看就吓倒。人家的sql全是写在 frm上的,直接调用一个公用方法连接到数据库。看样子跟他光用讲的不行。
于是G成员重新整理了一遍ORM的优缺点,又认认真真写了个demo,再交上去。这次主管2天没给信,以为有点效果。那天正吃完饭,主管就把G成员给喊过去了,这主管能啊,真是爱学习,电脑上整理一个vs2005:G啊,ORM这个问题,我不是很懂,但我问了几个朋友,他们说没啥必要,如果非要上呢,就用这个吧,你看看这是我朋友写给我的。G成员心里一颤,看来这事要完。一看代码果然是头凉到脚。这这这也是orm? 这写个sql读个datatable,再给他赋值给一个对象,然后这对象再给页面控件赋值,就算OOORRM了?真是 尼玛的有木有!!!!!!!!!!
看官跟你讲,这事还没完。ORM这事是没戏了,那咱就上个 微软企业库,这总行吧。企业库刚搭建完那天,主管就来了,说,这么长时间了,让我看看成果。这事没的说,领导看下工作成果,合情合理。可偏又看出个事。 ExecuteScalar 这个方法,常写sql连数据库的人都知道,获取单值。主管就问了ExecuteScalar这个奇怪的方法是啥意思,G成员就给他解释:这个是从数据库获取单值的。看看主管迷糊,又加一句:就是获取第一行第一列的那个值。这不加估计迷糊迷糊也就过去了,可这话一出口,主管就笑了(得意的笑,我得意的笑….),这他在行啊。主管说:这个方法以后不要用了,这个跟ExecuteDataSet重复了,你就取DateSet第一行第一列就行了。G成员当时就怒了,XX的,这有完没完。经过几回合的较量,终究姜还是老的辣,告到领导那被批回来了。G成员这次算是彻底服了。于是以后有什么东西该藏藏,大气不出一个,他说他的,我干我的。可这又何苦呢?唉~~~ 一声哀叹尽千愁啊!
      5. 对美工P的出走也交待几句。这美工呢,毕业没多久,但人好学,所以进步也快。进项目之前就是讲好了,等他把C#掌握得能完全参与项目了,他就不干美工了,想写代码。这领导也是同意的,结果一直干干干,从去年干到今年,提也提过几次,也没听到什么时候能找美工来替他。而且人家乃是蛟龙,能被这小水潭给困住?就另觅明主去也。这一换工作,工资直接翻番。
二、一个估计撑不了多久的项目的发展历程(原文)
之前发了一个抱怨文,把X项目前前后后几个人全给抱怨了。后面也有很多看管给我回复,有表示哀悼的,表示同情的,也有提意见,送安慰的,甚至还有抢占广告位的。不管怎么,本人在此表示感谢。
抱怨完了,也该反思下项目的历程和带来的教训。此文就先简述下项目的历程:估计还是以抱怨为多。望各位谅解。
一个估计撑不了多久的项目的
发展历程
记得去年的仲夏时间,天气正是当热,三十五度算凉快的,三十七八很正常,还经常能串到40的高温,当然40度往上天气预报是不敢报的,但明白人都知道,一个铁皮碗放太阳底下晒个两分钟,鸡蛋往里一敲,一个五成熟的煎蛋就出炉了,让人不得不佩服太阳核聚变的威力。
X项目就是在这样水热火也热的环境下启动的。X项目启动之初,领导只说让G开个项目,实现这样一个功能,然后给客户看看。因为领导并没有说是正式项目,就权当是原型开发,G就给顺便整理个小三层,把页面先给画起来,数据读出来展示,然后就给客户看了。这个时候的还是直接跟A公司的老总直接联系的,M先生尚未上任。然后客户就开始提意见了,要这样,要那样的,G就照着做,但一直没有正式项目的说法,就权当是按范例这样的简单方式都给写了。G也跟领导提过,这个情况是不是应该正式立个项,领导说,再等等。就这样拖拖拉拉一个多月过去了,代码也七七八八写了不少。
有一天,领导带来个人,跟G说,你做的那个程序现在呢正式立项了,这位以后就是你们这个项目的主管,以后有什么问题就找他。G一听心里乐呵,这个把月没白搞。Q和P两位项目成员也就陆续的来报道了。既然正式立项了,那不能按之前那种烂法子做啊,G就寻思着把项目重新架构下,这么大个事当然得跟主管商量下,这一来二去的还算顺利(这中间有个关于使用ORM的事,见前文)。就在此时,A公司的M先生也上任了,并指定了跟X项目接头。M先生之前兴许看过原来的demo版本的程序,一听项目调整,1到2个星期不能有功能产出(项目调整这说法是领导提的,可能是怕客户有意见),就有点不乐意,催着X项目组赶赶工,这时间太久了。这话就由领导传到了X项目组,既然是领导发话,X项目组只有惟命是从,G就给大家开了个小会,打打气,加了一个星期班,连架构,带实现,把之前一个月的事,大家给翻了个新。
这之后的一个月,都是领导亲自去客户调研,然后需求以口头和少许说明的形式,转到G这边,G再根据自己的理解分解素材,设计程序和数据库,把素材转化成任务,分配到QP和G自己。我想很多看管可能都看过这样的图:软件更新失败,一个失败软件项目的思考
在这样的需求调研下,程序表现往往跟客户的需求不符。领导就把需求调研的任务干脆就直接交给G了。G兼着项目管理和设计开发,本不想再参与需求调研的事,可主管说他很忙,没有太多时间调研写需求文档,公司又没有配置专门的需求调研人员,也就只好应承了下来。也就从这个时候开始,G认识了M先生,才了解到需求为何会如此零散。M先生的事,就不过多描述了,毕竟文章还是以X项目发展为主。
简单描述下X项目的组成:此项目为电子商务网站,而网站数据几乎全依赖外部接口,这个也是由于网站业务所决定的。X项目总共前前后后开发了各式各样9个外部接口。平均的说是一个月要针对一个接口开发。Q和P2位成员的能力,上个文章也说了,此处不再赘述。P要为X项目画界面做图片,尤其是M先生要求网站兼容IE678 FF 和谷歌浏览器,这也够P先生忙一壶的了。而这Q就闲了,每天只等着G设计好了,告诉他功能怎么怎么做,如果这天G没空设计新功能给他,他就聊一天QQ,听一天音乐。
由此可见G要忙到什么程度。人不怕辛苦就怕比较,辛苦都是可以撑下来的,可要有个人在旁边这么一比较,这心理真是全然不同了。G是农村出来的娃,辛苦点不怕,只是你辛辛苦苦一天忙到晚,人家坐你旁边聊QQ,看新闻,这感觉很是不爽,最最关键是这闲的主还是得听你指挥的小弟,这真是要命。于是就出现了前文中要求加人或者换人这样的事情。无奈项目组加人不成功,G只能继续匍匐前行。
转眼就到了年底,根据初始合同需求确认书,半年过去了,却才做完了三分之一多些的功能项,G的心中有些不安。有一次G去A公司跟M先生进行需求调研,讲到了对项目目前状况的担忧,M先生不愧是过来人,信心满满的说,你放心好了,这个项目我肯定给他搞活了,不可能倒。大有一切尽在掌握中的气势。虽然M先生如是说,但G心中始终是放下不,就细细总结了这半年的得失,和来年的一些计划和改进方案,交到主管手里。
这个文档的主要内容这里摘一下:
1. 确定项目驱动因素,不能由M先生带领大家挤牙膏,必须要进行项目远景定义和功能集合的详细定义。
2. 项目风险总结,将近半年的各种问题进行一个简单汇总,并请求主管对项目组外的风险提出相应的处理方案。
3. 延迟发布周期为1周(原来是,M先生说个需求和修改意见,然后马上开动,做完就发布给M先生审查,几乎是一个星期要发布2到3次),而且一周内已经定好在做的功能,不接受任何修改。
4. 提出项目缺陷管理。对项目问题进行统一管理,这中间引起的客户和开发时间问题,希望主管能够帮助协调。
5. 每日会议的召开。(原来只有1到2周有次项目总结会议),随时捕获风险。
6.望主管能够部分接收G手中的项目管理工作,减轻G的工作负担。
也主要摘下主管对这个文档的回应:
1. 明年会跟客户进行有效沟通。
2. 明年会跟领导报告,尽量解决。
3. 同意一周发布一次,但也要尽量满足客户要求。
4. 缺陷问题很重要,要管理,但还是要以客户发布时间为第一位。缺陷可以后期修改。
5. 每日会议,有这个必要吗?如果没啥必要就不要开了,大家都比较忙。
6. 明年再定,看明年能不能抽出时间来。
再主要写下年后的实际执行情况:
1. 不知道主管有没沟通,反正M先生照旧。
2. 好像汇报了,但没有相应反馈动作。
3. 被G强制压为一周一次发布。M先生起初不满,但不发布他也无奈,就算是默认了。
4. 几乎是直接就丢下了。原因:主管再次表示要以客户发布时间为第一位。而G照旧被压着这么多的活,再来一个缺陷管理完全是自讨苦吃,也就放弃了。
5. 这个倒是坚持了3个多月,效果还不错. 本来Q在表达方面就有些问题,就是话说不明白,每次都要被G追问很多遍才能说出点头绪来。而这导致了Q对G的一些不满和抵抗。于是美工P一走,就剩下G跟Q2个人,每日会议干脆停掉了。
6. 看到4就知道,这个没戏了。
回到去年年底,客户对项目总体上表示满意,G估摸着这里面有M先生很多功劳。而公司领导以业绩不好为由,让每人领了200块的过节费就匆匆回家团聚去了。当年后G从M先生那里了解到年前公司已经拿了X项目绝大部分款项时,已经离过年甚远,追悔莫及了。而这事也在X项目组3个成员的心里埋下了许多的怨愤。连G先生在内的成员也少不了在闲聊时间,发泄一些对公司的不满。这也一定程度上,导致了P的出走。有句话叫兵败如山倒,这P一走,Q也开始紧锣密鼓的找新单位,终于这个月公司也接到了Q的离职申请。本就人丁不旺的X项目组就剩下2个光杆司令:G和主管。其实Q的离职对项目影响并不大,而P的离职相对来说大的多,P这一走,美工就没了,而多功能的M先生重操就业兼职做美工,这是G也得佩服的事,说啥来啥,都能上。
项目晃晃悠悠的走到了今天,眼看着也要到头了。而G站在项目的边缘不知何去何从。
三、从G犯的错误来看X项目如何失败的(原文)
下面做个简单分析,从G犯的错误来看看X项目失败的原因。
1. G兼着项目管理和设计开发,本不想再参与需求调研的事,可主管说他很忙,没有太多时间调研写需求文档,公司又没有配置专门的需求调研人员,也就只好应承了下来。 
G本身已经兼着太多的职位,导致工作不能做好了,还要去接下一个本可以不搭的事情。这就导致时间更不够用。时间不够用将辐射出更多的问题:
1) 因为很有多工作压着,为了能够按时完成,必然胡乱瞎搞,能省则省,最终的结果就是质量完全不到位。
2) 没有办法进行有效的总结回顾
3) 长此以往,必将导致心理失衡,产生厌烦心理,从快乐工作变成了赶鸭子上架。
4) 以上3点连在一起,必然出现恶心循环,最终要么人崩溃,要么项目崩溃。
目前想来,解决方法只能是,积极主动而又耐心的反复沟通,对主管和领导进行主动劝说,让公司安排个人来专门调研需求以及分担G的其他工作,让G能够专心与程序设计开发或者项目管理。上文回复中,有很多人提到了PM的重要性和存在的问题,而G身兼PM在内的多职,必然导致了项目问题,偏偏这么重要的问题,被领导主管和G在内的X项目组成员所忽略。
2. 人不怕辛苦就怕比较,辛苦都是可以撑下来的,可要有个人在旁边这么一比较,这心理真是全然不同了。G是农村出来的娃,辛苦点不怕,只是你辛辛苦苦一天忙到晚,人家坐你旁边聊QQ,看新闻,这感觉很是不爽,最最关键是这闲的主还是得听你指挥的小弟,这真是要命。
这个是心理因素,说明G这个人心理不够淡定。合理的解决方案之一兴许跟1中的一样,让人分担G的工作,使G专心与自己的所长。再者,就是人员合理流动,对于不能达到岗位需求的员工,要及时更换或补充,一方面不至于引起其他成员的不满,另一方面也是项目成功的必要条件。敏捷开发的中心思想就是以人为本,如果人心涣散,项目想成功都难。黎叔说了,人心散了,队伍不好带了,于是黎叔也被抓了。
 3. Q在表达方面就有些问题,就是话说不明白,每次都要被G追问很多遍才能说出点头绪来。而这导致了Q对G的一些不满和抵抗。 
这是对人的管理不善,带来的严重后果之一,G明显缺乏那种让人家为他或者为公司卖力的领导能力。此点在2中已经有表现。
4. 连G先生在内的成员也少不了在闲聊时间,发泄一些对公司的不满。
G在一定意义上是X项目的负责人,而他带头在成员面前表示对公司的不满,这必将给项目组成员带领极坏的影响。这个举动说明了G在管理这个岗位上的不成熟,没有从大的方向进行考虑问题,而只是一泄心中怨气。致使人心动摇。 
5. M先生提出需求和修改,然后马上开动,做完就发布给M先生审查,几乎是一个星期要发布2到3次(此处的发布指,将程序发布到测试服务器,供客户试用和审查) 
这个就是明显的没有做好项目管理,简直就是把项目交给客户来管。程序发布,还是比较消耗精力的,而发布后,客户发现的各种错误异常,会要求马上更正,就导致了有时候一天中多次发布的情况出现,就因为几个很小的错误,导致时间消耗在 修改-发布-修改-发布 上。
解决方法:
1) 按照较长的时间周期进行发布,但不宜过长,比如1周或者2周。并且跟客户协商好,当前发布版本的错误,推迟到下个版本修改。
2) 每周邀请客户代表到公司来参与项目进展会议。让客户每星期都能了解到项目在进展,放宽他的心。约定好,每个月发布一次测试版本,让客户测试体验,相关问题,放到后续版本中修复。
3) 对需求的获取和变更进行控制,比如要求客户形成书面文字,进行需求提交等。
6. G照旧被压着这么多的活,再来一个缺陷管理完全是自讨苦吃,也就放弃了。
G因为手里活来不及而放弃了缺陷管理,这是可悲的。缺陷管理可以说产品质量的保障之一,缺陷管理那长长的列表尖叫着提醒项目存在的问题。这是一个项目出现质量问题,最显眼的警报器。没有缺陷管理,几乎很难进行项目质量的监督。缺陷管理一开始的缺少和后来提出又放弃,很大程度上决定了项目的失败的必然,一个不注重质量的项目,谈何成功?即使功能全部完成,你敢放到公网上让黑客转悠2圈吗?
综上所述,凸显出G项目管理能力上的匮乏和心理的不成熟。这是项目走向失败一个很大的原因所在。在此希望各位看官,能更多的提出G的问题所在,也希望能提供相应的改正建议。在此感激不尽。

posted @ 2012-06-02 00:02 探路者 阅读(271) | 评论 (0)编辑 收藏

基层管理

 引用  http://www.crazycoder.cn/ProjectManagement/Article175760.html
    几乎每种行业都有基层主管(或基层管理人员),而软件行业的基层主管一般是项目经理、技术经理、开发经理、组长等。其职责是资源协调、风险预估、项目管控、团队建设,说白一点大多数的企业现状就是项目负责人带领团队攻下一个又一个项目的过程。很多公司以项目成败作为项目负责人考核的唯一标准,因为项目规模、成本、客户满意度等容易量化,并且是直接跟公司的利润有很大关系;而相反团队建设却难以衡量,如何衡量一个普通技工晋升成高级技工到底是基层主管的培养还是原员工本身就具备高级技工的技能。因此,难免出现以项目额度论英雄的局面,这样往往造成一将功成万骨枯的悲壮场面,并不利于团队的发展。卡耐基曾经说过,带走我的员工,把我的工厂留下,不久后工厂就会长满杂草;拿走我的工厂,把我的员工留下,不久后我们还会有个更好的工厂。我的观点是,从短期目标来看,项目成功是解决温饱问题的指标,如果温饱问题未解决,还如何谈吃得好,如团队经常疯狂加班赶项目就是温饱尚未解决的一种表现;从长远角度来看,团队建设是迈向“吃得好”的改进过程,或着说是一种手段。
一只狮子带领一群绵羊的团队会战胜由一只绵羊带领一群狮子的团队。如果你是一只绵羊,配备一群狮子般的团队也是枉然。说到这里,我曾经从一个电视节目看到十多只年轻狮子攻击一头河马,由于缺乏领队,结果导致河马成功逃过一劫并且一头狮子牺牲这样难以置信的场面。后来,同样这群狮子在一只经验丰富的狮子带领下,战胜比河马更强壮更凶猛的对手。
      基层主管往往来自基层的优秀员工,在成功转型之前必须先把自己的宝剑磨利。在NBA赛场上,5个科比组成的球队可能会输给联盟任何一只球队一个项目一般需要若干个成员组成团队——售前、需求、设计、开发、测试、部署等。而现实情况往往是,要么项目急、要么人员未到位,或者两种情况都有,那么开发人员由于在公司所占比例大,可能会兼做需求和测试。这样造成的后果是,需求把握不明确,测试不到位导致软件质量差,客户不断投诉。正如下象棋一样,必须了解各种棋子的角色和作用,以及它们应该摆放的位置。否则拿车当兵使,后果可想而知。
     当项目出现危机,基层主管的第一反应是?如果第一反应是“这个问题是哪个兔崽子造成的”,意味着该主管的思维是先追究责任。有一次,需要向客户演示现有的开发成果,由于各种原因导致离演示的前一天发现很多功能都未完善,如果照这样演示给客户看的话简直是演猴戏。当时,基层主管唯有向上汇报,而高级主管的第一反应是,评估完善这些功能需要多少时间?现在增加资源是否能够缩短工期?需要哪些资源才能完成?并且在该项目所有成员目前表示,他不想追究任何一个人的错误,首要任务是先分析如何能够解决问题。事后,他再向基层主管了解如何改进才能避免同类问题的发生,通过这种方式相当于给基层主管上了一次深刻的管理课程。
     聆听团队的意见。善于聆听是良好沟通的铺垫。如果你的团队成员在他的岗位上老老实实的干了几个月,突然他有一天告诉你(或者从其他成员了解到),他觉得自己的工作没意思。这其实不完全是分工的问题,很大程度上是沟通出现问题。试问,他是觉得没意思,而不是不合适,即他胜任这份工作,可惜觉得缺少了什么,可能是缺少锻炼机会、想换个项目等等。主动去了解团队成员的意向,因为把他们放在感兴趣的岗位会发挥他们潜在的能力。当然并不是每个人都能找到适合自己的岗位,恰当的给予激励会鼓舞士气,保持工作的热情。某些基层主管把开空头支票当激励,结果失信于团队。
    最大限度地发挥团队的力量是每个基层主管的职责。最大限度地发挥团队的力量的前提是得深入了解每个团队成员的技能和喜好。特别是软件工程师,很多不善于语言表达也不会表现自己,更多的需要基层主管去观察。如A君喜欢钻研技术但是缺乏经验,如果有技术难度较高地先分配给他,帮他分析解决方案,更重要的是给他信心。
   不要说我以为。基层主管不同于普通员工,一旦犯错就勇于承担。如果说“我以为…,想不到会…,结果造成…”,是一种借口,是不成熟的表现。
   让团队主动反馈。基层主管要了解他管理的不是一群机器,是一支优秀的队伍。如果把团队训练成机器,最终会累死基层主管。例如,你需要挨个挨个的去问他们工作进展的怎么样,或者索性让他们写日报周报来反馈。这样得到的结果就是一切看起来很美好,而实际情况就可能隐藏一个个定时炸弹。如果并非团队成员的主动反馈,“被反馈”往往是走走形式,例如某某说“A功能完成了,B功能差不多了”。到了第二天、第三天还是“B功能差不多了”。主动反馈是简要说说工作的问题和解决方法,如某某说“A功能解决方案是…,B功能遇到…问题,C功能预计明天可完成”,并且更重要的是,不是等到你去问他才反馈。
   以上纯粹是自己对基层管理的个人看法,也是自己一些微薄经验的一个小结,思维有点混乱。这里抛砖引玉,希望听到更多的建议。

posted @ 2012-06-01 23:27 探路者 阅读(179) | 评论 (0)编辑 收藏

C++ 单元测试

测试并不只是测试工程师的责任,对于开发工程师,为了保证发布给测试环节的代码具有足够好的质量( Quality ),为所编写的功能代码编写适量的单元测试是十分必要的。

 

单元测试( Unit Test ,模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确,通过编写单元测试可以在编码阶段发现程序编码错误,甚至是程序设计错误。

单元测试不但可以增加开发者对于所完成代码的自信,同时,好的单元测试用例往往可以在回归测试的过程中,很好地保证之前所发生的修改没有破坏已有的程序逻辑。因此,单元测试不但不会成为开发者的负担,反而可以在保证开发质量的情况下,加速迭代开发的过程。

对 于单元测试框架,目前最为大家所熟知的是 JUnit 及其针对各语言的衍生产品, C++ 语言所对应的 JUnit 系单元测试框架就是 CppUnit 。但是由于 CppUnit 的设计严格继承自 JUnit ,而没有充分考虑 C++ 与 Java 固有的差异(主要是由于 C++ 没有反射机制,而这是 JUnit 设计的基础),在 C++ 中使用 CppUnit 进行单元测试显得十分繁琐,这一定程度上制约了 CppUnit 的普及。笔者在这里要跟大家介绍的是一套由 google 发布的开源单元测试框架( Testing Framework ): googletest 。

应用 googletest 编写单元测试代码

googletest 是由 Google 公司发布,且遵循 New BSD License (可用作商业用途)的开源项目,并且 googletest 可以支持绝大多数大家所熟知的平台。与 CppUnit 不同的是: googletest 可以自动记录下所有定义好的测试,不需要用户通过列举来指明哪些测试需要运行。

定义单元测试

在应用 googletest 编写单元测试时,使用 TEST() 宏来声明测试函数。如:

清单 1. 用 TEST() 宏声明测试函数
TEST(GlobalConfigurationTest, configurationDataTest) 
TEST(GlobalConfigurationTest, noConfigureFileTest)

分别针对同一程序单元 GlobalConfiguration 声明了两个不同的测试(Test)函数,以分别对配置数据进行检查( configurationDataTest ),以及测试没有配置文件的特殊情况( noConfigureFileTest )。

实现单元测试

针对同一程序单元设计出不同的测试场景后(即划分出不同的 Test 后),开发者就可以编写单元测试分别实现这些测试场景了。

在 googletest 中实现单元测试,可通过 ASSERT_* 和 EXPECT_* 断言来对程序运行结果进行检查。 ASSERT_* 版本的断言失败时会产生致命失败,并结束当前函数; EXPECT_* 版本的断言失败时产生非致命失败,但不会中止当前函数。因此, ASSERT_* 常常被用于后续测试逻辑强制依赖的处理结果的断言,如创建对象后检查指针是否为空,若为空,则后续对象方法调用会失败;而 EXPECT_* 则用于即使失败也不会影响后续测试逻辑的处理结果的断言,如某个方法返回结果的多个属性的检查。

googletest 中定义了如下的断言:

基本断言二进制比较字符串比较
ASSERT_TRUE(condition);
EXPECT_TRUE(condition);
condition为真
ASSERT_FALSE(condition);
EXPECT_FALSE(condition);
condition为假
ASSERT_EQ(expected,actual);
EXPECT_EQ(expected,actual);
expected==actual
ASSERT_NE(val1,val2);
EXPECT_NE(val1,val2);
val1!=val2
ASSERT_LT(val1,val2);
EXPECT_LT(val1,val2);
val1<val2
ASSERT_LE(val1,val2);
EXPECT_LE(val1,val2);
val1<=val2
ASSERT_GT(val1,val2);
EXPECT_GT(val1,val2);
val1>val2
ASSERT_GE(val1,val2);
EXPECT_GE(val1,val2);
val1>=val2
ASSERT_STREQ(expected_str,actual_str);
EXPECT_STREQ(expected_str,actual_str);
两个 C 字符串有相同的内容
ASSERT_STRNE(str1,str2);
EXPECT_STRNE(str1,str2);
两个 C 字符串有不同的内容
ASSERT_STRCASEEQ(expected_str,actual_str);
EXPECT_STRCASEEQ(expected_str,actual_str);
两个 C 字符串有相同的内容,忽略大小写
ASSERT_STRCASENE(str1,str2);
EXPECT_STRCASENE(str1,str2);
两个 C 字符串有不同的内容,忽

posted @ 2012-06-01 10:37 探路者 阅读(464) | 评论 (0)编辑 收藏

DVR 视频控件开发

总结前段时间用MFC开发视频监控控件过程中遇到的一些问题.
1.获取控件当前所在路径,用于读取该目录下的INI配置文件
char m_ConfigIni[512]; // 存放配置文件路径
char szApp[512]; // 当前控件所在完整路径(带文件名)
    
GetModuleFileName(AfxGetInstanceHandle(), szApp, MAX_PATH);
//注意第一个参数,平常应用程序开发时候一般传NULL即可,ActiveX中不行,会获取不到准确的路径
memcpy(m_ConfigIni, szApp, sizeof(szApp));
m_ConfigIni[strrchr(m_ConfigIni, 
0x5c- m_ConfigIni+1= 0// 去除控件文件名    
strcat(m_ConfigIni, "Config.ini");    
    
char path[512];
GetPrivateProfileString(
"StorePath""RecordPath""D:\\DvrData", path, 512, m_ConfigIni);
2.获取当前运行控件的电脑上的固定盘符列表,用于本地录像文件存放
char m_cHardDriver[26];    // 盘符数组
int     m_iDriverNum;         // 盘符个数  
BOOL F_GetSystemInfo();    // 获取固定盘符列表的函数


// 获取当前运行控件的电脑上的固定盘符列表
BOOL CWebPlayerApp::F_GetSystemInfo()
{
DWORD dw
=GetLogicalDriveStrings(0, NULL);
LPTSTR pAllDrivers
=new char[dw];
::GetLogicalDriveStrings(dw, pAllDrivers);
LPTSTR pDriver
=pAllDrivers;
char tempDriver[26];
DWORD DriverNum
=0;
while(pDriver[0!= 0)
{
tempDriver[DriverNum
++= *pDriver;
pDriver 
= _tcschr(pDriver,0+ 1;    //定位到下一个盘符
    }

//volume information
    TCHAR lpVolumeNameBuffer[200];
DWORD dwVolumeSerialNumber, dwMaxComLength;
DWORD dwFileSystemFlags;
TCHAR lpFileSystemNameBuffer[
50];

DWORD HardNum
=0;
for(DWORD num=0; num < DriverNum; num++)
{
CString csRootPath;
csRootPath.Format(
"%c%s", tempDriver[num], ":\\");

if(GetDriveType(csRootPath) == DRIVE_FIXED)
{
if(GetVolumeInformation(csRootPath,lpVolumeNameBuffer, sizeof(lpVolumeNameBuffer), &dwVolumeSerialNumber,
&dwMaxComLength, &dwFileSystemFlags, lpFileSystemNameBuffer, sizeof(lpFileSystemNameBuffer)))
{            
this->m_cHardDriver[HardNum++]=tempDriver[num];
}
}        
}
m_iDriverNum
=HardNum;    
delete[] pAllDrivers;

return TRUE;
}
3.视频1,4,9,16路画面切换显示
较简单地实现,在窗体上拖16个STATIC控件(定义数组为panels),动态调整它们的位置大小即可,然后定义一个类如CPlayStatic去继 承CStatic,每一个STATIC控件就由CPlayStatic管理;因为我们要在Static控件上添加鼠标,键盘事件处理,鼠标单击事件,选中 该一播放面板时绘制绿色边框,可以很明显地看出当前是选中那一路视频播放窗体,同时恢复上一路选中边框为默认灰色边框;鼠标双击事件,实现视频浏览窗口的 全屏功能(按多路预览-->单屏预览-->全屏-->单路浏览-->多路预览);右击菜单,对当前画面进行操作,如本地录像,语 音对讲,抓图等操作;键盘事件处理,如该窗体当前正在预览按F2/F键进入全屏模式,按Esc退出全屏,恢复普通模式(需让该窗体获得焦点,处理 KeyDown事件)
void CRealPlayDlg::ArrangeWindow()
{
//channelNum当前需要的视频路数
    int i=0, j=0, k=-1;
switch (channelNum)
{
case 1://DVR只有1个视频通道时候 只显示播放窗口1
//show=GetDlgItem(IDC_S01);
//show->MoveWindow(0,0,640,520);
            panels[0]->SetWindowPos(NULL,0,0,640,520,SWP_NOZORDER);
panels[
0]->ShowWindow(SW_SHOW);

//隐藏其他通道播放面板
            for (i=1; i<16; i++)
{
panels[i]
->ShowWindow(SW_HIDE);
}
m_Expanded 
= true;

break;
case 4://DVR有4个视频通道
            panels[0]->MoveWindow(0,0,319,259);
panels[
0]->ShowWindow(SW_SHOW);

panels[
1]->MoveWindow(320,0,320,259);
panels[
1]->ShowWindow(SW_SHOW);

panels[
2]->MoveWindow(0,260,319,260);
panels[
2]->ShowWindow(SW_SHOW);

panels[
3]->MoveWindow(320,260,320,260);
panels[
3]->ShowWindow(SW_SHOW);

for (i=4; i<16; i++)
{
panels[i]
->ShowWindow(SW_HIDE);
}
m_Expanded
=false;

break;
case 9://DVR有9个视频通道
            for (i=0; i<10; i++)
{
= i % 3;
if (j == 0)
{
k
++;
}
panels[i]
->SetWindowPos(NULL,j * 214,k * 174,213,173,SWP_NOZORDER);
panels[i]
->ShowWindow(SW_SHOW);
}
for (i=9; i<16; i++)
{
panels[i]
->ShowWindow(SW_HIDE);
}
m_Expanded
=false;

break;
case 16://DVR有16个视频通道
            for (i=0; i<16; i++)
{
j
=% 4;
if (j == 0)
{
k
++;
}
panels[i]
->SetWindowPos(NULL,j * 160,k * 130,159,129,SWP_NOZORDER);
panels[i]
->ShowWindow(SW_SHOW);
}
m_Expanded
=false;

break;
}
Invalidate();
//立即重绘窗体,显示效果
}

posted @ 2012-04-09 10:28 探路者 阅读(821) | 评论 (1)编辑 收藏

仅列出标题
共6页: 1 2 3 4 5 6 

导航

统计

常用链接

留言簿

随笔分类

随笔档案

文章分类

文章档案

新闻档案

Android

Compiler Course

VIM

编译技术集合

测试

高性能计算

个人博客

框架/组件/库

搜索

最新评论

阅读排行榜

评论排行榜