DirectX3D 学习

学习DirectX3D

2008年11月19日

<a href="http://www.AWSurveys.com/HomeMain.cfm?RefID=kkxx"><img src="http://www.AWSurveys.com/Pictures/AWS_ad3_150by150.jpg" width="150" height="150"></a>
posted @ 2008-11-19 13:34 xpcer 阅读(861) | 评论 (0)编辑 收藏
     摘要: function replace_date(d){ var da = d.split(' '); day = " "+da[2].replace(/,/,"")+" "; // 12 month = " "+da[1].slice(0,3)+" "; // December document.write(day+month); } T...  阅读全文
posted @ 2008-11-19 12:55 xpcer 阅读(1585) | 评论 (0)编辑 收藏

2008年11月17日

ATL 提供了2个智能指针的模板包装类,CComPtr<> CComQIPtr<>,这两个类都在 <atlbase.h> 中声明。CComQIPtr<> 包含了 CComPtr<>的所有功能,因此我们可以完全用 CComQIPtr<> 来使用智能接口指针,唯一要说明的一点就是:CComQIPtr<> 由于使用了运算符的重载功能,它会自动帮我们调用QueryInterface()函数,因此 CComQIPtr<> 唯一的缺点就是不能定义 IUnknown * 指针。

   // 智能指针 smart pointer,按照匈牙利命名法,一般以 sp 开头来表示变量类型

   CComPtr < IUnknown > spUnk;      // 正确

   // 假设 IFun 是一个接口类型

   CComPtr < IFun > spFun; // 正确

   CComQIPtr < IFun > spFun;    // 正确

   CComQIPtr < IFun, &IID_IFun > spFun;       // 正确

   CComQIPtr < IUnknown > spUnk;   // 错误!CComQIPtr不能定义IUnknown指针

  给智能指针赋值的方法:

   CComQIPtr < IFun > spFun;    // 调用构造函数,还没有赋值,被包装的内部接口指针为 NULL

   

   CComQIPtr < IFun > spFun( pOtherInterface );     // 调用构造函数,内部接口指针赋值为

   // 通过 pOtherInterface 这个普通接口指针调用QueryInterface()得到的IFun接口指针

   

   CComQIPtr < IFun > spFun( spOtherInterface ); // 调用构造函数,内部接口指针赋值为

   // 通过 spOtherInterface 这个只能接口指针调用QueryInterface()得到的IFun接口指针

   

   CComQIPtr < IFun > spFun ( pUnknown );    // 调用构造函数,由IUnknownQueryInterface()得到IFun接口指针

   

   CComQIPtr < IFun > spFun = pOtherInterface;      // = 运算符重载,含义和上面一样

   spFun = spOtherInterface; // 同上

   spFun = pUnknown; // 同上

   

   pUnknown->QueryInterface( IID_IFun, &sp );      // 也可以通过QueryInterface赋值

   

   // 智能指针赋值后,可以用条件语句判断是否合法有效

   if ( spFun ){}            // 如果指针有效

   if ( NULL != spFun ){}     // 如果指针有效

   

   if ( !spFun ){}           // 如果指针无效

   if ( NULL == spFun ){}    // 如果指针无效

  智能指针调用函数的方法:

   spFun.CoCreateInstance(...);    // 等价与 API 函数::CoCreateInstance(...)

   spFun.QueryInterface(...); // 等价与 API 函数::QueryInterface()

   

   spFun->Add(...);       // 调用内部接口指针的接口函数

 

   // 调用内部接口指针的QueryInterface()函数,其实效果和 spFun.QueryInterface(...) 一样

   spFun->QueryInterface(...);     

   

   spFun.Release(); // 释放内部的接口指针,同时内部指针赋值为 NULL

   spFun->Release();      // 错!!!一定不要这么使用。

   // 因为这个调用并不把内部指针清空,那么析构的时候会被再次释放(释放了两次)

2 使用智能指针

使用智能指针的好处是不需要我们去显示的释放接口指针

首先要加载atlbase.h文件

#i nclude “atlbase.h”

#i nclude “xxx.h”

#i nclude “xxx_i.c”

 

CComPtr<IUnknown>       pIUnknown// 定义 IUnknown 的智能指针

       CComPtr<IObj1>              pObj1;          // 定义 IObj1 的智能指针

       HRESULT hr;

       try

       {

              //可以用CLSID,也可以用PROGID启动组件

              hr = pIUnknown.CoCreateInstance(CLSID_Obj1);

              if(FAILED(hr))

              {

                     throw(_T("启动组件出错"));

              }

              hr = pIUnknown.QueryInterface(&pObj1);

              if(FAILED(hr))

              {

                     throw(_T("Query接口错误"));

              }

              long pVal;

              hr = pObj1->add(147,258,&pVal);

              if(FALSE(hr))

              {

                     throw(_T("加载函数出错"));

              }

              CString sMsg;

              sMsg.Format( _T("147 + 258 = %ld"), pVal );

              AfxMessageBox( sMsg );

       }

       catch (LPCTSTR lpstr)

       {

              AfxMessageBox(lpstr);

       }

3    CComPtr的代码

template <class T>

class CComPtr

{

public:

       typedef T _PtrClass;

       CComPtr()

       {

              p=NULL;

       }

       CComPtr(T* lp)

       {

              if ((p = lp) != NULL)

                     p->AddRef();

       }

       CComPtr(const CComPtr<T>& lp)

       {

              if ((p = lp.p) != NULL)

                     p->AddRef();

       }

       ~CComPtr()

       {

              if (p)

                     p->Release();

       }

       void Release()

       {

              IUnknown* pTemp = p;

              if (pTemp)

<
posted @ 2008-11-17 22:08 xpcer 阅读(1136) | 评论 (0)编辑 收藏

2008年10月4日

多线程程序设计的相关问题


一、    什么是进程?什么是线程?
   进程是一大堆系统对象拥有权的集合。如进程拥有内存上下文,文件句柄,可以派生出很多线程,也可以拥有很多DLL模块。在windows系统中,进程并不完成实质的工作,只是提供一个相对独立的运行环境,线程才是完成实际工作的载体。线程从属于进程,共享进程所拥有的系统对象。线程是操作系统调度的单位。实质上,线程就是一段可执行代码。
采用多进程的优点和缺点:
优点:运行环境相对独立,某一进程的崩溃一般不会影响到其它进程的执行。
缺点:
耗时耗资源:启动一个进程需要申请大量的系统资源,其中包括虚拟内存、文件句柄以及加载各种必要的动态链接库;线程则不需要以上动作,因为它共享进程中的所有资源。
“系统准备一个进程环境可能需要好几M的空间”
通信复杂:进程的地址空间独立,进程A的地址X,在进程B中可能是无意义的,这样,当进程间需要共享数据时,就需要特殊的机制来完成这些工作。线程则在同一地址空间,数据共享方便快捷。“线程是一个物美价廉的选择,在一个Windows上拥有500个线程是一件很轻易的事情,但是500个进程将是难以想象的”。

二、    为什么需要多线程(解释何时考虑使用线程)
从用户的角度考虑,就是为了得到更好的系统服务;从程序自身的角度考虑,就是使目标任务能够尽可能快的完成,更有效的利用系统资源。综合考虑,一般以下场合需要使用多线程:
1、    程序包含复杂的计算任务时
主要是利用多线程获取更多的CPU时间(资源)。
2、    处理速度较慢的外围设备
比如:打印时。再比如网络程序,涉及数据包的收发,时间因素不定。使用独立的线程处理这些任务,可使程序无需专门等待结果。
3、    程序设计自身的需要
WINDOWS系统是基于消息循环的抢占式多任务系统,为使消息循环系统不至于阻塞,程序需要多个线程的来共同完成某些任务。
三、    使用多线程可能出现的问题(列举问题)
事实上,单纯的使用线程不会产生任何问题,其启动、运行和结束都是非常简单的事情。在Win32环境下,启动:CreateThread,运行就是函数执行的过程,中止就是函数返回的过程或者调用ExitThread。但是由于下列原因可能会使在使用线程的过程中带来一系列问题:
1、    版本问题
多任务的概念是随着实际需求的提出而产生,最初的程序设计者并没有考虑到代码需要在多线程环境下运行,在多线程环境下使用这些代码无疑将产生访问冲突。最典型的例子就是C runtime library。最早的C runtime library产生于20世纪70年代,当时连多任务都是一个新奇的概念,更别说什么多线程了,该版本的库中使用了大量全局变量和静态变量(产生竞争条件的根源,对局部变量无此要求,因为局部变量都使用栈,每个线程都有自己的栈空间,另外在启动线程时,给线程函数的参数应该是尽量使用值,而非指针或引用,这样可以避免因此带来的冲突问题),如在该库中统一使用一个errno变量来表明程序的错误码,如果在多线程中使用该库,并且都需要设置错误码时,此时即产生了一个冲突。
VC为防止以上问题,提供了另外一个线程安全的C runtime library,因此在写多线程程序时,需要注意所连接库的版本是否正确(该过程一般由应用程序向导完成,因此平时编程并无此问题)。与此有关的还有一些其它版本:单线程版、多线程版调试版和多线程发行版。
2、    线程间共享资源时形成竞争条件(race condition)
一般而言,线程并不是单独行动,通常是多个线程分工协作,完成一个大任务中的不同小任务,此时,这些线程之间就需要共同操作一些资源,比较典型的例子是多个线程进行文件操作或屏幕打印的情况:线程A在写文件进行了一半时,发生了context switch,另外一个线程B继续进行写文件操作,此时文件的内容将会凌乱不堪。甚至造成异常错误。典型的例子是,三个线程,线程A在堆中申请了一块内存并填入了一个值,线程B读取了该值后将该内存释放,如果线程C还要对该内存操作时,将导致异常。
3、    线程间的通信问题
线程协作完成某一任务时,有时还需要通信以控制任务的执行步骤,典型的例子就是读写者线程:写线程在对某内存区域写完数据后,需要通知读线程来取,读完之后又需要通知写线程可以继续往里写入数据。更为广泛的例子是:某线程需要等待某一事件发生,以决定是否继续工作。此时,如果没有正确控制线程的执行过程,将导致不可预料的错误发生。
4、    由于不规范的使用线程导致系统效率下降
进程中包含了一个以上的线程,这些线程可能会动态的申请某些资源,如某些数据库线程可能会动态加载数据库方面的动态链接库,但是在该线程结束时,并没有及时释放该动态链接库即被其他线程强行终止,于是该进程中的该动态链接库引用计数不为0,从而导致该动态链接库在该进程中存有一个副本。当这种情况频繁时,将对系统效率产生很大的影响。
四、    线程的类型(解释UI线程和WORKER线程的区别和联系)
严格说来,线程并没有什么本质区别,但是Win32编程文档中却反复强调UI线程和Worker线程的区别。并给出了它们的定义:
UI线程就是:拥有消息队列和窗口的线程,并且它的主要职责是处理窗口消息。Worker线程则没有消息队列,但是当Worker线程产生一个用户界面(消息框和模式对话框除外)时,则该线程则摇身一变,成为UI线程。
问题:
1、    线程的消息队列和窗口的消息队列
在Win32中,每个线程都有它自己专属的消息队列,而窗口并不总是有消息队列,因为一个UI线程可以创建很多个窗口。
2、    UI线程到底跟Worker线程存在什么差别?
职责不一样:UI线程负责处理与用户界面有关的消息,一般而言,用户界面消息来自用户输入(如鼠标键盘消息)、系统消息(如WM_PAINT)以及程序产生的用户自定义消息。因此,在该线程下一般不能存在等待(wait…)函数,这样该线程就会挂起,从而影响消息队列的处理。Worker线程不用处理用户界面消息,而是完成一般性的计算任务,该线程等待计算过程中必要的资源时,不会影响到界面的刷新动作。
操作系统的管理不一样:对UI线程来说,产生一个UI线程实际上产生了两个线程,一个是其自身,另一个是操作系统为响应其GDI调用而产生的影子线程。
3、    Worker线程变成UI线程有什么不好?
Worker线程一般用于计算,此时如果它转换为UI线程的话,将无暇顾及用户界面的消息响应。
4、    Worker线程可否拥有自己的消息队列?
Worker线程同样可以拥有自己的消息队列,该队列一般通过PeekMessage()调用建立,通过GetMessage调用来解析。(具体实现看源码)
5、    用以下规则来管理win32中线程、消息和窗口的互动
所有传送给某一窗口的消息,将由产生该窗口的线程负责处理。
五、    线程的启动和中止(解释启动线程的不同方式及其它们的区别和实用场合)
随C Runtime Library库的更新和编程环境的不同,线程的启动方式也有所不同,以下介绍几种典型的线程启动方式。
1、_beginthread和_endthread
该函数是C Runtime Library中的函数,它负责初始化函数库;其原型如下unsigned long _beginthread( void( __cdecl *start_address )( void * ), unsigned stack_size, void *arglist );“该函数被认为是头脑简单的函数”,使用该函数导致无法有效的控制被创建线程,如不能在启动时将该线程挂起,无法为该线程设置优先权等。另外,该函数为隐藏Win32的实现细节,启动线程的第一件事情即将自己的Handle关闭,因此也就无法利用这个Handle来等待该线程结束等操作。该函数是早期的C Runtime Library的产物,不提倡使用,后期的改良版本为_beginthreadex。
通过_beginthread启动的线程在应当通过调用_endthread结束,以保证清除与线程相关的资源。
2、_beginthreadex和_endthreadex
该函数是C Runtime Library中的一个函数,用标准C实现,相比_beginthread,_beginthreadex对线程控制更为有力(比前者多三个参数),是_beginthread的加强版。其原型为unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr );该函数返回新线程的句柄,通过该句柄可实现对线程的控制。虽然,该函数是用标准C写的(即可不加修改就可以移植到其他系统执行),但是由于它与Windows系统有着紧密的联系(需要手动关闭该线程产生的Handle),因此实现时,往往需要包含windows.h。
通过_beginthreadex启动的线程通过调用_endthreadex做相关清理。
3、CreateThread和ExitThread
CreateThread是Win32 API函数集中的一个函数,其原型为HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD wCreationFlags,LPDWORD lpThreadId);该函数使用Win32编程环境中的类型约定,只适用于Windows系统。参数形式与_beginthreadex一致,对线程控制能力也与之一致,只是该函数与C Runtime Library没有任何关系,它不负责初始化该库,因此在多线程环境中,如果使用该函数启动线程,则不应使用C Runtime Library中的多线程版本的函数。取而代之的应该是功能相对应的 Win32 API函数;另外,应当自己手工提供线程同步的代码。
通过CreateThread创建的线程则通过ExitThread做清理工作。
4、AfxBeginThread和AfxEndThread
AfxBeginThread是MFC提供的线程启动方式,它是个重载函数,有两种调用形式:Worker线程版和UI线程版。MFC对Win32线程做了小心的很好的封装(CWinThread),虽然其总是调用了_beginthreadex来启动一个线程,但是其额外做的工作使得在MFC环境下,操作线程变得简单明了,并且不需要太多的关注细节问题。MFC在线程的封装方面主要做了下列事情:
1、    自动清除CWinThread对象
2、    关闭线程handle,线程对象自动释放
3、    存储了线程相关的重要参数,即线程handle和线程ID
4、    辅之以其它MFC同步对象,方便的实现线程同步
5、    使用了严格的断言调试语句,使线程调试变得相对简单

“(C Runtime Library是用标准C开发的实用函数集)如果多线程程序中使用了标准C库函数,并用CreateThread()和ExitThread(),则会导致内存泄漏。解决这个问题的方法是用C运行库(run-time library)函数来启动和终止线程,而不用WIN32 API定义的CreateThread()和ExitThread()。在C运行库函数中,它们的替代函数分别是_beginthreadex()和_exitthreadex(),需要的头文件是_process.h。在VC6.0下,还需在Project->Settings->C/C++->Code Generation中选择Multithreaded Runtime Library。当然,也可以通过避免使用C标准库函数的方法来解决上述问题,WIN32提供了一些C标准库函数的替代函数,例如,可用wsprintf()和lstrlen()来代替sprintf()和strlen()。这样,使用CreateThread()和ExitThread()不会出现问题。”
六、    线程的同步问题(介绍Windows的同步机制)
1、    怎样等待一个线程结束(忙等(busy loop)和高效的等(WaitForSingleObject))
1)    忙等(busy loop)
hThrd = CreateThread(NULL,0,ThreadFunc,(LPVOID)1,0,&threadId );
for (;;)
{
GetExitCodeThread(hThrd, &exitCode);
if ( exitCode != STILL_ACTIVE )
break;
}
CloseHandle(hThrd);
缺点:耗费CPU资源,且如果在UI线程中这样等待将导致窗口无法刷新。不推荐使用。
2)    高效的等待
(1)WaitForSingleObject;
关于WaitForSingleObject的参数,前者为等待的对象,后者为等待的时间,对某些执行时间较长的线程,可以设置一个合适的值,等待完这个时间后,更新界面,然后继续等待,或者强行终止线程。
将以上的等待部分的代码改为:
WaitForSingleObject(hThrd,INFINITE);
该函数相当于Sleep函数,当需要等待的对象(句柄)没有被触发时,等待的线程将被自动挂起。该方法解决了耗费CPU时间的问题,但是在UI线程中,仍不能使用该方法来等待某一线程结束。
解决方法之一:创建一个Worker管理者线程,在该线程中等待,工作者线程完成,然后由管理者线程发消息通知UI线程更新窗口。
(2)WaitForMultipleObject
该函数允许在同一时间等待多个对象,函数的原型如下:
DWORD WaitForMultipleObject(DWORD nCount,CONST HANDE *lpHandles,BOOL bWaitAll,dwMilliseconds);
第一个参数表示句柄数组的大小;等待的对象不能超过64
第二个参数为句柄数组;
第三个参数表明是否等待所有对象激发。True表示是。
第四个参数为等待时间。
关于WaitForMultipleObject的返回值:
当bWaitAll为True时,返回值为WAIT_OBJECT_0;
当bWaitAll为false时,返回值减去WAIT_OBJECT_0,就是激发对象所在的下标。
应用:
A)    解决多个工人n完成多个任务m(n<m)的问题(bWaitAll设置为false)
解决的思路如下:先从m个任务中取出n个任务,对应地用n个工人去完成,然后利用该函数等待其中任意一个工人结束任务,一旦结束则让其做另外一个任务
B)    解决等待多个资源的问题(bWaitAll设置为true)
哲学家就餐问题:5个哲学家在圆桌旁,每个哲学家左手边放着1只筷子,哲学家做两件事情,吃饭和思考,吃饭时同时需要其左右的两只筷子。
解决思路:将哲学家模拟为线程,筷子为资源,只有哲学家线程同时获得两个资源时,方可进一步动作(吃饭)。即:
WaitForMultipleObjects(2, myChopsticks, TRUE, INFINITE);
MyChopsticks是一个大小为5的核心对象数组。
        (3)MsgWaitForMultipleObjects
        原型:
DWORD MsgWaitForMultipleObjects( DWORD nCount,CONST HANDLE pHandles,BOOL fWaitAll,DWORD dwMilliseconds,DWORD dwWakeMask);
    前几个参数含义同WaitForMultipleObject,最后一个是消息屏蔽标识,指示接收消息的类型。此外返回值也有额外的意义:当消息到达时,该函数返回WAIT_OBJECT_0+nCount。以下是常见的使用MsgWaitForMultipleObjects的架构:
  while (!quit)
    {   // Wait for next message or object being signaled
        DWORD   dwWake;
        dwWake = MsgWaitForMultipleObjects(
                                gNumPrinting,
                                gPrintJobs,
                                FALSE,
                                INFINITE,
                                QS_ALLEVENTS);

        if (dwWake >= WAIT_OBJECT_0 && dwWake < WAIT_OBJECT_0 + gNumPrinting)
        {  
            //对象被触发
        } // end if
        else if (dwWake == WAIT_OBJECT_0 + gNumPrinting)
        {
            //有消息到达
            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
            {   // Get Next message in queue
                 if (msg.message == WM_QUIT)
                 {
                     quit = TRUE;
                     exitCode = msg.wParam;
                     break;
                 } // end if
                 TranslateMessage(&msg);
                 DispatchMessage(&msg);
           } // end while
        }
    } // end while
2、    怎样有效的控制一个线程
在任何情况下,切记线程的核心属性为:线程的句柄,线程的ID号。因此控制一个线程也需从这两方面着手。
1)    使用能返回线程Handle的启动函数来启动线程(除_beginthread外)
2)    尽量不要使一个工作量较大的线程成为“闷葫芦”,从而使该线程能够接收外界通知消息;如下列代码:


MSG msg;
    while(1)
    {
        PeekMessage(&msg,NULL,0,0,PM_REMOVE);
        if(msg.message==WM_MY)
            break;
        Sleep(100);
    }
注:GetMessage也是用来得到消息队列中一条消息的函数,它们的区别在于GetMessage是同步的,即如果消息队列中没有消息的话,该线程将自动挂起。使用GetMessage可以使Worker线程成为一个一步一动的线程!
    MSG msg;
    while(GetMessage(&msg,NULL,0,0))
    {
        if(msg.message==WM_MY)
        {
            //Do something here
}
    }
以上的过程也可以通过事件对象予以实现。
悬而未决的问题:怎么控制一个正在等待其他事件的线程。如:一个TCP监听线程,在某一Socket上listen,此时该线程处于挂起状态!但是现在主线程又需要关闭该线程,应该怎么操作!

3、    怎样互斥访问一个资源(CMutex和Critical Section)
何时需要一个互斥对象?
常见的情形:多个线程需要不定时的操作同一链表(锁链表的头指针);多个线程需要不定时的进行写文件或是进行屏幕输出(锁文件句柄或屏幕句柄);多个线程需要不定时对某个计数器进行操作(锁这个变量);在多线程环境吓,凡是涉及到对全局变量、静态变量、堆中的内存进行访问时,都应该考虑,是否可能出现一个race condition(竞争条件)。
1)    互斥器
Win32提供了对互斥资源访问的一整套机制,其中之一就是互斥器,MFC将这些API函数加以封装,形成了CMutex互斥类,使用这两种方法都能够实现对资源的互斥访问。
Win32中的API:
CreateMutex:
原型:
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,   BOOL bInitialOwner,  LPCTSTR lpName );
第一个参数为安全属性;
第二个参数用来指示互斥器的拥有者是否为当前线程;
第三个参数为互斥器的名称;
当不再需要互斥器时,应当调用CloseHandle关闭。
约定:互斥器产生之后,由某一线程完成锁定工作(即调用Wait…函数),此时系统将该mutex的拥有权交于该线程,然后短暂地将该对象设置为激发态,于是Wait…函数返回,做完相应的工作之后(如:修改链表指针、修改计数器、写文件等),调用ReleaseMutex释放拥有权。周而复始。
MFC中的互斥器CMutex对象:
A、    利用其构造函数产生一个互斥器对象
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner, LPCTSTR lpName);
B、    配合CSingleLock或者CmutipleLock产生一个临时对象,对产生的互斥器进行加锁和释放的动作;
2)    临界区
另一个提供互斥访问的机制是Critical Section,该机制较前一种方法廉价,因为它不属于不是系统的核心对象;临界区可以反复进入,这一点与Mutex有所区别,这需要我们在使用临界区时,保证进入的次数要等于离开的次数。
相关函数为InitializeCriticalSection、DeleteCriticalSection、EnterCriticalSection、LeaveCriticalSection。
4、    怎样等待多个不同(或者相同)资源(WaitForMultiObject)
等待多个不同资源在多线程程序设计中时常遇到,如:等待某一线程结束和某一个资源被释放,等待缓冲区和设备准备好两个资源;这种现实情况,可以分别为不同的资源设置系统对象,然后利用WaitForMultiObject进行等待。
5、    怎样等待多个资源中的一个(使用CSemaphore)
现实中还可能出现如下情形:客人租相机的问题:有若干客人需要,租相机,总相机数为n,相机租完后,客人必须等待,只要有一个相机,则某客人就可以等到租借。还有许多问题可以用这种Producer/consumer模型加以概括。
这种情形即是等待多个资源中的一个的情况,在Win32程序设计中则经常使用信号量(Semaphore)来解决此问题。
Win32系统中,信号量具有以下特性:
一个信号量可以被锁定N次,N一般代表可用资源的个数,上例中即可代表相机的个数,信号量初始化后,在Win32环境下调用一次Wait…操作即表示对其的一次锁定,信号量的值相应加1,操作完后,调用ReleaseSemaphore操作,即代表资源释放(上述例子中就是归还相机)。MFC对Win32信号量的相关API函数进行了封装(CSemaphore),配合CMultiLock 或者 CSingleLock即可实现锁定和资源释放的动作。
七、    线程间的通信
线程间的通信有许多方法可以实现,视场合不同也有不同的应用,大致可以分为两类:进程内的线程通信和进程间的通信。关于进程内线程的通信,前面所述的各种同步互斥等待机制也可归属线程间通信的范畴,
1、    使用线程消息实现线程通信
2、    使用事件对象实现线程通信
Win32还提供了一种比较灵活的核心对象,该对象完全受控于程序(只是清除的时候由系统回收),这就是Event(事件)对象。事件对象一般用于线程间的通知。下面先看事件对象的一些属性:
创建一个事件对象可以调用Win32 API函数完成,也可以使用MFC封装的事件对象。其API原型为:
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState, LPCTSTR lpName );
第二个参数指示事件对象是否为手动修改状态(手动修改需要显式调用ResEvent函数);第三个参数设置事件对象的初态,true为激发态,false为非激发态。第四个参数为事件的名字。
事件对象自从创建后即在程序的控制下处于激发态和非激发态之间翻转。
八、    线程代码的调试
九、    什么是线程安全的代码
十、    多线程程序设计的几个原则

posted @ 2008-10-04 13:19 xpcer 阅读(1014) | 评论 (0)编辑 收藏

2008年9月30日

placement new 是重载operator new的一个标准、全局的版本,它不能被自定义的版本代替(不像普通的operator new和operator delete能够被替换成用户自定义的版本)。

它的原型如下:
void *operator new( size_t, void *p ) throw() { return p; }

首先我们区分下几个容易混淆的关键词:new、operator new、placement new
new和delete操作符我们应该都用过,它们是对堆中的内存进行申请和释放,而这两个都是不能被重载的。要实现不同的内存分配行为,需要重载operator new,而不是new和delete。

看如下代码:
class MyClass {…};
MyClass * p=new MyClass;

这里的new实际上是执行如下3个过程:

1. 调用operator new分配内存 ;2. 调用构造函数生成类对象;3. 返回相应指针。

operator new就像operator+一样,是可以重载的。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重载的,一般你重载的其中一个,那么最后把其余的三个都重载一遍。

至于placement new才是本文的重点。其实它也只是operator new的一个重载的版本,只是我们很少用到它。如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void*p实际上就是指向一个已经分配好的内存缓冲区的的首地址。

我们知道使用new操作符分配内存需要在堆中查找足够大的剩余空间,这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。

使用方法如下:
1. 缓冲区提前分配
可以使用堆的空间,也可以使用栈的空间,所以分配方式有如下两种:
class MyClass {…};
char *buf=new char[N*sizeof(MyClass)+sizeof(int)];或者char buf[N*sizeof(MyClass)+sizeof(int)];

2. 对象的构造
MyClass * pClass=new(buf) MyClass;

3. 对象的销毁
一旦这个对象使用完毕,你必须显式的调用类的析构函数进行销毁对象。但此时内存空间不会被释放,以便其他的对象的构造。
pClass->~MyClass();

4. 内存的释放
如果缓冲区在堆中,那么调用delete[] buf;进行内存的释放;如果在栈中,那么在其作用域内有效,跳出作用域,内存自动释放。

注意:

在C++标准中,对于placement operator new []有如下的说明: placement operator new[] needs implementation-defined amount of additional storage to save a size of array. 所以我们必须申请比原始对象大小多出sizeof(int)个字节来存放对象的个数,或者说数组的大小。
使用方法第二步中的new才是placement new,其实是没有申请内存的,只是调用了构造函数,返回一个指向已经分配好的内存的一个指针,所以对象销毁的时候不需要调用delete释放空间,但必须调用析构函数销毁对象。

posted @ 2008-09-30 19:00 xpcer 阅读(939) | 评论 (0)编辑 收藏

2008年9月18日

转  ::
题二:一个自然数可以分解为若干个自然数相乘,求出每种分解自然数之和最少的一个。 如12=2*2*3,和为7=2+2+3
分析:如果把用穷举法把所有可能的组合计算出来,那无疑是复杂的。 假设a=b*c。其中b,c>=2。则a>=2*max{b,c}>=a+b。由此可见a因数分解后的和比a小。显然a的完全因数分解之后的和最小。问题就变成了自然数完全因数分解求和。

#include <math.h>
unsigned 
int minsum(unsigned int n)
{
    unsigned 
int sum = 0;
    unsigned 
int div_idx = 2;
    unsigned 
int sqrt_n=sqrt(n);
    
    
while (1)
    {
        
if (div_idx > sqrt_n)
            break;
        
if (n % div_idx ==0)
        {
            sum 
+= div_idx;
            n 
/= div_idx;
            sqrt_n 
= sqrt(n);
        }
        
else
            div_idx
++;
    }
    return sum
+n;
}
posted @ 2008-09-18 14:00 xpcer 阅读(1063) | 评论 (0)编辑 收藏

2008年9月17日

LPDIRECT3DSURFACE9 ScreenShotSurface;
    
if (GetKeyState('s')&0x80 || GetKeyState('S')&0x80)
    
{
        D3DDISPLAYMODE d3dm;
        g_pD3DDevice
->GetDisplayMode(0,&d3dm);
        g_pD3DDevice
->CreateOffscreenPlainSurface(d3dm.Width,d3dm.Height,d3dm.Format,D3DPOOL_DEFAULT, &ScreenShotSurface, NULL);

        g_pD3DDevice
->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO, &ScreenShotSurface);
        D3DXSaveSurfaceToFile(
"save.bmp",D3DXIFF_BMP,ScreenShotSurface,NULL,NULL);

    }
posted @ 2008-09-17 19:29 xpcer 阅读(916) | 评论 (0)编辑 收藏
void deletestr(char *str,char ch)
{
    
if (str==NULL)
    
{
        
return;
    }

    
char *p=str;
    
while (*p)
    
{
        
if ((*p==ch))
        
{
            strcpy(p,p
+1);
        }

        p
++;
    }

//for test
//    cout<<str;
}
posted @ 2008-09-17 13:47 xpcer 阅读(845) | 评论 (0)编辑 收藏

2008年9月16日

int maxSumRect(int a[],int left,int right)
{
    
if(left==right) //base case
        if(a[left]>0)
            
return a[left];
        
else
            
return 0;
        
        
int center = (left + right)/2;
        
int maxLeftSum = maxSumRect(a, left, center);
        
int maxRightSum = maxSumRect(a, center+1, right);
        
        
int maxLeftBorderSum = 0, leftBorderSum = 0;
        
        
for(int i = center; i >= left; i--)
            
{
            leftBorderSum 
+= a[i];
            
if(leftBorderSum > maxLeftBorderSum)
                maxLeftBorderSum 
= leftBorderSum;
        }

        
        
int maxRightBorderSum = 0, rightBorderSum = 0;
        
for( i = center + 1; i<=right; i++)
        
{
            rightBorderSum 
+= a[i];
            
if(rightBorderSum > maxRightBorderSum )
                maxRightBorderSum 
= rightBorderSum;
        }



        
return max(maxLeftSum,max(maxRightSum,maxLeftBorderSum+maxRightBorderSum));
}



int maxSum2(int a[],int len)
{
    
int maxSum = 0;
    
int thisSum = 0;
    
for(int i = 0; i < len; i++)
    
{
        thisSum 
+= a[i];
        
if(thisSum > maxSum)
            maxSum 
= thisSum;
        
else if( thisSum < 0)
            thisSum 
= 0;
    }

    
return maxSum;

}

void MaxSubseq_DP(int nums[], int count, int &resStart, int &resEnd, int &resMax) 

    
// 求数组nums[]中连续子序列的最大和,并标出该子序列 
    
// 设 f[x] 为以 a[x] 终止且包含 a[x] 的最大序列的和,有: 
    
// f[1] = a[1]; 
    
// f[x+1] = f[x] > 0 ? f[x] + a[x+1] : a[x+1] 
    
// 那么最大子序列的和就是 f[1] .. f[n] 中最大的一个 
    int start, max; 
    
int i; 
    
    start 
= resStart = resEnd = 0//初始化当前子序列和最大子序列为nums[0] 
    max = resMax = nums[0]; 
    
    
for (i = 1; i < count; ++i) 
        
if (max > 0
            max 
+= nums[i]; 
        }
 else 
            max 
= nums[i]; //抛弃当前子序列 
            start = i; //开始新的子序列搜索 
        }
 
        
        
if (resMax < max) //更新最大子序列 
            resMax = max; 
            resStart 
= start; 
            resEnd 
= i; 
        }
 
    }
//for 
    
    
return
}
posted @ 2008-09-16 15:51 xpcer 阅读(921) | 评论 (0)编辑 收藏
重载与覆盖
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。

“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,
规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。
     此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。
     此时,基类的函数被隐藏(注意别与覆盖混淆)。

如下示例程序中:
(1)函数Derived::f(float)覆盖了Base::f(float)。
(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。

#include<iostream.h>

class Base{
public:
virtual void f(floatx){cout<<"Base::f(float)"<<x<<endl;}
        void g(floatx){cout<<"Base::g(float)"<<x<<endl;
        void h(floatx){cout<<"Base::h(float)"<<x<<endl;}
};

class Derived:publicBase{
public:
virtual void f(floatx){cout<<"Derived::f(float)"<<x<<endl;}
        void g(intx){cout<<"Derived::g(int)"<<x<<endl;}
        void h(floatx){cout<<"Derived::h(float)"<<x<<endl;}
};
void main(void){
  Derived d;
  Base *pb=&d;
  Derived *pd=&d;
  
  //Good:behavior depends solely on type of the object
  pb->f(3.14f);     //Derived::f(float)3.14
  pd->f(3.14f);     //Derived::f(float)3.14

  //Bad:behavior depends on type of the pointer
  pb->g(3.14f);     //Base::g(float)3.14
  pd->g(3.14f);     //Derived::g(int)3(surprise!)

  //Bad:behavior depends on type of the pointer
  pb->h(3.14f);     //Base::h(float)3.14(surprise!)
  pd->h(3.14f);     //Derived::h(float)3.14

posted @ 2008-09-16 15:28 xpcer 阅读(835) | 评论 (0)编辑 收藏
仅列出标题  下一页

导航

<2019年2月>
272829303112
3456789
10111213141516
17181920212223
242526272812
3456789

统计

常用链接

留言簿(1)

随笔分类

随笔档案

Graphics

搜索

最新评论

阅读排行榜

评论排行榜