2007年11月11日星期日

Standard MIDI File Format


                                                        Standard   MIDI   File   Format  
                                                                  Dustin   Caldwell  
   
              The   standard   MIDI   file   format   is   a   very   strange   beast.   When   viewed   as   a  
    whole,   it   can   be   quite   overwhelming.   Of   course,   no   matter   how   you   look   at   it,  
  describing   a   piece   of   music   in   enough   detail   to   be   able   to   reproduce   it  
  accurately   is   no   small   task.   So,   while   complicated,   the   structure   of   the   midi  
  file   format   is   fairly   intuitive   when   understood.    
              I   must   insert   a   disclaimer   here   that   I   am   by   no   means   an   expert   with  
    midi   nor   midi   files.   I   recently   obtained   a   Gravis   UltraSound   board   for   my   PC,  
  and   upon   hearing   a   few   midi   files   (.MID)   thought,   "Gee,   I'd   like   to   be   able   to  
  make   my   own   .MID   files."   Well,   many   aggravating   hours   later,   I   discovered   that  
  this   was   no   trivial   task.   But,   I   couldn't   let   a   stupid   file   format   stop   me.  
  (besides,   I   once   told   my   wife   that   computers   aren't   really   that   hard   to   use,  
  and   I'd   hate   to   be   a   hypocrite)   So   if   any   errors   are   found   in   this  
  information,   please   let   me   know   and   I   will   fix   it.   Also,   this   document's   scope  
  does   not   extend   to   EVERY   type   of   midi   command   and   EVERY   possible   file  
  configuration.   It   is   a   basic   guide   that   should   enable   the   reader   (with   a  
  moderate   investment   in   time)   to   generate   a   quality   midi   file.  
   
  1.   Overview  
   
              A   midi   (.MID)   file   contains   basically   2   things,   Header   chunks   and   Track  
  chunks.   Section   2   explains   the   header   chunks,   and   Section   3   explains   the   track  
  chunks.   A   midi   file   contains   ONE   header   chunk   describing   the   file   format,  
  etc.,   and   any   number   of   track   chunks.   A   track   may   be   thought   of   in   the   same  
  way   as   a   track   on   a   multi-track   tape   deck.   You   may   assign   one   track   to   each  
  voice,   each   staff,   each   instrument   or   whatever   you   want.    
   
  2.   Header   Chunk  
   
              The   header   chunk   appears   at   the   beginning   of   the   file,   and   describes   the  
  file   in   three   ways.   The   header   chunk   always   looks   like:  
   
  4D   54   68   64   00   00   00   06   ff   ff   nn   nn   dd   dd  
   
  The   ascii   equivalent   of   the   first   4   bytes   is   MThd.   After   MThd   comes   the   4-byte  
  size   of   the   header.   This   will   always   be   00   00   00   06,   because   the   actual   header  
  information   will   always   be   6   bytes.    
   
  ff   ff   is   the   file   format.   There   are   3   formats:  
   
  0   -   single-track    
  1   -   multiple   tracks,   synchronous  
  2   -   multiple   tracks,   asynchronous  
   
  Single   track   is   fairly   self-explanatory   -   one   track   only.   Synchronous   multiple  
  tracks   means   that   the   tracks   will   all   be   vertically   synchronous,   or   in   other  
  words,   they   all   start   at   the   same   time,   and   so   can   represent   different   parts  
  in   one   song.   Asynchronous   multiple   tracks   do   not   necessarily   start   at   the   same  
  time,   and   can   be   completely   asynchronous.    
   
  nn   nn   is   the   number   of   tracks   in   the   midi   file.  
   
  dd   dd   is   the   number   of   delta-time   ticks   per   quarter   note.   (More   about   this  
  later)  
   
   
  3.   Track   Chunks  
   
  The   remainder   of   the   file   after   the   header   chunk   consists   of   track   chunks.  
  Each   track   has   one   header   and   may   contain   as   many   midi   commands   as   you   like.  
  The   header   for   a   track   is   very   similar   to   the   one   for   the   file:  
   
  4D   54   72   6B   xx   xx   xx   xx  
   
  As   with   the   header,   the   first   4   bytes   has   an   ascii   equivalent.   This   one   is  
  MTrk.   The   4   bytes   after   MTrk   give   the   length   of   the   track   (not   including   the  
  track   header)   in   bytes.    
              Following   the   header   are   midi   events.   These   events   are   identical   to   the  
  actual   data   sent   and   received   by   MIDI   ports   on   a   synth   with   one   addition.   A  
  midi   event   is   preceded   by   a   delta-time.   A   delta   time   is   the   number   of   ticks  
  after   which   the   midi   event   is   to   be   executed.   The   number   of   ticks   per   quarter  
  note   was   defined   previously   in   the   file   header   chunk.   This   delta-time   is   a  
  variable-length   encoded   value.   This   format,   while   confusing,   allows   large  
  numbers   to   use   as   many   bytes   as   they   need,   without   requiring   small   numbers   to  
  waste   bytes   by   filling   with   zeros.   The   number   is   converted   into   7-bit   bytes,  
  and   the   most-significant   bit   of   each   byte   is   1   except   for   the   last   byte   of   the  
  number,   which   has   a   msb   of   0.   This   allows   the   number   to   be   read   one   byte   at   a  
  time,   and   when   you   see   a   msb   of   0,   you   know   that   it   was   the   last   (least  
  significant)   byte   of   the   number.   According   to   the   MIDI   spec,   the   entire   delta-  
  time   should   be   at   most   4   bytes   long.    
              Following   the   delta-time   is   a   midi   event.   Each   midi   event   (except   a  
  running   midi   event)   has   a   command   byte   which   will   always   have   a   msb   of   1   (the  
  value   will   be   >=   128).   A   list   of   most   of   these   commands   is   in   appendix   A.   Each  
  command   has   different   parameters   and   lengths,   but   the   data   that   follows   the  
  command   will   have   a   msb   of   0   (less   than   128).   The   exception   to   this   is   a   meta-  
  event,   which   may   contain   data   with   a   msb   of   1.   However,   meta-events   require   a  
  length   parameter   which   alleviates   confusion.    
              One   subtlety   which   can   cause   confusion   is   running   mode.   This   is   where  
  the   actual   midi   command   is   omitted,   and   the   last   midi   command   issued   is  
  assumed.   This   means   that   the   midi   event   will   consist   of   a   delta-time   and   the  
  parameters   that   would   go   to   the   command   if   it   were   included.    
   
  4.   Conclusion  
   
              If   this   explanation   has   only   served   to   confuse   the   issue   more,   the  
  appendices   contain   examples   which   may   help   clarify   the   issue.   Also,   2  
  utilities   and   a   graphic   file   should   have   been   included   with   this   document:    
   
  DEC.EXE   -   This   utility   converts   a   binary   file   (like   .MID)   to   a   tab-delimited  
  text   file   containing   the   decimal   equivalents   of   each   byte.  
   
  REC.EXE   -   This   utility   converts   a   tab-delimited   text   file   of   decimal   values  
  into   a   binary   file   in   which   each   byte   corresponds   to   one   of   the   decimal  
  values.  
   
  MIDINOTE.PS   -   This   is   the   postscript   form   of   a   page   showing   note   numbers   with  
  a   keyboard   and   with   the   standard   grand   staff.  
                                                                                                                                                    Appendix   A  
   
  1.   MIDI   Event   Commands  
   
  Each   command   byte   has   2   parts.   The   left   nybble   (4   bits)   contains   the   actual  
  command,   and   the   right   nybble   contains   the   midi   channel   number   on   which   the  
  command   will   be   executed.   There   are   16   midi   channels,   and   8   midi   commands   (the  
  command   nybble   must   have   a   msb   of   1).  
  In   the   following   table,   x   indicates   the   midi   channel   number.   Note   that   all  
  data   bytes   will   be   <128   (msb   set   to   0).  
   
  Hex             Binary               Data                     Description  
  8x               1000xxxx           nn   vv                   Note   off   (key   is   released)  
                                                                          nn=note   number  
                                                                          vv=velocity  
   
  9x               1001xxxx           nn   vv                   Note   on   (key   is   pressed)  
                                                                          nn=note   number  
                                                                          vv=velocity  
   
  Ax               1010xxxx           nn   vv                   Key   after-touch  
                                                                          nn=note   number  
                                                                          vv=velocity  
   
  Bx               1011xxxx           cc   vv                   Control   Change  
                                                                          cc=controller   number  
                                                                          vv=new   value  
   
  Cx               1100xxxx           pp                         Program   (patch)   change  
                                                                          pp=new   program   number  
   
  Dx               1101xxxx           cc                         Channel   after-touch  
                                                                          cc=channel   number  
   
  Ex               1110xxxx           bb   tt                   Pitch   wheel   change   (2000H   is   normal   or   no  
                                                                          change)  
                                                                          bb=bottom   (least   sig)   7   bits   of   value  
                                                                          tt=top   (most   sig)   7   bits   of   value  
  The   following   table   lists   meta-events   which   have   no   midi   channel   number.   They  
  are   of   the   format:  
   
  FF   xx   nn   dd  
   
  All   meta-events   start   with   FF   followed   by   the   command   (xx),   the   length,   or  
  number   of   bytes   that   will   contain   data   (nn),   and   the   actual   data   (dd).  
   
  Hex             Binary               Data                     Description  
  00               00000000           nn   ssss               Sets   the   track's   sequence   number.  
                                                                          nn=02   (length   of   2-byte   sequence   number)  
                                                                          ssss=sequence   number  
   
  01               00000001           nn   tt   ..             Text   event-   any   text   you   want.  
                                                                          nn=length   in   bytes   of   text  
                                                                          tt=text   characters  
   
  02               00000010           nn   tt   ..             Same   as   text   event,   but   used   for  
                                                                          copyright   info.  
                                                                          nn   tt=same   as   text   event  
   
  03               00000011           nn   tt   ..             Sequence   or   Track   name  
                                                                          nn   tt=same   as   text   event  
   
  04               00000100           nn   tt   ..             Track   instrument   name  
                                                                          nn   tt=same   as   text   event  
   
  05               00000101           nn   tt   ..             Lyric  
                                                                          nn   tt=same   as   text   event  
   
  06               00000110           nn   tt   ..             Marker  
                                                                          nn   tt=same   as   text   event  
   
  07               00000111           nn   tt   ..             Cue   point  
                                                                          nn   tt=same   as   text   event  
   
  2F               00101111           00                         This   event   must   come   at   the   end   of   each  
                                                                          track  
   
  51               01010001           03   tttttt           Set   tempo  
                                                                          tttttt=microseconds/quarter   note  
   
  58               01011000           04   nn   dd   ccbb   Time   Signature  
                                                                          nn=numerator   of   time   sig.  
                                                                          dd=denominator   of   time   sig.   2=quarter  
                                                                          3=eighth,   etc.  
                                                                          cc=number   of   ticks   in   metronome   click  
                                                                          bb=number   of   32nd   notes   to   the   quarter  
                                                                          note  
   
  59               01011001           02   sf   mi             Key   signature  
                                                                          sf=sharps/flats   (-7=7   flats,   0=key   of   C,  
                                                                          7=7   sharps)  
                                                                          mi=major/minor   (0=major,   1=minor)  
   
  7F               01111111           xx   dd   ..             Sequencer   specific   information  
                                                                          xx=number   of   bytes   to   be   sent  
                                                                          dd=data  
  The   following   table   lists   system   messages   which   control   the   entire   system.  
  These   have   no   midi   channel   number.   (these   will   generally   only   apply   to  
  controlling   a   midi   keyboard,   etc.)  
   
  Hex             Binary               Data                     Description  
  F8               11111000                                       Timing   clock   used   when   synchronization   is  
                                                                          required.  
   
  FA               11111010                                       Start   current   sequence  
   
  FB               11111011                                       Continue   a   stopped   sequence   where   left  
                                                                          off  
   
  FC               11111100                                       Stop   a   sequence  
   
   
  The   following   table   lists   the   numbers   corresponding   to   notes   for   use   in   note    
  on   and   note   off   commands.  
   
   
  Octave||                                           Note   Numbers  
        #     ||  
              ||   C       |   C#     |   D       |   D#     |   E       |   F       |   F#     |   G       |   G#     |   A       |   A#     |   B  
  -----------------------------------------------------------------------------  
        0     ||       0   |       1   |       2   |       3   |       4   |       5   |       6   |       7   |       8   |       9   |     10   |   11  
        1     ||     12   |     13   |     14   |     15   |     16   |     17   |     18   |     19   |     20   |     21   |     22   |   23  
        2     ||     24   |     25   |     26   |     27   |     28   |     29   |     30   |     31   |     32   |     33   |     34   |   35  
        3     ||     36   |     37   |     38   |     39   |     40   |     41   |     42   |     43   |     44   |     45   |     46   |   47  
        4     ||     48   |     49   |     50   |     51   |     52   |     53   |     54   |     55   |     56   |     57   |     58   |   59  
        5     ||     60   |     61   |     62   |     63   |     64   |     65   |     66   |     67   |     68   |     69   |     70   |   71  
        6     ||     72   |     73   |     74   |     75   |     76   |     77   |     78   |     79   |     80   |     81   |     82   |   83  
        7     ||     84   |     85   |     86   |     87   |     88   |     89   |     90   |     91   |     92   |     93   |     94   |   95  
        8     ||     96   |     97   |     98   |     99   |   100   |   101   |   102   |   103   |   104   |   105   |   106   |   107  
        9     ||   108   |   109   |   110   |   111   |   112   |   113   |   114   |   115   |   116   |   117   |   118   |   119  
      10     ||   120   |   121   |   122   |   123   |   124   |   125   |   126   |   127   |  
   
   
                                                                  BIBLIOGRAPHY  
   
      "MIDI   Systems   and   Control"   Francis   Rumsey     1990   Focal   Press  
   
      "MIDI   and   Sound   Book   for   the   Atari   ST"   Bernd   Enders   and   Wolfgang   Klemme  
                      1989   M&T   Publishing,   Inc.  
   
      MIDI   file   specs   and   general   MIDI   specs   were   also   obtained   by   sending   e-mail  
                    to   LISTSERV@AUVM.AMERICAN.EDU   with   the   phrase   GET   MIDISPEC   PACKAGE  
                    in   the   message.  
   
   
  -------------------------------   DEC.CPP   ------------------------------------  
   
  /*     file     dec.cpp  
   
  by     Dustin   Caldwell         (dustin@gse.utah.edu)  
   
  */  
   
   
  #include   <dos.h>  
  #include   <stdio.h>  
  #include   <stdlib.h>  
   
  void   helpdoc();  
   
  main()  
  {  
                  FILE   *fp;  
   
                  unsigned   char   ch,   c;  
   
                  if((fp=fopen(_argv[1],   "rb"))==NULL)                         /*   open   file   to   read   */  
                  {  
                                  printf("cannot   open   file   %s\n",_argv[1]);  
                                  helpdoc();  
                                  exit(-1);  
                  }  
   
                  c=0;  
                  ch=fgetc(fp);  
   
                  while(!feof(fp))                                                 /*   loop   for   whole   file   */  
                  {  
                                  printf("%u\t",   ch);                           /*   print   every   byte's   decimal   equiv.   */  
                                  c++;  
                                  if(c>8)                                                                   /*   print   8   numbers   to   a   line   */  
                                  {  
                                                  c=0;  
                                                  printf("\n");  
                                  }  
   
                                  ch=fgetc(fp);  
                  }  
   
                  fclose(fp);                                           /*   close   up   */  
  }  
   
  void   helpdoc()                                     /*   print   help   message   */  
  {  
                  printf("\n       Binary   File   Decoder\n\n");  
   
                  printf("\n   Syntax:     dec   binary_file_name\n\n");  
   
                  printf("by   Dustin   Caldwell     (dustin@gse.utah.edu)\n\n");  
                  printf("This   is   a   filter   program   that   reads   a   binary   file\n");  
                  printf("and   prints   the   decimal   equivalent   of   each   byte\n");  
                  printf("tab-separated.   This   is   mostly   useful   when   piped   \n");  
                  printf("into   another   file   to   be   edited   manually.     eg:\n\n");  
                  printf("c:\>dec   sonata3.mid   >   son3.txt\n\n");  
                  printf("This   will   create   a   file   called   son3.txt   which   can\n");  
                  printf("be   edited   with   any   ascii   editor.   \n\n");  
                  printf("(rec.exe   may   also   be   useful,   as   it   reencodes   the   \n");  
                  printf("ascii   text   file).\n\n");  
                  printf("Have   Fun!!\n");  
  }  
   
  ----------------------------   REC.CPP   ----------------------------------  
   
  /*     File     rec.cpp  
                  by   Dustin   Caldwell       (dustin@gse.utah.edu)  
  */  
   
  #include   <dos.h>  
  #include   <stdio.h>  
  #include   <ctype.h>  
  #include   <stdlib.h>  
   
  void   helpdoc();  
   
  main()  
  {  
                  FILE   *rfp,   *wfp;  
   
                  unsigned   char   ch,   c;  
                  char   s[20];  
   
                  if((rfp=fopen(_argv[1],   "r"))==NULL)                                         /*   open   the   read   file   */  
                  {  
                                  printf("cannot   open   file   %s   \n",_argv[1]);  
                                  helpdoc();  
                                  exit(-1);  
                  }  
   
                  if((wfp=fopen(_argv[2],   "wb"))==NULL)                                       /*   open   the   write   file   */  
                  {  
                                  printf("cannot   open   file   %s   \n",_argv[1]);  
                                  helpdoc();  
                                  exit(-1);  
                  }  
   
                  c=0;  
   
                  ch=fgetc(rfp);  
   
                  while(!feof(rfp))                                               /*   loop   for   whole   file   */  
                  {  
   
                                  if(isalnum(ch))                                   /*   only   'see'   valid   ascii   chars   */  
                                  {  
                                                  c=0;  
                                                  while(isdigit(ch))             /*   only   use   decimal   digits   (0-9)   */  
                                                  {  
                                                                  s[c]=ch;                 /*   build   a   string   containing   the   number   */  
                                                                  c++;  
                                                                  ch=fgetc(rfp);  
                                                  }  
                                                  s[c]=NULL;                                             /*   must   have   NULL   terminator   */  
   
                                                  fputc(atoi(s),   wfp);/*   write   the   binary   equivalent   to   file   */  
   
                                  }  
   
                                  ch=fgetc(rfp);                                     /*   loop   until   next   number   starts   */  
   
   
                  }  
   
                  fclose(rfp);                                         /*   close   up   */  
                  fclose(wfp);  
  }  
   
   
  void   helpdoc()                     /*   print   help   message   */  
  {  
                  printf("\n       Text   File   Encoder\n\n");  
   
                  printf("\n   Syntax:     rec   text_file_name   binary_file_name\n\n");  
   
                  printf("by   Dustin   Caldwell     (dustin@gse.utah.edu)\n\n");  
                  printf("This   is   a   program   that   reads   an   ascii   tab-\n");  
                  printf("delimited   file   and   builds   a   binary   file   where\n");  
                  printf("each   byte   of   the   binary   file   is   one   of   the   decimal\n");  
                  printf("digits   in   the   text   file.\n");  
                  printf("   eg:\n\n");  
                  printf("c:\>rec   son3.txt   son3.mid\n\n");  
                  printf("(This   will   create   a   file   called   son3.mid   which   is\n");  
                  printf("a   valid   binary   file)\n\n");  
                  printf("(dec.exe   may   also   be   useful,   as   it   decodes   binary   files)\n\n");  
                  printf("Have   Fun!!\n");  
  }  

WinMM.dll 函数汇总

auxGetDevCaps                  查询指定的辅助输出设备以确定其性能
auxGetNumDevs                  检取系统中存在的辅助输出设备的数量
auxGetVolume                   返回指定的辅助输出设备的当前卷设备
auxOutMessage                  向指定的辅助输出设备发送一条消息
auxSetVolume                   在指定的辅助输出设备中设置卷
CloseDirver                    关闭指定的可安装驱动器
DefDriverProc                  为任何不由可安装驱动器处理的消息提供的缺省处理
Drivercallback                 调用一个回调函数,发送一条消息给窗口或将一个线程的阻塞解除
DrvGetModuleHandle             返回包含指定可安装驱动器模块的实例句柄
DrvsendMessage                 把指定的消息发送给可安装驱动器
GetDriverModuleHandle          返回包含指定可安装驱动器模块的实例句柄
joyGetDevCaps                  查询指定的游戏杆设备以确定其性能
joyGetNumDevs                  返回系统支持的游戏杆设备的数量
joyGetPos                      查询指定的游戏杆设备的位置和活动性
joyGetPosEx                    查询一个游戏杆设备的位置和它的按扭状态
joyGetThreshold                查询指定的游戏杆设备的当前移动阈值
joyReleaseCapture              释放由JoySetCapture函数设置的在指定游戏杆设备上的捕获
joySetCapture                  发送一个游戏杆消息到指定的窗口
joySetThreshold                设置指定的游戏杆设备的移动阈值
mciGetCreatorTask              为指定的MCI设备检取其创建的任务
mciGetDeviceID                 返回和打开设备名相匹配的设备标识符
mciGetErrorString              检取描述指定媒介控制接口错误代码的字符串
mciGetYieldProc                返回和媒介控制接口的WAIT标志相关的回调函数的地址
mciSendCommand                 向指定的媒介控制接口设备发送一条命令
mciSendString                  向指定的媒介控制接口设备发送一个字符串
mciSetYieldProc                设置一个过程地址,在MCI设备因指定了WAIT标志而等待一个命令完成时,该过程被周期性调用
midiConnect                    将指定的MIDI输入设备连接到输出设备
midiDisconnect                 断开MIDI输入设备和输出设备的连接
midiInAddBuffer                向指定的音乐仪器数字接口的输入设备增加一个缓冲区
midiInClose                    关闭指定的音乐仪器数字接口的输入设备
midiInGetDveCaps               查询指定的音乐仪器数字接口的输入设备,以确定其性能
midiInGetErrorText             检取有关音乐仪器数字接口的输入设备指定错误的文本说明
midiInGetID                    获得一个音乐一起数字接口的输入设备的标识符
midiInGetNumDevs               检取系统中音乐仪器数字接口的输入设备的数量
midiInMessage                  向指定的音乐仪器数字接口的输入设备驱动器发送一条消息
midiInOpen                     打开指定的音乐仪器数字接口的输入设备
midiInPrepareHeader            为音乐仪器数字接口的输入设备准备一个缓冲区
midiInReset                    在给定的MIDI输入设备上输入,并将所有挂起的输入缓冲区标记为已执行的
midiInStart                    启动在指定的音乐仪器数字接口的输入设备上的输入
midiInStop                     停止在给定的音乐仪器数字接口的输入设备上的输入
midiInUnprepareHeader          消除由midiInPrepareHeader函数完成的准备
midiOutCacheDrumPatches        请求内部的一个MIDI合成设备预装指定的基于键的击打音色集
midiOutCachePatches            请求内部的音乐仪器数字接口的合成设备预装指定的音色集
midiOutClose                   关闭指定的音乐仪器数字接口的输出设备
midiOutGetDevCaps              查询指定的音乐仪器数字接口的输出设备,以确定其性能
midiOutGetErrorText            检取有关MIDI输出设备指定采取的文本说明
midiOutGetID                   检取指定的MIDI输出设备的标识符
midiOutGetNumDevs              检取系统中存在的MIDI输出设备的数量
midiOutGetVolume               返回一个MIDI输出设备的当前卷设置
midiOutLongMsg                 向指定的MIDI输出设备发送一条系统专用的MIDI消息
midiOutMessage                 向一MIDI输出设备驱动器发送一条消息
midiOutOpen                    打开指定的MIDI输出设备进行回放
midiOutPrepareHeader           为MIDI输出设备准备一个缓冲区
midiOutReset                   为指定的MIDI输出设备关闭所有MIDI通道上的所有标志
midiOutSetVolume               设置一个MIDI输出设备的卷
midiOutShortMsg                向指定的MIDI输出设备发送一条短MIDI消息
midiOutUnprepareHeader         清除由midiOutPrepareHeader函数完成的准备
midiStreamClose                关闭一个打开的MIDI流
midiStreamOpen                 为输出,打开一个MIDI流
midiStreamOut                  在MIDI输出设备上播放或排队一个MIDI数据流
midiStreamPause                暂停一个MIDI流的播放
midiStreamPosition             在一个MIDI流中检取当前位置
midiStreamProperty             设置或检取与MIDI输出设备相关MIDI数据流的特性
midiStreamRestart              重新启动一个暂停的MIDI流
midiStreamStop                 关掉指定MIDI输出设备的所有MIDI通道
mixerClose                     关闭指定的混频器
mixerGetControlDetails         检取和一个声频指线路相关的单一控件的细节
mixerGetDevCaps                查询指定的混频器以确定其性能
mixerGetID                     获取指定混频器的标识符
mixerGetLineContrils           检取和一个声频线路相关的一个或多个控件
mixerGetLineInfo               检取混频器有关特有线路的信息
mixerGetNumDevs                返回系统中存在的混频器的数量
mixerMessage                   把一个定制混频器驱动器消息直接发送给混频器驱动器
mixerOpen                      打开指定的混频器,在应用程序关闭该句柄前保证该设备不被移走
mixerSetControlDetails         设置和一个声频指线路相关的单一控件的细节
mmioAsvance                    填充一个文件的IO缓冲区
mmioAscend                     取出一个RIFF文件块
mmioClose                      关闭有mmioOpen打开的文件
mmioCreateChunk                创建由mmioOpen函数打开的RIFF文件中的一个块
mmioDescend                    进入由mmioOpen函数打开的RIFF文件的块中,并查找一个块
mmioFlush                      把文件缓冲区的数据写入磁盘中
mmioGetInfo                    检取有关由mmioOpen函数创建的RIFF文件的信息
mmioInstallIOProcA             装入或删除一个自定义的IO过程
mmioOpen                       为输入输出打开一个文件
mmioRead                       从由mmioOpen函数打开的文件中读取指定字节数的数据
mmioRename                     重新命名指定的文件
mmioSeek                       改变由mmioOpen函数打开的文件中的当前指针位置
mmioSendMessage                向与指定文件相联系的IO过程发送一条消息
mmioSetBuffer                  允许或禁止文件缓冲区的IO,或改变这个缓冲区,或改变这个缓冲区的大小
mmioSetInfo                    更新从被打开文件中检取的信息
mmioStringToFOURCC             把一个以NULL结束的字符串转换成一个4字符代码
mmioWrite                      向由mmioOpen函数打开的文件中写入指定字节数的数据
mmsystemGetVersion             返回多媒体扩展系统软件的当前版本号
OpenDriver                     打开一个可安装驱动器实例,并用缺省设置或指定值初始化该实例
PlaySound                      播放一个波形声音
SendDriveMessage               向指定的可安装驱动器发送一条消息
SndPlaySound                   播放一个由文件名或由登记的[sound]段的入口指定的波形声音
timeBeginPeriod                设置应用程序或驱动程序使用的最小定时器分辨率
timeEndPeriod                  清除应用程序或驱动程序使用的最小定时器分辨率
timeGetDevCaps                 查询定时器设备以确定其性能
timeGetSystemTime              检取从WINDOWS开始已逝去的毫秒数
timeGetTime                    检取从WINDOWS开始已逝去的毫秒数,此函数比上一条函数开销小
timeKillEvent                  毁掉指定的定时器回调事件
timeSetEvent                   设置一个定时器回调事件
waveInAddBuffer                向波形输入设备添加一个输入缓冲区
WaveInClose                    关闭指定的波形输入设置
waveInGetDevCaps               查询指定的波形输入设备以确定其性能
waveInGetErrorText             检取由指定的错误代码标识的文本说明
waveInGetID                    获取指定的波形输入设备的标识符
waveInGetNumDevs               返回系统中存在的波形输入设备的数量
waveInGetPosition              检取指定波形输入设备的当前位置
waveInMessage                  发送一条消息给波形输入设备的驱动器
waveInOpen                     为录音而打开一个波形输入设备
waveInPrepareHeader            为波形输入准备一个输入缓冲区
waveInReset                    停止给定的波形输入设备的输入,且将当前位置清零
waveInStart                    启动在指定的波形输入设备的输入
waveInStop                     停止在指定的波形输入设备上的输入
waveInUnprepareHeader          清除由waveInPrepareHeader函数实现的准备
waveOutBreakLoop               中断给定的波形输出设备上一个循环,并允许播放驱动器列表中的下一个块
waveOutClose                   关闭指定的波形输出设备
waveOutGetDevCaps              查询一个指定的波形输出设备以确定其性能
waveOutGetErrorText            检取由指定的错误代码标识的文本说明
waveOutGetID                   检取指定的波形输出设备的标识符
waveOutGetNumDevs              检取系统中存在的波形输出设备的数量
waveOutGetPitch                查询一个波形输出设备的当前音调设置
waveOutGetPlaybackRate         查询一个波形输出设备当前播放的速度
waveOutGetPosition             检取指定波形输出设备的当前播放位置
waveOutGetVolume               查询指定波形输出设备的当前音量设置
waveOutMessage                 发送一条消息给一个波形输出设备的驱动器
waveOutOpen                    为播放打开一个波形输出设备
waveOutPause                   暂停指定波形输出设备上的播放
waveOutPrepareHeader           为播放准备一个波形缓冲区
waveOutRestart                 重新启动一个被暂停的波形输出设备
waveOutSetPitch                设置一个波形输出设备的音调
waveOutSetPlaybackRate         设置指定波形输出设备的速度
waveOutSetVolume               设置指定的波形输出设备的音量
waveOutUnprepareHeader         清除由waveOutPrepareHeader函数实现的准备
waveOutWrite                   向指定的波形输出设备发送一个数据块

2007年9月10日星期一

二分法查找&二分法排序

二分查找1、二分查找(Binary Search)
二分查找又称折半查找,它是一种效率较高的查找方法。
二分查找要求:线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。不妨设有序表是递增有序的。
2、二分查找的基本思想
二分查找的基本思想是:(设R[low..high]是当前的查找区间)
(1)首先确定该区间的中点位置:
(2)然后将待查的K值与R[mid].key比较:若相等,则查找成功并返回此位置,否则须确定新的查找区间,继续二分查找,具体方法如下:
①若R[mid].key>K,则由表的有序性可知R[mid..n].keys均大于K,因此若表中存在关键字等于K的结点,
则该结点必定是在位置mid左边的子表R[1..mid-1]中,故新的查找区间是左子表R[1..mid-1]。
②类似地,若R[mid].key<K,则要查找的K必在mid的右子表R[mid+1..n]中,即新的查找区间是右子表R[mid+1..n]。下一次查找是针对新的查找区间进行的。
因此,从初始的查找区间R[1..n]开始,每经过一次与当前查找区间的中点位置上的结点关键字的比较,就可确定查找是否成功,
不成功则当前的查找区间就缩小一半。这一过程重复直至找到关键字为K的结点,或者直至当前的查找区间为空(即查找失败)时为止。

3、二分查找算法
  int BinSearch(SeqList R,KeyType K)
{ //在有序表R[1..n]中进行二分查找,成功时返回结点的位置,失败时返回零
int low=1,high=n,mid; //置当前查找区间上、下界的初值
while(low<=high){ //当前查找区间R[low..high]非空
mid=(low+high)/2;
if(R[mid].key==K) return mid; //查找成功返回
if(R[mid].kdy>K)
high=mid-1; //继续在R[low..mid-1]中查找
else
low=mid+1; //继续在R[mid+1..high]中查找
}
return 0; //当low>high时表示查找区间为空,查找失败
} //BinSeareh

二分查找算法亦很容易给出其递归程序【参见练习
6、二分查找的优点和缺点
虽然二分查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算。既使采用高效率的排序方法也要花费O(nlgn)的时间。
二分查找只适用顺序存储结构。为保持表的有序性,在顺序结构里插入和删除都必须移动大量的结点。因此,二分查找特别适用于那种一经建立就很少改动、而又经常需要查找的线性表。
对那些查找少而又经常需要改动的线性表,可采用链表作存储结构,进行顺序查找。链表上无法实现二分查找。

二分法排序

#include <stdlib.h>
#include <stdio.h>
void TwoInsertSort(int array[],int n)
{
int left,right,num;
int middle,j,i;
for(i = 1;i < n;i++)
{
left = 0;// 准备
right = i-1;
num = array[i];
while( right >= left)// 二分法查找插入位置
{
middle = ( left + right ) / 2; // 指向已排序好的中间位置
if( num < array[middle] )// 即将插入的元素应当在在左区间
right = middle-1;
else // 即将插入的元素应当在右区间
left = middle+1;
}
for( j = i-1;j >= left;j-- )// 后移排序码大于R[i]的记录
array[j+1] = array[j];
array[left] = num;// 插入
}
}

int rcmp( const int *a, const int *b)
{
return (*a-*b);
}
void main()
{
int array[50];
int i;
printf("The original array is :\n");
for( i=0; i<50; i++ )//数组初始化并显示
{
array[i] = 50-i;
printf("array[%d]:%d\n", i, array[i]);
}
TwoInsertSort(array,sizeof(array)/sizeof(int));//二分法排序
printf("\nAfter sorted :\n");
for( i=0; i<50; i++ )
printf("array[%d]:%d\n", i, array[i]);

//库函数bsearch用二分法查找一个有序数组中的一个特定数,并返回该数的地址

a = (int *)bsearch(&b, numarray, sizeof(numarray)/sizeof(numarray[0]), sizeof(int),rcmp);

}


七种场景下的软件工作量估算步骤

七种场景下的软件工作量估算步骤

场景一:合同前的工作量估算
场景描述
(1) 没有实施过CMMI2
(2) 合同未签,需要给客户报价
(3) 有客户的概要需求,有类似的项目数据可供参考
(4) 需要估计整个项目的总工作量,以便于估算总成本,给客户报价
估算步骤
1)寻找类似的历史项目,进行项目的类比分析,根据历史项目的工作量凭经验估计本项目的总工作量;
2)进行WBS分解,力所能及地将整个项目的任务进行分解;
3)参考类似项目的数据,采用经验法估计WBS中每类活动的工作量;
4)汇总得到项目的总工作量;
5)与第(1)步的结果进行印证分析,根据分析结果,确定估计结果。
场景二:基于详细需求的经验估计
场景描述
(1) 只有详细需求,没有历史数据
估算步骤:
1WBS分解,将任务分解到一个人或者一个小团队可以执行的颗粒度;WBS分解时要识别出所有的交付物、项目管理活动、工程活动等。
2)采用经验法估计每个活动的工作量;
3)汇总得到:每个阶段的工作量、项目的总工作量。
其他说明:
在该场景下,只使用了经验法,无法对结果进行印证,难以判断结果的合理性。
场景三:由编码估算整体
场景描述
1)有类似项目的历史数据
2)有编码活动的生产率数据
3)有详细需求
4)实施了CMMI2级,但是没有积累历史项目的工作量分布数据
估算步骤
1)产品分解,将系统分为子系统,子系统分解为模块;
2WBS分解,将任务分解到一个人或者一个小团队可以执行的颗粒度;WBS分解时要识别出所有的交付物、项目管理活动、工程活动等。
3)建立WBS分解中的活动与产品元素的映射关系,识别出WBS中哪些活动可以采用模型法估算;
4)估计产品元素的规模,可以采用代码行法或功能点法,并估计每个产品元素的复杂度、复用率等;
5)根据历史的编码阶段的生产率数据和产品元素的规模估计、复杂度、复用率等采用模型法计算每个产品元素的编码工作量;
6)根据历史的类似项目的数据及估算人的经验估计其他活动的工作量,可以采用经验法。
7)汇总得到:每个阶段的工作量、项目的总工作量。
其他说明:
在该场景下,混合使用了经验法与模型法,这2种方法互相补充,而不是互相印证。
场景四:由总体印证基于WBS的估计
场景描述
1)有类似项目的历史数据
(2) 有类似项目的全生命周期的生产率数据(含管理工作量)
3)有详细需求
4)实施了CMMI2级,但是没有积累历史项目的工作量分布数据
估算步骤
1)产品分解,将系统分为子系统,子系统分解为模块;
2)估计产品元素的规模,可以采用代码行法或功能点法;
3)累计出整个产品的总规模,并估计产品总体的复杂度、复用率等;
4)根据类似项目的全生命周期的生产率数据和产品的总规模、复杂度、复用率等采用模型法计算总的开发工作量;
5WBS分解,将任务分解到一个人或者一个小团队可以执行的颗粒度;WBS分解时要识别出所有的交付物、项目管理活动、工程活动等。
6)根据历史的类似项目的数据及估算人的经验估计所有活动的工作量,可以采用经验法。
7)汇总得到:每个阶段的工作量、项目的总工作量。
8)与第(4)步得出的工作量进行比较印证,如果偏差不大,则以第(7)步的结果为准,如果偏差比较大,要仔细分析原因,可能的原因举例如下:
Ø 类似项目的生产率数据不适合本项目;
Ø WBS分解的颗粒度不够详细;
Ø 估算专家的经验不适合本项目;
Ø 具体任务的估计不合理;
针对原因,对估算的结果进行调整,使其趋向合理。
其他说明
在该场景下,对于项目的总工作量有2个结果或者多个结果,这些结果可以互相印证,以发现估算过程中的不合理之处,是估计更加合理。
场景五:三维印证基于WBS的估计
场景描述
1)有类似项目的历史数据
(2) 有类似项目的全生命周期的生产率数据(含管理工作量)
3)有详细需求
4)实施了CMMI3级,有历史项目的工作量分布数据(阶段分布、工种分布)
估算步骤
1)产品分解,将系统分为子系统,子系统分解为模块;
2)估计产品元素的规模,可以采用代码行法或功能点法;
3)累计出整个产品的总规模,并估计产品总体的复杂度、复用率等;
4)根据类似项目的全生命周期的生产率数据和产品的总规模、复杂度、复用率等采用模型法计算总的开发工作量;
5)根据历史项目的工作量分布数据及第(4)步估算的项目总工作量,计算:
Ø 每个阶段的工作量
Ø 每个工种的工作量
6WBS分解,将任务分解到一个人或者一个小团队可以执行的颗粒度;WBS分解时要识别出所有的交付物、项目管理活动、工程活动等。
7)根据历史的类似项目的数据及估算人的经验估计所有活动的工作量,可以采用经验法。
8)汇总得到:每个阶段的工作量、每个工种的工作量、项目的总工作量。
9)与第(4)、(5)步得出的工作量进行比较印证,如果偏差不大,则以第(7)步的结果为准,如果偏差比较大,要仔细分析原因,可能的原因举例如下:
Ø 类似项目的生产率数据不适合本项目;
Ø WBS分解的颗粒度不够详细;
Ø 估算专家的经验不适合本项目;
Ø 具体任务的估计不合理;
针对原因,对估算的结果进行调整,使其趋向合理。
其他说明
在该场景下,对于项目的总工作量有2个结果或者多个结果,并且采用2种方法都得到了每个阶段、每个工种的工作量、项目的总工作量,可以从上述的3个维度对这些结果进行互相印证,以发现估算过程中的不合理之处,是估计更加合理。
场景六:四维印证基于WBS的估计
场景描述
1)有类似项目的历史数据
(2) 有类似项目的编码活动的生产率数据(不含管理工作量)
3)有详细需求
4)实施了CMMI3级,有历史项目的工作量分布数据(阶段分布、工种分布、阶段工种分布)
5)项目采用了瀑布模型
估算步骤
1)产品分解,将系统分为子系统,子系统分解为模块;
2)估计产品元素的规模,可以采用代码行法或功能点法,并估计每个产品元素的复杂度、复用率等;
3)根据类似项目的编码活动的生产率数据和产品元素的规模、复杂度、复用率等采用模型法计算每个产品元素的编码工作量;
4)根据历史项目的按工种的工作量分布数据及第(3)步的估算的编码工作量依次计算:
i)根据历史项目的编码的工作量占编码阶段的工作量的百分比与第(3)部计算出的编码工作量计算编码阶段的总工作量;
ii)根据历史项目的编码阶段各工种的工作量分布百分比计算编码阶段每个工种的工作量;
iii)根据历史项目的其他阶段的工作量与编码阶段的工作量比例计算其他阶段的总工作量;
iv)根据历史项目的其他阶段的每个工种的工作量分布百分比及第iii)步的结果计算其他阶段的每个工种的工作量;
5WBS分解,将任务分解到一个人或者一个小团队可以执行的颗粒度;WBS分解时要识别出所有的交付物、项目管理活动、工程活动等。
6)根据历史的类似项目的数据及估算人的经验估计所有活动的工作量,可以采用经验法。
7)汇总得到:每个阶段每个工种的工作量、每个阶段的工作量、每个工种的工作量、项目的总工作量。
8)与第(4)步得出的工作量进行比较印证,如果偏差不大,则以第(6)步的结果为准,如果偏差比较大,要仔细分析原因,可能的原因举例如下:
Ø 类似项目的生产率数据不适合本项目;
Ø WBS分解的颗粒度不够详细;
Ø 估算专家的经验不适合本项目;
Ø 具体任务的估计不合理;
针对原因,对估算的结果进行调整,使其趋向合理。
其他说明
在该场景下,对于项目的总工作量有2个结果或者多个结果,并且采用2种方法都得到了每个阶段的工作量、每个工种的工作量、每个阶段每个工种的工作量、项目的总工作量,可以从上述的4个维度对这些结果进行互相印证,以发现估算过程中的不合理之处,是估计更加合理。

场景七:需求变更的工作量估计
场景描述
(1) 有变更的需求描述
(2) 项目进行到了编码阶段
(3) 有本项目的编码的生产率
估算步骤:
1)进行需求变更的波及范围分析
2)进行本次变更的的WBS分解
3)对于变更引起的代码变化进行规模、复杂度等其他属性的估计
4)根据本项目的编码的生产率及估计的规模采用模型法估计工作量
5)对于WBS分解中其他活动进行经验估计
6)汇总所有的工作量得到本次变更的工作量估计

线程的使用

线程的使用
1.创建简单的线程;
在视图类的鼠标的消息映射函数中加入:
    AfxBeginThread((AFX_THREADPROC)TestThread,this);
然后在视图类中加入线程的入口函数:
UINT TestThread(LPVOID pParam)
{
    AfxMessageBox("这是一个测试工作线程的对话框");
    return 0;
}
函数说明:
CWinThread*
AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int
nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD
dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
CWinThread*
AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority =
THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
PfnThreadProc:
    指向工作者线程的控制函数,不能是NULL,函数的定义必须如下:
UINT MyControllingFunction( LPVOID pParam );
PThreadClass:CwinThread的RUNTIME_CLASS对象.
PParam:传递给控制函数的参数.
Npriority:线程的优先级,可以用函数
BOOL SetThreadPriority(
  HANDLE hThread, // handle to the thread
  int nPriority   // thread priority level
);来设定
nStackSize:堆栈的大小,如果为0,就和创建的主线程一样的大小.
DwCreateFlags:创建线程时的附加标志,可以为CREATE_SUSPENDED,代表挂起线程,直到调用DWORD ResumeThread( );函数时才执行,为0,表示立即执行.
LpSecurityAttrs:指向一个SECURITY_ATTRIBUTES描述线程的安全性,如果与主线程一样的安全属性,可以为NULL.
另外可以用CwinThread类的成员函数
BOOL
CreateThread( DWORD dwCreateFlags = 0, UINT nStackSize = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );来创建一个线程,具体的参数和上面一样.
还可以用API来创建线程:
HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes
  DWORD dwStackSize,                         // initial thread stack size
  LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread function
  LPVOID lpParameter,                        // argument for new thread
  DWORD dwCreationFlags,                     // creation flags
  LPDWORD lpThreadId                         // pointer to receive thread ID
);
DWORD WINAPI YourThreadFunc(LPVOIDlpvThreadParm);
lpThreadId:32位标示符,用来接收线程的标示
函数返回的都是创建线程的句柄.
2.如何结束一个线程:
void AfxEndThread( UINT nExitCode );//结束当前正在运行的线程.
NExitCode:为线程退出代码.
BOOL TerminateThread(
  HANDLE hThread,    // handle to the thread
  DWORD dwExitCode   // exit code for the thread
);
如果线程调用了VOID ExitThread(UINTfuExitCode);也可以终止

3.如何取得线程的退出代码;
使用函数
BOOL GetExitCodeThread(
  HANDLE hThread,      // handle to the thread
  LPDWORD lpExitCode   // address to receive termination status
);
4.设置线程的优先级;
BOOL SetThreadPriority(
  HANDLE hThread, // handle to the thread
  int nPriority   // thread priority level
);
nPriority;
线程的优先级,有:THREAD_PRIORITY_ABOVE_NORMAL,THREAD_PRIORITY_BELOW_NORMAL,
THREAD_PRIORITY_HIGHEST,THREAD_PRIORITY_IDLE,THREAD_PRIORITY_LOWEST,
THREAD_PRIORITY_NORMAL,THREAD_PRIORITY_TIME_CRITICAL具体的函数参见MSDN。
在上面创建的工作者线程中我们可以这样设置优先级:
    SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST);//设置比通常高两个点
5.如何创建多线程多任务。
可以调用利用上面的线程创建函数,传递给同一个线程函数不同的参数,也可以开始另外一个线程。
6.挂起和恢复线程:

前我提到过可以创建挂起状态的线程(通过传递CREATE_SUSPENDED标志给函数CreateThread来实现)。当你这样做时,系统创建指定
线程的核心对象,创建线程的栈,在CONTEXT结构中初始化线程CPU注册成员。然而,线程对象被分配了一个初始挂起计数值1,这表明了系统将不再分配
CPU去执行线程。要开始执行一个线程,一个线程必须调用ResumeThread并传递给它调用CreateThread时返回的线程句柄。
DWORD ResumeThread(HANDLE hThread);
一个线程可以被挂起多次。如果一个线程被挂起3次,则该线程在它被分配CPU之前必须被恢复3次。
除了在创建线程时使用CREATE_SUSPENDED标志,你还可以用SuspendThread函数挂起线程。
DWORDSuspendThread(HANDLE hThread);
7.线程同步对象的使用;

步对象有:Critical_section(临界区),Event(事件),Mutex(互斥对象),Semaphores(信号量)。都有相应的
API创建函数和MFC类,同步对象可以处于两种状态:信号状态(signal
state)或非信号状态,当一个线程与某个对象相关联时,若该对象处于非信号状态,则要等到其变成信号状态线程才能继续执行。
WIN32 API提供了等待命令WaitForSingleObject和WaitForMutipleObjects:
  DWORD WaitForSingleObject(
                HANDLE hObject,                         // 要等待的对象句柄
                DWORD dwMilliseconds);  // 最大等待时间(毫秒)
        DWORD WaitforMultipleObjects(
                DWORD dwNumObjects,             // 要等待的对象数
                LPHANDLE lpHandles,             // 对象句柄数组
                BOOL bWaitAll,               // 是否等待所有对象都有信号才返回
                DWORD dwMilliseconds);  // 最大等待时间

果在指定时间内对象达到 信号状态则返回WAIT_OBJECT_0,超时返回WAIT_TIMEOUT,出错返回
WAIT_FAILED。对于互斥量、信号量和自动重置(auto-reset)事件对象,等待成功
时将它们改成非信号状态(信号量计数器减1),以实现对象的互斥访问。
件:jianuangzhiganhue
8.事件:
"事件"――一个允许一个事件发生时线程通知另一个线程的同步对象。在一个线程需要了解何时执行任务时,事件是十分有用的。
CEvent对象有两种类型:自动和手工。一个手工CEvent对象存在于由ResetEvent 或SetEvent设置的状态中,直到另一个函数被调用。
一个自动CEvent对象在至少一个线程被释放后自动返回一个无标记(无用的)状态。
CEvent(
BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName
= NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
BInitiallyOwn;如果为TRUE, CMultilock or CsingleLock对象就可以使用,为FALSE,线程就必须等到直到所需的资源被释放.
BManualReset:为TRUE,设置为手动复位事件对象,为FALSE,设置为自动复位事件对象.
LpszName:事件对象的名称,不能指定一个已经存在的对象
LpsaAttribute:事件对象的安全属性
与同步对象相关的多个同步访问对象CMultilock 和单个同步访问对象 CsingleLock对象.
CSingleLock( CSyncObject* pObject, BOOL bInitialLock = FALSE );
PObject:指向一个要处理的同步对象
BinitialLock:指定是否初始的时候就尝试访问对象。
CMultiLock( CSyncObject* ppObjects[ ], DWORD dwCount, BOOL bInitialLock = FALSE );
DwCount:指定同步对象的个数,必须大于0。
下面简单介绍他们的应用:

用此成员函数构造一个已命名或未命名的CEvent对象。要访问或释放一个CEvent对象,可建立一个CSingleLock或CMultiLock对
象并调用其Lock和Unlock成员函数。要将CEvent对象的状态改为已标记(无须等待的线程),可调用SetEvent或PulseEvent。
要设置一个CEvent对象为无标记(必须等待的线程),可调用ResetEvent。
可以在你的线程开始设置CEvent::SetEvent,其他线程使用CSingleLock::Lock来等待你线程处理结束。当你的线程处理完毕,
使用CEvent::ResetEvent清除事件,其他程序就可以继续执行了。
API函数:
    HANDLE CreateEvent(
                LPSECURITY_ATTRIBUTES lpsa,     // 安全属性指针,Win98忽略
                BOOL bManualRest,              // 是否手动重置(manual-reset)
                BOOL bInitialState,            // 初始状态是否为信号状态
                LPCTSTR lpName);              // 对象名字符串指针

动重置(auto-reset)事件和手动重置(manual-reset)事件,这由CreateEvent()的第二个参数指定。对于自动重置事件,
WaitForSingleObject()和
WaitForMultipleObjects()会等待事件到信号状态,随后又自动将其重置为非信号状
态,这样保证了等待此事件的线程中只有一个会被唤醒。而手动重置事件需要用户调用ResetEvent()才会重置事件。可能有若干个线程在等待同一事
件,这样当事件变 为信号状态时,所有等待线程都可以运行了。
SetEvent()函数用来把事件对象设置成信号状态,ResetEvent()把事件对象重置成非信号状态,两者均需事件对象句柄作参数。
9.临界区

一时刻只允许一个线程存取资源或代码区。临界区在控制一次只有一个线程修改数据或其它的控制资源时非常有用。在运行性能比较重要而且资源不会跨进程使用
时,建议采用临界区代替信号灯。有关在MFC中使用信号灯的详细信息,请参阅CMutex。使用CCriticalSection对象之前,需要构造它。
在构造函数返回后,就可以使用临界区了。在使用完之后要调用UnLock函数。存取由CCriticalSection控制的资源时,要在资源的存取函数
中定义一个CSingleLock型的变量。然后调用加锁对象的Lock成员函数(如CSingleLock::Lock)。此时,调用的线程要么获得对
资源的存取权,要么等待他人释放资源等待加锁,或者等待他人释放资源,但又因为超时而加锁失败。这样就保证了一次只有一个线程在存取临界资源。释放资源只
需调用成员函数UnLock(例如CSingleLock:Unlock),或让锁对象在作用范围之外。
此外,可以单独地建立一个CCriticalSection对象,并在存取临界资源之前显式地存取它。这种方式有助于保持代码的清晰,
但是更容易出错,因为要记住在存取临界资源前加锁,存取之后开锁。
构造函数:CCriticalSection ( );
成员函数:BOOL Lock( ); BOOL Lock( DWORD dwTimeout ); 本函数用于取得对临界区对象的存取权。Lock是一个成块性的操作,直到取得临界区对象的存取权才返回。
virtual
BOOL UnLock( );
如果临界区是被本线程占用的并且开锁成功,则返回非零值。否则为0。本函数释放线程占用的CCriticalSection对象。如果
CCriticalSection是单独使用的,在独占使用完临界资源以后,应尽快调用UnLock开锁。如果使用了CSingleLock对象,
CCriticalSection::Unlock将会在它的成员函数Unlock中调用。
API函数:
CRITICAL_SECTION cs; 然后初始化,调用下面函数(参数为空对象的指针):
     VOID InitializeCriticalSection(LPCRITICAL_SECTION lpcs);
     VOID EnterCriticalSection(LPCRITICAL_SECTION lpcs); 进入和离开临界区分别调用函数:
     VOID LeaveCriticalSection(LPCRITICAL_SECTION lpcs);
     VOID DeleteCriticalSection(LPCRITICAL_SECTION lpcs); 删除临界区对象用:
10. Mutex(互斥对象)
它为一个同步对象,允许某线程共同访问同一资源。在仅仅一个线程被允许用于修改数据或其它被控制的资源时。

种使用CMutex
对象的方法就是一个CMutex类型的变量,将其作为你希望控制类的数据成员。在被控制对象的构造过程中,若互斥对象最初拥有了互斥对象的名称或期待的安
全属性,那么就调用CMutex数据成员指定的构造函数,以这种方式访问由CMutex
对象控制的资源,首先要在资源访问的成员函数中创建CSingleLock类型或CMultiLock类型的变量。然后调用封锁对象的Lock成员函数
(例如,
CSingleLock::Lock)。这样,你的线程要么就获得资源的访问权,以等待将要释放的资源,并获取访问权,要么就等待将要释放的资源,当超时
后,返回失败。在任何一种情况下,都可以在线程安全的模式下访问资源。若要释放这些资源,使用封锁对象的Unlock成员函数(例如,
CSingleLock::Unlock),或允许封锁对象越界。
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL,LPSECUR-ITY_ATTRIBUTES lpsaAttribute = NULL );
BInitiallyOwn:指定是否创建了CMutex 对象的线程最初拥有由互斥对象控制资源的访问权。
lpszName:
CMutex
对象的名称。若其它的互斥对象由同样的名称,那么必须提供lpszName,以便对象能够通过进程边界。若该值为NULL,那么互斥对象将没有命名。若该
名称匹配已经存在的互斥对象,那么构造函数将创建一个参考互斥对象名称的新CMutex
对象。若该名称匹配已存在的不是互斥对象的同步对象,那么构造过程将失败。
LpsaAttribute:对象的安全性
API函数:

HANDLE CreateMutex(
                LPSECURITY_ATTRIBUTES lpsa,     // 安全属性指针
                BOOL bInitialOwner,             // TR UE表示线程将拥有该信号量
                LPCTSTR lpName);                // 对象名

别的进程中可以用相同的互斥量名调用CreateMutex()和
OpenMutex()来打开该对象,这样互斥量就可用于多个进程中,线程通过WaitForSingleObject()和
WaitForMultipleObject()来等待一个互斥量,用ReleaseMutex()来释放一个互斥量。
11.Semaphores(信号量)
    一个CSemaphore类对象代表一个"信号"一个同步对象,它允许有限数目的线程在一个或多个进程中访问同一个资源。
一个Semaphore对象保持了对当前访问某一指定资源的线程的计数。
对于一个只能支持有限数目用户的共享资源来说,CSemaphore是很有用的。
CSemaphore
对象的当前计数是还可以允许的其它用户的数目。当这个计数达到零的时候,所有对这个由CSemaphore对象控制的资源的访问尝试都将被插入到一个系统
队列中等待,直到它们的时间用完或计数值不再为零。这个被控制的资源可以同时接受访问的最大用户数目是在CSemaphore对象的构造期间被指定的。
使
用CSemaphore对象的另一种方法是,将一个CSemaphore类型的变量添加到你想要控制的类中作为一个数据成员。在被控制对象的构造期间,调
用CSemaphore数据成员的构造函数来指定访问计数的初始值,访问计数的最大值,信号的名字(如果它要在整个进程中使用),以及需要的安全标志。要
访问由CSemaphore对象用这种方式控制的资源,首先要在你的资源的访问成员函数中创建一个CSingleLock类型或CMultiLock类型
的变量。然后调用加锁对象的Lock成员函数(例如,CSingleLock::Lock)。这时,你的线程将达到对资源的访问,等待资源被释放并访问
它,或者是在等待资源被释放的过程中超过了时间,对资源的访问失败。不管是哪一种情况,你的资源都是以一种线程安全(thread-safe)方式被访问
的。要释放资源,可以使用加锁对象的Unlock成员函数(例如,CSingleLock::Unlock),或者是让加锁对象超越范围。
CSemaphore(
LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName=
NULL,       LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
LInitialCount:信号的初始使用计数。必须是大于或等于0,并且小于或等于lMaxCount。
LMaxCount:信号的使用计数的最大值。必须大于0。
PstrName:
信号的名字。如果此信号将在整个进程中被访问,则必须提供这个名字。如果是NULL,则对象将是没有名字的。如果这个名字与一个已经存在的信号的名字一
样,则构造函数创建一个新的CSemaphore对象,此对象引用具有这个名字的对象。如果这个名字与一个已经存在的但不是一个信号的同步对象的名字一
样,则构造函数会失败。
API使用:
HANDLE CreateSemaphore(
                LPSECURITY_ATTRIBUTES lpSecAttr,        // 安全属性指针
                LONG InitialCoutn,                       // 初始信号量数目
                LONG lMaxCount,                        // 允许的最大信号量数目
                LPCTSTR lpszSemName);                 // 信号量对象名指针

信号量数目大于0时,就处于信号状态。若lpszSemName参数为NULL时,信号量局限于一个进程的线程中;若给予一个字符串对象名,则其它进程也
可以使
用该信号量。别的进程中可以在调用CreateSemaphore()和OpenSemaphore()时把这个字符串名作参数,以打开该信号量,这样可
以实现多个进程间的同步。WaitForSingleObject()和WaitForMultipleObjects()来等待一个信号量。当完成同步
任务时,线程应用ReleaseSemaphore()来释放信号量:
BOOL ReleaseSemaphore(HANDLE hSema, LONG lReleaseCount, LPLONG  lpPrevious);
12.用户界面线程的创建:
创建用户界面线程时,必须首先从 CWinThread 派生类。必须使用 DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREAT宏声明并实现此类。
用户界面线程通常用于处理用户输入和响应用户事件。
13.进程的创建和终止;
调用CreateProcess函数创建新的进程,运行指定的程序。CreateProcess的原型如下:
BOOL CreateProcess(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
其中:
lpApplicationName指向包含了要运行模块名字的字符串。
lpCommandLine指向命令行字符串。
lpProcessAttributes描述进程的安全性属性,NT下有用。
lpThreadAttributes描述进程初始线程(主线程)的安全性属性,NT下有用。
bInHeritHandles
表示子进程(被创建的进程)是否可以继承父进程的句柄。可以继承的句柄有线程句柄、有名或无名管道、互斥对象、事件、信号量、映像文件、普通文件和通讯端
口等;还有一些句柄不能被继承,如内存句柄、DLL实例句柄、GDI句柄、URER句柄等等。
子进程继承的句柄由父进程通过命令行方式或者进程间通讯(IPC)方式由父进程传递给它。
dwCreationFlags表示创建进程的优先级类别和进程的类型。创建进程的类型分控制台进程、调试进程等;
优先级类别用来控制进程的优先级别,分Idle、Normal、High、Real_time四个类别。
lpEnviroment指向环境变量块,环境变量可以被子进程继承。
lpCurrentDirectory指向表示当前目录的字符串,当前目录可以继承。
lpStartupInfo指向StartupInfo结构,控制进程的主窗口的出现方式。
lpProcessInformation指向PROCESS_INFORMATION结构,用来存储返回的进程信息。
从其参数可以看出创建一个新的进程需要指定什么信息。
从上面的解释可以看出,一个进程包含了很多信息。若进程创建成功的话,返回一个进程信息结构类型的指针。进程信息结构如下:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
}PROCESS_INFORMATION;
进程信息结构包括进程句柄,主线程句柄,进程ID,主线程ID。
进程的终止
进程在以下情况下终止:
调用ExitProcess结束进程;
进程的主线程返回,隐含地调用ExitProcess导致进程结束;
进程的最后一个线程终止;
调用TerminateProcess终止进程。
当要结束一个进程时,发送WM_QUIT消息给主窗口,当然也可以从它的任一线程调用ExitProcess。

9.边界 测试
在测试用例部分已经说过,设计测试用例时要考虑边界输入和非法输入,这里统称为特殊输入,程序员在编写代码时也要考虑特殊输入,要为特殊输入编写处理代
码。在实际工作中,程序员没有考虑到某些特殊输入是很常见的,这也是程序错误的一个重要来源。不幸的是,如果编写代码和建立测试用例时都没有考虑这些输
入,那么白盒覆盖也不能自动发现,因为白盒覆盖是以代码为基础的,如果相应的代码根本不存在,白盒覆盖当然不会告诉用户"某某代码未覆盖"。

  特殊输入通常与数据类型有关,例如,如果一个参数是指针,空指针就是一个特殊输入,对于一个整数类型,最大值、最小值、0、1、-1都可以算是特殊输
入;另一方面,当输入特殊数据时,如果程序未作合适的处理,运行结果常常是产生异常。根据这两个条件,如果预先为各个数据类型定义特殊值,然后由测试工具
自动生成测试用例,使用这些特殊值或其组合作为输入数据进行测试,通常可以发现"未处理特殊输入"而产生的程序错误,这就是边界测试。