博文

一个精悍而巧妙的程序(2006-8-4 16:29:00)

近期用CreateProcess的时候出了些问题,发现第一个时间片的末尾总会有重复的指令在执行,于是想看看_beginthreadex能不能解决这个问题,无奈_beginthreadex却不接受我的线程函数,问了问v兄,v兄也说不清楚原因,给出了互斥体的方法来解决。问题虽然解决了,但是无法用_beginthreadex总让我觉得很不爽,毕竟谁知道CreateProcess什么时候会出点问题呢。于是到处搜索资料,却意外的发现了一个叹为观止的进程间的自我通信,虽然与我本来的目的相差了很多,但是这个程序巧妙的构思让我兴奋不已,不敢独享,拿出来让大家也欣赏一下:

#include"iostream.h"
#include"windows.h"


static LPCTSTR g_szContinueEvent="w2kdg.EventDemo.event.Continue";

BOOL CreateChild(LPCTSTR szAppName)
{
 STARTUPINFO si;
 ::ZeroMemory(reinterpret_cast<void*>(&si),sizeof(si));
 si.cb=sizeof(si);
 PROCESS_INFORMATION pi;
 BOOL bCreateOK=::CreateProcess(szAppName,
                             "child",
           NULL,
           NULL,
           FALSE,
           0,
           NULL,
           NULL,
           &si,
           &pi);
 if(bCreateOK)
 {
  ::CloseHandle(pi.hProcess);
  ::CloseHandle(pi.hThread);
 }
 return bCreateOK;
}

void WaitForChild(LPCTSTR szAppName)
{
 HANDLE hEventContinue=::CreateEvent(NULL,
                                  TRUE,
          FALSE,
          g_szContinueEvent);
 
 if(hEventContinue!=NULL)
 {
  if(::CreateChild(szAppName))
  {
   ::WaitForSingleObject(hEventContinue,INFINITE);
  }
  ::CloseHandle(hEventContinue);
  hEventContinue=INVALID_HANDLE_VALUE;
 }
}

void SignalParent()
{
 HANDLE hEventContinue=::OpenEvent(EVENT_MODIFY_STATE,
                                FALSE,
           g_szContinueEvent);
 if(hEventContinue!=NULL)
 {
  ::SetEvent(hEventContinue);
 }
 ::CloseHandle(hEventContinue);
 hEventContinue=INVALID_HANDLE_VALUE;
}

int main(int argc,char *argv[])
{
 cout<<"\n"<<argv[0]<<endl;
 if(::strcmp(argv[0],"child")!=0)
 {
  cout<<"Parent waiting on child"<<endl;
  ::WaitForChild(argv[0]);
  cout<<"Parent released"<<endl;
 }
 else
 {   
  ::SignalParent();
 }
 return 0;
}


阅读全文(2189) | 评论:5 | 复制链接

OpenGL基本框架(2006-8-4 16:09:00)

在北大上了4节游戏动画设计的课,老师真的是很牛,也很BT,他上课只讲算法,基本上不涉及代码,竟然要我们在第8节课之前交上去一个不少于1500行代码的OpenGL/Direct3D程序,作为大作业。
众所周知,DirectX比较难,不适于初学图形学的人,而且有平台的限制。OpenGL相对简单一点,而且功能强大,是高端的工业标准。我为了能够把更多的经历放在算法上面,而不是MS“诡异”的操作上面,选择了OpenGL.
去书店搜了搜书,D3D的书倒是琳琅满目,但是OpenGL的书却不多,基本上就是那一本“红宝书”。不过翻看红宝书一看,都是console下的列子,毕竟我不想只用GL函数,windows api也是很好的东西,况且用mfc也可以让我偷点懒。
于是放弃了红宝书,决定自己上网找资料进行实践。呵呵,现在看来这个决定还是蛮正确的。
对于windows下的编程,似乎无论是win32 app还是dll,或者mfc,最重要的,也是最难的,都是那个框架。OpenGL也是如此。好在我已经饱经mfc的洗礼,明白如何下手分析框架。不过发现网上的很多程序框架都不是完全一样,不知道这里有没有什么标准?有些代码段去掉之后也仍然没有问题的。这似乎印证了csdn上的一句话:OpenGL出了错,什么都不会做; DirectX出了错,什么都做的出来。^-^
研究了很多例子之后,我自己总结出了一个最简单、易懂的通用框架,并且给出了重要部分的注释:
 
/******************************by CRACKER007*******************************/
 
 
BOOL CGdlg::InitialPixelFormat()    //此函数被后面的CreateRC调用
{
 static PIXELFORMATDESCRIPTOR pfd=
 {
  sizeof(PIXELFORMATDESCRIPTOR),
   1,
   PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER,
   PFD_TYPE_RGBA,
   24,
   0,0,0,0,0,0,
   0,
   0,
   0,
   0,0,0,0,
   32,
   0,
   0,
   PFD_MAIN_PLANE,
   0,
   0,0,0
 };
 int pixelformat;  //下面两个if用来选择与pDC最匹配的硬件像素格式
 if((pixelformat=ChoosePixelFormat(pDC->GetSafeHdc(),&pfd))==0)
 {
  MessageBox("ChoosePixelFormat failed");
  return FALSE;
 }
 if(SetPixelFormat(pDC->GetSafeHdc(),pixelformat,&pfd)==FALSE)
 {
  MessageBox("SetPixelFormat failed");
  return FALSE;
 }
 return TRUE;
}
----------------------------------------------------------------------------
void CGdlg::CreateRC()    //创建RC的一系列操作
{
 PIXELFORMATDESCRIPTOR pdf; 
 int n;
 HGLRC hrc;  //rc句柄
 pDC=new CClientDC(this);  //pDC声明于类定义中: CClientDC *pDC
 ASSERT(pDC!=NULL);
 if(!InitialPixelFormat())  //调用前面定义的函数设置像素格式
 {
  return;
 }
 n=::GetPixelFormat(pDC->GetSafeHdc()); //这两行用于测试像素格式
 ::DescribePixelFormat(pDC->GetSafeHdc(),n,sizeof(pfd),&pfd);
 hrc=wglCreateContext(pDC->GetSafeHdc());//创建rc
 wglMakeCurrent(pDC->GetSafeHdc(),hrc); //使rc当前化
}
---------------------------------------------------------------------------
void CGdlg::FirstDraw()   //初始化
{
        glClearDepth(1.0f);  //设置深度缓存
        glEnable(GL_DEPTH_TEST);  //启用深度测试
        glDepthFunc(GL_LEQUAL);  //深度测试的类型
        glMatrixMode(GL_MODELVIEW);
 glLoadIdentity();
}
---------------------------------------------------------------------------
void CGdlg::ReleaseRC()    //释放RC
{
 HGLRC hrc;
 hrc=::wglGetCurrentContext();
 ::wglMakeCurrent(NULL,NULL);
 if(hrc)
 {
  ::wglDeleteContext(hrc);
 }
 if(pDC)
 {
  delete pDC;
 }
}
----------------------------------------------------------------------------
void CGdlg::ReDraw()    //用于OnDraw/OnPaint
{
 static BOOL bBusy=FALSE;
 if(bBusy)
 {
  return;
 }
        bBusy=TRUE;
 glClearColor(0.2f,0.2f,0.5f,1.0f);  //设置背景色
 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//刷新深度缓存
                                                         //以及背景色    
        glMatrixMode(GL_MODELVIEW);
 glLoadIdentity();
 //DrawOG();       //具体绘图(如果用glClearColor设置了背景色,
                          //那么要记得用glClear刷新
 glFinish();
 SwapBuffers(wglGetCurrentDC());
 bBusy=FALSE;
}
---------------------------------------------------------------------------
开始OpenGl操作所对应的消息函数内容应该包含:
CreateRC();
FirstDraw();
Invalidate(); (或者RedrawWindow(),关于两者的区别,请看注)
----------------------------------------------------------------------------
结束OpenGl操作所对应的消息函数内容应该包含:
ReleaseRC();
----------------------------------------------------------------------------
OnDraw/OnPaint的内容应该包含:
ReDraw();
----------------------------------------------------------------------------
注一:
1 使用Invalidate(..)可以触发WM_PAINT,但系统并不立即重绘,这是因为WM_PAINT的2个特性:low priority、Windows combines multiple WM_PAINT messages in the  message queue into a single message, 如果想立即重绘,那么就需要调用UpdateWindow();
2 RedrawWindow()可以实现立即重绘;
3 如果更新的是一个区域RGN的时候它没法调用Invalidate实现,因为Invalidate只对矩形有效,但是RedrawWindow可以。
注二:
如果要使用GDI函数,必须在glFinish()和SwapBuffers(wglGetCurrentDC())函数之后调用,否则会产生闪烁。
如果一定要在绘制OpenGL结束之前使用GDI函数,那么想要除去闪烁则只能使用SingleBuffer模式。

阅读全文(2062) | 评论:6 | 复制链接

vc之pragma宏的简单应用(2006-8-4 16:08:00)

整理了一下msdn中关于pragma的一些用法,在此列出我认为是比较有用的一些pragma指令,并加上我的一些说明:
 
1 The following pragma causes the linker to search for the EMAPI.LIB library while linking. The linker searches first in the current working directory and then in the path specified in the LIB environment variable:
#pragma comment( lib, "emapi" )
-----------------------------------------------------------------------
2 When the compiler encounters a deprecated symbol, it issues C4995:
void func1(void) {}
void func2(void) {}
int main() {
   func1();
   func2();
   #pragma deprecated(func1, func2)
   func1();   // C4995
   func2();   // C4995
}
----------------------------------------------------------------------
3 The following code fragment uses the message pragma to display a message during compilation:
#if _M_IX86 == 500
#pragma message( "Pentium processor build" )
#endif
-----------------------------------------------------------------------
4 The warning-number-list can contain any warning numbers. Multiple options can be specified in the same pragma directive as follows:
#pragma warning( disable : 4507 34; once : 4385; error : 164 )
This is functionally equivalent to:
#pragma warning( disable : 4507 34 )  // Disable warning messages
                                      //  4507 and 4034.
#pragma warning( once : 4385 )        // Issue warning 4385
                                      //  only once.
#pragma warning( error : 164 )        // Report warning 4164
                                      //  as an error.
Note that the compiler will add 4000 to any warning number that is between 0 and 999.
-----------------------------------------------------------------------
5 This pragma specifies packing alignment for structure, union, and class members,the parameter can only be 1,2,4,8,16,default is 8:
注:如果设置的值比结构体中字节最长的类型还要大,则这个变量(注意仅针对这一个变量)只按照它的字节长度对齐,即不会出现内存浪费的情况。请参见(4)。
(1)
#pragma pack(1)        //每个变量按照1字节对齐
struct A
{
 char x;    //aligned on byte boundary 0
 int y;     //aligned on byte boundary 1
}a;
sizeof(a)==5
(2)
#pragma pack(2)        //每个变量按照2字节对齐
struct A
{
 char x;    //aligned on byte boundary 0
 int y;     //aligned on byte boundary 2
}a;
sizeof(a)==6
(3)
#pragma pack(4)        //每个变量按照4字节对齐
struct A
{
 char x;    //aligned on byte boundary 0
 int y;     //aligned on byte boundary 4
}a;
sizeof(a)==8
(4)
#pragma pack()        //默认,相当于#pragma pack(8) 每个变量按照8字节对齐
struct A
{
 char x;    //aligned on byte boundary 0
 int y;     //aligned on byte boundary 4
}a;
sizeof(a)==8
但是这里y的大小是4字节,所以不会按照8字节对齐,否则将造成1个int空间的浪费

阅读全文(2454) | 评论:1 | 复制链接

宽字符集下汉字的输出(2006-8-4 16:08:00)

vc6与devcpp确实让我难以取舍,如果两个能够很好的融合就好了。最近写宽字符输出程序的时候,它们之间又出了矛盾。哎,devcpp对标准支持的好,但是vc6使用更加方便,而且我经常要写mfc。看来是时候换vc2005了。
先把眼前的问题解决了吧,对于如何设置宽字符集,我先做一个简单的介绍:
 
C程序员一般是用 char 关键字像下面这样来声明一个字符串数组:
char str[100];      
像下面这样声明函数原形:
void strcpy( char *out, char *in );       
为了将上面的声明改成支持双字节的 UNICODE 字符集,可以用下面的方法:
wchar_t str[100];以及void wcscpy( wchar_t *out, wchar_t *in );
 
此外,微软还提供一种通过预处理指令来实现 UNICODE。每当用 Visual C++ 创建新工程时,只要确定是否支持另外一种字符集,则 AppWizard 将会在头文件中插入预处理指令。这些指令告诉编译器程序想要支持何种字符集。这样在使用VC++提供的通用数据类型时,编译器将用相应的数据类型把通用数据类型替换成所需要支持的字符集。这样很容易将代码重新编译成支持其它字符集的程序。
为了在 Visual C++ 6.0 中激活 UNICODE 标准,可以这样做:
Project=>Settings=>C/C++标签=>Preprocessor=>预处理定义中加上:
UNICODE和_UNICODE。其中_UNICODE宏用于C运行时头文件,而UNICODE宏则用于Windows头文件。当编译源代码模块时,通常要同时定义这两个宏,因为不同的头文件可能使用不同的宏。
 
如何表示Unicode字符串常量?
字符集             实例
ANSI             “string”
Unicode           L“string”
ANSI/Unicode      TEXT(“string”) 或 __TEXT(“string”) (有两条下划线)
如何表示Unicode字符数组?
ANSI              char a[N]
Unicode           wchar_t a[N]
ANSI/Unicode      TCHAR a[N]
上述的ANSI/Unicode是通用型,由是否定义了UNICODE来决定是编译为ANSI,还是编译为UNICODE。
 
在进行汉字输出的时候要注意,devcpp与vc6会“打架”,要根据不同的编译器分别进行设置。代码如下:
 
#include<iostream>
#include<locale.h>

int main(int argc, char *argv[])
{
    wchar_t cur_buff[]=L"你";
    _wsetlocale(LC_ALL,L"chs");    //vc6必须加,devcpp不能加。
    std::wcout<<cur_buff;  //c语言可以用stdio.h中的wprintf(cur_buff);
    return 0;
}

阅读全文(2088) | 评论:2 | 复制链接

vc6中关于模板显式特化问题的解决(2006-8-4 16:07:00)

#include <iostream.h>
#include <string.h>
template<class T> T max(T t1,T t2)
{
    return (t1>t2?t1:t2);
}
typedef const char* pcc;
template<> pcc max(pcc s1,pcc s2) 
{                              
    return (strcmp(s1,s2)>0?s1:s2);
}
void main()
{
    int n=max(4,3);
    cout<<n<<endl;
    const char *p=max("bbb","aaa"); 
    cout<<p;
}
 
这段程序在devcpp下没有问题,调用的是特化之后的函数。但是在vc6下面却变成了通用模板函数。经过我的几次尝试,发现要加一点东西vc6才能认识:
#include <iostream.h>
#include <string.h>
template<class T> T max(T t1,T t2)
{
    return (t1>t2?t1:t2);
}
typedef const char* pcc;
template<> pcc max(pcc s1,pcc s2) 
{                              
    return (strcmp(s1,s2)>0?s1:s2);
}
void main()
{
    int n=max(4,3);
    cout<<n<<endl;
    const char *p=max<pcc>("bbb","aaa"); //这里,要“特别强调”pcc这个类型。
    cout<<p;
}

阅读全文(1437) | 评论:0 | 复制链接

左值与右值(2006-8-4 16:06:00)

什么情况下返回指针?什么情况下返回引用?指针和引用的效果一样么?
这里不仅涉及到引用的实现,也涉及到左值与右值的概念。因为返回值性质的不同决定了引用与指针必定不是相同的。相信你读过我写的这篇文章之后,会有一个比较清醒的认识。
 
左值(lvalue)和右值(rvalue)最先来源于C语言。最先在C语言中表示位于赋值运算符两侧的两个值,左边的就叫左值,右边的就叫右值。
比如:
int ii = 5;   //ii是左值,5是右值
int jj = ii;  //jj是左值,ii是右值
上面表明,左值肯定可以作为右值使用,但反之则不然。左值和右值的最早区别就在于能否改变。左值是可以变的,右值不能变,但是这一点在C++中已经不再成立。
在现代C++中,现在左值和右值基本上已经失去它们原本所具有的意义,对于左值表达式,通过具体名字(variant name)和引用(reference)来指定一个对象。非左值就是右值。我们来下一个定义:
左值表示程序中必须有一个特定的名字引用到这个值。
右值表示程序中没有一个特定的名字引用到这个值。
这跟它们是否可以改变,是否在栈或堆(stack or heap)中毫无关系。
1.左值
在下面的代码中:
int ii = 5;      
const int jj = ii;
int a[5];
a[0] = 100;
*(a+3) = 200;
const int& max(const int& a, const int& b) //call by reference
{
 return a > b ? a : b;
}
int& fun(int& a) //call by reference
{
 a += 5;
 return a;
}
ii,jj,a[0],*(a+3)这些值,还有函数max的返回值比如max(ii, jj),函数fun的返回值fun(ii)都是左值。因为它们都是被特定的名字所引用的值。
ii,jj,a[0],*(a+3),以及引用了max和fun的返回值的变量就是它们的名字。
注:有人会问max(8, 9)到底是左值还是右值,C++标准规定常量引用
(reference to const)可以引用到右值,所以max(8, 9)似乎应该是右值。但根据我们对左值的定义,说它是左值也对。不管它是左值,还是右值,我们都不能试图去改变它。为了与前面的概念一致,姑且认为它是左值,不可改变的常量左值。
左值有不能改变的,即被const所修饰的左值,比如上面的jj,max(ii, jj),没有被const困住的左值当然是可以改变的。
比如下面的代码都是成立的:
ii = 600;
a[0] = 700;
fun(ii) = 800; //OK!
我们的眼睛没有问题,fun(ii) = 800;完全正确,因为它是可以改变的左值。

2.右值
没有特定名字的值是右值。
看下面的代码:
int fun1()    //call by value
{
 …
}
int* fun2()   //call by value
{
 …
}
函数fun1的返回值fun1(),函数fun2的返回值fun2()都是右值,它们的值都没有特定的名字去引用。
也许有人会奇怪,fun2()也是右值?前面说的max(a,b)不是左值吗?请看清楚,函数fun2的返回值是pointer,pointer也是call by value,而函数max的返
回值是reference,reference是call by reference。
当然字面上的(literal)值,比如5,8.23,’a’等等也都是右值。
右值最初出现的时候,一个最大的特征就是不可改变。。但时代不同了,标准也变化了。
C++中有可以改变的右值。那就是用户自定义的类(class)的构造函数生成的临时对象。
至于原因,我思考了一下,我想是这样的:我们看类(class)的数据布置结构,会发现它的每一个数据成员都是有名字的,我想编译器在编译的过程中,都会生成一个外部不所知的对这个临时对象右值的名字引用。但当需要改变这个临时对象的时候,这个名字就用上了(实际这时变成了左值,因为有了名字引用)。
比如:
class Point
{
 public:
     int x;
     ……//其他各种成员函数
};
我们现在就可以改变右值,用到了匿名的引用名字。
Point().x = 6;//改变了右值
 
最后说一下引用的机制。很多人都在争论引用是怎么实现的?如果你看看vc做出的反汇编代码,你会发现在机器码的层次,指针与引用的实现是相同。但是,如果你以此断定引用就是指针,那么就错了。的确,引用是靠指针实现的,但是二者有区别。我们讨论引用的问题,一定要在c++的语言层面上讨论,而不能跑到汇编代码中去看。在c++中,引用就是个别名,本身不会另外分配空间。但是在汇编层次,你会发现“引用”有自己的空间。别忘了,你已经离开了c++的范围,这样的讨论失去了意义。总之,我们应该明白,c++中的引用就是个别名,具体的实现方法也要取决于编译系统和优化方案。

阅读全文(2257) | 评论:2 | 复制链接

重载、隐藏与覆盖(2006-8-4 16:05:00)

想必大家在学习c++的时候,经常会在虚函数机制那里遇到问题。搞清楚这些概念,不要被基础语法所困扰,才能顺利的深入学习。
下面我就一些容易混淆的概念作些自己的解释,解释依据于devcpp与vc的共同结果。
 
隐藏(Hide):
假设有:
class A
{
 public:
   void func(){}
};
class B: public class A
{
 public:
   void func(){}
};
那么,对于A* p;不管p指向A还是B,都只能调用A中的func;
同样,对于B* p;不管p指向A还是B,都只能调用B中的func;
总结:对于普通成员函数,调用的是哪个类的版本,要看指针的原型,而不是看指向对象的类型。
 
覆盖(Override):
假设有:
class A
{
 public:
   virtual void func(){}
};
class B: public class A
{
 public:
   void func(){}
};
那么,对于A a;p=&a;不管p是A*还是B*,都调用A中的func;
同样,对于B b;p=&b;不管p是A*还是B*,都调用B中的func;
总结:对于虚函数,调用的是哪个类的版本,要看指针指向什么类型的对象,而不是看指针的原型。
 
重载(Overload):
即实现同名函数。重载与类无关,更与virtual无关。一般的函数就可以实现。仅仅是返回类型不同是不能重载的,重载至少需要在参数个数、参数类型、参数顺序这三者之中的一个不同。

阅读全文(1423) | 评论:0 | 复制链接

字符指针的初始化(2006-8-4 16:04:00)

初学者经常被这个问题所困惑:
char *p="abc"==>"abc"是一个const char*,为什么能够赋值给char *?
 
很多论坛上都可以看到这样的提问帖,不过这些帖子有的讲的是错误的,有的虽然是对的,但是讲的不细致,初学者不好理解。对此,我根据我的经验,以及CSDN上的一些帖子仔细地说一下其中的道理。
 
按照 C/C++ 标准的描述,"abc" 是 string literal (字符字面量、字符文字量),具有静态存储性质,类型是数组类型,并且不能被改变。注意:"abc" 是一个数组类型的对象,是左值。当然左值可以转化为右值使用,就像数组类型的对象可以转化为指针一样。
虽然 "abc" 是数组类型,但是 C 和 C++ 在类型规定上是有区别的:在 C 中,"abc" 的类型是 char [4];在  C++ 中,其类型是 const char [4]。
由于数组类型可以转换为指针类型来使用,所以在 C 和 C++ 中 "abc" 可分别作为 char* 及 const char* 使用。
为了兼容c中char *p="abc"这种现象的存在,C++特别允许初始化时const char*到char*的自动转换。但是这条规则被 C++ 标明为 “Deprecated”,不被推荐使用。
综上所述,
在 C 中:char *p="abc"是完全合乎规则的事情
在 C++ 中:由于有特殊规定,所以这样也可以。
但在c++中要注意:
char *p="abc" 能不能编译通过要看你使用的编译器。鉴于大量遗留代码的存在,大部分编译器允许其通过,或者给个警告。当然,程序员自己必须保证绝不去修改其值:
a. 程序员不应该在代码中出现*p='A'这样的语句。这是当初约定好了的:编译器允许char *p="abc"通过,而程序员保证不去修改它。
b. *p='A'编译时应该允许通过,因为单就这条语句而言,它完全合法。
c. 运行时*p='A'能不能通过要看实际的运行环境,包括你使用的操作系统、编译器、编译器选项 等等,一句话,其运行结果由不得你,且不应该由你去关心,因为这种行为本身已经违反约定了。

阅读全文(1772) | 评论:3 | 复制链接