概述:

     近年来,由于中国消费产业的升级,中国老百姓消费水平和消费能力日益提高,投资、消费、出口这三驾马车中,消费的需求日渐增长,内需的逐渐增强,这对以后中国GPD的拉动作用不可小觑。随着中国消费的增长,优惠券打印机这种新鲜事物应运而生。

     不管是热闹的都市商场、超市、CBD,还是在人头攒动的候车亭,抑或是大型广场或电影院门口,都能见到优惠券打印机的身影,它秉乘者为广大消费者省钱,同时也给商家带来更多的客户和销售额的使命,已经慢慢的为广国民所接受,相信不久的将来,它将渐渐的成为我们生活不可或缺的一部分。

     目前市面上所见到的优惠券打印机,从外形结构上来区分,主要分为两种,一种是传统按键式优惠券打印机,另一种是触摸屏式优惠券打印机。他们到底有什么优缺点呢?

     首先,从易用性来说:

     按键式的优惠券打印机使用方便、快捷,从商家选取到一张优惠券打印完成,整个过程最短可以控制在10秒以内(其中高速热敏打印机打印速度仅2.5秒);一般每个商家都对应一个按键,按键旁边以广告灯箱显示商家LOGO,显眼、直观、所见即所得,一目了然,由于灯箱采用高亮LED背光,所以在20米开外都能看到显目的商家LOGO,能起到不错的广告效果。不管是年轻时尚一族,还是家庭主妇、老太太,都能很快上手使用;

    

     触摸屏式优惠券打印机,由于没有过多的按键,也没有广告灯箱,就算是20几寸的屏幕,也不可能一次性显示出所有的商家和优惠券,所以各商家及优惠券必须按分类来排列,界面设计上,一般采用分级菜单的形式,一级套一级,有的甚至超过五级菜单,菜单每深入一级,操作难度增加约20%,用户体验也跟着会降低。从商家选取到优惠券打印,至少30秒,甚至数分钟。一般非电脑族,可能不太能熟练使用这种系统。另外,对于大屏幕触摸屏,多数采用红外触摸屏,这种触摸屏的响应速度和触摸效果远远比不上电容式触摸屏的效果(I-PHONE,I-PAD都采用电容式触摸屏)

     其次,从硬件成本上来说:

     按键式优惠券打印机,由于在硬件设计上采用低价而耐用的轻触按键,主控制器一般采用低功耗ARM9ARM11 等嵌入式CPU,显示屏多采用7-12TFT显示屏(黑白或彩色),所以整体硬件成本相对低廉,根据具体配置的高低,一般整体价格在4000-6000元之间。

      触摸屏式优惠券打印机,一般采用10-22寸甚至32寸彩色TFT液晶屏, 外面再安装一块红外触摸屏作为输入装置,就这二项,成本大概在1000元以上;由于驱动大尺寸屏幕,这就要求有更强大的CPU和更大的内存,所以CPU多采用PC架构,以INTEL ATOM系列CPU为主,内存在512MB-2GB不等。所以整体成本比按键式的要高。市场价在4000-20000元不等。

再次,从维护成本上来看:

按键式优惠券打印机,由于屏幕不大,而且外面有一层透明的防爆PVC,不易被暴力破坏;易损部件主要是按键,按键一般采用寿命10万次以上的轻触按键,更换方便,成本低廉,不影响后续使用。由于采用嵌入式CPU,所以整机功耗小,一般待机功耗在20W以下,CPU发热量不大,根本不需要CPU风扇,所以不容易出现CPU过热死机的情况;

触摸屏式优惠券打印机,由于采用大屏幕液晶屏,最好有巡视人员值守,防止有人恶意破坏触摸屏或显示屏;由于多采用红外线触摸屏,所以要定期清理屏幕四周的灰尘和垃圾,否则红外线会被灰尘挡住,从而使触摸失效,不能正常使用。由于采用电脑CPU,所以功耗相对比较大,发热量也大,一般机箱上装有CPU风扇,所以要定期对风扇除尘和更换,否则会造成CPU过热而死机,影响正常使用。

综上所述,优惠券打印机最终的目的是为了广告运营,从运营的角度来看:

首先,按键式优惠券打印机投入产出比,比触摸屏式优惠券打印机的要高

其次,以使用效率来对比,按键式优惠券打印机的使用效率是触摸屏式优惠券打印机的好几倍。   

如果同样打印10张优惠券,按键式优惠券打印机从选取商家到打印完成,只需要1分钟之内就可以完成。之所以如此之快,是因为所有的按键操作,都是简单的重复,不需要很复杂的查找和搜索。所以消费者几乎不需要需排队等候,就能快速得打印出自己想要的优惠券;而触摸屏式优惠券打印机,由于操作流程复杂,随着菜单的一级一级深入,往往需要几分钟甚至十几分钟,才能打完自己想要的10张优惠券,估计没有消费者愿意在后面长时间排队等候了。所以,从广告学的立场来看,按键式打印机的广告受众,明显比触摸屏式打印机的广告受众多。

再次,从广告效果来看,每一台按键式优惠券打印机的广告位,大概就是固定的10几个灯箱位。因为每台机器的广告灯箱位是固定不变的,所以对于同一个展位的每个商家来说,他们都是公平的。如果以15个广告位来算,每个商家被消费者选择的概率就是1/15

而触摸屏式优惠券打印机,由于采用多级菜单和分类的方式显示商家和优惠券,某个展位的商家越多,每个商家被消费者选择的概率就越低,比如同一个展位的同一台机器,如果有100个商家,那么每个商家被消费者选择的概率只有1/100. 而且他们被选择的概率各不相同,就像是百度排名规则一样,排在前几页的商家容易被消费选择,而排在中间几页和后面几页的商家,往往容易被消费者忽略。

     

         

爱券:www.aiquan.cn

posted @ 2011-06-30 15:32 Jade 阅读(36) 评论(0) 编辑
posted @ 2010-05-10 19:55 Jade 阅读(81) 评论(0) 编辑

       大家知道,字体文件对于WINCE系统来说,一般都比较庞大,一般在2MB-15MB之间,如果在WINCE的NK中定制几种字体或者大容量字体,

势必会增加NK的大小,这对嵌入式系统是不现实的。

 

       好在WINCE系统提供了动态加载字体的API: AddFontResource, 利用这个API函数,我们可以动态加载字体,我们可以把字体放在NAND FLASH中,也可以放在SD卡上,因此不必担心NK会变大,消耗宝贵的FLASH资源问题。而且我们可以根据需要加载多个字体(注意:如果加载的字体太多或太大,也会占用系统内存)。但是要注意的是,系统退出时,记得要卸载动态加载的字体,否则会引起内存泄露。应用过程中,需要注意的问题是:结构体LOGFONT 的成员lfFaceName,应该为字体名,字体名在WINDOWS下,直接打开字体,最上面一行就有字体名显示。

 

闲话少说,贴上关键代码:

#define CUSTOM_FONT  L"Storage Card\\FZY3JW.TTF"

/////////////////////////////////////////////////////////////////////////////
// CFont_testDlg message handlers

BOOL CFont_testDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 // Set the icon for this dialog.  The framework does this automatically
 //  when the application's main window is not a dialog
 SetIcon(m_hIcon, TRUE);   // Set big icon
 SetIcon(m_hIcon, FALSE);  // Set small icon
 
 CenterWindow(GetDesktopWindow()); // center to the hpc screen

 // TODO: Add extra initialization here
 if (NULL != AddFontResource(CUSTOM_FONT))
 {
  ::SendMessage( HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
 }
 else
 {
  AfxMessageBox(L"加载方正准圆简体字体失败!");
 }

 if(NULL!= AddFontResource(XINGKAI_FONT))
 {
  ::SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
 }
 else
 {
  AfxMessageBox(L"加载行楷简体字体失败!");
 }

 LOGFONT lf;
 
 memset(&lf, 0, sizeof(LOGFONT));
 
 WCHAR lffn[]= TEXT("方正准圆简体");
 
 wcscpy(lf.lfFaceName,lffn);
 
 lf.lfHeight = 20;
 
 lf.lfWidth = 0;
 
 lf.lfQuality = CLEARTYPE_QUALITY;
 
 lf.lfCharSet = GB2312_CHARSET;
 
 //hFontNew = CreateFontIndirect(&lf);
 hFontNew.CreateFontIndirect(&lf);

 GetDlgItem(IDC_STC_TEXT)->SetFont(&hFontNew);

 return TRUE;  // return TRUE  unless you set the focus to a control
}

 

程序退出后,记得卸载字体,否则会造成内存泄露:

 if(RemoveFontResource(CUSTOM_FONT))
 {
  ::SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
 }
 else
  AfxMessageBox(L"Failed to UNLOAD font!");


上述代码经过本人在WINCE5.0模拟器和三星2440平台上测试通过。

 

声明: 如果需转载本文,请注明原文作者及出处的链接,否则本人将追究其责任!

 

posted @ 2010-05-10 00:11 Jade 阅读(893) 评论(0) 编辑

我们现在的模块用的是USB接口,然后模拟成一个COM6进行通信,在CE的: 控制面板->网络连接 里要新建一个连接,选择“拨号连接”然后设置好区域代码,还有附加解调器命令+CGDCON,"IP","CMNET",modem选择,与modem的串口通信速率等信息,然后连接,这上可以实现上网。

   添加的CE组件有:

CoreOS->Windwos CE devices->commuication services and newworking->networking-wide area network ->Dial up networking

        ->Autodail

        ->TAPI

        ->Virtual Private Networking(VPN)

 

   我们现在要实现的功能是,让用户只点击一个ICON,就能实现上网,不用这么麻烦去设置。

   我写了一个应用,通过RAS 接口调用可以实现拨号上网功能,但是,必须指定一个已建立并设好参数的连接,

 

   所以,还得在程序中创建一个网络连接才行,因为我们需要的简化的其实就是让用户输入附加拨号命令(+CGDCONT=1,"IP","CMNET"那么一长串,其实只需要用户输入一个APN和一个电话号码就OK了)。因为针对不同的运营商,这个APN是不同的,所以这个要能给用户设置。

查MSDN得知,调用RasSetEntryProperties加上RasSetEntryDialParams可以实现,但是这种方法貌似设置不了modem的串口属性、附加拨号命令等一些信息,

 

用RasSetEntryProperties设置一个Entry的属性的时候,如果Entry的参数中指定的EntryName已经存在了,那么就更改已有的Entry的设置,如果不存在,则创建了一个新的Entry,这样就实现了创建拨号连接的功能,

MSDN上关于RasSetEntryProperties函数中的倒数第二个参数的解释说明是说它是一个指向包含设备配置信息的缓冲区的指针,是关于设modem的串口属性、附加拨号命令等,

但是要查阅关于这个配置信息的更多详细内容,就要看TAPI相关的lineGetDevConfig函数,但是TAPI相关知识是相当庞大的一个体系,因为项目时间很紧,

直接在WINCE系统里的控制面板下,进入“网络和拨号连接”,手动新建一个能拨通上网的连接,我命名为“3GCONNECT”

 然后写测试程序,调用RasGetEntryProperties()函数,在倒数第二个参数传一个数组,然后打印出正确设置时的lpb里面的值,结果如下:

-                  lpb     0x001aed00 "0"       unsigned char[436]

                   [0x0]  0x30 '0'        unsigned char

                   [0x1]  0x00 ''          unsigned char

                   [0x2]  0x00 ''          unsigned char  //等待信用卡音频信号时间

                   [0x3]  0x00 ''          unsigned char

                   [0x4]  0x78 'x'        unsigned char  //120 秒这时间之内未建立连接,则取消拨号

                   [0x5]  0x00 ''          unsigned char

                   [0x6]  0x00 ''          unsigned char

                   [0x7]  0x00 ''          unsigned char

                   [0x8]  0x00 ''          unsigned char  //串口参数:流控制,0 -- 无 0x20 软件  0x10 硬件

 

                   [0x9]  0x01 'r'         unsigned char

                   [0xa]   0x00 ''          unsigned char

                   [0xb]  0x00 ''          unsigned char

 

                   [0xc]   0x00 ''          unsigned char  //串口波特率 19200

                   [0xd]  0x4b 'K'       unsigned char

 

                   [0xe]   0x00 ''          unsigned char

                   [0xf]   0x00 ''          unsigned char

                   [0x10] 0x00 ''          unsigned char // 对应“设备属性”对话框中的“手动拨号(用户提供拨号串)”,

//如果为0x04 表示选上此项

                   [0x11] 0x00 ''          unsigned char

 

                   [0x12] 0x08 ''       unsigned char //串口数据位 8位

                   [0x13] 0x00 ''          unsigned char   //停止位,00- 1位,  02 - 2位

                   [0x14] 0x00 ''          unsigned char   //奇偶校验位,00-无,01奇数,02偶数,03标记,04空格

                  

                   [0x15] 0x00 ''          unsigned char

 

                   [0x16] 0x2b '+'        unsigned char

                   [0x17] 0x00 ''          unsigned char

                   [0x18] 0x43 'C'       unsigned char

                   [0x19] 0x00 ''          unsigned char

                   [0x1a] 0x47 'G'       unsigned char

                   [0x1b] 0x00 ''          unsigned char

                   [0x1c] 0x44 'D'       unsigned char

                   [0x1d] 0x00 ''          unsigned char

                   [0x1e] 0x43 'C'       unsigned char

                   [0x1f] 0x00 ''          unsigned char

                   [0x20] 0x4f 'O'        unsigned char

                   [0x21] 0x00 ''          unsigned char

                   [0x22] 0x4e 'N'        unsigned char

                   [0x23] 0x00 ''          unsigned char

                   [0x24] 0x54 'T'        unsigned char

                   [0x25] 0x00 ''          unsigned char

                   [0x26] 0x3d '='        unsigned char

                   [0x27] 0x00 ''          unsigned char

                   [0x28] 0x31 '1'        unsigned char

                   [0x29] 0x00 ''          unsigned char

                   [0x2a] 0x2c ','         unsigned char

                   [0x2b] 0x00 ''          unsigned char

                   [0x2c] 0x22 '"'        unsigned char

                   [0x2d] 0x00 ''          unsigned char

                   [0x2e] 0x49 'I'         unsigned char

                   [0x2f] 0x00 ''          unsigned char

                   [0x30] 0x50 'P'        unsigned char

                   [0x31] 0x00 ''          unsigned char

                   [0x32] 0x22 '"'        unsigned char

                   [0x33] 0x00 ''          unsigned char

                   [0x34] 0x2c ','         unsigned char

                   [0x35] 0x00 ''          unsigned char

                   [0x36] 0x22 '"'        unsigned char

                   [0x37] 0x00 ''          unsigned char

                   [0x38] 0x43 'C'       unsigned char

                   [0x39] 0x00 ''          unsigned char

                   [0x3a] 0x4d 'M'       unsigned char

                   [0x3b] 0x00 ''          unsigned char

                   [0x3c] 0x4e 'N'        unsigned char

                   [0x3d] 0x00 ''          unsigned char

                   [0x3e] 0x45 'E'        unsigned char

                   [0x3f] 0x00 ''          unsigned char

                   [0x40] 0x54 'T'        unsigned char

                   [0x41] 0x00 ''          unsigned char

                   [0x42] 0x22 '"'        unsigned char

                   [0x43] 0x00 ''          unsigned char                  

[0x44]         0x00 ''          unsigned char后面的全为0

经过改不同的参数然后以上面的方法打出这个参数传回来的值,总结出

 

所以总结出以下结构体:

typedef struct

{                             //offerset

 U16INT  Reserved1;               //0               一般为0x30

 U16INT  WaitForCreditcard;           //0x02 等待信用卡音频信号时间,单位 ;秒

 U16INT  WaitForConnect;       //0x04      这时间之内未建立连接,则取消拨号

 U16INT  Reserved2;              //0x06    一般为0

 UINT8    Flowcontrol;           // 0x08  串口参数:流控制,0 -- 无 0x20 软件  0x10 硬件

 UINT8    Unknow1;              // 0x09    一般为1,暂时还不知它是什么作用

 U16INT  Reserved3;              // 0x0a   = 00

 U16INT  BaudRate;               //0x0c 串口波特率例如 19200

 U16INT  Reserved4;              // 0x0e   =0

 U16INT  Configbit2;     // 0x10  // 对应“设备属性”对话框中的“手动拨号(用户提供拨号串)”,如果为0x04 表示选上此项

 UINT8    DataBit;             // 0x12  串口数据位 例如:8位

 UINT8    StopBit;             // 0x13  停止位00- 1位,  02 - 2位

 UINT8    ParityBit;           // 0x14  奇偶校验位00-无,01奇数,02偶数,03标记,04空格

 UINT8    Reserved5;          // 0x15  i don't know it's function

 TCHAR    AttachedCmd[210];  // 0X16  从这开始是附加解调器命令,例如"+CGDCON=1,IP,COMNET"

         //注意:本人使用的是wince6.0,所以这个长度为436byte,其它平台可能会不一样,网上有牛人测出WM的是一百多的

}RASlpdInfo;

用这个结构在AttachedCmd中填入附加拨号命令,就可以了

 

  DWORD dwSize,

    dwError;

    TCHAR szError[100];

   RASlpdInfo lpb;

   unsigned long lpbSize = sizeof(RASlpdInfo);

    memset(&lpb,0,lpbSize);

    RASENTRY RasEntry;

    dwSize = sizeof (RASENTRY);

    memset (&RasEntry, 0, dwSize);

    RasEntry.dwSize = dwSize;

wcscpy(RasEntry.szDeviceName,L"CDMAModem");

wcscpy(RasEntry.szDeviceType,L"modem");                 //#define RASDT_Modem         TEXT("modem")     // Ras.h

RasEntry.dwCountryCode = 86; //国家代码

wcscpy(RasEntry.szAreaCode,L"10");    

TCHAR szLocalPhoneNumber[RAS_MaxPhoneNumber + 1] = {0};

wsprintf(szLocalPhoneNumber,L"%s",LocNum);  //-------------------容易出问题的地方

wcscpy(RasEntry.szLocalPhoneNumber,szLocalPhoneNumber );

RasEntry.dwfOptions= 4194304;//RASEO_ProhibitEAP|RASEO_RemoteDefaultGateway;  ;//4194304;0x00400208;//

RasEntry.dwfNetProtocols= RASNP_Ip;//4; Negotiate TCP/IP

RasEntry.dwFramingProtocol= RASFP_Ppp;//1; // Point-to-Point Protocol (PPP)

lpb.Reserved1 = 0x30;

      lpb.WaitForCreditcard = 0;

      lpb.WaitForConnect =120;

      lpb.Reserved2 = 0;

      lpb.Flowcontrol = 0;

      lpb.Unknow1 = 1;

      lpb.Reserved3 = 0;

      lpb.BaudRate = 19200;

      lpb.Reserved4 = 0;

      lpb.Configbit2 = 0;

      lpb.DataBit = 8;

      lpb.StopBit = 0;

      lpb.ParityBit = 0;

    TCHAR szATComd[420] = {0};

    wsprintf(szATComd,L"+CGDCONT=1,$IP$,$%s$",lpszAPN);

    szATComd[11] = 0x22; // 双引号"

    szATComd[14] = 0x22;

      szATComd[16] = 0x22;

    szATComd[22] = 0x22;

    wcscpy(lpb.AttachedCmd,szATComd);

   if (dwError = RasSetEntryProperties (NULL, lpszName,

                        &RasEntry, sizeof (RASENTRY),(LPBYTE)(&lpb), lpbSize))

    {

        wsprintf (szError, TEXT("Unable to create the phonebook entry.")

        TEXT(" Error %ld"), dwError);

        return FALSE;

    }

 


注意,WINCE6.0调用了RasSetEntryProperties()之后,就可以在控制面板的连接管理里看到这个连接图标了,但是,如果是在WM系统,那就得再做下面的工作才能看得到图标。


机器中控制面板的”管理现有连接“对应注册表中


HKEY_LOCAL_MACHINE\\Comm\\ConnMgr\\Providers\\{7C4B7A38-5FF7-4bc1-80F6-5DA7870BB1AA}\\Connections

[Connections]下的集合;

[Enabled]:      是否为默认连接;

[RequirePW]  是否需要用户名与密码

[DestId]:      表示属于哪种类型的连接;

[ConnectionGUID]:表示连接的标识ID,

 


拔号时获得当前状态

如果想在拔号时获得当前状态,可以这样:

拦截PreTranslateMessage这个消息事件,截获WM_RASDIALEVENT消息即可达到目的

BOOL CMy3GControlerDlg::PreTranslateMessage(MSG* pMsg)

{

if(pMsg->message == WM_RASDIALEVENT)

 {

  switch(pMsg->wParam)

  {

 case   RASCS_OpenPort:

      pShowstate->SetWindowTextW(_T("打开端口…"));

    break;  

 case   RASCS_PortOpened: 

  pShowstate->SetWindowTextW(_T("端口已打开…"));

     break;  

 case   RASCS_ConnectDevice:  

     break;  

 case   RASCS_DeviceConnected:

  pShowstate->SetWindowTextW(_T("设备已连接…"));

     break;  

    case   RASCS_Authenticate: 

  pShowstate->SetWindowTextW(_T("验证用户及密码…"));

     break;  

    case   RASCS_Authenticated:  

  pShowstate->SetWindowTextW(_T("用户及密码已通过验证"));

     break;  

 case   RASCS_Connected:

        pShowstate->SetWindowTextW(_T("已连接"));

     break;  

 case   RASCS_Disconnected:  

     pShowstate->SetWindowTextW(_T("断开连接"));

     break;  

 default:  

     return   (LRESULT)0;

  }

 }

 return CDialog::PreTranslateMessage(pMsg);

}  

 


RAS拨号的同步和异步问题

 dwRet = RasDial(NULL,NULL,&rdParams,0L,NULL,&m_hRasConn);//同步
 dwRet = RasDial(NULL,NULL,&rdParams,0L,RasDialFunc,&m_hRasConn);//异步回调 --只支持在PC
 dwRet = RasDial(NULL,NULL,&rdParams,0xFFFFFFFF,this->m_hWnd,&m_hRasConn); //异步消息--支持PC、CE

如果同步,可用线程监视同步过程,

异步:只在使用了PreTranslateMessage才成功

所以,在CE系统里,要实现连接状态显示,那就得使用

 dwRet = RasDial(NULL,NULL,&rdParams,0xFFFFFFFF,this->m_hWnd,&m_hRasConn);

 


 

拨号连接

void DialRAS(LPVOID Hahandle,LPWSTR ConnectionName,LPWSTR PhoneNumber)

{

        RASDIALPARAMS RasDialParams;

         BOOL                   fPassword;

     memset (&RasDialParams, 0, sizeof (RASDIALPARAMS));

         RasDialParams.dwSize  = sizeof(RASDIALPARAMS);

  wcscpy(RasDialParams.szEntryName,ConnectionName);  //已建连接的名字

    TCHAR szLocalPhoneNumber[RAS_MaxPhoneNumber + 1] = {0};

    wsprintf(szLocalPhoneNumber,L"%s",PhoneNumber);

    wcscpy(RasDialParams.szPhoneNumber,szLocalPhoneNumber);

         wcscpy(RasDialParams.szCallbackNumber, _T("")); 

         wcscpy(RasDialParams.szUserName, _T("")); 

         wcscpy(RasDialParams.szPassword, _T("")); 

         wcscpy(RasDialParams.szDomain, _T(""));

CMy3GControlerDlg* hthisp =(CMy3GControlerDlg*)Hahandle;

DWORD hRasRet = 0;

 // Try to establish RAS connection.

hRasRet = RasDial (NULL,            // Extension not supported

  NULL,            // Phone book is in registry

  &RasDialParams,  // RAS configuration for connection

  0xFFFFFFFF,      // Notifier type is a window handle

hthisp->m_hWnd,  // Window receives notification message

  &hRscon);

  printf("hRasRet == %d ", hRasRet);

    if ( hRasRet != 0) // Connection handle

    {

        printf("Could not connect using RAS, error == %d ", GetLastError());

    }

}

 

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ok138ok/archive/2009/12/03/4933061.aspx

posted @ 2010-01-25 16:31 Jade 阅读(454) 评论(1) 编辑
 下载WINCE IMAGE前记得格式化BINFS
 1、Storage Manager 加入 FAT File System;
  2、Registry Storage 加入Hive-based Registry ,默认的是RAM-based Registry;
  3、修改platform.reg

[HKEY_LOCAL_MACHINE\init\BootVars]
 "SYSTEMHIVE"="system.hv"   ;"SYSTEMHIVE"="Documents and Settings\\system.hv"
 ;"PROFILEDIR"="Documents and Settings"
 "Start DevMgr"=dword:1  ;"Start DevMgr"=dword:0
 "DefaultUser"="default" ;LIEAL
 "Flags"=dword:3   ;LIEAL
 "RegistryFlags"=dword:1 ;LIEAL
; END HIVE BOOT SECTION
; @CESYSGEN ENDIF FILESYS_FSREGHIVE

[HKEY_LOCAL_MACHINE\System\StorageManager\AutoLoad\Relfsd]
    "Dll"="relfsd.dll"
    "Paging"=dword:0
    "LoadFlags"=dword:1
        "Flags"=dword:1000;---->here
[HKEY_LOCAL_MACHINE\System\StorageManager\FATFS]
    "FriendlyName"="FAT FileSystem"
    "Dll"="fatfsd.dll"
    "Flags"=dword:00000024
    "Paging"=dword:1
    "EnableCache"=dword:1
    "CacheSize"=dword:0
    "Util"="fatutil.dll"
        "Flags"=dword:1000;----->here
[HKEY_LOCAL_MACHINE\System\StorageManager\BinFS]
    "FriendlyName"="BIN Filesystem"
    "Dll"="binfs.dll"
    "Paging"=dword:1
        "Flags"=dword:1000;----->here
[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\MSFlash]
        "DefaultFileSystem"="BINFS"
        "PartitionDriver"="mspart.dll"
        ; MountFlags 0x10 == shadow in \windows
        ; MountFlags 0x01 == hidden file system
        "MountFlags"=dword:11
        "Folder"="NAND Flash"
        "Name"="FLASH Disk Block Device"
                "Flags"=dword:1000;--->here
[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\HDProfile]
    "Name"="IDE Hard Disk Drive"
    "Folder"=LOC_STORE_HD_FOLDER
        "MountFlags"=dword:2;--->here

在platform.reg中包围在HIVE BOOT和END HIVE BOOT之间的驱动改了这些
[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\HDProfile]
"Name"="IDE Hard Disk Drive"
"Folder"="CFCARD"
"MountFlags"=dword:2 ---->here
posted @ 2010-01-22 14:47 Jade 阅读(324) 评论(0) 编辑

 

先说原理:

注册表类型分为基于对象存储的注册表和基于HIVE的注册表,在定制内核的时候只能选择其中一种。
从理论上讲这两种注册表都能够实现永久保存注册表数据,但是采用不同的类型会影响CE的启动顺序和启动速度,
还会影响内存的使用量。我还是趋向于采用基于HIVE的注册表来实现永久保存注册表数据,这也是一个发展趋势。
在讲解之前先简单描述如果CE采用基于HIVE的注册表,那么在启动时如何加载已经保存的注册表数据:


1, nk.exe执行,启动filesys.exe。
2, filesys.exe加载引导HIVE,此时引导HIVE位于nk.bin解压之后的文件中。
3, filesys.exe启动device.exe,之后处于等待状态,等待device.exe将包含系统HIVE的文件系统和存储设备的驱动程序加载完毕。
4, 而这个文件系统和存储设备的驱动程序存在于引导HIVE中。
5, device.exe加载上述所说的文件系统驱动程序和存储设备驱动程序,使之开始工作。之后device.exe处于等待状态。
6, filesys.exe被唤醒,加载并且安装系统HIVE。之后filesys.exe处于等待状态。
7, nk.exe按照系统HIVE的信息开始执行初始化工作。其中包括加载驱动程序和启动一些应用程序。
其中加载驱动程序一般由device.exe执行,而启动应用程序由filesys.exe执行。这时device.exe和filesys.exe已经被唤醒。


  因为引导HIVE和系统HIVE肯定有重复的地方,所以可能出现重复加载了驱动程序或者重复启动了应用程序。
为此,CE允许在描述驱动程序的注册表信息中加入防止重复的标志,而应用程序可以采用事件对象来防止重复启动,
如device.exe。


下面讲述如何设置基于HIVE的注册表,分基于硬盘和基于flash的2种情况

大家也可以看微软的帮助.

情况1,基于硬盘的永久保存

在PB中加入"Hive-based Registry",如果是Geode平台,再加入BSP_ENABLE_FSREGHIVE环境变量。
打开platform.reg,找到如下信息: ; HIVE BOOT SECTION
[HKEY_LOCAL_MACHINE\init\BootVars]
"SYSTEMHIVE"="Documents and Settings\\system.hv"
"PROFILEDIR"="Documents and Settings"
"Start DevMgr"=dword:0
IF BSP_ENABLE_FSREGHIVE
"Start DevMgr"=dword:1
ENDIF
; END HIVE BOOT SECTION
"SYSTEMHIVE"的值为系统HIVE文件的路径。
"Start DevMgr"是一个布尔值,指示是否开始就执行设备管理器device.exe,按照CE帮助文档的说法,
只有想把系统HIVE存储在对象存储中才在此设置为0,所以一般都要设置为1。
如果是多用户,可以在上述的注册表位置下输入"DefaultUser"="",指定默认的用户名。如果是单用户系统,可以不设置。
保证将包含系统HIVE的文件系统驱动程序的注册表信息和存储设备的驱动程序的注册表信息被包含在
"; HIVE BOOT SECTION"和"; END HIVE BOOT SECTION"之间,在这两个语句之间的注册表数据全部属于引导HIVE。
假如我们将系统HIVE文件system.hv存放在硬盘上,并采用FAT文件系统。
那么就要将[HKEY_LOCAL_MACHINE\System\StorageManager\FATFS]和
[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\HDProfile]移动到"; HIVE BOOT SECTION"下。


在"; HIVE BOOT SECTION"和"; END HIVE BOOT SECTION"之间的所有驱动程序的注册表信息中都加入下列一个标志:

"Flags"=dword:1000

这个标志是一个位掩码,它可以和其它已经存在的"Flags"或运算。
值1000表示此驱动程序只加载一次,这样device.exe就不会把当前驱动程序加载两次了。
在包含系统HIVE的存储设备的驱动程序的注册表信息中,加入如下标志(假设是硬盘):
[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\HDProfile]
"MountFlags"=dword:2

这个标志表示这个存储设备包含系统HIVE文件。
  按照如上所述设置后的内核就能实现永久存储注册表数据了。对于保存注册表数据的执行动作在此必须阐述清楚:
  正常情况下,CE能够保证重要的注册表数据能够从内存刷到(Flush)永久存储器上。
但是这并不能完全保证所有数据都能完整地保存而不丢失,所以要保证万无一失,
应该主动地调用RegFlushKey函数强制将内存中的数据刷到永久存储器上。
这个函数的参数只有一个,就是注册表分支。CE还增加一个注册表项(如下所示),
它的作用是每当函数RegCloseKey被调用时都自动调用RegFlushKey函数。
[HKEY_LOCAL_MACHINE\init\BootVars]
"RegistryFlags"=dword:1
  如果CE在启动过程中发现系统HIVE出现错误,它会自动删除文件并创建一个默认的系统HIVE文件,如果出现下面的注册表项,说明发生了这种事情。 [HKEY_LOCAL_MACHINE]
"RegPersisted"=dword:1



2,基于Flash的

1.在 "File Systems and Data Store" -- "Registry Storage"內,
選擇 "Hive-based Registry" .然后其它的都不用修改了
fat文件系统也不用加.

2,修改platform.reg的部分
找到下面的,修改
修改地方一:
; @CESYSGEN IF FILESYS_FSREGHIVE
; HIVE BOOT SECTION
[HKEY_LOCAL_MACHINE\init\BootVars]
"SYSTEMHIVE"="system.hv"
; "PROFILEDIR"="Documents and Settings" ;直接去掉了
"Start DevMgr"=dword:1 ;把0修改为1
; END HIVE BOOT SECTION
; @CESYSGEN ENDIF FILESYS_FSREGHIVE

[HKEY_LOCAL_MACHINE\init\BootVars] ;增加的地方
"DefaultUser"="default" ;增加的地方

修改地方二:
;HIVE BOOT SECTION ;自己加的,不知道有没有用
[HKEY_LOCAL_MACHINE\System\StorageManager\AutoLoad\FlashDrv]
"DriverPath"="Drivers\\BlockDevice\\FlashDrv"
"LoadFlags"=dword:1
"MountFlags"=dword:11
"BootPhase"=dword:0
"Flags"=dword:1000 ;自己加的

[HKEY_LOCAL_MACHINE\Drivers\BlockDevice\FlashDrv]
"Prefix"="DSK"
"Dll"="FLASHDRV.dll"
"Order"=dword:0
"Ioctl"=dword:4
"Profile"="FlashDrv"
"FriendlyName"="MS Flash Driver"
"MountFlags"=dword:11
"BootPhase"=dword:0
"Flags"=dword:1000 ;自己加的


; Bind BINFS to the block driver
[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\FlashDrv]
"DefaultFileSystem"="BINFS"
"PartitionDriver"="mspart.dll"
"AutoMount"=dword:1
"AutoPart"=dword:1
"MountFlags"=dword:2 ;11 -> 2
"Folder"="ResidentFlash"
"Name"="Microsoft Flash Disk"
"BootPhase"=dword:0
"Flags"=dword:1000 ;自己加的
;END HIVE BOOT SECTION ;自己加的,不知道有没有用

这样就能用了,
心里还有的疑问是:
微软的帮助文档说,
Set the following flag bit on each driver that is loaded during the first boot phase.
[HKEY_LOCAL_MACHINE\Drivers\...]
"Flags"=dword:1000
这个我没有理会,当然在上面有关flash的地方我还是加了,在common.reg里面很多地方都没加
但是能用.
还有,我没试上面的我加的
;HIVE BOOT SECTION
;END HIVE BOOT SECTION
本来的想法是,希望系统在加载注册表前,先加载必要的驱动,但是我觉得前面加";"分号了,就应该
屏蔽掉了,不知道到底起作用了没有,
具体在哪里设置加载自己的注册表前,应该加载哪些驱动.希望网友指教.
还有,希望网友能拿一个比较好的设置出来,偶估计自己的这个可能存在问题.

 

posted @ 2010-01-22 14:41 Jade 阅读(329) 评论(0) 编辑

//========================================================================
//TITLE:
//    消息处理函数的转移
//AUTHOR:
//    norains
//DATE:
//    Wednesday  03-January-2008
//Environment:
//    VS2005 + SDK-WINCE5.0-MIPSII  
//    EVC + SDK-WINCE5.0-MIPSII 
//========================================================================

    Windows CE有一个很有意思的API函数,通过SetWindowLong函数可以转移原窗口的消息处理函数为自定义的.敏感的朋友估计一看见,就已经明白可以做什么了.呵呵,难道不是么?
   
   
1.函数使用
 
    SetWindowLong的使用及其简单,比如我们将m_hEdWord窗口的消息处理函数置换为CtrlProc:

    SetWindowLong(m_hEdWord,GWL_WNDPROC,(DWORD)CtrlProc);
    就这么简单,现在只要m_hEdWord窗口收到消息,那么就会自动调用预先定义的CtrlProc.
 
    有设置,自然也有获取,不过这次我们是通过GetWindowLong函数:
    m_pPreProcEdWord = (WNDPROC)GetWindowLong(m_hEdWord,GWL_WNDPROC);
   现在m_pPreProcEdWord存储的就是目前m_hEdWord的消息处理函数地址.不过,其实通过SetWindowLong函数也能获取消息函数地址:
    m_pPreProcEdWord = SetWindowLong(m_hEdWord,GWL_WNDPROC,(DWORD)CtrlProc);
    不过这时候获取的却是在设置CtrlProc消息处理函数之前的函数地址.也就是说,这两段代码中m_pPreProcEdWord等价:
    1).
    m_pPreProcEdWord = SetWindowLong(m_hEdWord,GWL_WNDPROC,(DWORD)CtrlProc);
   
    2).
    m_pPreProcEdWord = (WNDPROC)GetWindowLong(m_hEdWord,GWL_WNDPROC);
    SetWindowLong(m_hEdWord,GWL_WNDPROC,(DWORD)CtrlProc);
    如果需要调用m_pPreProcEdWord指向的函数,则需要用上CallWindowProc:
    CallWindowProc(m_pPreProcEdWord,hWnd,wMsg,wParam,lParam);
   
2.接管Windows Control消息
 
    说了一大堆似乎很有哲理的话,现在就让我们来看看有什么实际的作用吧.
   
    估计这个函数用得最多是在Windows Control中.比如,你想在Edit Box输入某个字符时做一些特殊处理,只能通过SetWindowLong来置换内部的处理函数进而调用自己的特殊处理函数.
   
    我们来举一个非常简单的例子,首先创建一个Edit Box控件,如果在该控件中按下键盘的"ESC",则会退出应用程序.为了方便突出问题的重点,我们的CMainWnd窗口继承于CWndBase(关于CWndBase请见:http://blog.csdn.net/norains/archive/2007/11/10/1878218.aspx)

#pragma once
#include "wndbase.h"

class CMainWnd :
    public CWndBase
{
public:
    CMainWnd(void);
    ~CMainWnd(void);
    BOOL Create(HINSTANCE hInst, HWND hWndParent, const TCHAR *pcszWndClass, const TCHAR *pcszWndName);

protected:   
    static LRESULT CtrlProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);

private:
    HWND m_hEdWord; //The window is for inputing the word
    WNDPROC m_pPreProcEdWord; //Pointer to the previous window procedure for the input word window.
};

 

#include "stdafx.h"
#include "MainWnd.h"

//---------------------------------------------------------------------------------------
//Default value
#define IDC_EDIT_WORD        101


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CMainWnd::CMainWnd(void)
{
}

CMainWnd::~CMainWnd(void)
{
}


//----------------------------------------------------------------------
//Description:
//    Create the window. It's override function
//
//----------------------------------------------------------------------
BOOL CMainWnd::Create(HINSTANCE hInst, HWND hWndParent, const TCHAR *pcszWndClass, const TCHAR *pcszWndName)
{
    if(CWndBase::Create(hInst, hWndParent, pcszWndClass, pcszWndName) == FALSE)
    {
        return FALSE;
    }

    //The edit window for input the word
    m_hEdWord = CreateWindowEx(WS_EX_TOPMOST,
                                TEXT("EDIT"),
                                TEXT(""),
                                ES_LEFT  | WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_BORDER,
                                GetSystemMetrics(SM_CXSCREEN) / 3,
                                GetSystemMetrics(SM_CYSCREEN) / 3,
                                GetSystemMetrics(SM_CXSCREEN) / 3,
                                GetSystemMetrics(SM_CYSCREEN) / 3,
                                m_hWnd,
                                (HMENU)IDC_EDIT_WORD,
                                m_hInst,
                                NULL);

    //Store the pointer in the window
    SetWindowLong(m_hEdWord, GWL_USERDATA, (DWORD)this);
    //Get the previous window procedure
    m_pPreProcEdWord = (WNDPROC)GetWindowLong(m_hEdWord,GWL_WNDPROC);
    //Set the new window procedure
    SetWindowLong(m_hEdWord,GWL_WNDPROC,(DWORD)CtrlProc);

    return TRUE;
}


//----------------------------------------------------------------------
//Description:
//    Windows control process.
//
//----------------------------------------------------------------------
LRESULT CMainWnd::CtrlProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
    CMainWnd *pObject = (CMainWnd *)GetWindowLong(hWnd,GWL_USERDATA);

    if(pObject->m_hEdWord == hWnd)
    {
        switch(wMsg)
        {
            case WM_CHAR:
            {
                if((TCHAR) wParam == VK_ESCAPE)
                {
                    PostQuitMessage(0x00);
                }
                break;
            }
        }
        return CallWindowProc(pObject->m_pPreProcEdWord,hWnd,wMsg,wParam,lParam);
    }

   
    //It should never get here !
    return DefWindowProc(hWnd,wMsg,wParam,lParam);
}

    程序代码段很短,但有几点需要注意的地方.
   
    CtrlProc是我们用来置换原有过程的消息处理函数,因为CtrlProc为静态才能回调,而静态函数无法调用成员变量,所以我们在创建Edit Box实例之后在GWL_USERDATA地址存储了当前对象指针:


    SetWindowLong(m_hEdWord, GWL_USERDATA, (DWORD)this);
    然后在CtrlProc函数中获取存储在GWL_USERDATA中的对象指针,用该指针调用成员变量及函数:

    CMainWnd *pObject = (CMainWnd *)GetWindowLong(hWnd,GWL_USERDATA);
    因为我们只是处理WM_CHAR消息,其它消息都采用默认处理方式,所以直接返回调用先前的消息函数:

 return CallWindowProc(pObject->m_pPreProcEdWord,hWnd,wMsg,wParam,lParam);
   这个例子很简单,该工程可以在此下载:http://download.csdn.net/source/324833


3.系统必崩溃代码

    如果将消息处理函数处理转移到已经释放的内存上,那么会有什么结果呢?结果就是,系统崩溃!有兴趣的朋友可以试试这段代码:


LRESULT MyProc(HWND hWnd, UINT wMsg,WPARAM wParam, LPARAM lParam)
{
    return DefWindowProc(hWnd,wMsg,wParam,lParam);
}

int WINAPI WinMain(    HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    LPTSTR    lpCmdLine,
                    int       nCmdShow)
{
     // TODO: Place code here.

    HWND hWnd = GetForegroundWindow();
    SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)MyProc);
    return 0;
}

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/norains/archive/2008/01/03/2023986.aspx

posted @ 2010-01-19 16:30 Jade 阅读(357) 评论(0) 编辑
钩子

1:什是钩子
从字面上理解,钩子就是想钩住些东西,在程序里可以利用钩子提前处理些
Windows消息。
   
例子:有一个FormForm里有个TextBox,我们想让用户在TextBox里输入的时候,不管敲键盘的哪个键,TextBox里显示的始终为“A”,这时我们就可以利用钩子监听键盘消息,先往Windows的钩子链表中加入一个自己写的钩子监听键盘消息,只要一按下键盘就会产生一个键盘消息,我们的钩子在这个消息传到TextBox之前先截获它,让TextBox显示一个“A”,之后结束这个消息,这样TextBox得到的总是“A”。
  
消息截获顺序:既然是截获消息,总要有先有后,钩子是按加入到钩子链表的顺序决定消息截获顺序。就是说最后加入到链表的钩子最先得到消息。
  
截获范围:钩子分为线程钩子和全局钩子,线程钩子只能截获本线程的消息,全局钩子可以截获整个系统消息。我认为应该尽量使用线程钩子,全局钩子如果使用不当可能会影响到其他程序。

 2:使用钩子
使用钩子主要使用以下三个函数SetWindowsHookEx:创建钩子
CallNextHookEx:将消息传给钩子链中的下一个钩子
UnhookWindowsHookEx:释放钩子
对于创建钩子的函数SetWindowsHookEx,MSDN给出其原形如下:

HHOOK SetWindowsHookEx(
  int idHook,        // type of hook to install
  HOOKPROC lpfn,     // address of hook procedure
  HINSTANCE hMod,    // handle to application instance
  DWORD dwThreadId   // identity of thread to install hook for
);

  但是以上代码是Windows平台下的钩子使用方法,在wince内核下是不支持的。
  但我们可以通过其他的方法来实现钩子的功能。那就是直接获取钩子函数的地址,然后调用方法。
  g_hHookApiDLL = LoadLibrary(_T("coredll.dll"));
  SetWindowsHookEx = (_SetWindowsHookExW)GetProcAddress(g_hHookApiDLL, _T("SetWindowsHookExW"));
  如法炮制,可以获得其他两个函数的地址,有了这三个函数的地址,就可以类似这样使用了:
   g_hInstalledLLKBDhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardProc, hInstance, 0);

posted @ 2010-01-19 14:34 Jade 阅读(294) 评论(0) 编辑
目录

Windows CE跨进程内存注入之原理

近日,由于程序设计需要,我对WincowsCE 的内存布局进行了研究,由于发现国内在这方面的文档资料较少,于是在研究告一段落之际,形成这篇示例文档,以望抛砖引玉。
  

一、程序实现的先决条件

  由于Windows系统的窗体消息总是投递至一个特定进程的指定窗体消息函数中。于是在本地进程(自己的应用程序)中取得属于其它进程的窗体的消息必须实现以下两个部分:

  1、将需要挂接窗体的代码放到目标进程的地址空间中去。

  2、执行这一段代码,并获得目标进程窗体的消息。

  这两步看起来很简单,但在实现过程中就比较困难。由于Windows CE作为嵌入式移动设备操作系统,与Windows 98/2000/XP等桌面操作系统在内核的设计理念以及API的支持上有极大的区别。这就直接导致了常规的桌面系统利用全局鼠标钩子注入/远程线程注入等方法在CE中完全得不通。不过可喜的是,微软在开发工具中提供的remotexxx等远程调试程序使我清楚这个目标并不是不可能的任务,微软既然可以做到,那就是说在CE的内部一定有一套完整的跨进程内存访问/代码注入的机制。

  

二、程序实现的基本原理

  经过两天的google 搜索,在网上我发现了一个没有在微软文档中声明的有趣的API函数:PerformCallBack4,传说中这个函数可以在自己的应用程序中执行指定的进程中的一个函数,So Cool!这好象正是我所需要的东西。虽然网上也传闻这个函数在wm5不受支持,其实经过实践这个传闻只是谣传而已!

  PerformCallBack4函数的定义:

  [DllImport("coredll.dll")]

  public static extern uint PerformCallBack4(ref CallBackInfo CallBackInfo,

  IntPtr ni_pVoid1,IntPtr ni_pVoid2,IntPtr ni_pVoid3);

 

  其中函数的参数CallBackInfo结构定义:

  [StructLayout(LayoutKind.Sequential)]

  public struct CallBackInfo

  {

  public IntPtr hProc; //远程的目标进程

  public IntPtr pfn; //指向远程目标进程的函数地址的指针

  public IntPtr pvArg0; //函数的需要的第一个参数

  }//end struct

 

  而PerformCallback4的 ni_pVoid1、ni_pVoid2、ni_pVoid3为传递到远程目标进程执行函数的其它三个参数。

  至于将代码放到目标进程的内存空间,我们可以利用CE设计上的一个特性:

  1、为了节约内存使用,CE将所有程序调用的动态链接库(DLL)都映射到同一个内存地址中。

  2、CE的内存布局中划分有一个slot0的内存位置,这个内存位置是由正在执行的进程所占有的,每一个特定的时间片,只能有一个进程可以占有这个内存空间。在进程要求执行时,系统并不直接执行进程所处内存位置的代码,而是将该进程的执行代码复制到slot0的内存位置中产生一个副本执行。也就是说进程在执行时内存将会有进程执行代码的两个完全一样的版本:存在于slot0中正在执行的进程代码和进程本身所处的内存中的代码。

  在这个特性下,可以得到结论:如果进程A通过LoadLibrary函数装载Test.dll,而进程B也通过LoadLibrary函数装载同一个Test.dll,这个Test.dll的所有函数在进程A和进程B中执行时,相对于slot0中的进程执行代码都会得到同一地址。

  3、在CE中,系统在内存中划分出33个slot,slot0保留给正在执行的进程,然后在进程启动时将所有的代码放到除slot0以外的一个slot中(这就是臭名昭著的CE系统中内存最多只能有不多于32个程序执行的限制的来由)。在进程执行时,每个应用程序的内存访问默认只能访问slot0内存空间中的地址以及进程所处的slot内存空间的地址。 但为使设备驱动程序可以访问到它们所需的其它应用程序数据,CE提供了两个函数以打破这个限制,SetKmode和SetProcPermission,SetKmode函数告诉系统,当前运行的进程是否需要在内核模式中执行;SetProcPermission函数可以接受一个位掩码,每一位代码一个slot的访问控制,1代表可以访问该slot的内存内容。0表示不能访问该slot的内存内容。这两个函数在msdn中有帮助文档,可参阅msdn的文档说明。

posted @ 2010-01-19 14:34 Jade 阅读(199) 评论(2) 编辑
摘要: 通常我们捕获键盘消息会在WindowProc函数种用一个switch来监听WM_KEYUP和WM_KEYDOWN消息,当按下键盘时程序就会收到这个消息。但是,有一个前提是当焦点是在当前窗口时,这个WM_KEYUP和WM_KEYDOWN消息才会被捕获到。如果我们需要在任何情况下使用这个应用程序捕获到键盘的动作就需要使用钩子。Hook(钩子)是一种在消息到达目标窗口前进行截获的技术。使用钩子主要使用以...阅读全文
posted @ 2010-01-17 22:16 Jade 阅读(669) 评论(0) 编辑