kenlistian

厚积薄发. 勤为桨,思为帆

   :: 首页 :: 新随笔 ::  :: 聚合  :: 管理 ::
  73 随笔 :: 4 文章 :: 22 评论 :: 0 Trackbacks

2009年3月22日 #

采用PostThreadMessage即可
BOOL PostThreadMessage(
  DWORD idThread,     //线程ID,通过创建线程后的id
  UINT  Msg,           //消息id
  WPARAM wParam,    
  LPARAM lParam);
然后在线程通过GetMessage or PeekMessage去获取该消息.

代码片段如下:
unsigned int CALLBACK thread_func(LPVOID lp)
{
while(1)
{
MSG msg;
while (GetMessage(&msg, NULL,  0, 0)) 
//while (PeekMessage(&msg, NULL,  0, 0,PM_REMOVE)) 
switch(msg.message) 
case WM_MYMESSAGE:
printf("\n *thread_func1:%d", msg.wParam);
break;
}
        } 
}

其中发送线程片段如下:
UINT dwId
_beginthreadex(NULL, 0, thread_func, NULL, 0, &dwId);
        ...



附:
GetMessage(LPMSG lpMsg,  HWND hWnd,   UINT wMsgFilterMin,   UINT wMsgFilterMax)
PeekMessage(LPMSG lpMsg,  HWND hWnd,   UINT wMsgFilterMin,   UINT wMsgFilterMax,UINT wRemoveMsg)

    参数wRemoveMsg的作用是指定消息获取的方式,如果设为PM_NOREMOVE,那么消息将不会从消息队列中被移出,如果设为PM_REMOVE,那么消息将会从消息队列中被移出;

    两个函数主要有以下两个区别: 
    1.GetMessage将等到有合适的消息时才返回,而PeekMessage只是撇一下消息队列。(GetMessage 处于挂起等待消息来,而PeekMessage则不管有不有消息都返回)
    2.GetMessage会将消息从队列中删除,而PeekMessage可以设置最后一个参数wRemoveMsg来决定是否将消息保留在队列中。(如果保留在队列中,最好立即处理)

      在Windows的内部,GetMessage和PeekMessage执行着相同的代码。而两者最大的不同之处则体现在没有任何消息返回到应用程序的情况下。在此种情况下,PeekMessage会返回一个空值到应用程序,GetMessage会在此时让应用程序休眠。
(如果在线程中使用,用GetMessage or PeekMessage都无所谓,不需要考虑消息队列的remove,消息队列在各个线程中不过是个拷贝?而已.)


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1573835
posted @ 2009-03-22 13:06 kenlistian 阅读(2528) | 评论 (0)编辑 收藏

2009年3月17日 #

  该函数等待handle信号发来,它的好处在于可以等待多个信号发来.
原型:
DWORD WaitForMultipleObjects(
 DWORD nCount,
 const HANDLE* lpHandles,
 BOOL bWaitAll,
 DWORD dwMilliseconds
);

 第一个参数表示信号数目.
当设置bWaitAll = True,表示必须其信号必须都达到nCount才可以下一步.而不是lpHandle数组的信号都到.
    同时,如果lpHandle的某个信号SetEvent多次,是不会累计到nCount中,也就是说,当多个线程在处理完以后,可以在各自的线程体必须调用不同的event句柄来发信号,它才会被累计达到nCount后解锁进入下一步.
posted @ 2009-03-17 18:50 kenlistian 阅读(826) | 评论 (0)编辑 收藏

2009年3月13日 #

来自http://blog.cnii.com.cn/?75821/viewspace-19610.html的开发filter介绍

 pdf

posted @ 2009-03-13 19:45 kenlistian| 编辑 收藏

2009年3月9日 #

对于pin的连接过程,总结下.

   1.
   应用程序通过调用filter graph 管理器方法来连接filter.
   应用程序调用IFilterGraph::ConnectDirect
               IGraphBuilder::Connect来指定不同的filter直接连接,
              也可用IGraphBuilder::RenderFile自动实现连接
   应用程序可以通过IFilterGraph::AddFilter将filter 添加graph中,
   当一个filter被添加到graph中时,filter图表管理器通过IBaseFilter::JoinFilterGraph来通知filter.

   这点说明, 不是filter的直接连接函数相互链接,而是在以上内部调用实现的.

   2. 考虑到以前描述
              FilterA ---->FilterB
      的连接检查媒体类型 逻辑基本就是这样:

 循环FilterA的输出pin,再循环FilterB的输入Pin媒体类型是否和pmt媒体类型
 匹配

 for (j = 0 ; j<FilterB.PinIn.MediaTypeCount; j++)
 {
             if (FilterB.PinIn.MediaType[j] = pmt )
      {
            if(FilterA.PinIn.ReceiveConnection(FilterA.PinOut, FilterB.MediaType[i]) = OK)
                   return TRUE;
      }

        }

        for (i= 0; i< FilterA.PinOut.MediaTypeCount; i++)
 {
      if (FilterA.PinOut.MediaType[i] 是否在FilterB.PinIn中是否支持)
        if (FilterA.PinIn.ReceiveConnection(FilterA.PinOut, FilterA.MediaType[i]) = OK)
             return TRUE;
 }

  在实现上,调用次序以下过程:


filterGraph首先调用FilterA.PinOut::Connect().

FilterA.IPinOut::Connect()
       原型:IPin::Connect(IPin* pReceivePin, const AM_MEDIA_TYPE * pmt)     
       该Connect参数为
            pReceivePin 为 FilterB的输入Pin,
            pmt         是FilterA的当前媒体类型.
 


      在内部调用(主要)
         hr = AgreeMediaType(pReceivePin, pmt);
             检查pReceivePin 有否pmt的媒体类型.

  有,则自然ok
  没有,失败,退出该函数.
         则在AgreeMediaType做了以上逻辑循环.

  
   IPin::AgreeMediaType函数处理如下:
 
      1.判断pmt 是否是完全媒体类型,是则按全媒体类型模式出来
      2.非完全媒体类型
     IPin::EnumMediaTypes(IEnumMediaTypes** pEnum)
      获取枚举指针(指向Pin中的媒体类型集合).
   
           先枚举filterB的输入Pin的媒体类型的枚举集,
         调用TryMediaTypes 函数去判断是否匹配.
    还不匹配,取出FilterA的枚举类指针.再调用TryMediaTyes
    

    IPin::TryMediaType()处理
    原型:
   HRESULT CBasePin::TryMediaTypes(IPin*pReceivePin, const CMediaType*pmt,
         IEnumMediaType *pEnum)

    在该函数处理:
         for (pmt in 所有该枚举集中的枚举媒体类型 )
  {
     AttemptConnect(pReceivePin, pmt)
  }


    在AttemptConnection中调用
    CBasePin::AttemptConnection(IPin* pReceivePin, const CMediaType*pmt)
   
      检查FilterA 的CheckConnect(pReceivePin)
      FilterA的PInOut::CheckMediaType(pmt)
           ok,return
      FilterA的PinOut::SetMediaType(pmt)
       
      pReceivePin->ReceiveConnection(...)  (filterB 的PinIn)
           ok,return
      FilterA的PinOut::CompleteConnect(pReceivePin)

posted @ 2009-03-09 22:36 kenlistian 阅读(680) | 评论 (0)编辑 收藏

2009年2月6日 #


自定义消息处理
(不知道在wxpython,wxruby如何自定义类型消息?
最好装个wxpython,在其demo现场编写现场查看结果)

步骤如下:

1.在宏里处理如下:

a.

BEGIN_DECLARE_EVENT_TYPES()
    DECLARE_EVENT_TYPE(wxEVT_MYCOMMAND, xxxx)
END_DECLARE_EVENT_TYPES()


  xxxx 为自定义数字,不过翻到wxwidget内部,其宏定义中该value好像
  没用!(...)

b. 定义wxEvT_MYCOMMAND 标识

  DEFINE_EVENT_TYPE(wxEVT_MYMYCOMMAND)

 

c. 定义宏EVT_MYCOMMAND
   格式中把自定义标识改为自己即可.拷贝.

 

#define EVT_MYCOMMAND(id, fn) \
    DECLARE_EVENT_TABLE_ENTRY( \
        wxEVT_MYCOMMAND, id, wxID_ANY, \
        (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent( wxCommandEventFunction, 
&fn ), \
        (wxObject 
*) NULL \
    ),

 


2.在BEGIN_EVENT_TABLE表中自定义映射.
 

   EVT_MY_CUSTOM_COMMAND(wxID_ANY, MyFrame::OnMyfunction)

 

3.处理投递

    自定义消息发送,this 一般是接受消息handler,也可其它wxEvtHandler.

    wxCommandEvent myEvent(wxEVT_MY_CUSTOM_COMMAND);
    wxPostEvent(
this, myEvent);

 

4.自定义消息处理函数
   必须符合wxCommandEventFunction格式,既为如下:

     void OnMyfunction(wxCommandEvent& event);

 

 

posted @ 2009-02-06 12:06 kenlistian 阅读(2679) | 评论 (3)编辑 收藏

2009年2月3日 #

来源:http://www.oulan.com/com/2008/07/wxwidgets.html
[Commerial]
 DialogBlocks   http://www.anthemion.co.uk/dialogblocks/
 wxDesigner      http://www.roebling.de/

[Free]
wxGlade    http://www.roebling.de/
VisualWx  http://visualwx.altervista.org/
XRCed      http://xrced.sourceforge.net/

wiki: http://wiki.wxformbuilder.org/
官网: http://wxformbuilder.org/

推荐直接用DialogBlocks, 至于版本问题,没办法,先用着再说.
 Dialogblock是美国Anthemion公司开发的商业软件,如果只是简单
的包含控件,则免费使用.

另:
在现在编程中,利用界面设计工具把界面资源以xrc方式存放,
在程序中调用是一种趋势.



posted @ 2009-02-03 11:35 kenlistian 阅读(807) | 评论 (0)编辑 收藏

2009年1月5日 #


几个格式细节备记(混)


typedef struct  _MediaType {
    GUID      majortype; 
    GUID      subtype;
    BOOL      bFixedSizeSamples;
    BOOL      bTemporalCompression;
    ULONG     lSampleSize;
    GUID      formattype;
    IUnknown  *pUnk;                  //not use
    ULONG     cbFormat;
    BYTE *pbFormat;
} AM_MEDIA_TYPE;

主要有
   majortype  媒体类型大致说明
   subtype    更一步的细致说明
   formattype 
      包括有以下:其对应的不同的数据格式
       FORMAT_None
       FORMAT_DvInfo
       FORMAT_MPEGVideo
       FORMAT_MPEG2Video
       FORMAT_VideoInfo
       FORMAT_VideoInfo2 
       FORMAT_WaveFormatEx  
       GUID_NULL

  cbForamt成员指定了格式块pbFormat的大小.
  pbFormat指针指向格式子块。
            pbFormat是一个void*的指针,因为格式块会因为媒体类型
            的不同而有不同的指向。如音频填充的是WAVEFORMATEX结构
     数据.

     可以从中取出传来的数据格式。
    

 

//TWaveFormatEx 结构:
TWaveFormatEx = packed record
  wFormatTag: Word;       {指定格式类型; 默认 WAVE_FORMAT_PCM = 1;}
  nChannels: Word;        {指出波形数据的通道数; 单声道为 1, 立体声为 2}
  nSamplesPerSec: DWORD;  {指定样本速率(每秒的样本数)}一般为8000
  nAvgBytesPerSec: DWORD; {指定数据传输的平均速率(每秒的字节数)} 每秒的字节数:
  nBlockAlign: Word;      {指定块对齐(单位字节), 块对齐是数据的最小单位}
  wBitsPerSample: Word;   {采样大小(字节)}每个样本的BIT数目,一般为16
  cbSize: Word;           {应该是该结构的大小}
end;

nChannels       :   对于pcm,其nchannels不超过2,对于非pcm格式,则超过2.
nSamplesPerSec  :   通常为8kHz, 11.025 kHz, 22.05 kHz, and 44.1 kHz.
nAvgBytesPerSec :   每秒传送字节数 = nSamplesPerSec * nBlockAlign
nBlockAlign     :   对齐字节  = nChannels * wBitsPerSample / 8
                    就是表示一个样本的最小字节.
wBitsPerSample  :   在格式默认情况下,一般为8,16,表示的是样本的bit 数

对于一个8位,11k传输的立体声则
nChannels  = 2
nSamplesPerSec(每秒的样本数) = 11025  就是取样数
nBlockAlign  = 2 * 8 / 8= 2           对齐字节,最小样本字节数
nAvgBytesPerSec = 11025 *  2 = 22050
wBitsPerSample  = 8
 

 下面的图列清楚从另一个方面表达样本
 样本1  样本2 ...n
8位单声道 0声道 0声道
8位立体声 0声道L         1声道R  0声道L  1声R道
16位单声道 0声道(低字节)    0声道(高字节) 0声道(低字节)    0声道(高字节)
16位立体声

0声道(低字节)0声道(高字节)1声道(低) 1声道(高) 

同左

                                                                                                                     
                                        
                                   
---------

waveform-audio 缓存格式     
  typedef   struct   {    
          LPSTR     lpData;           //内存指针,放置音频pcm样本数据
          DWORD     dwBufferLength;   //长度    
          DWORD     dwBytesRecorded;  //已录音的字节长度  
          DWORD     dwUser;    
          DWORD     dwFlags;    
          DWORD     dwLoops;           //循环次数  
          struct   wavehdr_tag* lpNext; //保留    
          DWORD     reserved;           //保留
  }   WAVEHDR; 
 
  其中lpdata 即为pcm格式样本数据。
 
采样大小为8位,则采样的动态范围为20*log(256)分贝=48db。
样本大小为16位,则采样动态范围为20*log(65536)大约是96分贝

振幅大小:   20*log(A1/A2)分贝,A1,A2为两个声音的振幅。
则对于的音频:
          8位       20 * lg( lpData[0] /256)
   16位      20 * lg( lpData[0]--lpData[1] / 65536)
 考虑到单双道,还需要相应取出左右声道的值。
 考虑到lg求值为负48至0之间,则在实际转换中需要+48or96.


样本大小  数据格式      最大值  最小值
8位PCM    unsigned int   256     0
16位PCM   int            32767  -32767

 8位音频是unsigned 存放波形,取振幅要-127.
 而16位因其存放为int 类型,直接套用公式.
 

audiometer左右声道音量探测程序(参考代码(delphi版

 

posted @ 2009-01-05 14:16 kenlistian 阅读(780) | 评论 (0)编辑 收藏

2008年12月28日 #

RealMediaSplitter.ax

Source(.rm/.rmvb)->RealMediaSplitter->Video(Audio)->real a/v decoder->A/V   Render

其实复原并查找该ax很简单,安装暴风后在graphedit中,拖一个rm文件,
查看解码过程,然后在filter列表中找出该ax文件名,即可构建到自己的
播放器中。
posted @ 2008-12-28 11:21 kenlistian| 编辑 收藏

2008年12月27日 #

1、配置DirectDound的开发环境
包含以下
#include <mmsystem.h>
#include <dsound.h>
添加Dsound.lib库
comctl32.lib dxerr9.lib winmm.lib dsound.lib dxguid.lib odbc32.lib odbccp32.lib,

2 DiectDound几个对象
 
  创建一个设备对象,后通过设备对象创建缓冲区对象。
  辅助缓冲区由应用程序创建和管理,DirectSound会自动地创建和管理主缓冲区,


3 播放音频文件开发的基本流程

 a 创建一个设备对象,设置设备对象的协作度。

    调用DirectSoundCreat8创建一个支持IDirectSound8接口的对象,
      这个对象通常代表缺省的播放设备。
   
       如果没有声音输出设备,这个函数就返回error,或者,在VXD驱动程序下,
      如果声音输出设备正被某个应用程序通过waveform格式的api函数所控制,
      该函数也返回error。 

LPDIRECTSOUND8 lpDirectSound; 
HRESULT hr 
= DirectSoundCreate8(NULL,&lpDirectSound, NULL));

      当创建完设备对象后,调用IDirectSound8::SetCooperativeLevel来设置
      协作度,否则听不到声音.

  b.创建一个辅助Buffer,也叫后备缓冲区
      (IDirectSound8::CreateSoundBuffer)
      创建的buffer称作辅助缓冲区,Direcsound通过把几个后备缓冲区的声音
      混合到主缓冲区中,然后输出到声音输出设备上,达到混音的效果。

  c. 获取PCM类型的数据
   
  将WAV文件或者其他资源的数据读取到缓冲区中。

  d. 将数据读取到缓冲区
       其中用到以下来锁缓冲区。
          IDirectSoundBuffer8::Lock
          IDirectSoundBuffer8::Unlock.

  e. 播放缓冲区中的数据
       IDirectSoundBuffer8::Play  播放缓冲区中的音频数据,
       IDirectSoundBuffer8::Stop 暂停播放数据,
       
       获取或者设置正在播放的音频的音量的大小
   IDirectSoundBuffer8::GetVolume
         IDirectSoundBuffer8::SetVolume

      获取设置音频播放的频率
    IDirectSoundBuffer8::GetFrequency
         IDirectSoundBuffer8::SetFrequency   
              主缓冲区的频率不允许改动,

      设置音频在左右声道播放的位置
          IDirectSoundBuffer8::GetPan
          IDirectSoundBuffer8::SetPan

  包含全部音频数据的缓冲区我们称为静态的缓冲区,
        尽管不同的声音可能会反复使用同一个内存buffer,但静态缓冲区的数据只写入一次。

        静态缓冲区只填充一次数据,然后就可以play,
 
  给静态缓冲区加载数据分下面几个步骤
  1、用IDirectSoundBuffer8::Lock函数来锁定所有的内存,
            指定你锁定内存中你开始写入数据的偏移位置,并且取回该偏移位置的地址。
  2、采用标准的数据copy方法,将音频数据复制到返回的地址。
  3、调用IDirectSoundBuffer8::Unlock.,解锁该地址。


用static buffer 播放wav方法

  

 

LPDIRECTSOUNDBUFFER8    g_pDSBuffer8 = NULL; //buffer
LPDIRECTSOUND8                   g_pDsd = NULL; //dsound
CWaveFile                                   *g_pWaveFile= NULL;

//初始化DirectSound工作
HRESULT hr;
if(FAILED(hr = DirectSoundCreate8(NULL,&g_pDsd,NULL)))
 
return FALSE;

//设置设备的协作度
if(FAILED(hr = g_pDsd->SetCooperativeLevel(m_hWnd,DSSCL_PRIORITY)))
 
return FALSE;

g_pWaveFile 
= new CWaveFile;
g_pWaveFile
->Open(_T("c:\\test.wav"), NULL, WAVEFILE_READ);

DSBUFFERDESC dsbd;
ZeroMemory( 
&dsbd, sizeof(DSBUFFERDESC) );
dsbd.dwSize 
= sizeof(DSBUFFERDESC);
dsbd.dwFlags 
= DSBCAPS_GLOBALFOCUS               //设置主播
                                | DSBCAPS_CTRLFX
                                
| DSBCAPS_CTRLPOSITIONNOTIFY 
                                
| DSBCAPS_GETCURRENTPOSITION2;

dsbd.dwBufferBytes 
= g_pWaveFile->GetSize();    
dsbd.lpwfxFormat 
= g_pWaveFile->m_pwfx;

LPDIRECTSOUNDBUFFER lPBuffer;

//创建辅助缓冲区对象
if(FAILED(hr = g_pDsd->CreateSoundBuffer(&dsbd,&lpbuffer,NULL)))
 
return ;
if( FAILED(hr = lpbuffer->QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*&g_pDSBuffer8) ) )
 
return ; 
lpbuffer
->Release();

//播放
LPVOID lplockbuf;
DWORD len;
DWORD dwWrite;

g_pDSBuffer8
->Lock(0,0&lplockbuf,  &len,  NULL,  NULL, DSBLOCK_ENTIREBUFFER);

//g_pWaveFile 声音写入到lplockbuf所指地址
g_pWaveFile->Read((BYTE*)lplockbuf, len, &dwWrite);

g_pDSBuffer8
->Unlock(lplockbuf,len,NULL,0);

g_pDSBuffer8
->SetCurrentPosition(0);

g_pDSBuffer8
->Play(0,0,DSBPLAY_LOOPING);




   f  流缓冲区播放超大型的wave文件

   流缓冲区就是播放那些比较长的音频文件,边播放,边填充DirectSound缓冲区。

   DirectSound的通知机制
      因为Stream buffer 大小只够容纳一部分数据,在播放完缓冲区中的数据后,
      DirectSound就会通知应用程序,将新的数据填充到DirectSound的缓冲区中。

 

 

#define MAX_AUDIO_BUF 4                        //设置4个buffer
#define BUFFERNOTIFYSIZE 1920               //每个buffer尺寸为1920

BOOL g_bPlaying     
= FALSE;                                            //是否正在播放
LPDIRECTSOUNDNOTIFY8 g_pDSNotify = NULL; 
DSBPOSITIONNOTIFY        g_aPosNotify[MAX_AUDIO_BUF];     
//设置通知标志的数组

HANDLE g_event[MAX_AUDIO_BUF];
for(int i =0; i< MAX_AUDIO_BUF;i++)
{
 g_aPosNotify[i].dwOffset 
= i* BUFFERNOTIFYSIZE ;   
    g_aPosNotify[i].hEventNotify 
= g_event[i];
}

if(FAILED(hr = g_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID *&g_pDSNotify )))
 
return ;

g_pDSNotify
->SetNotificationPositions(MAX_AUDIO_BUF,g_aPosNotify);

g_pDSNotify
->Release();


     当DirectSound播放到buffer的1920,3840,5760,7680等位置时,
Directsound就会通知应用程序,将g_event,设置为通知态;
  应用程序就通过WaitForMultipleObjects 函数等待DirectSound的通知,
将数据填充到DirectSoun的辅助缓冲区。

 


 

posted @ 2008-12-27 18:30 kenlistian 阅读(2872) | 评论 (1)编辑 收藏

2008年12月15日 #



  Moniker 是个接口。
posted @ 2008-12-15 10:07 kenlistian 阅读(946) | 评论 (0)编辑 收藏

2008年12月10日 #



  测试神龙卡路数,其中pDecoderFilter要对应不同的设备。
         int iDev = 0;
        
for (int i = 0; i < 10 ; i++)
        
{
            
//这里可以判断有几路神龙卡
            hr = CoCreateInstance(CLSID_RM_MPEG2_VIDEO_SUBPICTURE, NULL, CLSCTX_INPROC_SERVER,
                IID_IBaseFilter, (LPVOID 
*&pDecoderFilter);
            
if(hr != S_OK) 
                
break;    
            
            iDev
++;
        }


        
char sMsg[256];
        sprintf(sMsg,
"%d", iDev);        
        MessageBox(ghApp, sMsg, 
"Msg", MB_OK);
posted @ 2008-12-10 10:45 kenlistian 阅读(473) | 评论 (0)编辑 收藏

2008年12月9日 #

1.采用系统设备枚举器来枚举.
   根据硬件系统种类来枚举系统中注册的filter。
   每一种不同的硬件可能有自己的filter,也可能所有的硬件设备共用filter。

 系统设备枚举器是根据不同的种类来创建的,如,音频压缩,视频捕捉。
  不同种类的枚举器对于每一种设备返回一个独立的名称(moniker)。

下面的步骤是使用设备枚举器来获取设备:

1) 创建枚举器组件,CLSID为CLSID_SystemDeviceEnum
2) 指定某一种类型设备,获取该种类枚举器
    通过ICreateDevEnum::CreateClassEnumerator获取某一种类的枚举器,
    该函数返回一个IEnumMoniker接口指针,
    通过检查返回值是否为S_OK来判断是否获取到该种类枚举器.
3) 用IEnumMoniker::Next枚举每一个moniker。
     这个方法返回一个IMoniker接口指针。
4) 通过IMoniker::BindToStorage获取设备的名称

大致例子如下:
   

HRESULT hr;
ICreateDevEnum 
*pSysDevEnum = NULL;
hr 
= CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
          IID_ICreateDevEnum, (
void **)&pSysDevEnum);
if (FAILED(hr)) return hr;

//获取video compressor 种类枚举器 
IEnumMoniker *pEnumCat = NULL;
hr 
= pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pEnumCat,0);
if (hr == S_OK){

 IMoniker 
*pMoniker = NULL;
 ULONG cFetched;

 
while(pEnumCat->Next(1&pMoniker, &cFetched) == S_OK){

  IPropertyBag 
*pPropBag;
  hr 
= pMoniker->BindToStorage(00, IID_IPropertyBag, (void **)&pPropBag);
  
if (SUCCEEDED(hr))
  
{
   
// 获取设备名称
   VARIANT varName;
   VariantInit(
&varName);
   hr 
= pPropBag->Read(L"FriendlyName"&varName, 0);
   
if (SUCCEEDED(hr)){   
        
//获取设备名称
   }

   VariantClear(
&varName);
  }

  pMoniker
->Release();
 }

 pEnumCat
->Release();
}

pSysDevEnum
->Release();


2.采用Filer Mapper。
   类似条件查询。

   比系统设备枚举器(System Device Enumerator)的效率要低一些。
   当要枚举某特定种类的filter时,应采用系统设备枚举器方法,但搜索支持某种
   媒体类型的filter时,用filter mapper.

   Filter Mapper 通过IFilerMapper2接口搜索接口,
   通过调用IFilterMapper2::EnumMatchingFilters方法,传递一些参数来定义搜索条件,
   返回一个适合条件的filter的枚举器,

   返回的是一个IEnumMoniker接口,并对于每个适合的filter都提供一个单独的moniker。

例子:

//枚举所有的支持DV,并且至少有一个输出pin的filter,
//这个filter支持任何媒体类型。
IFilterMapper2 *pMapper = NULL;
IEnumMoniker 
*pEnum = NULL;

hr 
=CoCreateInstance( CLSID_FilterMapper2,NULL, CLSCTX_INPROC, IID_IFilterMapper2, (void **&pMapper);
if (FAILED(hr)) {   }

GUID arrayInTypes[
2];
arrayInTypes[
0= MEDIATYPE_Video;
arrayInTypes[
1= MEDIASUBTYPE_dvsd;
hr 
= pMapper->EnumMatchingFilters(
             
&pEnum,
                 
0,           // Reserved.
             TRUE,        // Use exact match?
             MERIT_DO_NOT_USE+1,  // Minimum merit.
             TRUE,        // At least one input pin?
             1,                 // Number of major type/subtype pairs for input.
            arrayInTypes,     // Array of major type/subtype pairs for input.
            NULL,   // Input medium.
            NULL,   // Input pin category.
            FALSE,  // Must be a renderer?
            TRUE,   // At least one output pin?
             0,      // Number of major type/subtype pairs for output.
              NULL,   // Array of major type/subtype pairs for output.
           NULL,   // Output medium.
             NULL);  // Output pin category.
                   
IMoniker 
*pMoniker;
ULONG cFetched; 

//枚举filter,
while(pEnumCat->Next(1&pMoniker, &cFetched) == S_OK)
{
  IPropertyBag 
*pPropBag = NULL;
  hr 
= pMoniker->BindToStorage(00, IID_IPropertyBag, (void **)&pPropBag);
  
if (SUCCEEDED(hr))
  
{
            VARIANT varName;
     VariantInit(
&varName);
     hr 
= pPropBag->Read(L"FriendlyName"&varName, 0);
   
if (SUCCEEDED(hr))
   
{
                   
//处理
    }

    VariantClear(
&varName);
 }

 pMoniker
->Release();
}


pMapper
->Release();
pEnum
->Release();

注:
 * 有关directshow的api函数,估计只能在网上搜索,至于下载的msdn没有,或者下个dssdk2002版本的help才能有。
 * 在directshow中,关于capture dev,有专门的demo演示如何获取音视设备。这里只是
明了获取设备的方法。以备了解。

posted @ 2008-12-09 11:43 kenlistian 阅读(1395) | 评论 (0)编辑 收藏

2008年12月8日 #

使用DirectShow写一个音频捕捉例子

DirectShow对硬件的支持是通过特定的包装Filter来实现的。
声卡使用的是Audio Capture Filter,Filter内部使用以waveIn开头
的一套API实现(如waveInOpen等)。
运行GraphEdit,插入Filter时,在“Audio Capture Sources”目录下,
就能看到所有代表本地机器上的声卡的各个Filter(装了几张声卡,就会有几个Filter)


DirectShow加入一个硬件Filter,是靠“枚举”;声卡Filter也不例外。
代表声卡的Filter都注册在CLSID_AudioInputDeviceCategory目录下,
使用系统设备枚举器枚举这个目录,就能发现要创建的声卡对象。
 (如何枚举这里就不再赘述了。)
当成功加入声卡Filter后,接下去的问题就是要将这个Filter与其他Filter相连。


 想捕捉生成一个Wave文件,采用过滤器的勾连如下
     声卡filter--->Wave Dest Filter  ---->File Writer Filter
 Wave Dest Filter是微软DirectX SDK自带的过滤器
    其功能是,当结束捕捉时,往Wave文件中写入一个文件头信息。

 Filte Write Filter 是微软系统过滤器。
用graphedit可以勾连后测试下。


//采用程序来连接过滤器的大致方法:
//没有处理错误
void BuildAudioCaptureGraph(void)
{
  IBaseFilter *pSrc = NULL,            //捕捉音频设备
               *pWaveDest = NULL,         //处理音频过滤器
        *pWriter = NULL;                      //产生文件过滤器

  IFileSinkFilter *pSink= NULL;
  IGraphBuilder *pGraph;

  // Create the Filter Graph Manager.
  CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,     IID_IGraphBuilder, (void**)&pGraph);

  // Add the audio capture filter.
     //这里省略了枚举设备处理
  FindAudioCapture(&pSrc);
 
  // audio capture devices and picks one.
  pGraph->AddFilter(pSrc, L"Capture");

  // Add the WavDest and the File Writer.
  AddFilterByClsid(pGraph, L"WavDest", CLSID_WavDest, &pWavDest);
  AddFilterByClsid(pGraph, L"File Writer", CLSID_FileWriter, &pWriter);

  //是writer接口中属性
  pWriter->QueryInterface(IID_IFileSinkFilter, (void**)&pSink);
  pSink->SetFileName(L"C:\test.wav", NULL);

  //连接filter
  ConnectTwoFilters(pGraph, pSrc, pWavDest);
  ConnectTwoFilters(pGraph, pWavDest, pWriter);
}


 

posted @ 2008-12-08 20:21 kenlistian 阅读(769) | 评论 (0)编辑 收藏

DirectShow技术是建立在DirectDraw和DirectSound组件基础之上的,
它通过DirectDraw对显卡进行控制以显示视频,
通过DirectSound对声卡进行控制以播放声音。

DirectShow功能实现:

1.可提供高质量的多媒体流的捕获和回放功能;
2.支持多种媒体格式,包括ASF(Advanced Systems Format),MPEG(Motion Picture Experts Group),AVI(Audio-Video Interleaved),MP3(MPEG Audio Layer-3)和WAV声音文件;
3.可从硬件上捕获媒体数据流;
4.可自动检测并使用视频和音频加速硬件。

故,DirectShow是用于多媒体应用开发。(其实就是一个软编码(or解码))
它充分发挥媒体的性能,提高运行速度,可以简化媒体播放、媒体间的格式转换
和媒体捕获等工作。同时,它还具有极大的可扩展性和灵活性,可以由用户自己
创建组件,并将这个组件加入DirectShow结构中以支持新的格式或特殊的效果。

应用程序与DirectShow组件以及DirectShow所支持的软硬件之间的关系
如图1

 


二。概念
1.过滤器
过滤器分为以下几种类型:
 a 源过滤器(source filter):
    源过滤器引入数据到过滤器图表中,数据来源可以是文件、网络、照相机等。
    不同的源过滤器处理不同类型的数据源。

 b 变换过滤器(transform filter):
   变换过滤器的工作是获取输入流,处理数据,并生成输出流。
   变换过滤器对数据的处理包括编解码、格式转换、压缩解压缩等。

 c 提交过滤器(renderer filter):
    接收数据并把数据提交给外设。

 d 分割过滤器(splitter filter):
    把输入流分割成多个输出。
    如,AVI分割过滤器把一个AVI格式的字节流分割成视频流和音频流。

 e 混合过滤器(mux filter):
    把多个输入组合成一个单独的数据流。
    如,AVI混合过滤器把视频流和音频流合成一个AVI格式的字节流。
     过滤器的这些分类并不是绝对的,如一个ASF读过滤器(ASF Reader filter)
       既是一个源过滤器又是一个分割过滤器。


2  filter graph

    过滤器图表用来连接过滤器以控制媒体流,它也可以将数据返回给应用程序,
    并搜索所支持的过滤器。
    过滤器有三种可能的状态:运行、停止和暂停。
    暂停是一种中间状态,停止状态到运行状态必定经过暂停状态。
    暂停可以理解为数据就绪状态,是为了快速切换到运行状态而设计的。   
    在暂停状态下,数据线程是启动的,但被提交过滤器阻塞了。
   
    通常情况下,过滤器图表中所有过滤器的状态是一致的。

3. 引脚(pin)
 过滤器可以和一个或多个过滤器相连,
    连接的接口也是COM形式的,称为引脚。
   
    过滤器利用引脚在各个过滤器间传输数据。
    每个引脚都从Ipin这个COM对象派生出来的。
    每个引脚都是过滤器的私有对象,过滤器可以动态的创建引脚,销毁引脚,自由控制引脚的生存时间。
    引脚分输入引脚(Input pin)和输出引脚(Output pin)两种类型,
    两个相连的引脚必须是不同种类的,即输入引脚只能和输出引脚相连

 过滤器之间的连接(也就是引脚之间的连接),实际上是连接双方媒体类型(Media Type)协商的过程。(媒体类型,不完全媒体类型 再下一节有讲解)


   连接的大致过程为:
     如果调用连接函数时已经指定了完整的媒体类型,则用这个媒体类型进行连接,
  成功与否都结束连接过程;
      如果没有指定或不完全指定了媒体类型,
      则枚举过程见后面.其两个filter的连接设定如下.
    
                 Filter A     ------------------> Filter B



------------------------------------------------------------- ------------------

说明:
媒体类型(Media Type)
    两个过滤器相连时,必须使用一致的媒体类型,否则这两个过滤器就不能相连。

    媒体类型能识别上一级过滤器传送给下一级过滤器的数据类型,并对数据进行分类。
   媒体类型的结构  AM_MEDIA_TYPE
 
  AM_MEDIA_TYPE由三部分组成:
      Major type
      Subtype
      Format type
   
   都使用GUID 来唯一标示
    
     Major type主要定性描述一种媒体类型,这种媒体类型可以是视频、音频、比特数据流或MIDI数据等;
     Subtype   进一步细化媒体类型,
              拿视频的说就是进一步指定是RGB-24,还是RGB-32,或是UYVY等;
     Format type则用一个结构更进一步细化媒体类型。

 媒体类型的三个部分都指定了某个具体的GUID值,则称这个媒体类型是完全指定的;
    媒体类型的三个部分中有任何一个值是GUID_NULL,则称这个媒体类型是不完全指定的。
    GUID_NULL起通配符作用


pinout和pinin的连接过程可以用下面逻辑语言表达.

1.如调用连接函数时已经指定了完整的Media type,则用这个Media type进行连接,
成功与否都结束连接过程;

2.如没有指定或不完全指定了Media type,
则如下:

  BOOL CheckFilterB_PinIn()
  {
       for(i = 0 ; i < FilterB.FPinIn.MediaTypeCount ; i++)
      {
           if (IsSameMediaType(FilterA.FPinOut,FilterB.FPinIn.MediaType[i]) = True)
          {
                 return TRUE;   //Pin之间的连接成功;
          }
       }
        return FALSE;          //在Input pin不支持该媒体类型,失败.
    }


  返回FALSE再枚举Output pin上的所有Media type,并逐一用这些Media type与Input pin进行连接。
  
   for(i = 0 ; i < FilterA.FPinOut.MediaTypeCount; i++)
   {
      if (CheckFilteB_PinIn(FilterA.FPinOut.MediaType[i]) = True )
      {
         return TRUE;
      }
   }
   return FALSE;  //filterA和filterB的连接失败.



 

posted @ 2008-12-08 12:50 kenlistian 阅读(1030) | 评论 (0)编辑 收藏

DirectShow 的几个接口说明
(1) IGraphBuilder接口
     用于构造Filter Graph的接口,建立和管理一系列的Filter,过滤和处理源媒体流。
(2) IMediaControl接口
     用于控制多媒体流在Filter Graph中的流动,如流的启动和停止。
(3) IMediaEvent接口
     用于捕获播放过程中发生的事件,并通知应用程序,如EC_COMPLETE等。
(4) IVideoWindow接口
     用于控制视频窗口的属性。
(5) IMeadiaSeeking接口
     用于查找媒体的接口,定位流媒体,控制多媒体数据播放提供精确控制。
(6) IBaseFilter接口
    从ImediaFilter接口继承,用来定义一个具体的过滤器指针,并对多媒体数据进行处理。
(7) IPin接口
    用于管理两个过滤器之间的Pin,从而连接过滤器。
(8) IsampleGrabberCB接口
   是Sample Grabber过滤器的一个接口,用于当流媒体数据通过过滤器时进行采样以获得帧图象。


Filter必须加入到Filter Graph并接入到工作链路中才能发挥作用。
如想绕过Filter Graph而直接使用Filter实现的功能模块,那就要将Filter功能
移植成DirectX媒体对象(DMO)。
Filter有3种状态:停止、暂停和运行。

Filter Chain是相互连接着的一条Filter链路,并且链路中的每个Filter全都有一个处于“已连接”状态的
输入Pin,至多有一个处于“已连接”状态的输出Pin,这条Filter链路中的数据流不依赖链路外的其他Filter。

Filter Chain通过IFilterChain接口来进行相关操作。
当Filter Graph处于运行状态下,Filter Chain可以在运行和停止状态之间切换;
当Filter Graph处于暂停状态下,Filter Chain可以在暂停和停止状态之间切换。
Filter Chain只有两种状态转换。

Filter的数据传送
Filter之间以Sample的形式传送数据。
Sample是一个封装了一定大小数据内存的COM组件。
用于数据传输的一般是输入pin上实现的IMemInputPin接口。

posted @ 2008-12-08 11:38 kenlistian 阅读(716) | 评论 (0)编辑 收藏

注意:
    以下配置均以vc6为主.

一、配置方法
1 先编译Samples\C++\DirectShow\BaseClasses中的工程文件。
注意工程的默认选项是 Debug_Unicode的,在非Unicode的工程中使用还需要编译Debug版本的。

 注意:
  一般用 ansi debug版.不怕麻烦把release也生成,取出该strmbasd.lib(strmbase.lib).
  放在dxsdk中的lib中.


2 需要设置好VC中目录的设置 include的目录中添加 C:\DXSDK\Samples\C++\DirectShow\BaseClasses Lib的目录中添加 C:\DXSDK\Samples\C++\DirectShow\BaseClasses\Debug
注意:
  建议把把classes 把头文件可以单独拷贝到dxsdk的include和lib中

3、使用DirectShow的工程需要添加下面两个头文件
#include   //DS接口、基类的定义
#include    //CComPtr模板的定义
#pragma comment(lib,"strmbasd.lib")
#pragma comment(lib,"winmm.lib")
#pragma comment(linker,"/NODEFAULTLIB:libcmtd.lib")

注意:
  VC自带的库中也有strmbasd.lib文件。
一定要保证连接到DS的库中,否则会出现 
strmbasd.lib(dllsetup.obj) : error LNK2001: unresolved external symbol "class CFactoryTemplate * g_Templates" (?g_Templates@@3PAVCFactoryTemplate@@A)
strmbasd.lib(dllentry.obj) : error LNK2001: unresolved external symbol "class CFactoryTemplate * g_Templates" (?g_Templates@@3PAVCFactoryTemplate@@A)
strmbasd.lib(dllsetup.obj) : error LNK2001: unresolved external symbol "int g_cTemplates" (?g_cTemplates@@3HA)
strmbasd.lib(dllentry.obj) : error LNK2001: unresolved external symbol "int g_cTemplates" (?g_cTemplates@@3HA) 之类的错误。

  以上link报错各异,最好把lib的位置上下调调,别链到vc自带的库中.鬼问题名堂太多,有时也和direct showsdk版本有关.

二.编译后出现的错误:
1 syntax error : identifier 'DWORD_PTR'
如下:
e:\directx9\samples\c++\directshow\baseclasses\wxutil.h(53) : error C2061: syntax error : identifier 'DWORD_PTR'
e:\directx9\samples\c++\directshow\baseclasses\ctlutil.h(43) : error C2504: 'IBasicVideo2' : base class undefined
e:\directx9\samples\c++\directshow\baseclasses\ctlutil.h(904) : error C2146: syntax error : missing ';' before identifier 'm_dwAdvise' e:\directx9\samples\c++\directshow\baseclasses\ctlutil.h(90) : error C2501: 'DWORD_PTR' : missing storage-class or type specifierse
   如果发生这种情况,你应该从"Tools"目录中选择"Option",
   然后在include directory中将Platform SDK加到VC inlcude中.

注意:
 1. 没有该sdk,需要先下载platform sdk. 在我的随笔中包含该链接.
2. 要放在Include最前面.


2 LINK : fatal error LNK1104: cannot open file "mfc42ud.lib" mfc42ud.lib是专门给unicode用的 build-->set active Configuration--> XXX win32 debug 这样就可以了 

3“CComPtr< ”怎么用?
CComPtr m_spApi;在控制台程序中可以编译成功,
   但mfc中报错,怎么回事?错误信息:
error C2143: syntax error : missing ';' before '<'
error C2501: 'CComPtr' : missing storage-class or type specifiers
error C2059: syntax error : '<'
error C2238: unexpected token(s) preceding ';'
原因:缺少文件,在vc6中是atlbase.h,可能在.net中是atlcomcli.h
解决方法:在stdafx.h中加入#include
 注意:
    以上lib路径要对.


5 调用CoInitializeEx(),编译后显示未定义
CoInitializeEx()是利用COM组件时每个线程都要调用的函数,
使用这个函数需要有如下设置:
  在Project -> setting -> C/C++标签下的Preprocessor definitions中加入"_WIN32_WINNT=0x400"语句


posted @ 2008-12-08 01:11 kenlistian 阅读(2430) | 评论 (0)编辑 收藏

2008年11月14日 #


经常在链接是报link2001错误,其中一般是函数体没有定义错误。但是排除以上简单的外,还有一种如下无法解析系统文件中的函数定义。如下:

LINK : warning LNK4098: 默认库“LIBCMT”与其他库的使用冲突;请使用 /NODEFAULTLIB:library
libcpmtd.lib(cerr.obj) : error LNK2001: 无法解析的外部符号 __CrtDbgReportW
libcpmtd.lib(stdthrow.obj) : error LNK2001: 无法解析的外部符号 __CrtDbgReportW
。。。。。。
libcpmtd.lib(_tolower.obj) : error LNK2019: 无法解析的外部符号 __calloc_dbg,该符号在函数 __Getctype 中被引用

像这种情况,可以推测运用了错误的运行库包。

在一程序中使用不同类型的运行时库或使用调试和非调试版本的运行时库,则将收到此链接错误。

例如,编译一个文件以使用一种运行时库,而编译另一个文件以使用另一种运行时库(例如单线程运行时库对多线程运行时库),试图链接它们,则将得到此警告。应将所有源文件编译为使用同一运行时库。

其实就是调正编译器选项参数:/MD、/MT   和   /LD

在vc8中,在“配置属性-->C/C++-->代码生成-->运行时库”中将“多线程(/MT)”统一改为“多线程调试(/MTd)”即可。



posted @ 2008-11-14 01:26 kenlistian 阅读(5604) | 评论 (2)编辑 收藏

2008年11月5日 #

前言:wxWidgets 是跨平台的GUI库,用VC6会影响它的跨平台性吗?当然不会,我们只是用VC6充当编译器和编辑器,只要编写代码时注意不使用Windows相关的特性,写出的代码仍然是跨平台的,仍然是可以在其它操作系统下(如Linux)使用其它C++编译器(如GCC)编译并运行的。

为什么用VC6,只不过此文专门针对VC6而已。

创建项目

点击菜单:File -> New... 创建一个"Win32 Application" Project,项目名称为"wxProject",点击OK按钮,

在下一步的提示中选择"An Empty Project",点击Finish按钮完成项目的创建。

以下的设置和操作可能有一些繁琐,但这是一劳永逸的事情。只要你完成了第一个空白工程,以后再需要创建工程时复制一份就可以了。

 

设置项目属性

以下四个编译配置并不要求都必须设置好,如果您不打算使用Unicode,那么不用设置"Win32 Unicode Debug"和"Win32 Unicode Release",如果您仅仅想调试程序而非发布,则只需设置相应的"Debug"不用设置"Release"。最简单的情况下,只需设置"Win32 Debug"。

还有一点要注意,您需要事先编译出相应版本的 wxWidgets 库文件。如"Win32 Unicode Debug"需要 Unicode+Debug 版本的 wxWidgets 库。(wxWidgets 各种版本库均可通过 \build\msw\wx.dsw 进行编译)。

点击菜单:Project -> Settings... 打开项目属性设置对话框。

Win32 Debug:

C/C++ General:

Preprocessor definitions: WIN32,_DEBUG,__WXMSW__,__WXDEBUG__,_MBCS,_WINDOWS,NOPCH

C/C++ Code Generation:

Use run-time library: Debug Multithreaded DLL

Link General:

Object/library modules: wxmsw26d_xrc.lib wxmsw26d_html.lib wxmsw26d_adv.lib wxmsw26d_core.lib wxbase26d_xml.lib wxbase26d.lib wxtiffd.lib wxjpegd.lib wxpngd.lib wxzlibd.lib wxregexd.lib wxexpatd.lib kernel32.lib user32.lib gdi32.lib comdlg32.lib winspool.lib winmm.lib shell32.lib comctl32.lib ole32.lib oleaut32.lib uuid.lib rpcrt4.lib advapi32.lib wsock32.lib odbc32.lib 

Win32 Release:

C/C++ General:

Preprocessor definitions: WIN32,NDEBUG,__WXMSW__,_MBCS,_WINDOWS,NOPCH

C/C++ Code Generation:

Use run-time library: Multithreaded DLL

Link General:

Object/library modules: wxmsw26_xrc.lib wxmsw26_html.lib wxmsw26_adv.lib wxmsw26_core.lib wxbase26_xml.lib wxbase26.lib wxtiff.lib wxjpeg.lib wxpng.lib wxzlib.lib wxregex.lib wxexpat.lib kernel32.lib user32.lib gdi32.lib comdlg32.lib winspool.lib winmm.lib shell32.lib comctl32.lib ole32.lib oleaut32.lib uuid.lib rpcrt4.lib advapi32.lib wsock32.lib odbc32.lib 

进行以下操作之前,请先通过菜单 Build -> Configurations... 增加两个编译配置"Win32 Unicode Debug"和"Win32 Unicode Release"(分别复制于"Win32 Debug"和"Win32 Release")。

Win32 Unicode Debug:

C/C++ General:

Preprocessor definitions: WIN32,_DEBUG,__WXMSW__,__WXDEBUG__,_UNICODE,_WINDOWS,NOPCH

C/C++ Code Generation:

Use run-time library: Debug Multithreaded DLL

Link General:

Object/library modules: wxmsw26ud_xrc.lib wxmsw26ud_html.lib wxmsw26ud_adv.lib wxmsw26ud_core.lib wxbase26ud_xml.lib wxbase26ud.lib wxtiffd.lib wxjpegd.lib wxpngd.lib wxzlibd.lib wxregexud.lib wxexpatd.lib kernel32.lib user32.lib gdi32.lib comdlg32.lib winspool.lib winmm.lib shell32.lib comctl32.lib ole32.lib oleaut32.lib uuid.lib rpcrt4.lib advapi32.lib wsock32.lib odbc32.lib 

Win32 Unicode Release:

C/C++ General:

Preprocessor definitions: WIN32,NDEBUG,__WXMSW__,_UNICODE,_WINDOWS,NOPCH

C/C++ Code Generation:

Use run-time library: Multithreaded DLL

Link General:

Object/library modules: wxmsw26u_xrc.lib wxmsw26u_html.lib wxmsw26u_adv.lib wxmsw26u_core.lib wxbase26u_xml.lib wxbase26u.lib wxtiff.lib wxjpeg.lib wxpng.lib wxzlib.lib wxregexu.lib wxexpat.lib kernel32.lib user32.lib gdi32.lib comdlg32.lib winspool.lib winmm.lib shell32.lib comctl32.lib ole32.lib oleaut32.lib uuid.lib rpcrt4.lib advapi32.lib wsock32.lib odbc32.lib 

 

设置wxWidgets目录

在前面的设置中,指定了wxWidgets的库文件(*.lib),但VC可能并不知道到哪个目录去寻找这些文件。同时,我们的源代码中也要包含 (include)wxWidgets的头文件,其头文件所在目录也需要指定。另外,为了更好的调试wx程序,最好把wxWidgets的源代码所在目录 也设置好。

点击菜单 Tools -> Options...,进入 Directories 页,分别加入以下路径(下面的表示wxWidgets安装目录)

Include files:

\include

\include\msvc

Library files:

\lib\vc_lib

Source files:

\src

这一设置是针对VC全局的,以后再用VC创建wxWigets程序,就不用设置这些路径了。

 

创建wxWidgets预编译头文件

各个编译器不同,有的支持预编译头文件,有的不支持,支持预编译头文件的,使用的语法也有所不同,如果在每个源文件中都重复的写未免不爽,还是集中到一个头文件中来比较好。但是注意,有了此文件并不决定或限制你使用还是不使用预编译头文件,用不用以及怎么用还是在你。

点击菜单 File -> New...,新建一个C/C++头文件 wx_pch.h,其内容如下:

#ifndef WX_PCH_H_INCLUDED
#define WX_PCH_H_INCLUDED

#if ( defined(USE_PCH) && !defined(WX_PRECOMP ) )
#define WX_PRECOMP
#endif // USE_PCH

// basic wxWidgets headers
#include <wx/wxprec.h>

// for use xrc files
#include <wx/xrc/xmlres.h>

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef
WX_PRECOMP
#include <wx/wx.h>
#endif

#ifdef
USE_PCH
// put here all your rarely-changing header files

#endif // USE_PCH

#endif // WX_PCH_H_INCLUDED

wxWidgets官方文档是大概也是这样推荐,Code::Blocks中基本上就是这样子,我只是简单的增加了一行"#include "(为了使用XRC文件)。

以后,工程中的源文件,只要包含(include) wx_pch.h 文件就可以了。

 

创建wxApp子类

点击菜单 Insert -> New Class...,新建一个名称为"App"的类(类名称可以随意),考虑到代码的跨平台性,建议将其所在文件的名称修改为全部使用小写字母(如 app.h/app.cpp)。此操作将生成文件 app.h 和 app.cpp。

VC在这里生成的类代码显然是不满足我们的要求的,需要进行以下修改:

app.h

增加预编译头文件 wx_pch.h 的包含(以后创建的每个.h文件都要包含它):#include "wx_pch.h"

指定App类的父类为wxApp:即将"class App"修改为"class App : public wxApp"

为类增加虚方法OnInit()的声明:virtual bool OnInit();

在类声明的下方增加 wxWidgets App 声明:DECLARE_APP(App)

最终 app.h 的内容如下(其中经过手工改写的地方已用黄色背景突出显示):

// by: liigo.com

#if
!defined( AFX_APP_H__B4514AF3_2125_487B_BD66_AF638A80E73A__INCLUDED_)
#define AFX_APP_H__B4514AF3_2125_487B_BD66_AF638A80E73A__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "wx_pch.h"

class App : public wxApp
{
public :
   
App();
   
virtual ~App();
   
virtual bool OnInit ();
};

DECLARE_APP(App )

#endif // !defined(AFX_APP_H__B4514AF3_2125_487B_BD66_AF638A80E73A__INCLUDED_)

app.cpp

增加头文件包含(此头文件将在下面创建MainFrame类时创建):#include "mainframe.h"

增加 OnInit() 方法的定义(其中用到的MainFrame类定义于mainframe.h,见后文):

bool App::OnInit()
{
    MainFrame* mainFrame = new MainFrame(NULL, _("MainFrame
by liigo.com"));
   
mainFrame->Show ();
   
SetTopWindow(mainFrame);
   
return true;
}

在类定义的上方增加 wxWidgets App 定义:IMPLEMENT_APP(App)

最终 app.cpp 的内容如下(其中经过手工改写的地方已用黄色背景突出显示):

#include "app.h"

IMPLEMENT_APP (App)

App:: App()
{
}

App::~App()
{
}

bool App::OnInit()
{
   
MainFrame* mainFrame = new MainFrame(NULL, _("MainFrame by liigo.com"));
   
mainFrame-> Show();
   
SetTopWindow(mainFrame);
   
return true;
}

 

创建wxFrame子类

点击菜单 Insert -> New Class...,新建一个名称为"MainFrame"的类(类名称可以随意),考虑到代码的跨平台性,建议将其所在文件的名称修改为全部使用小写字母 (如 mainframe.h/mainframe.cpp)。此操作将生成文件 mainframe.h 和 mainframe.cpp。

下面对VC生成的类代码进行相应的修改:

mainframe.h

增加预编译头文件的包含:#include "wx_pch.h"

指定MainFrane类的父类为wxFrame:class MainFrame : public wxFrame

修改构造函数的声明:MainFrame(wxWindow* parent, const wxString& title);

在类定义的末尾增加事件表声明:DECLARE_EVENT_TABLE()

最终 mainframe.h 的内容如下(其中经过手工改写的地方已用黄色背景突出显示):

#if !defined(AFX_MAINFRAME_H__1BC90331_B69E_40F2_BDF7_197550D70F07__INCLUDED_ )
#define AFX_MAINFRAME_H__1BC90331_B69E_40F2_BDF7_197550D70F07__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "wx_pch.h"

class MainFrame : public wxFrame
{
public:
   
MainFrame( wxWindow* parent, const wxString & title);
   
virtual ~MainFrame();

   
DECLARE_EVENT_TABLE()
};
#endif // !defined(AFX_MAINFRAME_H__1BC90331_B69E_40F2_BDF7_197550D70F07__INCLUDED_)

mainframe.cpp

修改构造函数的定义:

MainFrame::MainFrame(wxWindow* parent, const wxString& title) : wxFrame(parent, wxID_ANY, title)
{
   
//wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, _("some text"));
}

增加事件表定义(BEGIN_EVENT_TABLE 与 END_EVENT_TABLE 之间保留空白,留待以后绑定事件):

BEGIN_EVENT_TABLE(MainFrame, wxFrame)

END_EVENT_TABLE()

最终 mainframe.cpp 的内容如下(其中经过手工改写的地方已用黄色背景突出显示):

#include "mainframe.h"

BEGIN_EVENT_TABLE (MainFrame, wxFrame)

END_EVENT_TABLE()


MainFrame::MainFrame( wxWindow* parent, const wxString& title) : wxFrame (parent, wxID_ANY, title)
{
    //wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, _("some text"));
}

MainFrame ::~MainFrame()
{
}

 

至此,一个wxWidget的空白Project已经创建完毕


编译生成的 exe 文件的大小:

可执行文件大小 Debug Release
Unicode 3.78M 956K
非Unicode 3.60M 932K

此数据全部是静态链接wxWidgets的结果。动态链接的话,EXE的大小没有意义——别忘了wxWidgets的版DLLs的大小总共约4到5M(Release版)。

 

添加子控件

向 wxFrame 或 wxDialog 中添加子控件是比较容易的,只需在其子类的构造函数中 new 相应的子控件就可以了。

这是最简单的情况:

MainFrame::MainFrame(wxWindow* parent, const wxString& title) : wxFrame(parent, wxID_ANY, title)
{
   
wxTextCtrl * text = new wxTextCtrl( this, wxID_ANY, _("some text"));
}

没错,只要"new"一下就搞定了,控件会自动出现在wxFrame中。这是运行结果:


如果界面再复杂一些,上面这种方法就行不通了,我们需要引入"Sizer"(详见http://www.wxwidgets.org/manuals/2.6.3/wx_sizeroverview.html(Sizer一览)):

MainFrame::MainFrame(wxWindow* parent, const wxString& title) : wxFrame(parent, wxID_ANY, title)
{
    wxTextCtrl * textCtrl = new wxTextCtrl( this, ID_TEXTCTRL, _T("some text"), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE );
   
wxButton * button = new wxButton(this , ID_BUTTON, _("测试按钮"), wxDefaultPosition , wxDefaultSize, 0 );

   
wxBoxSizer* vBoxSizer = new wxBoxSizer(wxVERTICAL);
   
this ->SetSizer(vBoxSizer);

   
vBoxSizer->Add (textCtrl, 1, wxALL|wxEXPAND, 5);
   
vBoxSizer->Add(button, 0 , wxALIGN_CENTER_HORIZONTAL|wxALL|wxALIGN_BOTTOM, 5);
}

上面是多行编辑框控件,下面是按钮控件,当窗口大小变化时,编辑框控件将在水平和垂直方向上自动扩展,而按钮始终位于窗口底部居中。


上述代码中涉及的控件ID(ID_TEXTCTRL,ID_BUTTON)是我们在 mainframe.cpp 中自行定义的(定义控件ID的目的是为了下一步了事件处理):

enum CtrlID
{
   
ID_TEXTCTRL, ID_BUTTON
};

 

参考文档:http://www.wxwidgets.org/manuals/2.6.3/wx_sizeroverview.html(Sizer一览)

采用XML格式文件(XRC文件)定义程序界面也是不错的方式,详见:http://www.wxwidgets.org/manuals/2.6.3/wx_xrcoverview.html(基于XML的资源系统一览)。

无论如何,手工进行界面布局总是很繁杂,我们需要(可视化)工具的帮助:http://www.wxwidgets.org/apps2.htm

 

处理事件

在wxWidgets中处理事件,主要有两个步骤:编写"事件处理函数(方法)",填写"事件表(EVENT_TABLE)"。

事件处理函数(方法)视事件的不同而有所不同,但也有规律:没有返回值,只有一个引用型参数(且一定是wxEvent的子类),不是虚方法(virtual method)。事件处理函数(方法)的名称没有特殊规定,可以自行命名。

作为示例,我们来处理上图中"测试按钮"被按下的事件。

根据wxWidgets文档,要处理按钮事件,需在自己的类中添加如下事件处理函数(方法):void MainFrame::OnButtonClick (wxCommandEvent &event)

具体说来就是,在 mainframe.h 文件中的 MainFrame 类中增加新的 OnButtonClick() 方法声明:

private:
   
void OnButtonClick( wxCommandEvent& event);

并在 mainframe.cpp 文件中增加 OnButtonClick() 方法的定义:

void MainFrame::OnButtonClick( wxCommandEvent &event)
{
   
//取编辑框中的文本并用信息框显示出来
    wxString text = ((wxTextCtrl*)this-> FindWindow(ID_TEXTCTRL))->GetValue();
   
wxMessageBox (text);
}

下面需要在 mainframe.cpp 中填写"事件表(EVENT_TABLE)",以便我们的"事件处理函数(方法)"能在适当的时机(即事件触发时)被调用:

BEGIN_EVENT_TABLE(MainFrame, wxFrame )
   
EVT_BUTTON(ID_BUTTON, MainFrame ::OnButtonClick)
END_EVENT_TABLE()

在这个事件表中,我们使用宏 EVT_BUTTON 指定了按钮的ID,以及"事件处理函数(方法)"。

注:上面一直讲"事件处理函数(方法)",其实是"方法(method)"不是"函数(function)",只是"方法"这个词在编程领域和在日 常生活中可以有不同的理解("方法"也可以理解为"方式"),我如果说成"事件处理方法",难免会产生歧义。当然,"事件处理函数(方法)"似乎也并不十 分合适,应称为"事件处理'方法'"或"事件处理方法(method)"?再深究下去就有咬文嚼字的嫌疑了,聪明的读者早已明白我的意思了吧?



 

如何处理其它事件?

说白了,关键要知道两点:事件处理函数(方法)的参数是什么类型,填写参数表时用哪一个宏(EVT_*)。

再补充一点:要知道"什么控件"在"什么时机"会触发"什么事件"。

要知道这些,就需要对wxWidgets的事件处理有一个比较全面的了解。

建议看一下wxWidgets官方文档中的这篇文章:http://www.wxwidgets.org/manuals/2.6.3/wx_eventhandlingoverview.html(事件处理一览)

尤其是其中的 Event macros summary(事件宏概要)一段。

电子书《Cross-Platform GUI Programming with wxWidgets》附录9(Appendix I, 617页)中对事件处理时所涉及的事件类型(wxXXXEvent)和事件宏(EVT_*)有比较好的总结,建议看一下,最好打印出来放在手边,以便随时参考。

本文所涉及的完整源代码可在此下载:http://liigo.diy.myrice.com/article/wxProject/wxProject.zip

 

更进一步

了解 Sizer,熟悉界面设计:http://www.wxwidgets.org/manuals/2.6.3/wx_sizeroverview.html

了解 事件处理:http://www.wxwidgets.org/manuals/2.6.3/wx_eventhandlingoverview.html

了解 wxWidgets 提供了哪些控件,它们各自的属性、方法、事件,以及它们的用法。

去 wxWidgets.org 上找第三方的控件/库:http://www.wxwidgets.org/contrib2.htm#classes

wxWiki 上找第三方的控件/库:http://www.wxwidgets.org/wiki/index.php/Table_Of_Contents#Pages_about_classes.2C_functions_or_macros

GUI库嘛?无非就是控件(component)的使用:布局、操作、事件处理。

posted @ 2008-11-05 00:55 kenlistian 阅读(1645) | 评论 (0)编辑 收藏

2008年10月29日 #


一个函数指针的理解:
 有一段程序存储在起始地址为 0的一段内存上,如果我们想要调用这段程序,请问该如何去做?
 答案是 (*(void (*)( ) )0)( )。

 首先,最基本的函数声明: void function (paramList);
 最基本的函数调用: function(paramList);

  鉴于问题中的函数没有参数,函数调用可简化为 function();
   根据问题描述,可以知道 0是这个函数的入口地址,也就是说,0是一个函数的指针。
   使用函数指针的函数声明形式是:void (*pFunction)(),相应的调用形式是: (*pFunction)(),
   则问题中的函数调用可以写作:(*0)( )。

  大家知道,函数指针变量不能是一个常数,因此上式中的 0必须要被转化为函数指针。

  我们先来研究一下,对于使用函数指针的函数:
        比如 void (*pFunction)( ),函数指针变量的原型是什么?
    这个问题很简单,pFunction函数指针原型是( void (*)( ) ),即去掉变量名,
    清晰起见,整个加上()号。

  所以将 0强制转换为一个返回值为void,参数为空的函数指针如下:( void (*)( ) )。

   OK,结合2)和3)的分析,结果出来了,那就是:(*(void (*)( ) )0)( ) 。

  答案分析:从头到尾理解答案

   (void (*)( )) ,是一个返回值为void,参数为空的函数指针原型。
   (void (*)( ))0,把0转变成一个返回值为void,参数为空的函数指针,指针指向的地址为0.
   *(void (*)( ))0,前面加上*表示整个是一个返回值为void的函数的名字
   (*(void (*)( ))0)( ),这当然就是一个函数了。

  我们可以使用 typedef清晰声明如下:

   typedef void (*pFun)();

这样定义之后,pFun就是一个返回类型为void无参数的函数指针变量了。

  这样函数变为 (*(pFun)0)();


----
在调用动态库时,习惯用typedef重新定义动态库函数中的函数地址(函数指针),
如在动态库(test.dll)中有如下函数:
int DoCase(int, long);

则,在调用动态库是有两种方法:
1. 先声明一个与动态库中类型一致的指针函数变量:
int (*DOCASE)(int ,long);  //用于指向动态库中的DoCase函数地址
HINSTANCE gLibMyDLL = NULL;
gLibMyDLL 
= LoadLibrary("test.dll");
if(gLibMyDLL != NULL)
{
  DOCASE 
= (int(*)(int,long))GetProcAddress(gLibMyDLL, "DoCase");
}
int s = DOCASE(1,1000);

2.用typedef定义一个指针函数:
typedef (
*DOCASE)(int ,long);

HINSTANCE gLibMyDLL 
= NULL;
DOCASE _docase;
gLibMyDLL 
= LoadLibrary("test.dll");
if(gLibMyDLL != NULL)
{
  _docase 
= (DOCASE)GetProcAddress(gLibMyDll, "DoCase");
}

int s=_docase(1,1000);


----------------
在C++类中使用函数指针。
//typedef 返回类型(类名::*新类型)(参数表)
class CA
{
public:
  
char lcFun(int a){ return; }
};

CA ca;
typedef 
char (CA::*PTRFUN)(int);
PTRFUN pFun;
void main()
{
  pFun 
= CA::lcFun;
  ca.(
*pFun)(2);
}


指针的定义与使用都加上了“类限制”或“对象”,用来指明指针指向的函数是哪个类的,
这里的类对象也可以是使用new得到的。
如:
CA *pca = new CA;
pca->(*pFun)(2);
delete pca;
而且这个类对象指针可以是类内部成员变量,你甚至可以使用this指针。
如:类CA有成员变量PTRFUN m_pfun;
void CA::lcFun2()
{
   (this->*m_pFun)(2);
}
一句话,使用类成员函数指针必须有“->*”或“.*”的调用。


--------------------

void test(void* );
void tt()
{
   printf(
"kao,没玩过这种\n");
}

int main(int argc, char* argv[])
{
    typedef 
void(*Fun)();
    Fun mytest;
    mytest 
= tt;
    test((
void*)mytest);
    
return 0;
}

void test(void * p)
{
    (
*(void(*)())p)();
}


posted @ 2008-10-29 22:23 kenlistian 阅读(784) | 评论 (1)编辑 收藏

2008年10月27日 #



关于偏移量的宏定义

#define offsetof(s,m)   (size_t)&(((s *)0)->m)

s是一个结构名,它有一个名为m的成员(s和m 是宏offsetof的形参,
它实际是返回结构s的成员m的偏移地址


(s *)0 是骗编译器说有一个指向类(或结构)s的指针,其地址值0 

&((s *)0)->m   是要取得类s中成员变量m的地址 
          因基址为0,这时m的地址当然就是m在s中的偏移

最后转换size_t 型,即unsigned int。

有例子如: 
  struct   AAA 
  { 
    int   i; 
    int   j; 
  }; 
  
  struct   AAA   *pAAA; 
  pAAA = new   AAA; 
  这时,pAAA实际上是一个Pointer, 指向某一确定的内存地址,
     如0x1234; 
  而pAAA->i 整体是一个int型变量,其地址是&(pAAA->i),
  '&'为取址运算符; 
  那么&(pAAA->i)一定等于0x1234,因 i 是结构体AAA的第一个元素。 
  而&(pAAA->j)一定是0x1234 + 0x4 = 0x1238; 因为sizeof(int) = 4; 
  
  这个做法的巧妙之处就是:它把“0”作为上例中的pAAA,那么&(pAAA->j)就是j的 
  offset啦, 

  解析结果是: 
  (s*)0,将 0 强制转换为Pointer to "s"   
  可以记 pS = (s*)0,pS是指向s的指针,它的值是0; 
  那么pS->m就是m这个元素了,而&(pS->m)就是m的地址,
  就是offset啦    




posted @ 2008-10-27 17:16 kenlistian 阅读(674) | 评论 (0)编辑 收藏

2008年7月17日 #

ado在dll中使用如下的头文件

#pragma warning(disable:4146)
#import "C:\Program Files\Common Files\System\ADO\msado15.dll" named_guids rename("EOF","adoEOF"), rename("BOF","adoBOF")
#pragma warning(default:4146)

using namespace ADODB;

"Provider=SQLOLEDB,Data Source=serverName;Initial Catalog=databaseName;User ID=userName;Password=userPassword;"

#import "c:\program files\common files\system\ado\msado15.dll" no_namespaces rename("EOF","adoEOF")


http://www.pconline.com.cn/pcedu/empolder/gj/vc/0507/653859.html
posted @ 2008-07-17 10:21 kenlistian 阅读(706) | 评论 (0)编辑 收藏

2008年7月15日 #


vc6 下的msdn版本:最后一个支持VS6的版本是2001年10月版;


DirectX SDK:DirectX 9.0 Summer 2004 SDK Update,

下载页面:http://www.microsoft.com/downloads/details.aspx?FamilyID=fd044a42-9912-42a3-9a9e-d857199f888e&DisplayLang=en
这个版本的Direct 9 SDK是不完全支持VC6的,

注意下载页面的Overview:
This download contains all the extra files that are not included in the DirectX 9.0 SDK Summer Update 2004 release such as a d3dx.lib which supports VC 6.0, older plug-ins and Japanese version of the documentation.
即DirectX SDK Summer Update 2004里面附带的d3dx.lib是不支持VC6的,支持VC6的版本在Extras包里面。

还须下DirectX 9.0 Summer 2004 SDK Update Extras(http://www.microsoft.com/downloads/details.aspx?FamilyID=736585e1-10f0-4e85-b940-828cba9971f1&DisplayLang=en),

另如编译时出现了DWORD_PTR或者其他什么类型未定义之类的错误,是因为微软把BASETSD.H从DirectX SDK发行包里拿掉了,这个文件在Platform SDK里有,在VC的Include路径中把Platform SDK的include路径提到最前面就可以了。
Platform SDK,最后一个支持VC6的Platform SDK是February 2003 Edition,
下载:http://www.microsoft.com/msdownload/platformsdk/sdkupdate/psdk-full.htm


****************************************************
因为现在从微软下载需要验证

可以下DirectX 9 SDK 精简版,7M大,这里DirectX 9 SDK 精简版的下载地址:
http://www.gameres.com/Resource/dx9sdk.zip

从MSDN6里面提取出来的DirectShow头文件:
http://www.shenglu.com/UserFiles/File/files/DirectShow_Include_files_from_msdn6cd1.zip



感谢上面提供者。
posted @ 2008-07-15 19:28 kenlistian 阅读(1352) | 评论 (0)编辑 收藏

2008年7月14日 #


 采用bind1st和bind2nd的意思,就是把参数绑定在第一位还是第二位。
继承于binary_function 类.
描述如下
Class binder1st binds the value to the first argument of the binary function, and binder2nd does the same thing
for the second argument of the function.

如下:例子

struct compare_str :binary_function<ST_DataResult*, char*, bool>
{
public:
    bool operator()(ST_DataResult* pDataRet, char* szTypeCode) const
    {
        return strcmp(pDataRet->sType , szTypeCode) == 0 ? true : false;
       
    }
};


。。。

char szTypeCode[4] = {'\0'};
strcpy(szTypeCode, sTypeCode);
pIt = find_if(m_d_ret_data.begin(), m_d_ret_data.end(),
        bind2nd(compare_str(), szTypeCode));

其中把szTypeCode变量传入到compare_str所定义的第二个参数位置传入。
如果写成bind1nd, 则是把szTypeCode作为第一个参数传入,那么会报错。
因为类型不对。


posted @ 2008-07-14 16:56 kenlistian 阅读(845) | 评论 (0)编辑 收藏

2008年6月3日 #

wxWidgets框架例子。
直接粘贴到vc中,运行即可。当然需要安装好wxwidgets及其配置好lib和include设置。

其解释部分非常详细,基本上wxwidgets和mfc差不了多少,但是扩充性和跨平台性是大于mfc,同时
也对熟悉ruby下的wxruby和wxpython是非常快速的。

#include "wx/wx.h"

// 定义应用程序类
class MyApp : public wxApp
{
public:
    virtual bool OnInit();
};

// 定义主窗口类
class MyFrame : public wxFrame
{
public:
    // 主窗口类的构造函数
    MyFrame(const wxString& title);

    // 事件处理函数
    void OnQuit(wxCommandEvent& event);
    void OnAbout(wxCommandEvent& event);

private:
    // 声明事件表
    DECLARE_EVENT_TABLE()
};

// 有了这一行就可以使用 MyApp& wxGetApp()了
DECLARE_APP(MyApp)

// 告诉wxWidgets主应用程序是哪个类
IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{
    // 创建主窗口
    MyFrame *frame = new MyFrame(wxT("Minimal wxWidgets App"));

    // 显示主窗口
    frame->Show(true);
    return true;
}

// MyFrame类的事件表
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
    EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
    EVT_MENU(wxID_EXIT,  MyFrame::OnQuit)
END_EVENT_TABLE()

void MyFrame::OnAbout(wxCommandEvent& event)
{
    wxString msg;
    msg.Printf(wxT("Hello and welcome to %s"),
               wxVERSION_STRING);

    wxMessageBox(msg, wxT("About Minimal"),
                 wxOK | wxICON_INFORMATION, this);
}

void MyFrame::OnQuit(wxCommandEvent& event)
{
    Close();
}

MyFrame::MyFrame(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title)
{
    // 设置窗口图标
    //SetIcon(wxIcon(mondrian_xpm));

    // 创建菜单条
    wxMenu *fileMenu = new wxMenu;

    // 添加“关于”菜单项
    wxMenu *helpMenu = new wxMenu;
    helpMenu->Append(wxID_ABOUT, wxT("&About...\tF1"),
                     wxT("Show about dialog"));

    fileMenu->Append(wxID_EXIT, wxT("E&xit\tAlt-X"),
                     wxT("Quit this program"));

    // 将菜单项添加到菜单条中
    wxMenuBar *menuBar = new wxMenuBar();
    menuBar->Append(fileMenu, wxT("&File"));
    menuBar->Append(helpMenu, wxT("&Help"));

    // ...然后将菜单条放置在主窗口上
    SetMenuBar(menuBar);

    // 创建一个状态条来让一切更有趣些。
    CreateStatusBar(2);
    SetStatusText(wxT("欢迎使用wxWidgets!"));
}



注意的是:
1.必须是windows框架而不是control程序框架
2.通过向导生成的stdafx.h 可以通过project/set/ "c/c++"中选择无编译头来去掉
stdafx.h
3.直接拷贝这些lib到工程设置
wxmsw28d_aui.lib wxmsw28d_html.lib wxmsw28d_xrc.lib wxmsw28d_adv.lib wxmsw28d_core.lib wxbase28d_xml.lib wxbase28d.lib wxtiffd.lib wxjpegd.lib wxpngd.lib wxzlibd.lib wxregexd.lib wxexpatd.lib
kernel32.lib user32.lib gdi32.lib comdlg32.lib winspool.lib winmm.lib shell32.lib comctl32.lib ole32.lib oleaut32.lib uuid.lib rpcrt4.lib advapi32.lib wsock32.lib odbc32.lib
4.附加该lib的位置,或者把以上lib放置到vc6可以找到地方.
5.如果报有LIBCD.lib重复定义,填到ignore lib中
6.以上都是在vc6环境测试下通过。


posted @ 2008-06-03 22:54 kenlistian 阅读(3086) | 评论 (1)编辑 收藏

 
目前是wxWidgets-2.8.7,
下载http://sourceforge.net/project/showfiles.php?group_id=9863

其中在windows下,则选择其中msw的压缩下载。
按照install文档要求,编译wxWdigets中的builder目录中的wx。bsp。
由于该wxwdgets支持多种编译器,选择相关的工程文件加载。

传统采用vc6.

生成的编译文件在lib目录中。如果采用vc则目录为vc_lib.

生成的lib文件有(release or debug):
 wxbase28.lib              wxbase28d.lib
wxbase28_net.lib          wxbase28d_net.lib
wxbase28_xml.lib          wxbase28d_xml.lib
wxmsw28_core.lib          wxmsw28d_core.lib
wxmsw28_html.lib          wxmsw28d_html.lib
wxmsw28_adv.lib           wxmsw28d_adv.lib
在vc中设置lib,可以把该目录加入,也可以直接把生成的文件丢在vc的lib目录。
关于unicode模式编辑如上。
在include设置中,需要加入wxWidgets目录中的include目录。
运行demo程序测试.

注意:在include也需要把setup.h头文件加入。该头也根据生成的lib目录中调用。
不过最好加在预处理的include directory目录编辑框中。
 
  
 
 
posted @ 2008-06-03 18:13 kenlistian 阅读(895) | 评论 (1)编辑 收藏

2008年5月8日 #


这个一定要改,磨刀不误砍柴人,老话了,看来就是拿科学道理来讲,
道理就是有道理。


早在75年之前,大多数行业就已经放弃了加班赶进度。数不清的行业经验和研究事实证明:要想完成工作,加班赶工是成本最高的做法。

文/Evan Robinson  译/乔梁(《程序员》2008年3月刊)

缘起

2004 年,某国际电子游戏公司员工的家人以Ea_spouse为名,在某网站上发布文章,讲述了其配偶由于高强度、长时间的加班,对自己的身体健康以及家庭生活造成了很不好的影响。一石激起千层浪,关于电子游戏开发行业人士生活质量的话题再次引起了大家的热烈讨论。Ea_spouse收到了上千个回复,此事也很快被主要媒体跟踪报导。通过网络,上千人参与了这个自发的大规模讨论,内容涉及强制加班、工作效率、怠惰、工会、诉讼以及对很多公司的控诉等议题。

我已经做了二十多年软件项目的开发与管理工作。过去的每一年,做过的每一个项目,都让我更坚信:加班赶进度——“赶工”——是一种高成本、低产出且极具破坏性的工作方式。“工作持续时间越长,工作效率越低”已是常识了。然而随着时间的推移,我已注意到:由于过多额外加班导致工作效率下降的速度,要超过大多数软件行业管理者的认知。随着调查深入,我惊讶地发现我并不是第一个认识到这一点的人:近一个世纪以来,传统工业的工程师们对于我所观察到的问题早已达成共识。

历史

1908 年,工业效率研究先驱 Ernst Abbe公布了他的研究结论,即每天的工作时间从九小时减少到八小时,其日产出量会增加。而他也不是第一个注意到这件事的人。早在1893年,William Mather在Salford钢铁公司(Salford Iron Works)就已经采纳了每天八小时的工作制。在1909年,Sidney J. Chapman发表了《工作实践》(Hours of Labour)。他在文中描述了每天的工作小时数与工人的生产力之间的变化。下文会对其结论进行详细阐述。亨利?福特在1926年大张旗鼓地采纳每周40小时工作制,至少已经进行了十二年的试验让他确信:将每日工作10小时调整为8小时,并把每周工作六天改为五天,实际上会增加总产出并降低生产成本。福特大肆宣扬缩短工作时间的社会效益,并强调增加消费时间对每个人都有好处。但其核心观点仍是:减少当班时间可以带来更多产出。

我发现很多组织(如商业、大学、工业协会和军事单位等)进行的研究结果都持有同样的基本观点,即对于大多数人来说,“每天工作八小时、每周工作五天”是保证高产出和低身心消耗之间的最佳平衡点。在十九世纪的三十年代至五十年代,类似研究进行过上百次;到了六十年代,40小时工作制所带来的好处在美国企业界已被无可争辩地接受了。1962年,商业协会(Chamber of Commerce)甚至专门发行了一个小宣传手册,来称赞减少工作时间所带来的生产率提高。

然而,不知何故,硅谷却把其抛在了脑后。Ea_spouse写到:

“现在的强制工作时间是从早上九点到晚上十点,且每周工作七天,基于员工的良好表现,星期六的下班时间偶尔会提前到晚上六点半。平均算下来,这相当于每周工作85小时。”

实际上,一周有六天是从早上九点工作到晚上十点,还有一天是从早上九点到晚上六点半,算下来是每周工作87.5小时(6×13+9.5=87.5)。可是在工作这么长时间以后,谁还会算得那么详细呢?

在这一方面,电子艺界与其它高科技公司没什么不同。请希望提高员工生产力且同时让他们保持头脑清醒的人一起来看一下:管理者在工作时间、产出、效率以及生产成本这几个方面做出的假设,以及一个世纪的产业研究如何证明这些假设是完全错误的。

管理者想要什么?

当管理层将员工送上“死亡之旅”时,他们究竟想要得到什么呢?我们真的相信EA的CEO想看到员工撅着屁股在办公室里连续工作7×24小时吗?

管理者想从雇员那里得到最大的产出,是用最少的成本投入生产出最好的产品。不到万不得已,他们不希望为了完成产品出钱雇佣额外的人力资源,从而增加成本。表面看来,要想达到这两个目的,“赶工”好象是最显而易见且合理的方法了。

假设产出是利用计件方式来衡量的话,没读过上述研究报告的管理者可能会推断:假如一个人在八小时内生产16件产品,那么,他在九个小时内应该能生产出18件产品,在十个小时内可能生产20件。我们可以用下面这个简单的公式来表示这种观点:

O = X/Y * t

其中,O是总产出,X是基准时间Y(以小时为计量单位)内的产出,而t是实际的工作小时数。在这种假设前提下,增加时间t是提高产出O的最简方法。在个别情况下,这种假设是有效的,例如只在很短的一段时间里延长工作小时数以便能在最后期限时交付。但是,其它行业的长期试验和研究表明:加班冲刺的极限持续时间比大家想象的要短;而且当达到极限以后,这种冲剌也会陷入困境。

小时生产率很重要

对于如何看待“工人的产出”,更现实的方法是考虑小时生产率会随着工作时间的长短发生变化。这种变化有两种主要来源:在一天中最后几个小时里,产生在脑力和体力上的明显疲劳状况;随着已经延长了工作时间的工作日不断增加,脑力和体力的疲劳也不断积聚。

下面的等式表达了这种更复杂一点儿的情况:

O = P(t1 , t2 , t3 , … , tn )

其中,O代表总产出,P( )表示小时生产率随时间(t1-tn)的变化。在这个等式中,P( )是一个函数,不是一个常量。P( )根据工人的不同而变化,因为某些人生产力要高于其他人。P( )也随时间变化,因为人不是机器,在第14个小时完成的工作并不完全等于在第1个小时完成的工作。另外,P ( )也会随工人最近的状态而变化,例如,开夜车后的早上与睡了一个好觉后的早上,其工作效率也不可能是完全一样的。

在1909年Sidney J. Chapman发表的《工作时间》一文中展示了下面这张示意图:



其中,曲线P表示“固定数量的劳动力(根据每个工作日的小时数)产出边际价值的长期变化值”。OX轴表示一天内的工作时间,OY轴表示产出的价值。在OX轴的n点上,也就是说如果每天工作n个小时,其总产值是图形Onda的面积(详情请参见 http: //www.worklessparty.org/timework/chapman.htm)。可以发现:曲线P的高度就代表了工人的工作效率(每天在给定的工作小时数下,每个单位时间的产出)。

聪明的读者已经注意到在点b,工作更多小时不会创造更多的价值。而且在b点以后,每多工作一个小时,产出反而是负值。怎么会这样呢?

Chapman 的工作曲线图假设给定工作小时数的工作日会持续一定的时间。因此,它将每天的疲劳状况和长期的疲劳累计统一在一个模型之内体现。首先,小时产出率的下降反映了在接近一天工作结束时,疲劳效应对工作质量和数量上的影响。但是到后来,每天的疲劳是由累积疲劳复合组成的。也就是说,在头一天加班时间导致疲劳产生的生产力下降,会对此后工作日的小时生产率下降产生叠加效应。

即便是在一天之内,当精疲力竭的雇员再也无法投入工作时,产出就会停滞不前。如果某个已经变得麻木的雇员犯了灾难性的错误,破坏了前面已经完成的工作和投入资本,那产出就会变成负数。

就工厂而言,一个工人的生产率会随时间下降。某工人在一个班次开始,可能每小时生产10个工件,而在接近下班时,可能每小时只能生产6个,在这其间,有几个小时可能会达到峰值12个。再往后,其工作会变得更慢,而且会犯更多错误。这种减速和错误最终会使生产力成为零,即花了很长时间才生产一个工件,而到了最后一个总会有点毛病。流水线的管理者很久之前就发现:当达到这种疲劳程度时,会带来较大的失误,并导致更大的成本损失,如昂贵的机器受损,存货被毁,或者工人严重受伤等。

作为脑力工作者,得到充分休息后,程序员会生产更多高质量的代码,而且bug也会比较少。在开始工作的第一个小时,程序员会逐渐进入状态,接下来的 几个小时是最佳状态。在此之后,我们会感到疲劳,小时生产率也就下降;我们会花很长时间才能修复一个简单的bug,或增加一个简单的特性。而这样的事情如果拿到前几个小时做,可能也就是几分钟的活儿。再恶劣一些的状况—似乎很多电子娱乐行业的公司大部分时间都在这种极端状况下工作—一个过度疲倦的IT技术人员可能会删除很有价值的文件,从而要花费额外的工作去恢复备份;他也可能在回家的路上遇到交通事故,结果在几个月内都无法上班。

这就是第一课:在一个工作日中,生产率随时间h变化。在前四至六个小时里,生产效率最高。随着时间的流逝,生产率会降为0,甚至会变成负数。

平衡点在哪里?

如果在一个工作日内,生产率本来就会随时间下降,而且长时间工作会导致低生产率,那么我们如何找到一种方法来达到最大产出呢,平衡点又在哪里?

不幸的是,希望量化脑力劳动者的产出是相当困难的。我也希望能给出一个简单的公式,只要代入几个数字,就可以计算得出每个人达到最大产出所需工作小时数。但是我不能,因为即使存在这样的公式,也不能就找到并代入哪些基本数据达成一致。常见软件度量方式,如代码行法或功能点法,要么只是简单收集数字,无法让人信服其价值,要么就是数据很难定义和收集。有用的度量数据,如造成bug数目和修复bug数目等,其可信度也不高,并有可能会被不公平地用于年度考评上,也可能被聪明的程序员利用来算计年终考评或绩效奖金。架构师的产出可以很容易地用某些数据(如模型或架构图的数量)来衡量,但同样很难用另外一些数据(如主观质量、观感,及模型复杂度)衡量。如果用发现的独特bug数来衡量测试人员的产出很容易,但用代码覆盖率来衡量就有点儿难了,假如用发现缺陷的总百分比来衡量就难上加难。

总而言之,对于团队的产出,大多数公司好象陷入了一种“最小公分母式”的度量。要么用游戏出厂数量和销售量,要么就不用。虽然这些的确是大多数股东所在意的度量数据,但对于生产率的度量完全没用,尤其是每日或每小时的生产效率。

 第二课:对于脑力劳动者,生产效率很难量化。

所以,我们只好同其它行业中进行类比:以下内容来自Work Less Institute of Technology名为《IT行业中的精神物理学》的一篇文章,是对Ea_spouse的回复:

“这是一个多世纪前,Dr. Ernst Abbe 对德国耶拿的蔡司光学工厂(Zeiss Optical Works)工作时间与产出的调察。 Dr. Abbe是工厂的董事,他将工作时间从每日9小时减少到8小时,并详细记录了调整前后每个工人的每日产出,其结果与十九世纪的其他研究一致,即适当减少工作时间确实提高了总产出。”

Tom Walker指出:

“产出的增减与工作时间的长短不成正比”好象是每一代人的必修课。1848年英国议会通过10小时工作法案,结果每个人每天的总产出增加了。到十九世纪九十年代,雇主又广泛尝试8小时工作制,不断发现每个工人的总产出仍在提升。“科学管理理论”创始人泰勒 (Frederick W. Taylor)指出减少工作时间会显著增加个人产出。

在上个世纪二十年代,亨利?福特对工作时间安排进行了多年试验,最终在1926年引入每周工作五天共计40小时,却付六天薪水的工作制度。福特为什么要这么做呢?因为他的试验表明,其工厂在五天内的产出要比六天的产出还多。而在工作制变迁的每一步上(1840s,1890s和1920s),总有些行业人士坚持认为:缩短工作时间会降低产出,使经济受损。

 第三课:经过上一个世纪的研究表明,每周五天且每天8小时的工作时间,从长远看其产出将会最大。有什么理由让我们认为:我们这个行业可以不遵守这个规则呢?

短期的产出又当如何?

如果每周40小时工作制是收到最多产出的最佳工作时间安排方式,我们能否期待“短期内的延长工作时间”能够有短期的成效?简而言之,从几天到几个月,你通过延长工作时间能够得到多少额外的产出,取决于一个工作日工作多长时间。

显然,如果在八小时工作制下一个工人每小时生产一个工件,他在十六小时工作制下生产的工件数会在八到十六个之间。这是隐藏在“赶工”背后不那么明显的本质。另外,工人生产率还依赖于其所处状态。

与每周工作40小时相比,每周工作60小时会导致生产效率下降。刚开始时,这额外的20个小时会弥补生产效率的下降,使总产出增加。但研究表明,当改为每周工作60小时以后,建筑工人的生产率很快就会开始下降。这种下降很快可以感觉到,一周之内就会很明显,而且还将不断下滑。在两个月内,累积生产力的损失会下降到与每周工作40小时的产出同样的水平。同一篇报告引证的研究显示出:每天工作八小时的总产出会高于每天工作九小时总产出16%或者20%。

所以,没错,“赶工”可在短期内提高产出。但在每周工作60小时的情况下,这个“短期”绝不能超过八个星期。因为从这一点开始,成本耗费会严重超过带来的收益。不仅会失去赶工带来的成果,还会使员工感到疲倦、易怒、情绪难于控制。当恢复为每周工作40小时后,还需要一段时间,他们的产出才能恢复到原来的水平。

一周如果工作87.5小时会怎么样呢?虽然缺乏确凿的数据,但我估计效率在一个月内将跌至原来的50%,即使每周额外的47.5小时工作(两倍于"正常"工作时间)在最初阶段可能有相当高的产出。

◆ 第四课:每星期工作60小时的情况下,由于长时间工作所导致的生产率下降,会抵消几个月超时工作所带来的产出。

睡眠因素

在评估“赶工”是否有用时,还要考虑另一个因素:如果工人没有足够的睡眠,他们能保持高产多长时间?

Gregory Belenky上校是Walter Reed 军队研究所神经精神科的主任。他曾为五角大楼研究如何使士兵在战斗条件下最大化其效率和警惕性。在其1997年的论文《持续作战中睡眠、剥夺睡眠与人体的表现》中,他指出:

“实验室内研究表明,每连续保持清醒24小时,神智功能下降25%。被剥夺睡眠的个体虽然能够保持认知活动的准确性,但是反应速度明显下降。”

在我们的研究中,来自第82空降兵团炮火指示中心的团队接受了模拟持续战斗状态的测试,共持续了36小时。在测试开始阶段,当我们要求向一所医院进行模拟开火时,团队还能查看方位图,评估目标状况,拒绝开火请求。到模拟测试后期,他们就无视目标性质毫不犹豫地开火了。

在进行模拟演习的第15天,每晚睡四小时的炮兵连的成绩仅是每晚睡七小时的炮兵连的三分之一。

 第五课:每连续工作24小时,认知能力会下降25%。连续开夜车的人会产生严重的累积影响。

认知迟钝与错误率

“赶工”引起生产率下降的重要因素之一就是产生错误数量的增加。尽管大多数错误很容易修复,但还是有一些错误会把从“赶工”中得到的收益消耗殆尽。“赶工”时间越长,相关人员遇到大麻烦的机会就越大。

程序员、架构师和测试人员能够拿到薪水,不是由于他们拥有发达的肌肉或者什么超能力能把重物从一点移到另一点,而是因为他们的大脑。工作时间越长,或者缺乏充足的睡眠(好比每晚只睡1~2小时)会大大降低他们使用大脑的效率。

Belenky上校指出:士兵即使每天仅少睡一个小时,后果是“减少了……保持清醒地进行高条理性脑力工作的能力。降低了个体和团队的效率”。

知识工作者应该感到幸运他们不必担心发生“友军误伤”的问题。

在《持续减少睡眠会产生严重恶劣后果》一文中提到:

“宾夕法尼亚大学的研究人员发现:连续十四天内每天只睡4至6小时的研究对象在认知表现方面显示出明显不足,其水平相当于连续三天不睡觉的人。然而,这些研究对象却说自己只感觉有点儿困,并没有意识到他们的状况有多糟。”

在2005年1月的《洛杉矶时报》上,Karen Kaplan有一篇名为《瞌睡的医学院实习生成为马路杀手》的文章:

“研究表明,在21个小时内没有睡眠的司机,其状态相当于血液内酒精含量检测达到0.08的人,而0.08是美国对非营利性驾驶司机进行血液内酒精含量检测是否超标的法定界限。”

令人感到讽刺的是,大多数软件公司会解雇一个工作时间喝酒的人,却能毫不犹豫地将今年最重要的项目交到由于缺少睡眠(相当于达到法定司机酒精含量超标标准)的人手里。事实上,是他们要求这些人在“违法醉酒状态”进行工作的,并以此作为雇员被继续雇佣的条件。

带来的风险很现实—引发的错误真能导致灾难发生。在Dr. William Dement写的《睡眠的承诺》一书写道:

“1989 年3月24日的夜晚清冷宁静,空气如水晶般透明。埃克森石油公司的油轮离开了阿拉斯加的瓦尔迪兹市,驶向威廉王子海湾。在如此清澈的气候条件下,油轮按原计划放下了输油管道,却没有及时收回。巨大的油轮搁浅,数百万加仑的原油泄漏到海湾之中。……在最后的报告中,国家运输安全委员会 (NTSB)发现,缺少睡眠是事件的直接原因。……导致美国历史上最严重的漏油事件的直接责任人是船上的三副,他在事发前48小时内仅睡6个小时,睡眠严重不足。”

罗杰斯调查委员会(Rogers Commission)在关于美国挑战者号航天飞机失事分析最终报告中说:在关键时刻的电话会议上,做出发射决策是有问题的。在“人为因素分析”章节提到睡眠缺乏“对此有重大影响”。

如果由于缺乏睡眠可以导致战斗失利,危害病人,搁浅油轮,引爆太空飞船;仔细想想这会为价值一千五百万美金的游戏项目带来什么?

◆ 第六课:错误率会随连续工作时间而攀升,尤其是睡眠时间不足的情况下。最终,失败会找上门来,导致灾难发生。当时间紧且预算投入大时,你真能承担得起这个风险吗?

这意味着什么?

意味着生产率下降。在保持每周五天共约40小时工作时间的情况下,工人可以维持生产率。工作更长时间,生产率开始下降。在四天和两个月之间某个时间点上,从加班工作中得到的收益会被小时生产率下降所抵消。在某些极端情况下(当工人无法保证每晚至少7到8小时睡眠的状况下,一到两天之内),效率会直线下降。

上面的研究内容很多都是来自于工业产业环境,可能有人会认为这些结论不适用那些更多地使用脑力的程序员、架构师和测试人员身上,因为他们与普通的体力劳动者不同。事实上,的确不同,Belenky上校明确表示: 

“与复杂的脑力活动相比,可以说,简单的心理活动、体力劳动和耐力基本不受缺乏睡眠的影响。”

需要完成复杂任务的脑力劳动者受睡眠缺乏影响比体力劳动者更明显,生产率下降得更快。在知识工作者中,由于过度工作而导致的生产率损失会比普通士兵更早更快,因为我们的工作更受脑力疲乏的影响。

Ea_spouse想告诉我们,她老公所在团队的工作效率远远低于最佳效率。在老板让他们进行每周87.5小时的超级“赶工”之前,每周工作60小时以上的状态就已持续几个月了。

二十世纪,在大部分的时间、地点和行业中,让手下雇员这样工作的管理者就被认为是不能胜任其工作。这不只是因为他们威胁到了良好的雇佣关系,还由于他们的错误管理方式将公司的生产力和资产至于危险境地。

一百多年的业界研究已经毋庸置疑地证明:员工因精疲力尽所产生的错误会推迟计划,损坏设备,增加成本,降低产品质量,最终威胁组织的生存。这是对项目的威胁,也是对其管理者、雇主、每个人,以及其自身的威胁。

无论如何, 将“赶工”用作长期策略,在经济上是不可行的。延时工作不能增加产出,除非是短期行为。另外,“赶工”也不能使产品更快推出,只会导致产品延迟发布。“赶工”不能提高产品质量,只能使其更糟糕。“赶工”增加了引发重大错误的机会,比如交付会擦除客户硬盘数据的软件,或者删除代码树,或者把可乐洒到最近没有做过备份的服务器中,甚至引起火灾。的确是这样,在“赶工”最后那些睡眼惺忪的日子里,我曾亲眼目睹过前三个后果。第四个后果迟早会发生,大概只是时间上的问题。

管理者决定赶工,是因为他们想告诉他们的老板“我已尽我所能”。他们赶工,是因为他们评估的是放在椅子上的“草人”而不是那些能开发游戏的“大 脑”。他们赶工,是因为他们没有认真考虑要做的工作,或没有考虑做工作的是人。他们赶工,是因为只知道要表现出自己在尽力做好工作的重要性,而不是真正去把工作做好。他们赶工,是因为他们回想到当他们还是程序员、测试人员、“助理制片人”或“副制片人”时,他们也是被要求这样做的。

但这不是唯一的方法。事实上,很多文献一次又一次地表明:加班赶工是最差的方式。这也是很多行业七十五年前就已放弃这种工作方式的根本原因。管理者、股东和员工都坚信:使用经过时间检验的——每天工作8小时、每周5天——管理实践,大家会因更快、更省地交付更好的产品而获益,而且不会损耗组织的人力资源和在公众中的声望。
posted @ 2008-05-08 17:51 kenlistian 阅读(887) | 评论 (0)编辑 收藏

2008年4月29日 #

1.正则表达式笔记

必须记住的几个符号和组合
.        匹配除换行符以外的所有字符一次
?        匹配 0 次或一次
*        匹配 0 次或多次
+        匹配 1 次或多次

               使用范例:

                x?        匹配 0 次或一次 x 字符串
                x*        匹配 0 次或多次 x 字符串,但匹配可能的最少次数,*必须跟随一个字符后面,不能单独出现
                x+        匹配 1 次或多次 x 字符串,但匹配可能的最少次数,+必须跟随一个字符后面,不能单独出现
                .*        匹配 0 次或一次的任何字符
                .+        匹配 1 次或多次的任何字符

界定范围和位置
^        匹配字符开头的字符
$         匹配字符结尾的字符
{m}          匹配刚好是 m 个 的指定字符串
{m,n}        匹配在 m个 以上 n个 以下 的指定字符串
{m,}          匹配 m个 以上 的指定字符串
[]           匹配符合 [] 内的字符
[^]         匹配不符合 [] 内的字符
[0-9]         匹配所有数字字符
[a-z]        匹配所有小写字母字符
[^0-9]        匹配所有非数字字符
[^a-z]        匹配所有非小写字母字符

\b         匹配以英文字母,数字为边界的字符串
\d         匹配一个数字的字符,和 [0-9] 语法一样
\w        英文字母或数字的字符串,和 [a-zA-Z0-9] 语法一样
\s         空格,和 [\n\t\r\f] 语法一样
\B         匹配不以英文字母,数值为边界的字符串
\D         非数字,其他同 \d
\S         非空格,和 [^\n\t\r\f] 语法一样
\W        非英文字母或数字的字符串,和 [^a-zA-Z0-9] 语法一样

a|b|c       匹配符合a字符 或是b字符 或是c字符 的字符串
abc         匹配含有 abc 的字符串
转义:      使用\ 来取消元字符的特殊意义。包括 . * + \  [ ] { } ( ) ^ $


?的多重定义-懒惰限定符
*?    重复任意次,但尽可能少重复
+?    重复1次或更多次,但尽可能少重复
??    重复0次或1次,但尽可能少重复
{n,m}?    重复n到m次,但尽可能少重复
{n,}?    重复n次以上,但尽可能少重复


断言匹配: 有4个

(?=exp)   零宽先行断言,它匹配文本中的某些位置,这些位置的后面能匹配给定的后缀exp。
             比如/b/w+(?=ing/b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),
              zc:根据后缀匹配而已。和$区别在于$是行尾匹配。
              如I'm singing while you're dancing.  它会匹配sing和danc。

(?<=exp)  零宽后行断言,它匹配文本中的某些位置,这些位置的前面能给定的前缀匹配exp。
               如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),
               zc:匹配前缀。
                例如在查找reading a book时,它匹配ading。

(?!exp)    零宽负向先行断言。会匹配后缀exp不存在的位置。
              zc: 若不是exp或者没有则匹配,用^在于存在一个不匹配某个exp的其他字符,而!保证不匹配exp外可以不跟任何字符。
             
如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字。 也可以是只有前3位数字。比较\d{3}(^\d)有区别.

(?<!exp)   零宽负向后行断言。查找前缀exp不存在的位置.

条件匹配: (zc:这个复杂,但也不复杂。属于perl中的扩展部分,略)

Conditional Expressions

(?(condition)yes-pattern|no-pattern)
   attempts to match yes-pattern if the condition is true, otherwise attempts to match no-pattern.

(?(condition)yes-pattern) attempts to match yes-pattern if the condition is true, otherwise fails.

condition may be either a forward lookahead assert, or the index of a marked sub-expression
(the condition becomes true if the sub-expression has been matched).

 

 

2. boost中分有match,search,replace,在vc中,其正则表达式以上\表示需要双反斜杠表示。其中在

match是匹配整个句子,在实际应用中,必须是构造整个句子的正则表达,而在一篇文章匹配的话,以search用的

比较多,如下见一个片段。没有用到std,可见用boost在匹配查询时,其比较麻烦:

void test123()
{      
    CString str = "singing while youre dancing.";
    regex ee_all("\\b\\w+\\b");
    cmatch result;
    CString ret;
    while(regex_search(str, result, ee_all,match_perl)){           
        for(int i=0; i < result.size(); i++){           
            ret = result[i].str().c_str();
            AfxMessageBox(ret);               
            str = result[i].second;
        }           
    }   
}

posted @ 2008-04-29 11:26 kenlistian 阅读(1226) | 评论 (0)编辑 收藏

2008年4月23日 #

     摘要: Base64 编码和解码, 说到Base64编码和解码,不过就是把3个字节进行处理后放置到4个字节空间。其原理就是:     待编码数据,以3个字节为单位,依次取6位数据并在前面补上两个0形成新的8位编码, 由于3*8=4*6,这样3个字节的输入会变成4个字节的输出,长度上增加了1/3。     以上处理存在着字符不是可见字符,...  阅读全文
posted @ 2008-04-23 12:06 kenlistian 阅读(774) | 评论 (0)编辑 收藏

2008年4月22日 #

1.这里下载boots,http://downloads.sourceforge.net,我下载的是boost_1_35_0.zip,链接如下:

    http://downloads.sourceforge.net/boost/boost_1_35_0.zip?modtime=1206795434&big_mirror=0

2.解压后打开目录

    \boost_1_35_0\boost_1_35_0\libs\regex\build编译vc6下版本vc6.mak文件,具体如下:

    a。打开dos环境,把vc6目录中的Microsoft Visual Studio\VC98\Bin下的VCVARS32.BAT拖入dos窗做环境设置。

    b。运行namke -f vc6.mak。

    c。把生成的dll和lib放在自己的目录中,最好在vc6的vc98下建一个目录,毕竟是vc6下的编译码。然后在vc6中设置包含lib file。

       文件include包含目录 boost_1_35_0\boost_1_35_0\boost_1_35_0

3.测试,粘贴到vc中直接编译。

如果编译出现minimal builder不支持的话,则去掉set中的minimal builder勾选。

还有若出现类似fatal error C1001: INTERNAL COMPILER ERROR错误的话,则采用rebuilder来重建,一般是可以通过。

为啥为什么出现这个内部编译错误,倒不清楚。不过,不影响vc下的正则处理使用暂时不管了。

#include "stdafx.h"
#include <boost/regex.hpp>
#include <string>
#include <iostream>

using namespace std;
using namespace boost;

regex ee("a+b");

int main()
{
    string str = "aaaaaaab";
    if(regex_match(str.c_str(), ee))
    {
      cout<<"match ok"<<endl;
    }
    getchar();
    return 0;
}

 

备注原文:http://dotnet.csdn.net/page/66e7a1c1-981e-4609-93fc-a3c34a6a5308

posted @ 2008-04-22 16:40 kenlistian 阅读(896) | 评论 (0)编辑 收藏

2008年4月21日 #

  其实,插件不过就是调用dll中的函数而已,不过通过类似一个com中的接口,再通过接口查询到相应的服务来处理。

复杂的插件,当然有考虑采用com方式的,不过作为编写程序的原则是简单,实效,通用。又何须采用太过专业的方法。

技术不过是手段,能在达到目的的最大化程度上实现,就足矣。

  下面的例子来自网上,作者不详,稍微整编下。直接贴代码在上面。源码打包放在自己博客的文档中。算是自己学习整理,

也感谢提供者。

源码学习:http://www.cppblog.com/Files/kenlistian/test_plus.rar

 

1.定义插件的接口结构

/*
  定义一个plus 接口结构
*/
typedef struct PlugInModule{
    DWORD Ver ;                    //版本
    char *Author ;                  //作者说明
    char *Description;             //模块说明

    BYTE *InputPointer;          //输入数据
    DWORD dwSize ;               //输入数据的大小
    HWND hParentWnd ;           //主程序的父窗口
    HINSTANCE hDllInst ;          //Dll句柄

    void (*PlugIn_Config)( struct PlugInModule * pModule ); //设置函数
    void (*PlugIn_Init)( struct PlugInModule * pModule );    //初始化函数
    void (*PlugIn_Quit)( struct PlugInModule * pModule );   //退出函数
    void (*PlugIn_Run )( struct PlugInModule * pModule );   //执行函数
} PlugInModule;

其中接口结构函数,被规定了4个,也就是说这个接口函数定死了,如果以后应为功能增加等等,

则估计这个结构都要改写。所以采用com方式接口方式则是一种好的选择,而那种模式,每次还要注册com,

则莫免麻烦和钉死在windows平台上。

2.以上接口结构放置在头文件中。作为主程序和dll共享的头文件,其中,再在头文件中具体声明以上结构体中函数。

void plusDll_Config( struct PlugInModule * pModule);  //设置函数
void PlusDll_Init( struct PlugInModule * pModule );   //初始化函数
void plusDll_Quit( struct PlugInModule * pModule );   //退出函数
void plusDll_Run( struct PlugInModule * pModule );     //执行函数

3.在头文件中声明一个返回该结构的函数。其实就是一个回调函数。把该结构返回给主程序的一个export 函数。

typedef PlugInModule* (*GETPLUGINMODULE)();       //声明接口函数地址

/**
  导出函数,主程序首先获取该接口函数,获得 dll中的函数地址,调用
*/
DLL_001_API PlugInModule* GetPlugInModuleFunction();     //DLL_001_API ==> __declspec(dllexport)

4.在dll中定义该插件结构,把地址通过GetPlugInModuleFunction传入到主程序。

5.分别实现dll中和主程序的定义部分。通过动态加载方式即可实现取出dll的结构体指针。

如下示:

        hDLL = LoadLibrary("dll_001\\debug\\dll_001.dll");
        if (hDLL)
                MessageBox(NULL,"plus_Dll load ok", "", MB_OK);
        else
        {                       
                        MessageBox(NULL, "not found plus_dll","",MB_OK);
                        return 0;
                    }
                    pFunction = (GETPLUGINMODULE)::GetProcAddress(hDLL,"GetPlugInModuleFunction");
                    if (pFunction != NULL)
                    {
                       dllplus_module = (*pFunction)();
                          dllplus_module->PlugIn_Init(dllplus_module);
                       dllplus_module->PlugIn_Run(dllplus_module);
                       dllplus_module->PlugIn_Quit(dllplus_module);

                    }
                    ::FreeLibrary(hDLL);//卸载MyDll.dll文件;

posted @ 2008-04-21 18:54 kenlistian 阅读(1981) | 评论 (2)编辑 收藏

      好长时间没更新了,主要这段时间忙学ruby。没多少时间温故一下c++及其细节。对于编程为生涯的人生中,其c/c++是一把好的工具,但是在长年累月编写代码中,更加明白的编程的思想才是灵魂,没有思想的编程,也不过如同拿把宝剑而无所作为。就是再精通c++也不过是称之为某语言专家。而对于使用工具的人来说,不仅是要善于运用工具,而且更应该是发挥工具去实现你的要求。

      但很多人在对待c/c++时,太多的时候,如同时常抚摸自己手中宝剑而忘却了要去面对的项目。

      我时常以CS游戏中的武器向同事和朋友来比喻,c如同CS中的阻击步枪,而C++如同Ak47,而java,c#如同制式武器中的m41突击步枪,这些都是在编程世界中的主力厮杀武器,我们可以选择C可以瞄准任何项目,也能性能极高的射中项目的要害,但是持C之人,所积累的经验,所对项目的开发时间缓急,都在一定程度上影响了采用c的要求,C++和AK47有点类似,狂暴有效,但不是一般人能够在项目开发中始终保持开发的速度和效率,在CS中AK47,头三枪效率极高,但如在编程世界中项目的大量和迫近时,往往和CS中AK47中子弹都飞散到敌手周围一样变得杂乱低效。所以一般而言,采用制式武器中的java,C#是作为现代工业编程的主要制式武器,精确有效,弹量充足。

     也许,选择以上制式武器也都是一种个人爱好和企业的侧重,但是再怎么着,也必须在装备了一主力武器外还得配备近战的武器,和朋友说起,你得像cs中配了主力武器外,还得配把近战手枪,以备各种要求。如同CS中持枪盘斗到最后,也得抽出短枪盘环近斗,这也类似在项目吃紧时,临时有额外的要求时,用制式工具也许来不及或者不值得花大工夫去处理时,用用那些胶水语言(动态语言)则是非常省力和高效率的。也就是说胶水语言就如同近战手枪,简单有效实用。

    谈到动态语言,有太多,其实熟悉和能运营一,二门即可,如老牌的perl,称之为千年老妖的python,新兴的杀手工具ruby,简单平淡的vb or javascript,还有那些我从来没用过的的lua,lisp,schema等,会这些,不能自以为是的又以为精通了一门语言来比较c/c++的优劣,但也不能以c/c++ 性能优异理由而拒绝学习其他的理由。只是方便我们达到我们的目的。

posted @ 2008-04-21 16:25 kenlistian 阅读(825) | 评论 (2)编辑 收藏

2008年3月5日 #

补:没办法;俺的cnit的博客发布不上去,就发在俺的cpp博客吧。

      由于工作缘故,需要处理一接口发送来的xml串,对方采用java以字节流模式post一个xml串,
  在asp中采用request估计把它加载到xml解析器中应该报无法解析。

     采用Request.binaryRead 即可解决,但是要注意几个细节方面。

     一一到来。

  1. 读取字节流

       Dim vtBody

       iReceive = Request.TotalBytes

       vtBody = Request.BinaryRead(iReceive)

2.   转换字节流为字符串,有以下几个函数可以任选。

  

' a。byte --> str  ,该转换只适用小数据,但是所有ie没有问题,,,
Function bytes2BSTR(vIn)
    strReturn = ""
    For i = 1 To LenB(vIn)
        ThisCharCode = AscB(MidB(vIn,i,1))
        If ThisCharCode < &H80 Then
            strReturn = strReturn & Chr(ThisCharCode)
        Else
            NextCharCode = AscB(MidB(vIn,i+1,1))
            strReturn = strReturn & Chr(CLng(ThisCharCode) * &H100 + CInt(NextCharCode))
            i = i + 1
        End If
    Next
    bytes2BSTR = strReturn
End Function

'b。采用 ado record来转换,该转换速度快,转换数据大,据说对ie5支持不够好,
function rsbinarytostring(xbinary)
Dim binary

  If  vartype(xbinary)= 8 then
    binary = multibytetobinary(xbinary)
  Else 
    binary = xbinary
  End If
  Dim rs, lbinary
  const adlongvarchar = 201
  Set rs = createobject("adodb.recordset")
  lbinary = lenb(binary)

  If lbinary>0 Then
    rs.fields.append "mbinary", adlongvarchar, lbinary
    rs.open
    rs.addnew
    rs("mbinary").appendchunk binary
    rs.update
    rsbinarytostring = rs("mbinary").Value
    rs.Close
  Else
   rsbinarytostring = ""
  end if
End Function

'***************************************************
'c。采用流解析字符串,该转换速度快,转换数据大,据说对ie5支持不够好,
function stream_binarytostring(binary, charset)
  set binarystream = createobject("adodb.stream")
  '读入字节流
  binarystream.type = 1
  binarystream.open
  binarystream.write binary

  '内部以字符方式返回
  binarystream.position = 0
  binarystream.type = 2
  If len(charset) > 0 Then
    binarystream.charset = charset
  Else
    binarystream.charset = "us-ascii"
  End If
  stream_binarytostring = binarystream.readtext
end function

则调用上面任一个进行转换即可。如:

  strBody = stream_binarytostring(vtBody, "utf-8")

3. 将strBody解码

   如果调用Response.Write strBody ,则在ie上可以看到正常的xml结构体部分。

   但是如果你要是写在文本中,你将会看到的是如下的样式:

%3C%3Fxml+version+%3D+%221.0%22+encoding%3D%22UTF-8%22+%3F%3E%3CROOT%3E%3CUSER%3Egtzx%3C%2FUSER%3E%3CPASS%3Egtzx%3C%2FPASS%3E%3CMO%3E%3CMOID%3E291AC5FDBD0DF4EF2B2A2950FB730610%3C%2FMOID%3E%3CMSGFORMAT%3E15%3C%2FMSGFORMAT%3E%3CCLASSID%3Ehttp%3A%。。。。%3E%3CSERVICEID%3E1981%3C%2FSERVICEID%3E%3CCITYID%3E102%3C%2FCITYID%3E%3CPROVINCEID%3E16%3C%2FPROVINCEID%3E%3CMOUSEID%3E2%3C%2FMOUSEID%3E%3CSPNUMBER%3E10666066%3C%2FSPNUMBER%3E%3CLINKID%3E%3C%2FLINKID%3E%3CREMARK%3E%3C%2FREMARK%3E%3C%2FMO%3E%3C%2FROOT%3E

  这表示是url 编码方式,它把utf-8编码进行了再一次编码,如果你要是xml解析器来解析的话,恐怕它是干不了活的。(也许有,但是asp中玩那个xmldocument实在是不想研究下去)

  不过再需要做个urlDecode转换,这个asp函数,网上一大把,搜出一个,粘贴下来就可以去掉%并转换utf-8格式。这里贴出一个修改的urlDecode函数,

'*****************************************************
'功能描述:URL解码码函数
'输入参数:vURL编码的字符串
'返回值:解码后的字符串
Public Function URLDecoding(sIn)
Dim s,i,l,c,t,n : s="" : l=Len(sIn)

For i=1 To l
    c=Mid(sIn,i,1)
    If c<>"%" Then
        s = s & c
    Else
        c=Mid(sIn,i+1,2) : i=i+2 : t=CInt("&H" & c)
        If t<&H80 Then
            s=s & Chr(t)
        Else
            c=Mid(sIn,i+1,3)
            If Left(c,1)<>"%" Then
                URLDecoding=s
                Exit Function
            Else
                c=Right(c,2) : n=CInt("&H" & c)
                t=t*256+n-65536
                s = s & Chr(t) : i=i+3
            End If
        End If
    End If
Next
s=Replace(s, "+"," ")
URLDecoding=s
End Function

  4. 调用xml解析器,加载以上字符串,即可解决。

'***********************************************************
'解析xml文件
'***********************************************************
Dim xml
Set xml = Server.CreateObject ("msxml2.DOMDocument")
xml.Async = False
xml.Loadxml(strBody)

5.读出xml中的节点,写入文本或者写入数据库,ok。

 

总结:

      在asp中采用xmlhttp发送或者接收,是不考虑字节流模式发送的,直接调用xmlhttp中的send即可。管它是

按啥模式发送的,但是在其他语言编程中,比如java,c#,or vc中,有可能是按字节流方式发送出去的,那么,

如果想图个方便,直接用asp写个接口处理下,就要考虑下和其他程序处理的细节。

posted @ 2008-03-05 16:12 kenlistian 阅读(2018) | 评论 (0)编辑 收藏

2008年2月18日 #

    今天本来想偷个懒,直接拿demo的一个chat代码做一个监控服务程序。采用的是传统的CSocket和CArchive方式处理序列化消息发送既可以了。

在做的途中,觉得搞一个线程处理读一个list字符串,有字符串就调用CSocket继承类,通过序列化方法发送出去就可以了。

   结果修改运行后,却本来想偷懒却变出偷不了懒,在通过序列化方式发送老是报一个Sockcore.cpp中的566的ASSERT错误。

#ifdef _DEBUG
void CAsyncSocket::AssertValid() const
{
    CObject::AssertValid();
    ASSERT(m_hSocket == INVALID_SOCKET || CAsyncSocket::FromHandle(m_hSocket) != NULL);
}

仔细看这个ASSERT,就是报socket的问题。

程序反复看了看,也没有查出问题。在google搜了搜,

http://topic.csdn.net/t/20020521/20/741527.html

http://topic.csdn.net/t/20020626/12/830990.html

   从中有些启发,把send部分从线程中采用主线程发送,立马解决问题。看来CSocket的继承类是估计不支持在线程模式下运行。也有人说CSocket继承类只能用于主线程,而不能在线程中,当然,是否这样,还需要确认。不过,如果想写线程下的socket类,最好还是从socket构造吧,免得绕弯路。否则,调试来调试去,困惑在深深的MFC代码内核代码中简直是浪费时间。

posted @ 2008-02-18 20:18 kenlistian 阅读(1425) | 评论 (0)编辑 收藏

仅列出标题  下一页