海风月影's profile海风月影BlogListsNetwork Tools Help

Blog


    测试一下

     

    新的Windows Live Writer,测试一下功能

    在百度也注册了一个BLOG

    Live Space 的速度实在不敢恭维,在baidu也注册了一个博客,两边一起写吧
     
     
     

    用WEBBROWSER控件实现在浏览器中点击超链接时不弹出新窗口,新页面永远在当前窗口显示

    用WEBBROWSER控件实现在浏览器中点击超链接时不弹出新窗口,新页面永远在当前窗口显示
     
    程序很简单,用VB6实现,其他语言类似,xp pro sp2 下编译通过。
    原理:建立2个WEBBROWSER控件,当WebBrowser1需要弹出新窗口的时候,将窗体设为我们的WebBrowser2,然后在WebBrowser2_BeforeNavigate2中cancel掉WebBrowser2,并将WebBrowser2 的URL传给WebBrowser1,用WebBrowser1打开网页。将WebBrowser2.visible设为flase,不可见。OK,这样就达到效果了
     
    代码:
     
    Private Sub WebBrowser1_NewWindow2(ppDisp As Object, Cancel As Boolean)
        Set ppDisp = Form1.WebBrowser2.Object
    End Sub

    Private Sub WebBrowser2_BeforeNavigate2(ByVal pDisp As Object, URL As Variant, Flags As Variant, TargetFrameName As Variant, PostData As Variant, Headers As Variant, Cancel As Boolean)
        WebBrowser1.Navigate URL
        Cancel = True
    End Sub
     

    偶变了。。。。。

    每天早上去公司,要坐公交车,这辆车早上老人比较多,以前偶都主动给老人让座,现在偶发现偶变了。。。基本不给别人让座了。。。。。。。。惭愧,惭愧。。。

    被点名,无奈回答问题


    忽然被传说中的fly点名,好像要回答问题

    1.你最喜欢的食物是什么?你会让你未来的爱人为你亲手做这道菜吗?
    吃过的都很喜欢,会的
    2.你曾经在逛街或者地铁站幻想过遇到一个帅哥/美女,他/她主动和你搭讪,你希望你们之间发生什么样的事情吗?
    没想过。。
    3.假如老天就给你只剩3天的时间可以活了,你打算怎么度过这宝贵的3天,期待有什么样的事情发生呢?
    睡觉,或者跳崖,前者轻松,后者刺激
    4.假如你有一个魔镜,你打算用它做什么?
    看看世界,从上帝的角度
    5.谈谈你未来10年的远大计划吧。
    努力,成为成熟技术人士
    6.心目中的白马王子/公主是谁?
    米有
    7.将来会不会要求自己的老公或为自己的老婆做家务?
    不一定
    8.对自己现在的生活满意吗?
    还好
    9.如果现在给你10万块钱,你会拿来做什么?
    投资
    10.打算什么时候把自己嫁出去或娶老婆?
    娶!
    11.男女之间有纯粹的友谊么?
    基本没有
    12.最近一次失眠是为了什么事情?
    工作犯愁
    13. 爱情重要么?
    很重要
    14.我在你眼里是怎样的人?
    恩,好人
    唉,文人啊
    15.你眼中的世界是什么颜色的?
    蓝色
    16.如果让你自己重新给世界上色,你会选择什么颜色?
    白色,世界很多地方太黑暗,要白一点
    17.你对自己的性别满意吗? 
    显然满意
    18  如果可以随意选择,你想要上什么大学,什么专业?
    麻省理工,计算机系
    19      如果世界上只能保留一种艺术,在音乐,绘画,雕塑,诗歌,etc.中,你会选择哪一种?
    音乐
    20  说说自己喜欢的季节和原因。
    秋季,不热不冷
    21  会不会因为文字和音乐莫名的喜欢上一座城市,是哪个和哪座。
    不会,没这个境界

    22  最近在看什么书呢?有什么心得吗?
    没书看。。
    23  本命年你会选择穿红色内裤吗?为什么?
    为什么要穿 啊
    24   最近生活中最大的变化是什么?
    越来越累
    25   老妈和老婆或是老公掉进河里,你会先救谁?
    不知道,没这个可能性
    26.你最想改变身上的哪一个缺点?
    近视
    27.如果给你一次机会,你想从自己多大那一年从头过起? 
    大一
    28.你未来最想定居的城市?
    天城,浮在空中
    29.你觉得你现在的工作和你的理想距离大吗? 
    有点大
    30.你觉得你的屁股够翘么?
    31.你爱你的男/女朋友么?
    当然
    32.你有情人么?
    不懂
     33.你会选择每天睡觉3小时的工作么?
    不会,太累了
    34.你最想成为一个什么样的人?最欣赏什么样的人?
    成功的人,十年磨一剑的成功人士
    35.2007年你最想做的是什么事情?
    找个好工作,并且好好工作
    36.最喜欢歌曲的名字(就一首)
    我BLOG上的
    37 是不是经常感到迷茫?
    是的,人活着太苦了
    38. 将来想过什么样的生活?
    快乐就行
    39. 长这么大给你印象最深的一件事/一本书/一部电影是什么?
    没想过,最近郁闷,想不出来
    40.最喜欢金庸小说里的哪个人物?
    乔峰!男子汉
    41.什么时候最开心?
    轻松的时候,没压力的时候
    42. 生过得最严重的病?
    水痘
    43. 小时候最大的梦想是什么?
    做个科学家,可惜没几会了
    44.你最后悔的一件事情是什么?你想怎么弥补?
    没有后悔的事情
    自己想的题目
    45.这样点名累嘛?
    很累
    开始点名:老卜,杨帆,逆风飞舞,苏苏,for beck

    Visual C++线程同步技术剖析 (转载)

    作者:中国电波传播研究所 郎锐来自:yesky

    摘要: 多线程同步技术是计算机软件开发的重要技术,本文对多线程的各种同步技术的原理和实现进行了初步探讨。

    关键词: VC++6.0; 线程同步;临界区;事件;互斥;信号量;

    正文

      使线程同步

      在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。

      如果不采取适当的措施,其他线程往往会在线程处理任务结束前就去访问处理结果,这就很有可能得到有关处理结果的错误了解。例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。

      为了确保读线程读取到的是经过修改的变量,就必须在向变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。象这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。

      线程同步是一个非常大的话题,包括方方面面的内容。从大的方面讲,线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法。其特点是同步速度特别快,适合于对线程运行速度有严格要求的场合。

      内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。

    临界区

      临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。

      临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用 EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的 CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。


    图1 使用临界区保持线程同步

      下面通过一段代码展示了临界区在保护多线程访问的共享资源中的作用。通过两个线程来分别对全局变量g_cArray[10]进行写入操作,用临界区结构对象g_cs来保持线程的同步,并在开启线程前对其进行初始化。为了使实验效果更加明显,体现出临界区的作用,在线程函数对共享资源g_cArray [10]的写入时,以Sleep()函数延迟1毫秒,使其他线程同其抢占CPU的可能性增大。如果不使用临界区对其进行保护,则共享资源数据将被破坏(参见图1(a)所示计算结果),而使用临界区对线程保持同步后则可以得到正确的结果(参见图1(b)所示计算结果)。代码实现清单附下:

    // 临界区结构对象
    CRITICAL_SECTION g_cs;
    // 共享资源
    char g_cArray[10];
    UINT ThreadProc10(LPVOID pParam)
    {
     // 进入临界区
     EnterCriticalSection(&g_cs);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = ’a’;
      Sleep(1);
     }
     // 离开临界区
     LeaveCriticalSection(&g_cs);
     return 0;
    }
    UINT ThreadProc11(LPVOID pParam)
    {
     // 进入临界区
     EnterCriticalSection(&g_cs);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = ’b’;
      Sleep(1);
     }
     // 离开临界区
     LeaveCriticalSection(&g_cs);
     return 0;
    }
    ……
    void CSample08View::OnCriticalSection()
    {
     // 初始化临界区
     InitializeCriticalSection(&g_cs);
     // 启动线程
     AfxBeginThread(ThreadProc10, NULL);
     AfxBeginThread(ThreadProc11, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }


      在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection ()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。

      MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的,只需在线程函数中用CCriticalSection类成员函数 Lock()和UnLock()标定出被保护代码片段即可。对于上述代码,可通过CCriticalSection类将其改写如下:

    // MFC临界区类对象
    CCriticalSection g_clsCriticalSection;
    // 共享资源
    char g_cArray[10];
    UINT ThreadProc20(LPVOID pParam)
    {
     // 进入临界区
     g_clsCriticalSection.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = ’a’;
      Sleep(1);
     }
     // 离开临界区
     g_clsCriticalSection.Unlock();
     return 0;
    }
    UINT ThreadProc21(LPVOID pParam)
    {
     // 进入临界区
     g_clsCriticalSection.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = ’b’;
      Sleep(1);
     }
     // 离开临界区
     g_clsCriticalSection.Unlock();
     return 0;
    }
    ……
    void CSample08View::OnCriticalSectionMfc()
    {
     // 启动线程
     AfxBeginThread(ThreadProc20, NULL);
     AfxBeginThread(ThreadProc21, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }

    管理事件内核对象

      在前面讲述线程通信时曾使用过事件内核对象来进行线程间的通信,除此之外,事件内核对象也可以通过通知操作的方式来保持线程的同步。对于前面那段使用临界区保持线程同步的代码可用事件对象的线程同步方法改写如下:

    // 事件句柄
    HANDLE hEvent = NULL;
    // 共享资源
    char g_cArray[10];
    ……
    UINT ThreadProc12(LPVOID pParam)
    {
     // 等待事件置位
     WaitForSingleObject(hEvent, INFINITE);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = ’a’;
      Sleep(1);
     }
     // 处理完成后即将事件对象置位
     SetEvent(hEvent);
     return 0;
    }
    UINT ThreadProc13(LPVOID pParam)
    {
     // 等待事件置位
     WaitForSingleObject(hEvent, INFINITE);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = ’b’;
      Sleep(1);
     }
     // 处理完成后即将事件对象置位
     SetEvent(hEvent);
     return 0;
    }
    ……
    void CSample08View::OnEvent()
    {
     // 创建事件
     hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
     // 事件置位
     SetEvent(hEvent);
     // 启动线程
     AfxBeginThread(ThreadProc12, NULL);
     AfxBeginThread(ThreadProc13, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }

      在创建线程前,首先创建一个可以自动复位的事件内核对象hEvent,而线程函数则通过WaitForSingleObject()等待函数无限等待 hEvent的置位,只有在事件置位时WaitForSingleObject()才会返回,被保护的代码将得以执行。对于以自动复位方式创建的事件对象,在其置位后一被WaitForSingleObject()等待到就会立即复位,也就是说在执行ThreadProc12()中的受保护代码时,事件对象已经是复位状态的,这时即使有ThreadProc13()对CPU的抢占,也会由于WaitForSingleObject()没有hEvent的置位而不能继续执行,也就没有可能破坏受保护的共享资源。在ThreadProc12()中的处理完成后可以通过SetEvent()对hEvent的置位而允许ThreadProc13()对共享资源g_cArray的处理。这里SetEvent()所起的作用可以看作是对某项特定任务完成的通知。

      使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,其前提是得到对此事件对象的访问权。可以通过OpenEvent()函数获取得到,其函数原型为:

    HANDLE OpenEvent(
     DWORD dwDesiredAccess, // 访问标志
     BOOL bInheritHandle, // 继承标志
     LPCTSTR lpName // 指向事件对象名的指针
    );

      如果事件对象已创建(在创建事件时需要指定事件名),函数将返回指定事件的句柄。对于那些在创建事件时没有指定事件名的事件内核对象,可以通过使用内核对象的继承性或是调用DuplicateHandle()函数来调用CreateEvent()以获得对指定事件对象的访问权。在获取到访问权后所进行的同步操作与在同一个进程中所进行的线程同步操作是一样的。

      如果需要在一个线程中等待多个事件,则用 WaitForMultipleObjects()来等待。WaitForMultipleObjects()与WaitForSingleObject ()类似,同时监视位于句柄数组中的所有句柄。这些被监视对象的句柄享有平等的优先权,任何一个句柄都不可能比其他句柄具有更高的优先权。 WaitForMultipleObjects()的函数原型为:

    DWORD WaitForMultipleObjects(
     DWORD nCount, // 等待句柄数
     CONST HANDLE *lpHandles, // 句柄数组首地址
     BOOL fWaitAll, // 等待标志
     DWORD dwMilliseconds // 等待时间间隔
    );

      参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。 dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回 WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值,则说明所有指定对象的状态均为已通知状态(当fWaitAll为TRUE时)或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引(当fWaitAll为FALSE 时)。如果返回值在WAIT_ABANDONED_0与WAIT_ABANDONED_0+nCount-1之间,则表示所有指定对象的状态均为已通知,且其中至少有一个对象是被丢弃的互斥对象(当fWaitAll为TRUE时),或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引(当fWaitAll为FALSE时)。下面给出的代码主要展示了对WaitForMultipleObjects()函数的使用。通过对两个事件内核对象的等待来控制线程任务的执行与中途退出:

    // 存放事件句柄的数组
    HANDLE hEvents[2];
    UINT ThreadProc14(LPVOID pParam)
    {
     // 等待开启事件
     DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
     // 如果开启事件到达则线程开始执行任务
     if (dwRet1 == WAIT_OBJECT_0)
     {
      AfxMessageBox("线程开始工作!");
      while (true)
      {
       for (int i = 0; i < 10000; i++);
       // 在任务处理过程中等待结束事件
       DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);
       // 如果结束事件置位则立即终止任务的执行
       if (dwRet2 == WAIT_OBJECT_0 + 1)
        break;
      }
     }
     AfxMessageBox("线程退出!");
     return 0;
    }
    ……
    void CSample08View::OnStartEvent()
    {
     // 创建线程
     for (int i = 0; i < 2; i++)
      hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
      // 开启线程
      AfxBeginThread(ThreadProc14, NULL);
      // 设置事件0(开启事件)
      SetEvent(hEvents[0]);
    }
    void CSample08View::OnEndevent()
    {
     // 设置事件1(结束事件)
     SetEvent(hEvents[1]);
    }

      MFC为事件相关处理也提供了一个CEvent类,共包含有除构造函数外的4个成员函数PulseEvent()、ResetEvent()、 SetEvent()和UnLock()。在功能上分别相当与Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函数。而构造函数则履行了原 CreateEvent()函数创建事件对象的职责,其函数原型为:

    CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );

      按照此缺省设置将创建一个自动复位、初始状态为复位状态的没有名字的事件对象。封装后的CEvent类使用起来更加方便,图2即展示了CEvent类对A、B两线程的同步过程:


    图2 CEvent类对线程的同步过程示意

      B线程在执行到CEvent类成员函数Lock()时将会发生阻塞,而A线程此时则可以在没有B线程干扰的情况下对共享资源进行处理,并在处理完成后通过成员函数SetEvent()向B发出事件,使其被释放,得以对A先前已处理完毕的共享资源进行操作。可见,使用CEvent类对线程的同步方法与通过 API函数进行线程同步的处理方法是基本一致的。前面的API处理代码可用CEvent类将其改写为:

    // MFC事件类对象
    CEvent g_clsEvent;
    UINT ThreadProc22(LPVOID pParam)
    {
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = ’a’;
      Sleep(1);
     }
     // 事件置位
     g_clsEvent.SetEvent();
     return 0;
    }
    UINT ThreadProc23(LPVOID pParam)
    {
     // 等待事件
     g_clsEvent.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = ’b’;
      Sleep(1);
     }
     return 0;
    }
    ……
    void CSample08View::OnEventMfc()
    {
     // 启动线程
     AfxBeginThread(ThreadProc22, NULL);
     AfxBeginThread(ThreadProc23, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }
    信号量内核对象

      信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用 CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过 ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。


    图3 使用信号量对象控制资源

      下面结合图例3来演示信号量对象对资源的控制。在图3中,以箭头和白色箭头表示共享资源所允许的最大资源计数和当前可用资源计数。初始如图(a)所示,最大资源计数和当前可用资源计数均为4,此后每增加一个对资源进行访问的线程(用黑色箭头表示)当前资源计数就会相应减1,图(b)即表示的在3个线程对共享资源进行访问时的状态。当进入线程数达到4个时,将如图(c)所示,此时已达到最大资源计数,而当前可用资源计数也已减到0,其他线程无法对共享资源进行访问。在当前占有资源的线程处理完毕而退出后,将会释放出空间,图(d)已有两个线程退出对资源的占有,当前可用计数为2,可以再允许2个线程进入到对资源的处理。可以看出,信号量是通过计数来对线程访问资源进行控制的,而实际上信号量确实也被称作Dijkstra计数器。

      使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、 WaitForSingleObject()和WaitForMultipleObjects()等函数。其中,CreateSemaphore()用来创建一个信号量内核对象,其函数原型为:

    HANDLE CreateSemaphore(
     LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
     LONG lInitialCount, // 初始计数
     LONG lMaximumCount, // 最大计数
     LPCTSTR lpName // 对象名指针
    );

      参数lMaximumCount是一个有符号32位值,定义了允许的最大资源计数,最大取值不能超过4294967295。lpName参数可以为创建的信号量定义一个名字,由于其创建的是一个内核对象,因此在其他进程中可以通过该名字而得到此信号量。OpenSemaphore()函数即可用来根据信号量名打开在其他进程中创建的信号量,函数原型如下:

    HANDLE OpenSemaphore(
     DWORD dwDesiredAccess, // 访问标志
     BOOL bInheritHandle, // 继承标志
     LPCTSTR lpName // 信号量名
    );

      在线程离开对共享资源的处理时,必须通过ReleaseSemaphore()来增加当前可用资源计数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。ReleaseSemaphore()的函数原型为:

    BOOL ReleaseSemaphore(
     HANDLE hSemaphore, // 信号量句柄
     LONG lReleaseCount, // 计数递增数量
     LPLONG lpPreviousCount // 先前计数
    );

      该函数将lReleaseCount中的值添加给信号量的当前资源计数,一般将lReleaseCount设置为1,如果需要也可以设置其他的值。 WaitForSingleObject()和WaitForMultipleObjects()主要用在试图进入共享资源的线程函数入口处,主要用来判断信号量的当前可用资源计数是否允许本线程的进入。只有在当前可用资源计数值大于0时,被监视的信号量内核对象才会得到通知。

      信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为没一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展示了类似的处理过程:

    // 信号量对象句柄
    HANDLE hSemaphore;
    UINT ThreadProc15(LPVOID pParam)
    {
     // 试图进入信号量关口
     WaitForSingleObject(hSemaphore, INFINITE);
     // 线程任务处理
     AfxMessageBox("线程一正在执行!");
     // 释放信号量计数
     ReleaseSemaphore(hSemaphore, 1, NULL);
     return 0;
    }
    UINT ThreadProc16(LPVOID pParam)
    {
     // 试图进入信号量关口
     WaitForSingleObject(hSemaphore, INFINITE);
     // 线程任务处理
     AfxMessageBox("线程二正在执行!");
     // 释放信号量计数
     ReleaseSemaphore(hSemaphore, 1, NULL);
     return 0;
    }
    UINT ThreadProc17(LPVOID pParam)
    {
     // 试图进入信号量关口
     WaitForSingleObject(hSemaphore, INFINITE);
     // 线程任务处理
     AfxMessageBox("线程三正在执行!");
     // 释放信号量计数
     ReleaseSemaphore(hSemaphore, 1, NULL);
     return 0;
    }
    ……
    void CSample08View::OnSemaphore()
    {
     // 创建信号量对象
     hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
     // 开启线程
     AfxBeginThread(ThreadProc15, NULL);
     AfxBeginThread(ThreadProc16, NULL);
     AfxBeginThread(ThreadProc17, NULL);
    }


    图4 开始进入的两个线程


    图5 线程二退出后线程三才得以进入

      上述代码在开启线程前首先创建了一个初始计数和最大资源计数均为2的信号量对象hSemaphore。即在同一时刻只允许2个线程进入由 hSemaphore保护的共享资源。随后开启的三个线程均试图访问此共享资源,在前两个线程试图访问共享资源时,由于hSemaphore的当前可用资源计数分别为2和1,此时的hSemaphore是可以得到通知的,也就是说位于线程入口处的WaitForSingleObject()将立即返回,而在前两个线程进入到保护区域后,hSemaphore的当前资源计数减少到0,hSemaphore将不再得到通知, WaitForSingleObject()将线程挂起。直到此前进入到保护区的线程退出后才能得以进入。图4和图5为上述代脉的运行结果。从实验结果可以看出,信号量始终保持了同一时刻不超过2个线程的进入。

      在MFC中,通过CSemaphore类对信号量作了表述。该类只具有一个构造函数,可以构造一个信号量对象,并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化,其原型如下:

    CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );

      在构造了CSemaphore类对象后,任何一个访问受保护共享资源的线程都必须通过CSemaphore从父类CSyncObject类继承得到的 Lock()和UnLock()成员函数来访问或释放CSemaphore对象。与前面介绍的几种通过MFC类保持线程同步的方法类似,通过 CSemaphore类也可以将前面的线程同步代码进行改写,这两种使用信号量的线程同步方法无论是在实现原理上还是从实现结果上都是完全一致的。下面给出经MFC改写后的信号量线程同步代码:

    // MFC信号量类对象
    CSemaphore g_clsSemaphore(2, 2);
    UINT ThreadProc24(LPVOID pParam)
    {
     // 试图进入信号量关口
     g_clsSemaphore.Lock();
     // 线程任务处理
     AfxMessageBox("线程一正在执行!");
     // 释放信号量计数
     g_clsSemaphore.Unlock();
     return 0;
    }
    UINT ThreadProc25(LPVOID pParam)
    {
     // 试图进入信号量关口
     g_clsSemaphore.Lock();
     // 线程任务处理
     AfxMessageBox("线程二正在执行!");
     // 释放信号量计数
     g_clsSemaphore.Unlock();
     return 0;
    }
    UINT ThreadProc26(LPVOID pParam)
    {
     // 试图进入信号量关口
     g_clsSemaphore.Lock();
     // 线程任务处理
     AfxMessageBox("线程三正在执行!");
     // 释放信号量计数
     g_clsSemaphore.Unlock();
     return 0;
    }
    ……
    void CSample08View::OnSemaphoreMfc()
    {
     // 开启线程
     AfxBeginThread(ThreadProc24, NULL);
     AfxBeginThread(ThreadProc25, NULL);
     AfxBeginThread(ThreadProc26, NULL);
    }
    互斥内核对象

      互斥(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。为便于理解,可参照图6给出的互斥内核对象的工作模型:


    图6 使用互斥内核对象对共享资源的保护

      图(a)中的箭头为要访问资源(矩形框)的线程,但只有第二个线程拥有互斥对象(黑点)并得以进入到共享资源,而其他线程则会被排斥在外(如图(b)所示)。当此线程处理完共享资源并准备离开此区域时将把其所拥有的互斥对象交出(如图(c)所示),其他任何一个试图访问此资源的线程都有机会得到此互斥对象。

      以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex()、OpenMutex()、 ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥对象前,首先要通过CreateMutex()或OpenMutex()创建或打开一个互斥对象。CreateMutex()函数原型为:

    HANDLE CreateMutex(
     LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针
     BOOL bInitialOwner, // 初始拥有者
     LPCTSTR lpName // 互斥对象名
    );

      参数bInitialOwner主要用来控制互斥对象的初始状态。一般多将其设置为FALSE,以表明互斥对象在创建时并没有为任何线程所占有。如果在创建互斥对象时指定了对象名,那么可以在本进程其他地方或是在其他进程通过OpenMutex()函数得到此互斥对象的句柄。OpenMutex()函数原型为:

    HANDLE OpenMutex(
     DWORD dwDesiredAccess, // 访问标志
     BOOL bInheritHandle, // 继承标志
     LPCTSTR lpName // 互斥对象名
    );

      当目前对资源具有访问权的线程不再需要访问此资源而要离开时,必须通过ReleaseMutex()函数来释放其拥有的互斥对象,其函数原型为:

    BOOL ReleaseMutex(HANDLE hMutex);

      其唯一的参数hMutex为待释放的互斥对象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函数在互斥对象保持线程同步中所起的作用与在其他内核对象中的作用是基本一致的,也是等待互斥内核对象的通知。但是这里需要特别指出的是:在互斥对象通知引起调用等待函数返回时,等待函数的返回值不再是通常的WAIT_OBJECT_0(对于WaitForSingleObject()函数)或是在 WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数),而是将返回一个WAIT_ABANDONED_0(对于WaitForSingleObject()函数)或是在WAIT_ABANDONED_0 到WAIT_ABANDONED_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数)。以此来表明线程正在等待的互斥对象由另外一个线程所拥有,而此线程却在使用完共享资源前就已经终止。除此之外,使用互斥对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同,其他内核对象在没有得到通知时,受调用等待函数的作用,线程将会挂起,同时失去可调度性,而使用互斥的方法却可以在等待的同时仍具有可调度性,这也正是互斥对象所能完成的非常规操作之一。

      在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。下面给出的示例代码即通过互斥内核对象hMutex对共享内存快g_cArray[]进行线程的独占访问保护。下面给出实现代码清单:

    // 互斥对象
    HANDLE hMutex = NULL;
    char g_cArray[10];
    UINT ThreadProc18(LPVOID pParam)
    {
     // 等待互斥对象通知
     WaitForSingleObject(hMutex, INFINITE);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = ’a’;
      Sleep(1);
     }
     // 释放互斥对象
     ReleaseMutex(hMutex);
     return 0;
    }
    UINT ThreadProc19(LPVOID pParam)
    {
     // 等待互斥对象通知
     WaitForSingleObject(hMutex, INFINITE);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = ’b’;
      Sleep(1);
     }
     // 释放互斥对象
     ReleaseMutex(hMutex);
     return 0;
    }
    ……
    void CSample08View::OnMutex()
    {
     // 创建互斥对象
     hMutex = CreateMutex(NULL, FALSE, NULL);
     // 启动线程
     AfxBeginThread(ThreadProc18, NULL);
     AfxBeginThread(ThreadProc19, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }

      互斥对象在MFC中通过CMutex类进行表述。使用CMutex类的方法非常简单,在构造CMutex类对象的同时可以指明待查询的互斥对象的名字,在构造函数返回后即可访问此互斥变量。CMutex类也是只含有构造函数这唯一的成员函数,当完成对互斥对象保护资源的访问后,可通过调用从父类 CSyncObject继承的UnLock()函数完成对互斥对象的释放。CMutex类构造函数原型为:

    CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );

      该类的适用范围和实现原理与API方式创建的互斥内核对象是完全类似的,但要简洁的多,下面给出就是对前面的示例代码经CMutex类改写后的程序实现清单:

    // MFC互斥类对象
    CMutex g_clsMutex(FALSE, NULL);
    UINT ThreadProc27(LPVOID pParam)
    {
     // 等待互斥对象通知
     g_clsMutex.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = ’a’;
      Sleep(1);
     }
     // 释放互斥对象
     g_clsMutex.Unlock();
     return 0;
    }
    UINT ThreadProc28(LPVOID pParam)
    {
     // 等待互斥对象通知
     g_clsMutex.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = ’b’;
      Sleep(1);
     }
     // 释放互斥对象
     g_clsMutex.Unlock();
     return 0;
    }
    ……
    void CSample08View::OnMutexMfc()
    {
     // 启动线程
     AfxBeginThread(ThreadProc27, NULL);
     AfxBeginThread(ThreadProc28, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }

      小结

      线程的使用使程序处理更够更加灵活,而这种灵活同样也会带来各种不确定性的可能。尤其是在多个线程对同一公共变量进行访问时。虽然未使用线程同步的程序代码在逻辑上或许没有什么问题,但为了确保程序的正确、可靠运行,必须在适当的场合采取线程同步措施。

    二进制格雷码与自然二进制码的互换

    二进制格雷码与自然二进制码的互换

    2006年03月19日, by mathon

    二进制格雷码与自然二进制码的互换

    中国科学院光电技术研究所 游志宇

    示例工程下载
      在精确定位控制系统中,为了提高控制精度,准确测量控制对象的位置是十分重要的。目前,检测位置的办法有两种:其一是使用位置传感器,测量到的位移量由变送器经A/D转换成数字量送至系统进行进一步处理。此方法精度高,但在多路、长距离位置监控系统中,由于其成本昂贵,安装困难,因此并不实用;其二是采用光电轴角编码器进行精确位置控制。光电轴角编码器根据其刻度方法及信号输出形式,可分为增量式、绝对式以及混合式三种。而绝对式编码器是直接输出数字量的传感器,它是利用自然二进制或循环二进制(格雷码)方式进行光电转换的,编码的设计一般是采用自然二进制码、循环二进制码、二进制补码等。特点是不要计数器,在转轴的任意位置都可读出一个固定的与位置相对应的数字码;抗干扰能力强,没用累积误差;电源切断后位置信息不会丢失,但分辨率是由二进制的位数决定的,根据不同的精度要求,可以选择不同的分辨率即位数。目前有10位、11位、12位、13位、14位或更高位等多种。
      其中采用循环二进制编码的绝对式编码器,其输出信号是一种数字排序,不是权重码,每一位没有确定的大小,不能直接进行比较大小和算术运算,也不能直接转换成其他信号,要经过一次码变换,变成自然二进制码,在由上位机读取以实现相应的控制。而在码制变换中有不同的处理方式,本文着重介绍二进制格雷码与自然二进制码的互换。

    一、格雷码(又叫循环二进制码或反射二进制码)介绍

      在数字系统中只能识别0和1,各种数据要转换为二进制代码才能进行处理,格雷码是一种无权码,采用绝对编码方式,典型格雷码是一种具有反射特性和循环特性的单步自补码,它的循环、单步特性消除了随机取数时出现重大误差的可能,它的反射、自补特性使得求反非常方便。格雷码属于可靠性编码,是一种错误最小化的编码方式,因为,自然二进制码可以直接由数/模转换器转换成模拟信号,但某些情况,例如从十进制的3转换成4时二进制码的每一位都要变,使数字电路产生很大的尖峰电流脉冲。而格雷码则没有这一缺点,它是一种数字排序系统,其中的所有相邻整数在它们的数字表示中只有一个数字不同。它在任意两个相邻的数之间转换时,只有一个数位发生变化。它大大地减少了由一个状态到下一个状态时逻辑的混淆。另外由于最大数与最小数之间也仅一个数不同,故通常又叫格雷反射码或循环码。下表为几种自然二进制码与格雷码的对照表:

    十进制数 自然二进制数 格雷码 十进制数 自然二进制数 格雷码
    0 0000 0000 8 1000 1100
    1 0001 0001 9 1001 1101
    2 0010 0011 10 1010 1111
    3 0011 0010 11 1011 1110
    4 0100 0110 12 1100 1010
    5 0101 0111 13 1101 1011
    6 0110 0101 14 1110 1001
    7 0111 0100 15 1111 1000

    二、二进制格雷码与自然二进制码的互换

    1、自然二进制码转换成二进制格雷码
      自然二进制码转换成二进制格雷码,其法则是保留自然二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似。
                                          

    2、二进制格雷码转换成自然二进制码
      二进制格雷码转换成自然二进制码,其法则是保留格雷码的最高位作为自然二进制码的最高位,而次高位自然二进制码为高位自然二进制码与次高位格雷码相异或,而自然二进制码的其余各位与次高位自然二进制码的求法相类似。

                                         

    三、二进制格雷码与自然二进制码互换的实现方法
    1、自然二进制码转换成二进制格雷码 
      A)、软件实现法(参见示例工程中的 Binary to Gray)
       根据自然二进制转换成格雷码的法则,可以得到以下的代码:

          static unsigned int DecimaltoGray(unsigned int x)
          {
             return x^(x>>1);
          }
          
         //以上代码实现了unsigned int型数据到格雷码的转换,最高可转换32位自然二进制码,超出32位将溢出。   
          static  int DecimaltoGray( int x)
          {
             return x^(x>>1);
          }
          
          //以上代码实现了 int型数据到格雷码的转换,最高可转换31位自然二进制码,超出31位将溢出。       
      上述代码即可用于VC控制程序中,也可以用于单片机控制程序中。在单片机程序设计时,若采用汇编语言编程,可以按相同的原理设计程序;若采用C语言编程,则可以直接利用上述代码,但建议用unsigned int函数。

     B)、硬件实现法

      根据自然二进制转换成格雷码的法则,可以得到以下电路图:

     

      上图所示电路图即可用异或集成电路74ls136实现,也可以利用可编程器件PLD等编程实现。

    2、二进制格雷码转换成自然二进制码
    A)、软件实现法(参见示例工程中的 Gray to Binary )
      根据二进制格雷码转换成自然二进制码的法则,可以得到以下的三种代码方式:
    •        static unsigned int GraytoDecimal(unsigned int x)
             {
                unsigned int y = x;
                while(x>>=1)
                  y ^= x;
                return y;
             }       
    •        static unsigned int GraytoDecimal(unsigned int x)
             {
                x^=x>>16;
                x^=x>>8;
                x^=x>>4;
                x^=X>>2;
                x^=x^1;
                return x;
             }       
    •        static unsigned int GraytoDecimal(unsigned int x)
             {
                int i;
                for(i=0;(1<<I)>(1<
      //以上代码实现了unsigned int型数据到自然二进制码的转换,最高可转换32位格雷码,超出32位将溢出。将数据类型改为int型即可实现31位格雷码转换。
      上述代码即可用于VC控制程序中,也可以用于单片机控制程序中。在单片机程序设计时,若采用汇编语言编程,可以按相同的原理设计程序;若采用C语言编程,则可以直接利用上述代码,但建议用unsigned int函数。
    B)、硬件实现法
      根据二进制格雷码转换成自然二进制码的法则,可以得到以下电路图:

                      

    上图所示电路图即可用异或集成电路74ls136实现,也可以利用可编程器件PLD等编程实现。
     

    最近不能写BLOG?

    怎么好像到今天才能上嘛。。。最近比较忙,唉~~

    要实习了

    考试前天终于考完了,晚上全班聚了聚,夜里去飙歌,真热闹。前所未有,聚餐来了28个,飙歌来了27个!!
    马上要实习了,还不知道是个什么样子,最近忙死了,还要回家一趟。据说宿舍东西要搬完。。。。汗~~什么学校啊
     

    招聘会

    今天本部招聘会,投了3家公司,新宇,神游,苏州软件评测公司。实在太挤了~~我这个专业去投简历让别人感到很奇怪,别人尴尬,我也尴尬
     
    不知结果如何~~心里没底

    传一些图片

    一些印章等图片
     
     

    K掉卡巴斯基(kis6)主动防御功能

    啥?我可不懂,记录一下而已,下载地址:
     
     
    XP + SP2 应该不会蓝屏。运行一下即可,没反应就是正常,不过卡巴斯基的主动防御已悄悄地离开了(想要地话重启一下)
     
    BTW:咔吧司机也被K掉了,还有几个几个AV能真正防御呢?所以,我们的做法是:不用杀毒软件
     

    当代“生物学的爱因斯坦”James D.Watson今天访问我们学院

    今天下午15:30分,James Watson先生来我院访问,会议室挤满了人,看到了几眼,领略了科学家的风采。
    资料:
    1953年James Watson与Francis Crick发现生命遗传机制DNA的双螺旋结构,开启了当代分子生物学、遗传学及生物技术的大门,荣获1962年诺贝尔奖。 1989年成为国家卫生院人类基因组研究全国中心的主任,成功地领导完成了全球参与的人类基因组序列分析。1953年James Watson与Francis Crick发现生命遗传机制DNA的双螺旋结构,开启了当代分子生物学、遗传学及生物技术的大门,荣获1962年诺贝尔奖。 1989年成为国家卫生院人类基因组研究全国中心的主任,成功地领导完成了全球参与的人类基因组序列分析。
     

    笔记本的光驱神奇地好了~~

    本来XP连光驱都找不到。。。。
    刚刚回宿舍开机,发现新硬件,找到光驱,找了一张盘试试,居然好了
     
    哇,谢天谢地~~~

    系统又坏了~~

    前几天的系统又坏了,汗~~~~被一个软件搞的。。
     
    SourceFormatX,里面大量破坏系统的代码,网上居然没有破解版,于是花了一点时间破解了,发布出来
     
     
    感谢tankaiha提供的空间

    VBox 4.3 脱壳 - MINITAB(Registered Trademark) Release 14.20

     
    【文章标题】: VBox 4.3 脱壳 - MINITAB(Registered Trademark) Release 14.20
    【文章作者】: 海风月影
    【作者主页】: http://depteicn.spaces.live.com
    【软件名称】: MINITAB(Registered Trademark) Release 14.20
    【软件大小】: 78M
    【下载地址】: 自己搜索下载
    【加壳方式】: VBOX 4.3
    【使用工具】: OllyDBG ,LordPE ,ImportREC ,PEID
    【操作平台】: WinXP SP2
    【软件介绍】:
     MINITAB统计软件是为六西格玛和其它质量改善项目采用的理想套装软件。从统计过程控制(Statistical Process Control)到试验设计(Design of Experiments),MINITAB为你提供实现质量项目各阶段目标的方法;同时“StatGuide”和“ReportPad”等工具将帮助你理解和联系你的操作结果。与普通的软件相比,MINITAB统计软件更为精确、可靠、易于使用。 除了具有比老版本更多的统计功能外,MINITAB 14 还有许多更令人兴奋的新特点,如:
    - 一个强大的新图形引擎将基于你的数据传递出精彩的、有洞察力的图形结果
    - 更容易创建、编辑、升级图形
    - 可自定义菜单、工具栏的功能将使你能够便捷地访问常用的工具
    【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
    --------------------------------------------------------------------------------
    【详细过程】

    由于专业需要用到统计软件,老师推荐了 MINITAB 14 ,当然比不过强大的 MATLAB ,不过比起MATLAB 6XXM的庞大身躯来说,minitab 14.10 只有15M,非常适合传播。
    老师给的是minitab 14.10 30天试用版,30天后就不能用了~~。老师还说暂时找不到破解版,有谁能通过某些途径获得破解版的,本课程期末可以加分=。=,我汗~~
     
    查壳,VBOX 4.3 - 4.6.x -> WeijunLi [Overlay]
    VBOX的anti不强,只是修复IAT稍有点麻烦。我最怕anti,所以拣个anti不强的软柿子来捏捏
     
    一,找OEP
    OD设置忽略所有异常,用HideOD插件选中 Auto Run HideOD,如果你没有HideOD,用IsDebug 1.4插件去掉Ollydbg的调试器标志。(什么?你也没有IsDebug 1.4插件?那么你的OD八成是某大侠秘传的,可以直接过IsDebug检测 )
     
    载入Mtb14.exe。
     
    如果先停在入口处,则F9运行它,直到出现试用的窗口,OD设置去掉忽略INT 3异常的勾,点 试用(Try)
    如果先出现试用窗口,则点 试用(Try) ,让它停在入口处,OD设置去掉忽略INT 3异常的勾,F9运行
     
    程序暂停在最后一次 INT 3 异常处,MINITAB 14.10只有1次异常,MINITAB 14.20有2次异常,不管有几次,停在最后一次
    Ctrl + F9 返回
    搜索二进制代码
     
    C3 8B ?? ?? 6A 01
     
    搜索到这里:
     
    07007F08   .  C3            retn                                     ;  <===搜索到这里
    07007F09   >  8B75 08       mov     esi, dword ptr ss:[ebp+8]        ;  <===这行下硬件执行断点
    07007F0C   .  6A 01         push    1
     
    下硬件执行断点: he 7007f09 , F9运行,停在 7007F09处,删除硬件断点 hd 7007f09,下面就可以单步执行或者下F2断点了,程序自校验已经结束。
    [ESI+14]里就是OEP,向下看,不远处:
     
    07007F08   .  C3            retn                                     ;  <===搜索到这里
    07007F09   >  8B75 08       mov     esi, dword ptr ss:[ebp+8]        ;  <===这行下硬件执行断点
    07007F0C   .  6A 01         push    1                                ;  向下看
    07007F0E   .  5F            pop     edi
    07007F0F   .  C745 FC 16000>mov     dword ptr ss:[ebp-4], 16
    07007F16   .  817E 0C F8000>cmp     dword ptr ds:[esi+C], 0F8
    07007F1D   .  75 1D         jnz     short vboxt430.07007F3C
    07007F1F   .  F645 A8 40    test    byte ptr ss:[ebp-58], 40
    07007F23   .  74 17         je      short vboxt430.07007F3C
    07007F25   .  8B46 14       mov     eax, dword ptr ds:[esi+14]       ;  <===[esi+14]里就是OEP★
    07007F28   .  3B05 D0210407 cmp     eax, dword ptr ds:[70421D0]      ;  kernel32.ExitProcess
    07007F2E   .  75 07         jnz     short vboxt430.07007F37
    07007F30   .  E8 04C4FFFF   call    vboxt430.07004339
    07007F35   .  EB 05         jmp     short vboxt430.07007F3C
    07007F37   >  E8 CEC1FFFF   call    vboxt430.0700410A
    07007F3C   >  8366 1C 00    and     dword ptr ds:[esi+1C], 0
    07007F40   .  817E 0C C7000>cmp     dword ptr ds:[esi+C], 0C7
    07007F47   .  8D5E 1C       lea     ebx, dword ptr ds:[esi+1C]
    07007F4A   .  75 61         jnz     short vboxt430.07007FAD
    07007F4C   .  8B46 14       mov     eax, dword ptr ds:[esi+14]       ;  <===[esi+14]里就是OEP★
    07007F4F   .  33C9          xor     ecx, ecx
    07007F51   .  3B05 D0210407 cmp     eax, dword ptr ds:[70421D0]      ;  kernel32.ExitProcess
    07007F57   .  0F94C1        sete    cl
    07007F5A   .  85C9          test    ecx, ecx
    07007F5C   .  894D 08       mov     dword ptr ss:[ebp+8], ecx
    07007F5F   .  75 10         jnz     short vboxt430.07007F71
    07007F61   .  FF75 14       push    dword ptr ss:[ebp+14]
    07007F64   .  FF75 10       push    dword ptr ss:[ebp+10]
    07007F67   .  FF75 0C       push    dword ptr ss:[ebp+C]
    07007F6A   .  FFD0          call    eax
    07007F6C   .  8946 18       mov     dword ptr ds:[esi+18], eax
    07007F6F   .  EB 04         jmp     short vboxt430.07007F75
    07007F71   >  8366 18 00    and     dword ptr ds:[esi+18], 0
    07007F75   >  837D 10 00    cmp     dword ptr ss:[ebp+10], 0
    07007F79   .  75 03         jnz     short vboxt430.07007F7E
    07007F7B   .  57            push    edi
    07007F7C   .  EB 1C         jmp     short vboxt430.07007F9A
    07007F7E   >  397D 10       cmp     dword ptr ss:[ebp+10], edi
    07007F81   .  75 2A         jnz     short vboxt430.07007FAD
    07007F83   .  837D 08 00    cmp     dword ptr ss:[ebp+8], 0
    07007F87   .  74 24         je      short vboxt430.07007FAD
    07007F89   .  E8 61D1FFFF   call    vboxt430.070050EF
    07007F8E   .  83F8 02       cmp     eax, 2
    07007F91   .  74 05         je      short vboxt430.07007F98
    07007F93   .  83F8 03       cmp     eax, 3
    07007F96   .  75 15         jnz     short vboxt430.07007FAD
    07007F98   >  6A 00         push    0
    07007F9A   >  53            push    ebx
    07007F9B   .  8D85 74FEFFFF lea     eax, dword ptr ss:[ebp-18C]
    07007FA1   .  FF75 0C       push    dword ptr ss:[ebp+C]
    07007FA4   .  50            push    eax
    07007FA5   .  E8 98050000   call    vboxt430.07008542
    07007FAA   .  83C4 10       add     esp, 10
    07007FAD   >  83BD BCFEFFFF>cmp     dword ptr ss:[ebp-144], 0
    07007FB4   .  74 0C         je      short vboxt430.07007FC2
    07007FB6   .  FFB5 BCFEFFFF push    dword ptr ss:[ebp-144]
    07007FBC   .  E8 5892FFFF   call    vboxt430.07001219
    07007FC1   .  59            pop     ecx
    07007FC2   >  8B76 14       mov     esi, dword ptr ds:[esi+14]       ;  <===[esi+14]里就是OEP★
     

    这3行都是取OEP,3行都下断点,F9运行,停下来,看一下OEP是多少
     
    ds:[05400014]=009DB8B4 (Mtb14.009DB8B4)
    esi=05400000, (ASCII "XlXl")
    跳转来自 07007FB4
    OEP=[05400014]=009DB8B4
     
    清除前面所有断点,下硬件执行断点: he 009DB8B4,F9运行,已经稳稳地停在了OEP处,程序是VC++写的

    009DB8B4  /.  55            push    ebp                              ;  <===OEP
    009DB8B5  |.  8BEC          mov     ebp, esp
    009DB8B7  |.  6A FF         push    -1
    009DB8B9  |.  68 00D5A700   push    Mtb14.00A7D500
    009DB8BE  |.  68 12BF9D00   push    Mtb14.009DBF12                   ;  jmp 到 MSLURT._except_handler3; SE 处理程序安装
    009DB8C3  |.  64:A1 0000000>mov     eax, dword ptr fs:[0]
    009DB8C9  |.  50            push    eax
    009DB8CA  |.  64:8925 00000>mov     dword ptr fs:[0], esp
    009DB8D1  |.  83EC 68       sub     esp, 68
    009DB8D4  |.  53            push    ebx
    009DB8D5  |.  56            push    esi
    009DB8D6  |.  57            push    edi
    009DB8D7  |.  8965 E8       mov     dword ptr ss:[ebp-18], esp
    009DB8DA  |.  33DB          xor     ebx, ebx
    009DB8DC  |.  895D FC       mov     dword ptr ss:[ebp-4], ebx
    009DB8DF  |.  6A 02         push    2
    009DB8E1  |.  5F            pop     edi
    009DB8E2  |.  57            push    edi
    009DB8E3  |.  FF15 C07FA300 call    dword ptr ds:[A37FC0]            ;  MSLURT.__set_app_type

    ◆小插曲
    对于VC++写的程序可以这样找OEP
    下断点BP GetVersion,然后F9运行,不过这个程序入口处没用到GetVersion,不过仔细看看可以发现VC++写的程序入口处也会出现这个函数MSLURT.__set_app_type,也可以用BP MSLURT.__set_app_type方式来找OEP,不过有个缺陷,就是MSLURT.dll加壳后不一定是静态导入的,如
    果不是静态导入是不能这样下断点的,用LORDPE查看加壳的文件,发现有MSLURT.dll文件,呵呵,那么找OEP就方便多了。
     
    ●简单找OEP的方法
    入口处下断点:bp mslurt.__set_app_type,把断点改在RETN处,忽略所有异常 , F9运行
     
    780067D4 >  8B4424 04       mov     eax, dword ptr ss:[esp+4]        ; mslurt.__set_app_type
    780067D8    A3 088F0378     mov     dword ptr ds:[78038F08], eax
    780067DD    C3              retn                                     ; <===把断点改在这里
     
    很稳地停在了 retn 处,F8返回
     
    009DB8B4    55              push    ebp                              ; <====OEP
    009DB8B5    8BEC            mov     ebp, esp
    009DB8B7    6A FF           push    -1
    009DB8B9    68 00D5A700     push    Mtb14.00A7D500
    009DB8BE    68 12BF9D00     push    Mtb14.009DBF12                   ; jmp 到 MSLURT._except_handler3
    009DB8C3    64:A1 00000000  mov     eax, dword ptr fs:[0]
    009DB8C9    50              push    eax
    009DB8CA    64:8925 0000000>mov     dword ptr fs:[0], esp
    009DB8D1    83EC 68         sub     esp, 68
    009DB8D4    53              push    ebx
    009DB8D5    56              push    esi
    009DB8D6    57              push    edi
    009DB8D7    8965 E8         mov     dword ptr ss:[ebp-18], esp
    009DB8DA    33DB            xor     ebx, ebx
    009DB8DC    895D FC         mov     dword ptr ss:[ebp-4], ebx
    009DB8DF    6A 02           push    2
    009DB8E1    5F              pop     edi
    009DB8E2    57              push    edi
    009DB8E3    FF15 C07FA300   call    dword ptr ds:[A37FC0]            ; MSLURT.__set_app_type
    009DB8E9    59              pop     ecx                              ; 返回到这里,向上看
     
     
    当然这里是可以DUMP的
    用LordPE ,Correct ImageSize后dump full,保存为dump.exe
     
    二,修复IAT
    运行ImportREC,选择Mtb14.exe,OEP填 5DB8B4 (9DB8B4-400000),搜索 IAT,GET Imports。
    开始地址:00636FFC
    大小:    00006D90
    可以看到有很多无效的指针,随便找一个看看。点右键,disassemble/hex view :
     
    056B0000    call 070103BA    // ~= vboxt430.dll/0001
    056B0005    outs dx,byte ptr es:[edi]
    056B0006    ja short 056AFF8F
    056B0008    add [eax],al
    056B000A    add [eax],al
     
    跑到VBOX空间里去了,显然这个指针不能CUT掉,再多看几个,发现都是call 070103BA,看来070103BA这个函数是解码API的函数。在OD中跳过去看看:
     
    070103BA  /.  55            push    ebp                              ;  <===VBOX 解码 API的过程
    070103BB  |.  8BEC          mov     ebp, esp
    070103BD  |.  83EC 10       sub     esp, 10
    070103C0  |.  53            push    ebx
    070103C1  |.  8945 FC       mov     dword ptr ss:[ebp-4], eax        ;  保存寄存器的值
    070103C4  |.  895D F8       mov     dword ptr ss:[ebp-8], ebx
    070103C7  |.  894D F4       mov     dword ptr ss:[ebp-C], ecx
    070103CA  |.  8955 F0       mov     dword ptr ss:[ebp-10], edx
    070103CD  |.  8D45 F0       lea     eax, dword ptr ss:[ebp-10]
    070103D0  |.  50            push    eax
    070103D1  |.  8D45 F4       lea     eax, dword ptr ss:[ebp-C]
    070103D4  |.  50            push    eax
    070103D5  |.  8D45 F8       lea     eax, dword ptr ss:[ebp-8]
    070103D8  |.  50            push    eax
    070103D9  |.  8D45 FC       lea     eax, dword ptr ss:[ebp-4]
    070103DC  |.  50            push    eax
    070103DD  |.  E8 12000000   call    vboxt430.070103F4                ;  解码过程
    070103E2  |.  83C4 10       add     esp, 10
    070103E5  |.  8B45 FC       mov     eax, dword ptr ss:[ebp-4]        ;  这4行恢复寄存器
    070103E8  |.  8B5D F8       mov     ebx, dword ptr ss:[ebp-8]
    070103EB  |.  8B4D F4       mov     ecx, dword ptr ss:[ebp-C]
    070103EE  |.  8B55 F0       mov     edx, dword ptr ss:[ebp-10]
    070103F1  |.  5B            pop     ebx                              ;  取出EBX
    070103F2  |.  C9            leave                                    ;  平衡堆栈
    070103F3  \.  C3            retn                                     ;  这里跳到正确的API函数
     

    vbox根据什么解码呢?是根据地址解码的,比如056B0000    call 070103BA ,是根据056B0000解码的。这个我就不解释了,shoooo大侠里关于ASProtect 里修复 Advanced Import protection 已经说得很清楚了(不解释其实是我解释不清楚,哈哈)。这里的vbox很温柔,解码后直接跳到正确的API函数,因此,我们就没必要跟进去看了,直接写段代码来修复一下就可以了。
     
    修复前我们需要得到一些数据,首先要知道IAT起始和结束位置,这个ImportREC已经帮我们做到了(什么?你还是找不到?填入OEP,点IAT
    Auto Search),然后要知道加密的IAT区域的上下限(不能把正确的函数也拿去修复吧),这个其实也很简单,在ImportREC看看那些无效的指针的值,最小的,和最大的就可以确定了(啥?太多了看不过来?看不过来就多修复几次,每修复一次肯定能少一些无效指针),当然还可以这样来找,按Alt + M,查看内存,找到这里:见图1,这里是下限
     

    然后向下找,一直找到这里:见图2,这里是上限
     

    这段区域内都是vbox申请的区域,所以肯定包含了加密IAT的区域。
    随便找块空地,写入下面代码
        E8 14000000     call    05470019
        00 70 A3 00     dd      00A37000                          ; IAT 起始位置
        00 84 DD A3     dd      00A3DD84                          ; IAT 结束位置
        00 00 F4 01     dd      01F40000                          ; 加密的IAT区域的下限
        00 00 93 06     dd      06950000                          ; 加密的IAT区域的上限
        90              nop
        90              nop
        90              nop
        90              nop
        5A              pop     edx
        81EA 05004705   sub     edx, 5470005        ; 自定位计算
        8B82 05004705   mov     eax, dword ptr ds:[edx+5470005]   ; 取当前IAT指针
        8B18            mov     ebx, dword ptr ds:[eax]           ; 取当前IAT
        3B9A 0D004705   cmp     ebx, dword ptr ds:[edx+547000D]   ; 是否小于指定区域
        7C 12           jl      short 05470042
        3B9A 11004705   cmp     ebx, dword ptr ds:[edx+5470011]   ; 是否大于指定区域
        7F 0A           jg      short 05470042
        FFD3            call    ebx                               ; 在区域内,调用它修复
        8B82 05004705   mov     eax, dword ptr ds:[edx+5470005]   ; 取当前IAT指针
        8918            mov     dword ptr ds:[eax], ebx           ; 保存正确的IAT
        8B82 05004705   mov     eax, dword ptr ds:[edx+5470005]
        83C0 04         add     eax, 4
        8982 05004705   mov     dword ptr ds:[edx+5470005], eax
        3B05 09004705   cmp     eax, dword ptr ds:[5470009]       ; 还没结束,循环
        7F 02           jg      short 0547005B                    ; 结束了,跳走
      ^ EB CB           jmp     short 05470026
      - EB FE           jmp     short 0547005B                    ; 原地打转,防止断点断不下来

    二进制代码:

    E8 14 00 00 00 00 70 A3 00 84 DD A3 00 00 00 F4 01 00 00 95 06 90 90 90 90 5A 81 EA 05 00 47 05
    8B 82 05 00 47 05 8B 18 3B 9A 0D 00 47 05 7C 12 3B 9A 11 00 47 05 7F 0A FF D3 8B 82 05 00 47 05
    89 18 8B 82 05 00 47 05 83 C0 04 89 82 05 00 47 05 3B 82 09 00 47 05 7F 02 EB CB EB FE
     
    进入解码 API的过程修改一下返回代码
    070103BA  /.  55            push    ebp                               ;  <===VBOX 解码API的过程
    070103BB  |.  8BEC          mov     ebp, esp
    070103BD  |.  83EC 10       sub     esp, 10
    070103C0  |.  53            push    ebx
    070103C1  |.  8945 FC       mov     dword ptr ss:[ebp-4], eax
    070103C4  |.  895D F8       mov     dword ptr ss:[ebp-8], ebx
    070103C7  |.  894D F4       mov     dword ptr ss:[ebp-C], ecx
    070103CA  |.  8955 F0       mov     dword ptr ss:[ebp-10], edx
    070103CD  |.  8D45 F0       lea     eax, dword ptr ss:[ebp-10]
    070103D0  |.  50            push    eax
    070103D1  |.  8D45 F4       lea     eax, dword ptr ss:[ebp-C]
    070103D4  |.  50            push    eax
    070103D5  |.  8D45 F8       lea     eax, dword ptr ss:[ebp-8]
    070103D8  |.  50            push    eax
    070103D9  |.  8D45 FC       lea     eax, dword ptr ss:[ebp-4]
    070103DC  |.  50            push    eax
    070103DD  |.  E8 12000000   call    vboxt430.070103F4
    070103E2  |.  83C4 10       add     esp, 10
    070103E5      8B55 F0       mov     edx, dword ptr ss:[ebp-10]        ;  取回我们的EDX
    070103E8      C9            leave                                     ;  平衡堆栈
    070103E9      5B            pop     ebx                               ;  取出正确的IAT
    070103EA      C3            retn                                      ;  返回我们的代码
    修改了4行,二进制代码
    8B 55 F0 C9 5B C3
    代码中带自定位,随便放在哪里都能执行,然后对最后一行原地打转的下断点,在call    05470019新建eip,F9运行,停在了最后一行,此时
    用ImportREC重新Get Imports,已经全部为有效的,Ok,Fix Dump。运行脱壳后的程序已经没有30天试用的信息了。

    三,后记
    注意:如果软件已经过期,是无法脱壳的,一定要能够试用才能够脱壳。我当时没有想过要去脱壳,只是好玩,把日期改到了1年以后,发现不
    能用了,改回来也不能用,换到同学机器上才把壳给脱掉==!
    老师给的是MINITAB 14.10,安装包只有15M,由于国庆没带回来,在网上找不到14.10的了,找了个14.20的,发现安装包有78M,汗~~升级了
    一个小版本,突然膨胀这么大。
     
     
    --------------------------------------------------------------------------------
    【版权声明】: 本文原创于海风月影, 转载请注明作者并保持文章的完整, 谢谢!
                                                           2006年10月02日 18:00:58

    穷人,买不起正版

    前2天用到一些专业软件,穷人,买不起,花了一些时间去破解,真累
     
    一个是中国人做的 xxxx生物信息软件 ,VB 6 写的,功能好像比较强大,一套要2000左右,学院穷,只买了一套,我们学生显然用不到了~~~,只好操刀“干”了它~。过程和软件名不写了,中国人的东西要支持,虽然我很穷
     
    还有一个是minitab,统计软件,当然是比不上大名鼎鼎的matlab,不过它有优势,只有38M,不像matlab随便就要X百兆。是小型统计软件中功能最全的一个,我们要的就是小巧玲珑。可惜,有30天试用期,我傻傻的把日期改到2007年,发现果然不让我用了,改回来也不行了。。。愤怒!上课还要用呢!!我啥都没做就不让我用了。查壳,VBOX 4.30 ,找脱壳的文章,发现不能试用就不能脱壳 ,抢了同学的一台电脑,花了一些时间,终于搞定了。过几天把过程写下来,这2天太累了
     
     

    转贴:正则表达式30分钟入门教程

    正则表达式30分钟入门教程

    作者:deerchao 来源:unibetter大学生社区 转载请注明来源

    本文目标

    30分钟内让你明白正则表达式是什么,并对它有一些基本的了解,让你可以在自己的程序或网页里使用它。一旦入门后,你可以从网上找到更多更详细的资料来继续学习。

    别被下面那些复杂的表达式吓倒,只要跟着我一步一步来,你会发现正则表达式其实并不像你想像中的那么困难。当然,如果你看完了这篇教程之后发现自己明白了很多,却又几乎什么都记不得,那也是很正常的--其实我认为没接触过正则表达式的人在看完这篇教程后能把提到过的语法记住80%以上的可能性为零。这里只是让你明白基本道理,以后你还需要多练习,多查资料,才能熟练掌握正则表达式。

    说明

    正则表达式是用于进行文本匹配的工具,所以本文里多次提到了在字符串里搜索/查找,这种说法的意思是在给定的字符串中,查找与给定的正则表达式相匹配的部分。有可能字符串里有不止一个部分满足给定的正则表达式,这时每一个这样的部分被称为一个匹配。匹配在本文里可能会有三种意思:一种是形容词性的,比如说一个字符串匹配一个表达式;一种是动词性的,比如说在字符串里匹配正则表达式;还有一种是名字性的,就是刚刚说到的“字符串中满足给定的正则表达式的一部分”。

    文本格式约定:专业术语 特殊代码/语法格式 正则表达式 正则表达式中的一部分(用于分析) 用于在其中搜索的字符串 对正则表达式或其中一部分的说明

    什么是正则表达式?

    很可能你使用过Windows/Dos下用于文件查找的通配符,也就是*?。如果你想查找某个目录下的所有的Word文档的话,你会搜索*.doc。在这里,*会被解释成任意的字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比通配符更能精确地描述你的需求--当然,代价就是更复杂。比如你可以编写一个正则表达式来查找所有以0开头,后面跟着2-3个数字,然后是一个连字号“-”,最后是7或8位数字的字符串(像010-123456780376-7654321)。

    入门

    在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。例如,\d+就是一个简洁的代码,代表着规则1位或更多位数字2008就符合这个规则,而A3则不符合(它包含了不是数字的字符)。

    学习正则表达式的最好方法是从例子开始,理解例子之后再自己对例子进行修改,实验。下面给出了不少简单的例子,并对它们作了详细的说明。

    假设你在一篇英文小说里查找hi,你可以使用正则正则表达式hi

    这是最简单的正则表达式了,它可以精确匹配这样的字符串:由两个字符组成,前一个字符是h,后一个是i。通常,处理正则表达式的工具会提供一个忽略大小写的选项,如果选中了这个选项,它可以匹配hi,HI,Hi,hI

    不幸的是,很多单词里包含hi这两个连续的字符,比如him,history,high等等。用hi来查找的话,这里边的hi也会被找出来。如果要精确地查找hi这个单词的话,我们应该使用\bhi\b

    \b是正则表达式规定的一个特殊代码,代表着单词的开头或结尾。虽然通常英文的单词是由空格或标点符号或换行为分隔的,但是\b并不代表这些单词分隔符中的任何一个,只代表一个位置

    假如你要找的是hi后面不远处跟着一个Lucy,你应该用\bhi\b.*\bLucy\b

    这里,.是另一个特殊代码,代表除了换行符以外的任意字符*同样是特殊的代码,不过它代表的不是字符,也不是位置,而是数量--它指定*前边的内容可以重复任意次以使整个表达式得到匹配。因此,.*连在一起就意味着任意数量的不包含换行的字符。现在\bhi\b.*\bLucy\b的意思就很明显了:先是一个单词hi,然后是任意个任意字符(但不能是换行),最后是Lucy这个单词

    如果同时使用其它的一些特殊代码,我们就能构造出功能更强大的正则表达式。比如下面这个例子:

    0\d\d-\d\d\d\d\d\d\d\d代表着这样的字符串:以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字(也就是中国的电话号码,当然,这个例子只能匹配区号为3位的情形,想同时匹配区号为4位的话,请在教程的下面寻找答案)。

    这里的\d是一个新的特殊代码,代表任意的数字(0,或1,或2,或。。。)-不是特殊代码,只代表它本身--连字号。

    为了避免那么多烦人的重复,我们也可以这样写这个表达式:0\d{2}-\d{8}

    这里\d后面的{2}({8})指定的是前面\d必须连续重复出现2次(8次)

    测试正则表达式

    如果你不觉得正则表达式很难读写的话,要么你是一个天才,要么,你不是地球人。正则表达式的语法很令人头疼,即使对经常使用它的人来说也是如此。由于难于读写,容易出错,所以很有必要创建一种工具来测试正则表达式。

    由于在不同的环境下正则表达式的一些细节是不相同的,本教程介绍的是Microsoft .net下正则表达式的行为,所以,我向你介绍一个.net下的工具The Regulator。首先你确保已经安装了.net Framework1.1,然后下载The Regulator,下载完后打开压缩包,运行setup.exe安装。

     

    特殊代码

    现在你已经知道几个具有特殊意义的代码了,如\b,.,*,还有\d.事实上还有更多的特殊代码,比如\s代表任意的空白符,包括空格,制表符(Tab),换行符\w代表着字母或数字

    下面来试试更多的例子:

    \ba\w*\b匹配以字母a开头的单词-先是某个单词开始处(\b),然后是字母a,然后是任意数量的字母或数字(\w*),最后是单词结束处(\b)

    \d+匹配1个或更多连续的数字。这里的+是和*类似的特殊代码,不同的是*代表重复任意次(可能是0次),而+则代表重复1次或更多次

    \b\w{6}\b 匹配刚好6个字母/数字的单词

    表1.常用的特殊代码
    代码/语法 说明
    . 匹配除换行符以外的任意字符
    \w 匹配字母或数字
    \s 匹配任意的空白符
    \d 匹配数字
    \b 匹配单词的开始或结束
    ^ 匹配字符串的开始
    $ 匹配字符串的结束

    特殊代码^以及$\b有点类似,都匹配一个位置。^匹配你要用来查找的字符串的开头,$匹配结尾。这两个代码在验证输入的内容时非常有用,比如一个网站如果要求你填写的QQ号必须为5位到12位数字时,可以使用:^\d{5,12}$

    这里的{5,12}和前面介绍过的{2}是类似的,只不过{2}代表只能不多不少重复2次{5,12}则是必须重复最少5次,最多12次,否则都不匹配。

    因为使用了^$,所以输入的整个字符串都要用来和\d{5,12}来匹配,也就是说整个输入必须是5到12个数字,因此如果输入的QQ号能匹配这个正则表达式的话,那就符合要求了。

    和忽略大小写的选项类似,有些正则表达式处理工具还有一个处理多行的选项。如果选中了这个选项,^$的意义就变成了匹配行的开始处和结束处

    字符转义

    如果你想查找特殊代码本身的话,比如你查找.,或者*,就出现了问题:你没法指定它们,因为它们会被解释成其它的意思。这时你就必须使用\来取消这些字符的特殊意义。因此,你应该使用\.\*。当然,要查找\本身,你也得用\\.

    例如:www\.unibetter\.com匹配www.unibetter.comc:\\windows匹配c:\windows,2\^8匹配2^8(通常这是2的8次方的书写方式)。

    重复

    你已经看过了前面的*,+,{2},{5,12}这几个代表重复的方式了。下面是正则表达式中所有指定重复的方式:

    表2.常用的量词
    代码/语法 说明
    * 重复零次或更多次
    + 重复一次或更多次
    ? 重复零次或一次
    {n} 重复n次
    {n,} 重复n次或更多次
    {n,m} 重复n到m次

    下面是一些使用重复的例子:

    Windows\d+匹配Windows后面跟1个或更多数字

    13\d{9}匹配以13后面跟9个数字(中国的手机号)

    ^\w+匹配一行的第一个单词(或整个字符串的第一个单词,具体代表哪个意思得看选项设置)

    字符类

    要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集的特殊代码,但是如果你想匹配没有预定义特殊代码的字符集比如元音字母(a,e,i,o,u),怎么办?

    很简单,你只需要在中括号里列出它们就行了,像[aeiou]就匹配任何一个元音字母[.?!]匹配标点符号(.或?或!)(英文语句通常只以这三个标点结束)。要注意的是,在中括号中,特殊代码不会被解释成其它意义,所以我们不需要写成[\.\?!](事实上这样写会出错,因为出现了两次\)。

    我们也可以轻松地指定一个字符范围,像[0-9]代表的含意与\d就是完全一致的:一位数字,同理[a-z0-9A-Z]也完全等同于\w。

    下面是一个更复杂的表达式:\(?0\d{2}[) -]?\d{8}

    这个表达式可以匹配几种格式的电话号码,像(010)88886666,或022-22334455,或02912345678等。我们对它进行一些分析吧:首先是一个转义字符\(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字({2}),然后是)-空格中的一个,它出现1次或不出现(?),最后是8个数字(\d{8})。不幸的是,它也能匹配010)12345678(022-87654321这样的“不正确”的格式。要解决这个问题,请在本教程的下面查找答案。

    反义

    有时需要查找不属于某个简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义

    表3.常用的反义代码
    代码/语法 说明
    \W 匹配任意不是字母和数字的字符
    \S 匹配任意不是空白符的字符
    \D 匹配任意非数字的字符
    \B 匹配不是单词开头或结束的位置
    [^x] 匹配除了x以外的任意字符
    [^aeiou] 匹配除了aeiou这几个字母以外的任意字符

    例子:\S+代表不包含空白符的字符串

    <a[^>]+>代表用尖括号括起来的以a开头的字符串

    替换

    好了,现在终于到了解决3位或4位区号问题的时间了。正则表达式里的替换指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。听不明白?没关系,看例子:

    0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)

    \(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。你可以试试用替换|把这个表达式扩展成也支持4位区号的。

    \d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:使用替换时,顺序是很重要的。如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配替换时,将会从左到右地测试每个条件,如果满足了某个条件的话,就不会去管其它的替换条件了。

    Windows98|Windows2000|WindosXP这个例子是为了告诉你替换不仅仅能用于两种规则,也能用于更多种规则。

    分组

    我们已经提到了怎么重复单个字符;但如果想要重复一个字符串又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(教程后面会有介绍)。

    (\d{1,3}\.){3}\d{1,3}是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}代表1到3位的数字(\d{1,3}\.}{3}代表三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。

    不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址(IP地址中每个数字都不能大于255)。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

    理解这个表达式的关键是理解2[0-4]\d|25[0-5]|[01]?\d\d?,这里我就不细说了,你自己应该能分析得出来它的意义。

    后向引用

    使用小括号指定一个子表达式后,匹配这个子表达式的文本可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:以分组的左括号为标志,从左向右,第一个分组的组号为1,第二个为2,以此类推。

    后向引用用于重复搜索前面某个分组匹配的文本。例如,\1代表分组1匹配的文本。难以理解?请看示例:

    \b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go, kitty kitty。首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(\b(\w+)\b),然后是1个或几个空白符(\s+,最后是前面匹配的那个单词(\1)。

    你也可以自己指定子表达式的组号或组名。要指定一个子表达式的组名,请使用这样的语法:(?<Word>\w+),这样就把\w+的组名指定为Word了。要反向引用这个分组捕获的内容,你可以使用\k<Word>,所以上一个例子也可以写成这样:\b(?<Word>\w+)\b\s*\k<Word>\b

    使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:

    表4.分组语法
    捕获
    (exp) 匹配exp,并捕获文本到自动命名的组里
    (?<name>exp) 匹配exp,并捕获文本到名称为name的组里
    (?:exp) 匹配exp,不捕获匹配的文本
    位置指定
    (?=exp) 匹配exp前面的位置
    (?<=exp) 匹配exp后面的位置
    (?!exp) 匹配后面跟的不是exp的位置
    (?<!exp) 匹配前面不是exp的位置
    注释
    (?#comment) 这种类型的组不对正则表达式的处理产生任何影响,只是为了提供让人阅读注释

    我们已经讨论了前两种语法。第三个(?:exp)不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面

    位置指定

    接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们用于指定一个位置,就像\b,^,$那样,因此它们也被称为零宽断言。最好还是拿例子来说明吧:

    (?=exp)也叫零宽先行断言,它匹配文本中的某些位置,这些位置的后面能匹配给定的后缀exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如果在查找I'm singing while you're dancing.时,它会匹配singdanc

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

    假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})*\b。请仔细分析这个表达式,它可能不像你第一眼看出来的那么简单。

    下面这个例子同时使用了前缀和后缀:(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)

    负向位置指定

    前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词--它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:

    \b\w*q[^u]\w*\b匹配包含后面不是字母u的字母q的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总是匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的\w+\b将会匹配下一个单词,于是\b\w*q[^u]\w*\b就能匹配整个Iraq fighting负向位置指定能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b

    零宽负向先行断言(?!exp),只会匹配后缀exp不存在的位置\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字

    同理,我们可以用(?<!exp),零宽负向后行断言来查找前缀exp不存在的位置(?<![a-z])\d{7}匹配前面不是小写字母的七位数字(实验时发现错误?注意你的“区分大小写”先项是否选中)。

    一个更复杂的例子:(?<=<(\w+)>).*(?=<\/\1>)匹配不包含属性的简单HTML标签内里的内容(<?(\w+)>)指定了这样的前缀:被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)。注意后缀里的\/,它用到了前面提过的字符转义;\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后缀本身)。

    注释

    小括号的另一种用途是能过语法(?#comment)来包含注释。要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以把上一个表达式写成这样:

          (?<=    # 查找前缀,但不包含它
          <(\w+)> # 查找尖括号括起来的字母或数字(标签)
          )       # 前缀结束
          .*      # 匹配任意文本
          (?=     # 查找后缀,但不包含它
          <\/\1>  # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签
          )       # 后缀结束
        

    贪婪与懒惰

    当正则表达式中包含能接受重复的量词(指定数量的代码,例如*,{5,12}等)时,通常的行为是匹配尽可能多的字符。考虑这个表达式:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。

    有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的量词都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:

    a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aabab

    表5.懒惰量词
    *? 重复任意次,但尽可能少重复
    +? 重复1次或更多次,但尽可能少重复
    ?? 重复0次或1次,但尽可能少重复
    {n,m}? 重复n到m次,但尽可能少重复
    {n,}? 重复n次以上,但尽可能少重复

    还有些什么东西没提到

    我已经描述了构造正则表达式的大量元素,还有一些我没有提到的东西。下面是未提到的元素的列表,包含语法和简单的说明。你可以在网上找到更详细的参考资料来学习它们--当你需要用到它们的时候。如果你安装了MSDN Library,你也可以在里面找到关于.net下正则表达式详细的文档。

    表6.尚未讨论的语法
    \a 报警字符(打印它的效果是电脑嘀一声)
    \b 通常是单词分界位置,但如果在字符类里使用代表退格
    \t 制表符,Tab
    \r 回车
    \v 竖向制表符
    \f 换页符
    \n 换行符
    \e Escape
    \0nn ASCII代码中八进制代码为nn的字符
    \xnn ASCII代码中十六进制代码为nn的字符
    \unnnn Unicode代码中十六进制代码为nnnn的字符
    \cN ASCII控制字符。比如\cC代表Ctrl+C
    \A 字符串开头(类似^,但不受处理多行选项的影响)
    \Z 字符串结尾或行尾(不受处理多行选项的影响)
    \z 字符串结尾(类似$,但不受处理多行选项的影响)
    \G 当前搜索的开头
    \p{name} Unicode中命名为name的字符类,例如\p{IsGreek}
    (?>exp) 贪婪子表达式
    (?<x>-<y>exp) 平衡组
    (?-<y>exp) 平衡组
    (?im-nsx:exp) 在子表达式exp中改变处理选项
    (?im-nsx) 为表达式后面的部分改变处理选项
    (?(exp)yes|no) 把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no
    (?(exp)yes) 同上,只是使用空表达式作为no
    (?(name)yes|no) 如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no
    (?(name)yes) 同上,只是使用空表达式作为no

    一些我认为你可能已经知道的术语的参考

    字符
    程序处理文字时最基本的单位,可能是字母,数字,标点符号,空格,换行符,汉字等等。
    字符串
    0个或更多个字符的序列。
    文本
    文字,字符串。
    匹配
    符合规则,检验是否符合规则,符合规则的部分。

    网上的资源

    VC中添加小音乐

    国外很多注册机都有小音乐,下面我们来尝试如何给直接的程序加上小音乐,这里用的是 winmm 版的
     
    先用ufmod中的工具 eff 选一首歌,生成一个歌的数据文件:如 xm.c
    将 xm.c , ufmod.h , ufmod.obj 复制到工程目录下
    在工程中加入上面3个文件,然后在主文件中加入下面2行:
    #include "ufmod.h"
    #include "xm.c"
    打开工程-设置,选连接,手工加入库文件 : winmm.lib
    然后在程序启动的时候也就是
    case WM_CREATE:
    后面加上播放歌的语句
    uFMOD_PlaySong ( xm , (void*)11163 , 1);
    其中的 11163是生成文件 xm 里那个 xm[]数组的大小 ,1代表是内存数据
    ok,收工。
     
    附上需要下载的工具
     
     
    更多的XM音乐下载: