VC编程功能总结

MFC设置窗体背景图片(画刷)

先载入一张图片,ID为IDB_BITMAP2

TestDlg.h中:
CBrush m_brBk;//在public中定义
TestDlg.cpp中:
在初始化函数OnInitDialog()中加入:
BOOL CTestDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CBitmap bmp;
bmp.LoadBitmap(IDB_BITMAP2);
m_brBk.CreatePatternBrush(&bmp);
bmp.DeleteObject();
return TRUE; // return TRUE unless you set the focus to a control
}

再打开类向导,找到WM_CTLCOLOR消息,重载得对应函数OnCtlColor(),
添加如下:
HBRUSH CTestDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

if (pWnd == this)
{
return m_brBk;
}
return hbr;
}

按照上面的方法一路COPY下来运行,OK!并且由于图片是做为背景显示的,所以再添的按钮都能很好的显示出来,非常方便。

总结一下其中出现的变量和函数。
CBrush:类CBrush封装了Windows图形设备接口(GDI)中的画刷,画刷也就是采取什么方案填充图形的背景的工具。
OnInitDialog ( ):用于对对话框类的变量的初始化(注意:是在产生对话框之前就初始化),是WM_INITDIALOG消息产生的消
息处理函数,覆盖该函数可改变对话框初始设置。
用法:
virtual BOOL OnInitDialog();返回值指定对话框是否对它的一个控件设置输入焦点。如果OnInitDialog返回非零
值,Windows 将输入焦点设在对话框的第一个控件上,只有在对话框明确将输入焦点设在某控件上,应用返回0。
CBitmap:类CBitmap封装了Windows图形设备接口(GDI)中的位图,并且提供操纵位图的成员函数。
LoadBitmap ( ):CBitmap类的一个成员函数,从应用的可执行文件中加载一个命名的位图资源来初始化位图对象。
用法:
BOOL LoadBitmap( LPCTSTR lpszRecourceName );BOOL LoadBitmap( UINT nIDResource );返回值调用成功时返回非零值,
否则为0。参数lpszResourceName指向一个包含了位图资源名字的字符串(该字符串以null结尾)。NIDResource指定位图资源
中资源的ID号。本函数从应用的可执行文件中加载由lpszResourceName指定名字或者由nIDResource指定的ID号标志的位图资
源。加载的位图被附在Cbitmap对象上。如果由lpszResourceName指定名字的对象不存在,或者没有足够的内存加载位图,函
数将返回0。可以调用函数CgdiObject::DeleteObject删除由LoadBitmap加载的位图,否则Cbitmap的析构函数将删除该位图对象。
CreatePatternBrush ( ):CBrush类的一个成员函数,用位图指定的模式初始化画刷。
用法:
BOOL CreatePatternBrush( CBitmap* pBitmap );返回值调用成功时返回非零值,否则为0。参数pBitmap指定一个位图。本
函数用位图指定的模式初始化画刷。此画刷随后就可用于任何支持光栅操作的设备上下文。由bBitmap指定的位图一般用以下
的函数初始化:CBitmap:: CreateBitmap、CBitmap::CreateBitmapIndirect、CBitmap::LoadBitmap或Cbitmap::
CreateCompatibleBitmap。
DeleteObject ( ):CgdiObject类的一个成员函数,从内存中删除附加给CGdiObject的Windows GDI对象,释放与此对象相关
的系统存储空间。GdiObject类为各种Windows图形设备接口(GDI)对象,如位图、区域、画刷、画笔、调色板、字体等提供
了一些基本类。我们不会直接构造一个CGdiObject对象,而是使用某一个派生类如CPen或CBrush创建。
用法:
BOOL DeleteObject( );如果GDI对象被成功删除,则返回非零值,否则为0。通过释放附加的GDI对象占有的系统存储来删除它
们。与CGdiObject对象有关的存储不受此调用的影响。如果CGdiObject对象正被选入设备上下文中,则应用不可对此对象调用
DeleteObject,。当一个模式画刷被删除时,与之相关联的位图不被删除。位图必须被独立删除。
HBRUSH:数据类型,用于定义画刷句柄。在Windows环境中,句柄是用来标识项目的,这些项目包括:module,task,
instance, file ,block of memory, menu, control, font, resource, icon, cursor,string, GDI object等,包括
bitmap, brush, metafile, palette, pen, region以及设备描述表device context。实际上,句柄是一个标识符,用来表示
对象或者项目,是一个32位的正整数。应用程序几乎总是通过调用一个Windows函数来获得一个句柄,之后其他的Windows函数
就可以使用这个句柄,以引用相应的对象。
WM_CTLCOLOR消息:WM_CTLCOLOR是一个由控制(Control)发送给它父窗口的通知消息(Notificationmessage)。利用向导映射
该消息产生函数:HBRUSHCAboutDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);参数pDC是TestDlg的设备上下
文,pWnd是TestDlg中发送该消息的control指针,nCtlColor是Control的类型编码。WM_CTLCOLOR是系统在绘制控件的时候自
动发送的,如果需要自定义,就截取这个消息并重载它的响应函数,用classWizard添加WM_CTLCOLOR消息然后编辑其
OnCtlColor函数。这样Windows向应用程序发送消息WM_CTLCOLOR,应用程序处理WM_CTLCOLOR消息并返回一个用来绘画窗体背
景的刷子句柄

====================================

//放在OnPaint()里

{//设置背景图片
CRect rect;
GetClientRect(&rect);
CDC *pDC=GetDC();
CDC memdc;
memdc.CreateCompatibleDC(pDC);
CBitmap bitmap;
//从资源中载入位图
bitmap.LoadBitmap(IDB_BITMAP1);
memdc.SelectObject(bitmap);
pDC->BitBlt(0,0,rect.Width(),rect.Height(),&memdc,0,0,SRCCOPY);
}


==========================================


对于VC++文档、视结构中的视图,从用户的角度来看,只是可以改变大小、位置的普通窗口,同其他基于Windows应用程序的窗口是一样的;从程序员的 角度来看,视图并不是普通的窗口,而是从MFC库中CView类派生的类对象。像任何VC++对象一样,视图对象的行为由类的成员函数(数据成员)决定, 包括派生类中应用程序定义的函数和从基类继承来的函数。

提出问题
视图的背景一般来说是白色的,在缺省情况下,它和系统定义的颜色COLOR_WINDOW是一致的。设计者一般会希望自己的 程序可以让用户轻松地改变窗口背景颜色,或是用漂亮的图片来充填背景。我们可以用Windows函数SetSysColors来重新指定 COLOR_WINDOW所对应的实际颜色,来达到改变视图背景颜色的目的。但这样会同时改变其他应用程序的视图窗口背景,使得整个Windows系统的 颜色设置产生混乱。另外,我们可能会用以下方法来设置视图的背景颜色,即在CView的OnDraw函数中添写如下一段程序代码:
void CTestView::OnDraw(CDC* pDC)
{
CTestDoc* pDoc =GetDocument();
ASSERT_VALID(pDoc);
CRect rectClient;
CBrush brushBkColor;
GetClientRect(rectClient);
brushBkColor.CreateSolidBrush(RGB(255,0,0));
pDC->DPtoLP(rectClient);
pDC->FillRect(rectClient,&brushBkColor);

}
这样可以达到改变当前应用程序的视图背景的目的,但同时也产生了一些不良影响,使得程序运行效果不尽如人意。

分析问题
我们知道,在VC++的文档、视结构中,CView的OnDraw函数用于实现绝大部分图形绘制的工作。如果用户改变窗口尺寸, 或者显示隐藏的区域,OnDraw函数都将被调用来重画窗口。并且,当程序文档中的数据发生改变时,一般必须通过调用视图的Invalidate(或 InvalidateRect)成员函数来通知Windows所发生的改变,对Invalidate的调用也会触发对OnDraw函数的调用。正因为 OnDraw函数被频繁调用,所以在其执行时,每次都刷新填充一次视图客户区域,便会使屏幕不稳定,产生闪烁现象。
笔者通过对VC++应用程 序框架结构和Windows消息映射系统的仔细研究,找到另外一种改变视图背景的方法,其执行效果比上述两种方法都好。其实在程序调用OnDraw函数之 前,会触发一个Windows消息:WM_ERASEBKGND,以擦除视图刷新区域。在缺省情况下,Windows系统使用视图窗口注册时窗口类中的成 员hbrBackground所描述的画刷来擦除屏幕,这一般会将屏幕刷新成COLOR_WINDOW所对应的颜色。因此,在OnDraw函数中设置背景 颜色的执行过程是这样的:先将屏幕刷新成COLOR_WINDOW所对应的颜色,接着又在OnDraw函数中填充其他颜色,这正是产生屏幕闪烁的根本原 因。

解决问题
通过上述分析,我们应将视图背景颜色填充移到Windows消息:WM_ERASEBKGND所对应的消息映射函数中,而不是在 OnDraw函数中。我们可以通过下列步骤实现这一过程:在文档类中增加一成员变量m_viewBkColor保存当前背景颜色,同时增加两个成员函数GetViewBkColor和SetViewBkColor对其进行读写操作。这样做的好处是可以对m_viewBkColor成员进行序列化,将其和 文档联系在一起,打开某一文档时,其背景将和上一次程序操作该文档时的背景保持一致。在视图类中为视图的Windows消息WM_ERASEBKGND增 加消息映射函数OnEraseBkgnd,代码如下:
BOOL CTestView::OnEraseBkgnd(CDC* pDC)
{
CRect rect;
CBrush brush;
brush.CreateSolidBrush(GetDocument()->GetViewBkColor());
pDC->GetClipBox(rect);
pDC->FillRect(rect,&brush);
return true;
}
在该函数中不需要对客户区域矩形进行设备坐标到逻辑坐标的转换,并且Windows在调用该函数时会自动进行裁剪区域的计算,使得需要刷新的屏幕面积达到最小。这样我们可以在程序中通过设计下列菜单函数轻松地改变视图背景的颜色,而且运行效果相当令人满意。
void CTestView::OnChangeViewBkcolor()
{
CColorDialog cdlg;
if(cdlg.DoModal()==IDOK)
{
GetDocument()->SetViewBkColor
(cdlg.GetColor());
InvalidateRect(NULL);
}
}

VC 实现无标题窗口的拖拽

到底我们怎样来欺骗Windows呢?

我们主动来响应WM_NCHITTEST消息。

用ClassWizard添加WM_NCHITTEST的消息响应函数。

注:用ClassWizard添加消息响应函数时候,在右下角的“Filterformessageavailable to”选择“Windows”。否则你找不到WM_NCHITTEST消息。

编写代码如下:

UINTCTestDlg::OnNcHitTest(CPointpoint)

{

//得到鼠标点击的窗口的部位

UINTnHitTest=CDialog::OnNcHitTest(point);

//如果鼠标点击的是客户区,则返回HTCAPTIONWindows

if(nHitTest==HTCLIENT)

{

returnHTCAPTION;

}

else

returnnHitTest;

}

Windows会根据紧接着发送的WM_NCLBUTTONDOWN消息的wParam的内容来决定是否启用“拖拽状态”。实际上,就是根据OnNcHitTest的返回值来确定是否启用。如果返回值是HTCAPTION,就启用。我们正是利用这个特性来欺骗了Windows。

你可能觉得这样处理会有问题,因为我们虽然欺骗了Windows,但鼠标确实不在标题栏上。试想鼠标拖动的时候,Windows必须使窗口和鼠标同步移动。此刻Windows误以为我们的鼠标就在标题栏上,要实现“同步移动”,Windows会一下子把标题栏移动到鼠标当前位置。

你的担心是多余的,Windows移动窗口的时候是根据鼠标位置的改变量来改变窗口坐标的。它不是以鼠标的实际坐标来直接确定窗口坐标的。

另外一个更简单的处理方法是,是在OnLButtonDown中加上一句话。如下:

voidCTsDlg::OnLButtonDown(UINTnFlags,CPointpoint)

{

SendMessage(WM_NCLBUTTONDOWN,HTCAPTION,MAKELPARAM(point.x,point.y));

CDialog::OnLButtonDown(nFlags,point);

}

上面已经说过,Windows先向窗口发送WM_NCHITTEST消息以确定鼠标点击的部位,如果点击的是非客户区,则又会发送WM_NCLBUTTONDOWN消息。我们现在伪造了一条WM_NCLBUTTONDOWN消息来欺骗Windows,这样更加直接。

========================================================================

从微软的站点看到一个简单的方法实现,不必计算RECT,不必处理鼠标消息的细节和窗口绘制,就能轻松实现没有标题栏的窗口移动的问题,就是使用OnNcHitTest消息。

  手工增加该消息映射:

用classwizard是无法增加该消息的,在BEGIN_MESSAGE_MAP中加入消息ON_WM_NCHITTEST(),然后在头文件中加入

  afx_msg UINT OnNcHitTest(CPoint point);


在实现文件中,加入LBUTTONDOWN消息函数

  void CClyzDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CTranDialog::OnLButtonDown(nFlags, point);//把CTranDialog改成你的基类
PostMessage( WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM( point.x, point.y));
}

  加入NCHITTEST消息函数

  UINT CClyzDlg::OnNcHitTest(CPoint point)
{
UINT nHitTest = CTranDialog::OnNcHitTest( point );//把CTranDialog改成你的基类
return (nHitTest == HTCLIENT) ? HTCAPTION : nHitTest;
}

  编译运行,没有出错就完成了,前后不到十行代码。

  我用基于对话框的应用程序,在vc6.0,winnt4.0下编译,运行良好。

==================================================================

WM_LButtonDown消息响应处理//鼠标左键

voidCDlgDlg::OnLButtonDown(UINTnFlags,CPointpoint)
{
PostMessage(WM_NCLBUTTONDOWN,HTCAPTION,MAKELPARAM(point.x,point.y));
CDialog::OnLButtonDown(nFlags,point);
}

按窗体任何位置都可移动窗体!-------------------------------------------

-----在无标题栏的窗体上右键功能实现:↓↓

WM_RButtonDown消息响应处理//鼠标右键

voidCDlgDlg::OnRButtonDown(UINTnFlags,CPointpoint)
{
CMenumenu,*sub;
menu.LoadMenu(IDR_MENU1);加载菜单
sub=menu.GetSubMenu(0);
ClientToScreen(&point);
sub->TrackPopupMenu(TPM_RIGHTBUTTON,point.x,point.y,this);
CDialog::OnRButtonDown(nFlags,point);
}

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

对于无标题栏的对话框,用鼠标移动它的简单方法为:对消息WM_NCHITTEST进行处理,然后做鼠标位置的判断,如果鼠标位置在要移动窗口的客户区,则返回为鼠标在标题栏的信号,也就是欺骗windows,让它误认为你在点击标题栏,于是,你就可以正常拖动窗口了。

WM_NCHITTEST消息响应处理

让窗口部分区域起移动功能的作用!

函数GetWindowPlacement得到当前窗口在屏幕上的位置.

这个函数的参数类型为WINDOWPLACEMENT结构。原型为
typedefstructtagWINDOWPLACEMENT{/*wndpl*/
UINTlength;
UINTflags;
UINTshowCmd;
POINTptMinPosition;
POINTptMaxPosition;
RECTrcNormalPosition;
}WINDOWPLACEMENT;
其中,第六个变量rcNormalPosition为窗口正常显示时的位置

UINTCTimeWakeDlg::OnNcHitTest(CPointpoint)
{
UINThit=CDialog::OnNcHitTest(point);
if(hit==HTCLIENT)
{
WINDOWPLACEMENTwinplace;
GetWindowPlacement(&winplace);
intxp=winplace.rcNormalPosition.left;
intyp=winplace.rcNormalPosition.top;

if((point.x>xp)&&(point.x<xp+50)&&(point.y>yp)&&(point.y<yp+30))
returnHTCAPTION;
else
returnhit;
}
else
returnhit;
}

好了,再在左上角画个图之类的,就更明显漂亮喽

进程间通信之管道

命名管道更加高级。它由一个名字来标识,以使得客户端和服务端应用程序可以通过它进行彼此通信。而且win32命名管道甚至可以在不同系统的进程间使用。命名管道有时候也被称为fifo。有了命名管道后。一个进程可以把数据放到管道中。另一个知道管道名字的进程把数据取走。命名管道与其他交换数据方式不同的地方在于。如果进程不知道这个管道的名字就不可能把数据取走。

管道实际是用于进程间通信的一段共享内存。创建管道的进程称为管道服务器。连接到一个管道的进程为管道客户机。可以用以下函数创建管道。

HandleCreateNamedPipe(LPCTSTR lpName,DWORDdwOpenMode,DWord dwPipeMode)

lpName命名规范:\\[host_name]\pipe\[Path]Name

比如:LPCSTRszPipeName=TEXT("\\\\.\\pipe\\ssnp\\");

第一部分\\[host_name]指定了服务器的名字。命名管道服务即在此服务器创建。其字符串部分可表示为小数点(本机),星号(当前网络字段),域名或是一个真正的服务。第二部分"\pipe"是一个硬编码字符串,第三部分为命名管道的名字。而且可以设置多级目录。

dwOpenMode:管道创建方式。可以是下面值的组合。

pipe_access_inbound:管道只能用作接收数据。

pipe_Access_outbound:管道只能用作发送数据。

pipe_Access_Duplex:管道即可以发送也可以接收数据。

上面这三个值只能够取其中一个。同时也可以包括以下一个或两个标示

File_flag_write_through:管道用于同步发送和接收数据。在系统内部对于命名管道的处理上不经过缓冲区并能直接发送。并且只有在数据被发送到目标地址时发送函数才会返回。

file_flag_overlapped管道可以用于异步输入和输出。

dwPipeMode:命名管道模式:

通信的实现流程

1:连接建立

服务端通过函数CreateNamedPipe创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。如果在已定义超时值变为0以前。有一个实例管道可以使用。则创建成功并返回管道句柄。并用以侦听来自客户端的连接请求。该功能通过connectnamepipe实现。另一方面。客户端通过函数waitnamepipe使服务进程等待来自客户的实例连接。如果在超时值变为0以前。有一个管道可以为连接使用。则waitnamedpipe返回true.并通过调用createfile或callnamedpipe来呼叫对服务端的连接。此时服务端将接受客户端的连接请求。成功建立连接。服务端connectnamepipe返回true.客户端createFile返回一个指向管道文件的句柄。

从时序上讲。首先是客户端通过waitnamedpipe使服务端的createfile在限定时间内创建实例成功。然后双方通过connectnamedpipe和createfile成功连接。并返回用于通信的文件句柄。此时双方即可进行通信。

2:通信实现

建立连接之后。客户和服务端可以通过得到的管道文件句柄利用readfile和writefile进行彼此间的信息交换。

3:连接终止

客户端应调用closefile而服务端应接着调用disconnectnamedpipe。当然服务端也可通过单方面调用disconnectnamedpipe终止连接。最后应调用closehandle来关闭该管道。

实例解析

服务端:

(1)定义一个定时器不断的更新收到的消息。

m_strPipe= thePipe.GetRequest();
UpdateData(FALSE);
(2)创建命名管道,并创建线程

m_strReply=strReply;
LPCSTR szPipeName=TEXT("\\\\.\\pipe\\ssnp\\");
// 为接收消息创建命名管道.
m_hPipe=CreateNamedPipe(szPipeName,
PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH,
// 阻塞模式.
PIPE_WAIT|PIPE_TYPE_BYTE,
PIPE_UNLIMITED_INSTANCES,
128,128,NULL,NULL);
// 检查是否命名管道被创建.
if (m_hPipe == INVALID_HANDLE_VALUE)
{
TRACE("Unable to create a named pipe.\n");
return;
}
m_pThread=AfxBeginThread(ServerReadProc, this); // 启动线程.

(3)等待客户端连接上并读写管道。

为避免在服务程序出现阻塞现象。创建了一个进程。

UINTCMyPipe::ServerReadProc(LPVOID lpVoid)
{
DWORD dwNumBytesRead,dwNumBytesWrite;
char toDisptxt[80];

// 允许客户连接命名管道,如果不成功,终止.
TRACE("Waiting for connection... \n");

CMyPipe *Parent= (CMyPipe*)lpVoid;
//ConnectNmaepipe:服务进程准备好一个连接到客户进程的管道。并等待一个客户进程连接上为至;

//当没有客户程序连接时。服务程序就停止在此处等待外界的连接。只有在接收到来自客户端的连接时。程序才继续向下执行。对于这种情况。如果不采用线程。服务程序窗口就不能显示出来。连接成功后。用readfile和writefile分别从管道里读取数据和向管道里写入数据。
if(!ConnectNamedPipe(Parent->m_hPipe, (LPOVERLAPPED) NULL))
{
TRACE("Unable to connect a named pipe. Error:%d\n",GetLastError());
CloseHandle(Parent->m_hPipe);
return 1;
}
// 反复检查消息直到程序终止.
while(1)
{
// 读取消息并检查读取数据是否成功.
if (!ReadFile(Parent->m_hPipe, toDisptxt,sizeof(toDisptxt),
&dwNumBytesRead, (LPOVERLAPPED) NULL))
{
TRACE("Unable to read from named pipe. Error: %d\n",GetLastError());
CloseHandle(Parent->m_hPipe);
return 1;
}
else
{
// 保存接收的字符串.
Parent->m_strRequest=toDisptxt;
strcpy(toDisptxt,Parent->m_strReply);
// 写回一个字符串.
WriteFile(Parent->m_hPipe, toDisptxt,sizeof(toDisptxt),
&dwNumBytesWrite, (LPOVERLAPPED)NULL);
}
}

return0;
}

客户端:

(1):在命名管道服务程序启动后。可以用createfile打开命名管道。就像创建文件一样。当文件名必须与服务端创建命名管道的名称相同。否则不能与服务相连。

LPCSTRszPipeName=TEXT("\\\\.\\pipe\\ssnp\\");
m_hPipe=CreateFile(szPipeName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ| FILE_SHARE_WRITE,NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE|FILE_FLAG_WRITE_THROUGH,
// FILE_FLAG_WRITE_THROUGH 设定阻塞.
NULL);
// 检查并判别命名管道文件是否被打开,如果没有被打开,终止程序.
if (m_hPipe == INVALID_HANDLE_VALUE)
TRACE("Unable to create a named pipe. Error:%d\n",GetLastError());

(2):向管道写入数据读出数据。

m_strRequest=strRequest;
char toSendtxt[80];

//反复发送消息直到程序终止.
while(1)
{
TRACE("Sending...\n");
DWORD dwNumBytesWritten,dwNumBytesRead;
strcpy(toSendtxt,m_strRequest);
// 向管道写入消息.
if (!WriteFile(m_hPipe,toSendtxt, (DWORD)sizeof(toSendtxt),
&dwNumBytesWritten,(LPOVERLAPPED) NULL))
{
// 如果向命名管道写入时出现错误,终止程序.
TRACE("Unable to write to named pipe. Error:%d\n",GetLastError());
CloseHandle(m_hPipe);
}
else{
ReadFile(m_hPipe,toSendtxt, (DWORD)sizeof(toSendtxt),
&dwNumBytesRead,(LPOVERLAPPED) NULL);
m_strReply=toSendtxt;
break;
}
// 在再次发送消息前等待.
Sleep(4800);
}

进程间通信交换数据——初级篇

1:采用自定义信息

发端:

(1)UpdateData();

(2)查找对象句柄 CWnd *pWnd =CWnd::FindWindow(NULL,_T("DataRecv1"));

(3)自定义消息WM_Comm1:#defineWM_COMM1 WM_USER+101,

(4) 发送
UINT uMsg;
uMsg = atoi(m_StrMes);
pWnd->SendMessage(WM_COMM1,NULL,(LPARAM)uMsg);

收端:

(1)定义相同的消息#defineWM_COMM1 WM_USER+101

(2)ON_MESSAGE(WM_COMM1,OnUserReceiveMsg)

(3)afx_msg voidOnUserReceiveMsg(WPARAM wParam,LPARAM lParam);

(4)voidCDataRecv1Dlg::OnUserReceiveMsg(WPARAM wParam,LPARAM lParam)
{
m_StrGetMes.Format("%d\n",int(lParam));
UpdateData(FALSE);
}

2:用注册消息进行通信

和自定义消息很类似

发端:

(1):注册消息const UINTwm_nRegMsg=RegisterWindowMessage("reg_data");

(2)同自定义消息

收端

(1):注册消息const UINTwm_nRegMsg=RegisterWindowMessage("reg_data");

(2):ON_REGISTERED_MESSAGE(wm_nRegMsg,OnRegReceiveMsg)//(注意采用的宏不一样)

(3),(4)同:

3: wm_copydata消息实现通信

发端:

(1):查找对象窗口的句柄

(2):发送copydatastruct结构体

COPYDATASTRUCT cpd;
cpd.dwData =0;
cpd.cbData = m_CopyData.GetLength();
cpd.lpData = (void*)m_CopyData.GetBuffer(cpd.cbData);
pWnd->SendMessage(WM_COPYDATA,NULL,(LPARAM)&cpd);

收端:

(1)在message_map中添加ON_WM_COPYDATA()

(2)afx_msg中添加afx_msg BOOLOnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct);

(3)消息映射

BOOLCDataRecv1Dlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
m_StrGetCopyData= (LPCTSTR)pCopyDataStruct->lpData;
m_StrGetCopyData=m_StrGetCopyData.Left(pCopyDataStruct->cbData);
UpdateData(FALSE);
return CDialog::OnCopyData(pWnd, pCopyDataStruct);
}

4:使用内存地址通信

发端:

(1)查找对象句柄

(2):获得当前句柄的进程ID:

DWORDPID;
GetWindowThreadProcessId(pWnd->m_hWnd,(DWORD*)&PID);

(3):根据进程ID打开进程获得进程句柄

HANDLEhProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,PID);

(4):分配虚拟内存

LPVOIDlpBaseAddress;
lpBaseAddress =VirtualAllocEx(hProcess,0,BUFFER_SIZE,MEM_COMMIT,PAGE_READWRITE);
char data[BUFFER_SIZE];
strcpy(data,m_strSendAddress);

(5):将数据写入对象进程地址空间中

WriteProcessMemory(hProcess,lpBaseAddress,data,BUFFER_SIZE,NULL);

(6):注册消息

constUINT wm_nMemMsg=RegisterWindowMessage("mem_data");

(7):发送注册消息

pWnd->SendMessage(wm_nMemMsg,NULL,(LPARAM)lpBaseAddress);
Sleep(100);

(8)释放申请的虚拟内存

VirtualFreeEx(hProcess,lpBaseAddress,0,MEM_RELEASE);

收端:

(1):const UINTwm_nMemMsg=RegisterWindowMessage("mem_data");

(2):ON_REGISTERED_MESSAGE(wm_nMemMsg,OnRegMemMsg)

(3):afx_msgvoid OnRegMemMsg(WPARAM wParam,LPARAM lParam);

(4):

voidCDataRecv1Dlg::OnRegMemMsg(WPARAM wParam,LPARAM lParam)
{
LPVOID lpBaseAddress=(LPVOID)lParam;

//打开进程
HANDLE hProcess = GetCurrentProcess();

chardata[BUFFER_SIZE];

//通过当前进程读内存中的数据
ReadProcessMemory(hProcess,lpBaseAddress,data,BUFFER_SIZE,NULL);
m_GetMemData = data;
UpdateData(FALSE);

}

5:利用内存映射通信

发端:

(1)://创建内存映像对象.

HANDLEhMapping;
LPSTR lpData;
hMapping=CreateFileMapping((HANDLE)0xFFFFFFFF,NULL,
PAGE_READWRITE,0,BUFFER_SIZE,"MYSHARE");

(2):将文件的视图映射到一个进程的地址空间上,返回LPVOID类型的内存指针.

lpData=(LPSTR)MapViewOfFile(hMapping,FILE_MAP_ALL_ACCESS,0,0,0);

(3:给这段映像内存写数据

sprintf(lpData,m_strFileMap);

(4)//释放映像内存.
UnmapViewOfFile(lpData);

收端:

和发端比较类似

hMapping=CreateFileMapping((HANDLE)0xFFFFFFFF,
NULL,PAGE_READWRITE,0,0x100,"MYSHARE");

if(hMapping==NULL)
{
AfxMessageBox("CreateFileMapping() failed.");
return;
}
lpData=(LPSTR)MapViewOfFile(hMapping,FILE_MAP_ALL_ACCESS,0,0,0);
if(lpData==NULL)
{
AfxMessageBox("MapViewOfFile() failed.");
return;
}

//给这段映像内存的数据赋给本地变量
m_strFileMap.Format("%s",lpData);
UnmapViewOfFile(lpData);

6:使用剪贴板数据通信

发端:

UpdateData();// 更新数据.
CString strData=m_strClipBoard; // 获得数据.

//打开系统剪贴板.
if (!OpenClipboard()) return;

//使用之前,清空系统剪贴板.
EmptyClipboard();

//分配一内存,大小等于要拷贝的字符串的大小,返回的内存控制句柄.
HGLOBAL hClipboardData;
hClipboardData = GlobalAlloc(GMEM_DDESHARE, strData.GetLength()+1);

// 内存控制句柄加锁,返回值为指向那内存控制句柄所在的特定数据格式的指针.
char * pchData;
pchData = (char*)GlobalLock(hClipboardData);
// 将本地变量的值赋给全局内存.
strcpy(pchData, LPCSTR(strData));
// 给加锁的全局内存控制句柄解锁.
GlobalUnlock(hClipboardData);
// 通过全局内存句柄将要拷贝的数据放到剪贴板上.
SetClipboardData(CF_TEXT,hClipboardData);
// 使用完后关闭剪贴板.
CloseClipboard();

收端:

//打开系统剪贴板.
if (!OpenClipboard()) return;

//判断剪贴板上的数据是否是指定的数据格式.
if (IsClipboardFormatAvailable(CF_TEXT)||IsClipboardFormatAvailable(CF_OEMTEXT))
{// 从剪贴板上获得数据.
HANDLE hClipboardData = GetClipboardData(CF_TEXT);
// 通过给内存句柄加锁,获得指向指定格式数据的指针.
char *pchData = (char*)GlobalLock(hClipboardData);
// 本地变量获得数据.
m_strClipBoard = pchData;
// 给内存句柄解锁.
GlobalUnlock(hClipboardData);
}
else
{ AfxMessageBox("There is no text (ANSI) data on the Clipboard.");
}
// 使用完后关闭剪贴板.
CloseClipboard();

//更新数据.
UpdateData(FALSE);

操作系统的电源相关消息

挂起操作

在“初识ACPI”中对电源管理有了一个基本的了解。在软件开发中对ACPI了解到这个程度已经可以了。这里,介绍一下在Windows操作系统中操作系统是怎样进行电源管理的,并通过什么消息通知应用程序。

仔细观察Windows系统,会发现在关机时会有待机、休眠两个选项。在英文操作系统中分别是Sleep和Hibernate。如果查阅一些资料的话,可能还会看到Stand-by。那么Sleep、Stand-by和Hibernate如果区分呢?查阅了一下MSDN,没有找到更好的解释。无意间在MSDN中看到一篇早期的文章,对这三个名字做了一个说明,内容如下:
For this discussion, theterm "sleep" means that the system is on standby or is inhibernation. To an application, standby and hibernation are the same. Thedifference occurs in how the operating system determines what gets powereddown. The application does not need to provide any additional feedback for theoperating system to make this determination.

我理解Sleep应该是Stand-by和Hibernate的统称,但是为什么在操作系统上显示的是Sleep,这个就说

不清楚了。好了,暂时先不去理会它们。为了继续这里把Stand-by称为待机,Hibernate称为休眠。在Windows

系统中待机对应的是S3状态,休眠对应的是S4状态。

到这里已经清楚了很多东西。接下来来了解一下Windows系统如果电源管理事件通知给应用程序的。

在MSDN中找了WM_POWERBROADCAST这样一个消息。该消息的描述:
Notifies applications that a power-management event has occurred. A windowreceives this message through its WindowProc function.

LRESULTCALLBACKWindowProc(HWND hwnd, // handle to window

 UINT uMsg, //WM_POWERBROADCAST

 WPARAMwParam, // power-management event

  LPARAM lParam); // function-specific data

通过MSDN的描述可以清楚的了解到应用程序通过重载WindowsProc这个函数就可以获得电源管理的消息。在接收到WM_POWERBROADCAST的同时,还会带给一个Event和一组Data。通过对Event和数据的分析可以得出目前操作系统处于哪种状态。这里需要注意一下,这个消息一定要通过WindowsProc 函数来取得,重载PreTranslateMessage函数对于这个消息是无效的。

以Windows XP系统为基础进行描述。当Vista系统与Windows XP存在差异时,会提取差异进行描述。

当操作系统准备进入S3和S4状态时,首先会广播WM_POWERBROADCAST消息,同时会携带PBT_APMQUERYSUSPEND事件给当前正在运行的应用程序。如果应用程序不对这个事件做任何处理,表示同意挂起请求。然后操作系统会发送PBT_APMSUSPEND时间,随后操作系统马上挂起。

如果某个应用程序不希望操作系统进行挂起操作,那么当接收到PBT_APMQUERYSUSPEND消息时可以阻止操作系统挂起,方法是return BROADCAST_QUERY_DENY;。这个操作即可以阻止系统自行发起的挂起(用户在电源管理部分进行的设定),也可以阻止用户手动发起的挂起操作。

如果应用程序想在系统进入挂起状态前进行一些处理也需要在接收到这个事件时进行处理。虽然,操作系统稍后还会发送PBT_APMSUSPEND事件过来,但是停留的时间很短。

如果应用程序在接收到PBT_APMQUERYSUSPEND事件时没有阻止系统挂起,却在PBT_APMSUSPEND事件时返回FALSE。这时,操作系统会进入挂起状态,但马上又恢复。所以,若想阻止操作系统进入挂起状态,只能在接收到PBT_APMQUERYSUSPEND事件时,进行处理。

假如,应用程序阻止操作系统挂起,返回了BROADCAST_QUERY_DENY,随后操作系统还会发送PBT_APMQUERYSUSPENDFAILED事件给应用程序,通知挂起失败。

当操作系统从挂起状态恢复时,仍然会使用WM_POWERBROADCAST消息进行广播,同时也会携带一个事件。事件的先后顺序是PBT_APMRESUMEAUTOMATIC,PBT_APMRESUMESUSPEND。由于这两个事件对应用程序的影响不大,这里就不做过多的说明了。

Vista操作系统和Windows XP存在很大的却别。在进入挂起状态前,操作系统不会广播PBT_APMQUERYSUSPEND事件,应用程序也不能阻止用户手动发起的挂起请求。操作系统从广播PBT_APMSUSPEND事件到挂起,只给应用程序预留了2秒中的处理时间。 当从S3状态恢复时,只有采用键盘或鼠标唤醒系统应用程序才会收到APMRESUMEAUTOMATIC事件。这需要引起开发人员的注意。

在Vista操作系统中虽然不能阻止由用户手动发起的挂起操作,但是可以阻止由操作系统发起的挂起操作(用户在电源管理方式中进行的设定)。Windows提供了SetThreadExecutionState函数可以完成这个操作。这个函数同样适用于Windows XP操作系统。

某段程序需要阻止操作系统进入挂起时,可以使用下面代码来完成。

::SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED |ES_CONTINUOUS);

当处理完成后,还要恢复操作系统的电源管理功能,使用下面代码完成。

::SetThreadExecutionState(ES_CONTINUOUS);

关机操作

当Windows系统准备关机时,会广播WM_QUERYENDSESSION消息。在这里应用程序可以做关机钱的最后处理。如果响应函数返回FALSE可以阻止操作系统关机。在MSDN中,有两个不同的描述。一个说如果在这个消息中返回非零值会接着发送WM_ENDSESSION消息,一个说返回零会发送WM_ENDSESSION消息。经过试验不论返回零还是非零在Windows XP操作系统下都会发送WM_ENDSESSION消息,

在Vista系统中如果在WM_QUERYENDSESSION消息的响应函数中返回FALSE,操作系统会弹出一个Block,然后由用户选择Yes Or No。当用户选择Yes,那么系统进行关机操作。

分类:vc++,win7开发

C++字符串大小写转换

由于ANSI和Unicode在函数名上有差别,故都列出来,不过本人以Unicode为主。

【1.用C语言标准库函数toupper,tolower】
头文件:cctype c下面:ctype.h
转大写
Ansi版: inttoupper(int c);</a>
Unicode版:inttowupper(wint_t c);
MSDN:toupper, _toupper, towupper, _toupper_l, _towupper_l

转小写:
int tolower(
int c
);

inttowlower(
wint_t c
);

MSDN:tolower

缺陷:只能转换单个字符

Example:

WCHAR wch = 'a';
wch = towupper(wch); // A

【2.用C++语言标准库函数_strlwr_s, _strupr_s】
注意:要使用安全的字符串函数,不要用_strlwr。
头文件:string.h
转小写:
Ansi:
errno_t _strlwr_s(
char *str,
size_t numberOfElements
);

Unicode:
errno_t _wcslwr_s(
wchar_t *str,
size_t numberOfElements
);

注意:numberOfElements 要加上最后NULL字符长度,即numberOfElements = strlen(str)+ 1;

MSDN:http://msdn.microsoft.com/en-us/library/y889wzfw(VS.80).aspx

转大写:
errno_t _strupr_s(
char *str,
size_t numberOfElements
);

errno_t_wcsupr_s(
wchar_t * str,
size_t numberOfElements
);

MSDN:http://msdn.microsoft.com/en-us/library/sae941fh(VS.80).aspx

Example:

WCHAR wideStr[] = L"Abc";
_wcslwr_s(wideStr, wcslen(wideStr) + 1); // abc
_wcsupr_s(wideStr, wcslen(wideStr) + 1);// ABC

【3.std::string 转换大小写】
很遗憾,std::string 没有提供大小写转换的功能,所以只能用STL中的transform结合toupper/tolower完成。
头文件: string,cctype,algorithm
转小写
transform(str.begin(),str.end(),str.begin(),tolower);
transform(wstr.begin(), wstr.end(), wstr.begin(), towlower);
转大写
transform(s.begin(), s.end(), s.begin(), toupper);
transform(wstr.begin(), wstr.end(), wstr.begin(), towupper);

Example:
wstring wstr =L"Abc";
transform(wstr.begin(), wstr.end(), wstr.begin(), towupper);

【4.boost库中string_algorithm 提供了大小写转换函数to_lower 和 to_upper】

Example:
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost;

wstringwstr =L"Abc";
boost::to_lower(wstr); // abc

====================================================================
附完整Example

**
* @file test.cpp
* @brief 字符大小写转换
* @author greenerycn@gmail.com
* @date 2009-7-1
*/

#include"stdafx.h"
#include <cstring>
#include <windows.h>
#include <cctype>
#include <algorithm>
#include "boost\algorithm\string.hpp"
using namespace std;

intwmain(int argc, WCHAR* argv[])
{
char ch = 'a';
ch = toupper(ch);

WCHAR wch = 'a';
wch = towupper(wch);

WCHAR wideStr[] = L"Abc";
_wcslwr_s(wideStr, wcslen(wideStr) + 1);

_wcsupr_s(wideStr, wcslen(wideStr) + 1);

wstring wstr =L"Abc";
transform(wstr.begin(), wstr.end(), wstr.begin(), towupper);

boost::to_lower(wstr);

return 0;
}

获得设备信息

/Class Driver GUID for SetupDiGetClassDevs()
GUID g_SYS_GUID = {0x4D36E97D, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08,0x00, 0x2B, 0xE1, 0x03, 0x18};
GUID g_VGA_GUID = {0x4D36E968, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08,0x00, 0x2B, 0xE1, 0x03, 0x18};
GUID g_AUDIO_GUID = {0x4D36E96C,0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18};
GUID g_MOUSE_GUID = {0x4D36E96F,0xE325, 0x11CE, 0xBF, 0xc1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18};

通过guid再来找这个guid下的设备。比如vga下的设备是什么厂家的。

guid和注册表中的HLK_SYSTEM_ControlSet001_Control_class下的值对应

添加

#include<setupapi.h>

#pragmacomment(lib,"setupapi.lib")

HDEVINFOhDevInfo = 0;
SP_DEVINFO_DATA spDevinofData = {0};

//getthe specified class

//这是就通过guid找到了类的信息。
hDevInfo = SetupDiGetClassDevs(pGUID,0,NULL,DIGCF_PRESENT);

//这个时候我们可以同下面的函数来获得类的描述。比如guid为vga的时候dwdata返回值为显示适配器

SetupDiGetClassDescriptionA(pGUID,szClass,128,&dwData);

//枚举设备信息

spDevinofData.cbSize= sizeof(SP_DEVINFO_DATA);
for(i=0;SetupDiEnumDeviceInfo(hDevInfo,i,&spDevinofData);i++)
{

//通过设备类,以及枚举出的设备信息数据获的具体的名称。
bRtn =SetupDiGetDeviceRegistryPropertyA(hDevInfo,&spDevinofData,SPDRP_DEVICEDESC,0L,(PBYTE)szBuf,128,0);

}

窗体的扩展样式和其值

WS_EX_ACCEPTFILES= 0x00000010
指明了一个已创建视窗具有拖拽文件功能


WS_EX_APPWINDOW = 0x00040000
强制一个可见的顶级视窗到工具栏上


WS_EX_CLIENTEDGE = 0x00000200
使一个视窗具有凹陷边框


WS_EX_COMPOSITED = 0x02000000
Windows XP:将一个窗体的所有子窗口使用双缓冲按照从低到高方式绘制出来,参阅remark项.如果这个视窗已经使用经典样式中的下列值CS_OWNDC ,CS_CLASSDC,WS_EX_CONTEXTHELP.此参数将不能使用.
这个样式的视窗在标题栏上有一个问号,当拥护点击着个问号,鼠标变成一个问号,如果用户然后点击一个子窗口,子窗就会收到一条WM_HELP消息.子窗口将把这个消息传递给他的父进程,这个父进程将用HELP_WM_HELP命令调用WinHelp函数.这个帮助程序常常弹出一个典型的包含其子窗口的帮助的窗口
本参数不能和WS_MAXIMIZEBOX,WS_MINIMIZEBOX一起使用


WS_EX_CONTROLPARENT = 0x00010000
这个窗体本身包含了参与对话框导航的子窗口.如果使用了这个参数,对话框管理器?入这个窗体的子窗口,当执行导航操作时,比如按住TAB键,方向键.


WS_EX_DLGMODALFRAME = 0x00000001
创建一个具有双边框的窗口,这个窗口可以通过使用WS_CAPTION样式被创建成具有一个标题栏的窗口.


WS_EX_LAYERED = 0x00080000
Windows 2000/XP:创建一个分层的窗口.注意,这不能用在子窗口上.同样,如果窗口具有CS_OWNDC ,CS_CLASSDC样式,这也不用使用.


WS_EX_LAYOUTRTL = 0x00400000
阿拉伯以及西伯来版本的98/ME,2000/XP创建一个水平起点在右边的窗口.越往左边水平坐标值变大.


WS_EX_LEFT = 0x00000000
创建一个窗口具有一般的左对齐属性.此为默认


WS_EX_LEFTSCROLLBAR = 0x00004000
如果外壳语言是西伯来,阿拉伯,或者其他阅读顺序的语言,竖滚动条将会在客户区的左边.对其他语言,此参数忽略.


WS_EX_LTRREADING = 0x00000000
窗体的文字按照从左到右排列.此为默认.


WS_EX_MDICHILD = 0x00000040
创建一个多文档界面的子窗口.


WS_EX_NOACTIVATE = 0x08000000
Windows 2000/XP:一个使用此参数创建的顶级窗口不会变成前台窗口,当用户点击他时.系统不会将此窗口放到前台,当用户最小化或者关闭这个前台窗口.
要激活这样的窗口,使用SetActiveWindow或者SetForegroundWindow函数
此类型的窗口默认不会显示在任务栏上.要强行将这样的窗口显示到任务栏上,使用WS_EX_APPWINDOW参数.


WS_EX_NOINHERITLAYOUT = 0x00100000
Windows 2000/XP:用此参数创建的窗体不会传递他的窗口布局给他的子窗口


WS_EX_NOPARENTNOTIFY = 0x00000004
指明一个使用此参数创建的窗口不发送WM_PARENTNOTIFY消息给他的父窗口当这个窗口被创建或者销毁的时候.


WS_EX_OVERLAPPEDWINDOW = (WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE)
联合了WS_EX_CLIENTEDGE andWS_EX_WINDOWEDGE styles


WS_EX_PALETTEWINDOW = (WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST)
联合了WS_EX_WINDOWEDGE,WS_EX_TOOLWINDOW, and WS_EX_TOPMOST styles.


WS_EX_RIGHT = 0x00001000
窗口具有一般的右对齐属性.这要依靠这个窗口的类.这个样式只有外壳语言是西伯来语,阿拉伯语等其他阅读顺序的语言才有影响,否则此样式别忽略
对文字标签或者编辑框使用WS_EX_RIGHT样式跟使用SS_RIGHT 或者 ES_RIGHT影响是一样的.对按钮使用这个样式跟使用BS_RIGHT 和BS_RIGHTBUTTON的影响是一样的


WS_EX_RIGHTSCROLLBAR = 0x00000000
竖直滚动条显示在客户区的右边.默认.


WS_EX_RTLREADING = 0x00002000
如果外壳语言是西伯来语,阿拉伯语等支持排列方式阅读的语言,窗体文字将按照从右到左的阅读顺序.对其他语言,此样式忽略.


WS_EX_STATICEDGE = 0x00020000
创建一个窗口具有三维边框用来表示一个项目不接受用户输入.


WS_EX_TOOLWINDOW = 0x00000080
创建一个工具窗口:也就是说,这个窗口被用来做浮动工具条.一个工具窗口具有一个比一般的标题栏短的标题栏,并且系统在标题栏使用小字体.作为工具窗口,它不显示在工具栏上.当用户用ALT+TAB切换时也不出现在对话框中.如果一个工具窗有系统菜单,那么他的图标不会被显示在标题栏上.但是,你可以通过键入ALT+TAB或者右键点击标题栏来显示系统菜单.


WS_EX_TOPMOST = 0x00000008
指明用此参数创建的窗口将会放在所有顶级视窗上并且停在最上面.即使这个窗口不是活动的.要添加或者移除他,使用SetWindowPos函数.


WS_EX_TRANSPARENT = 0x00000020
用此参数创建的的窗口在他同一线程的窗口被绘制前将不会被绘制.这个窗口透明的显示,因为同一线程的窗口已经绘制出来
要脱离这个限制激活透明,使用SetWindowRgn函数.


WS_EX_WINDOWEDGE = 0x00000100
使一个窗口具有凸起的边框.

Vc++知识点记录

1:.退出程序
if (MessageBox("Are you sure exit G-Sensor?","Tips",MB_YESNO|MB_DEFBUTTON2)==IDYES)
{
PostQuitMessage(0);
}

2:

.隐藏对话框,最不山寨的一种方法

定义一个bool变量visible,在构造函数中初始化为false

voidCGDIButtonTestDlg::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos)
{

//if(lpwndpos->flags & SWP_SHOWWINDOW)
if(!visible)
{
lpwndpos->flags &= ~SWP_SHOWWINDOW;
PostMessage(WM_WINDOWPOSCHANGING, 0, (LPARAM)lpwndpos);
ShowWindow(SW_HIDE);
}
else
CDialog::OnWindowPosChanging(lpwndpos);
}

在想正常显示的地方visible=true,ShowWindow(SW_SHOW);即能正常显示。
想正常隐藏,既visible=false,ShowWindow(SW_HIDE);

3:

判断当前键盘指示灯亮着

BYTEMyState[MAX_PATH];
GetKeyboardState((LPBYTE)&MyState);

CString MyInfo="当前亮着的键盘指示灯包括:";
if(MyState[VK_NUMLOCK]&1)
{
MyInfo+="NumLock";
::MessageBox(NULL,"NumLock","信息提示",MB_OK /*MB_ICONINFORMATION|MB_TASKMODAL*/);
}
if(MyState[VK_SCROLL]&1)
{
MyInfo+="、ScrollLock";
::MessageBox(NULL,"ScrollLock","信息提示",MB_OK /*MB_ICONINFORMATION|MB_TASKMODAL*/);
}
if(MyState[VK_CAPITAL]&1)
{
MyInfo+="、CapsLock";
::MessageBox(NULL,"VK_CAPITAL","信息提示",MB_OK /*MB_ICONINFORMATION|MB_TASKMODAL*/);
}
MessageBox(MyInfo,"信息提示",0);

判断当前键盘指示灯亮着

BYTEMyState[MAX_PATH];
GetKeyboardState((LPBYTE)&MyState);

CString MyInfo="当前亮着的键盘指示灯包括:";
if(MyState[VK_NUMLOCK]&1)
{
MyInfo+="NumLock";
::MessageBox(NULL,"NumLock","信息提示",MB_OK /*MB_ICONINFORMATION|MB_TASKMODAL*/);
}
if(MyState[VK_SCROLL]&1)
{
MyInfo+="、ScrollLock";
::MessageBox(NULL,"ScrollLock","信息提示",MB_OK /*MB_ICONINFORMATION|MB_TASKMODAL*/);
}
if(MyState[VK_CAPITAL]&1)
{
MyInfo+="、CapsLock";
::MessageBox(NULL,"VK_CAPITAL","信息提示",MB_OK /*MB_ICONINFORMATION|MB_TASKMODAL*/);
}
MessageBox(MyInfo,"信息提示",0);

4:类的向导不好用的解决办法

del /F *.ncb
del /F *.opt
del /F *.aps

del /F *.clw

5:添加对话框背景图片

方法一:

voidAbout::OnPaint()
{
CPaintDC dc(this); // device context for painting

// TODO: Add your message handler code here
CPaintDC dcc(this);
CRectrect;
GetClientRect(&rect);
CDCdcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmapbmpBackground;
bmpBackground.LoadBitmap(IDB_BITMAP1);
//IDB_BITMAP是你自己的图对应的ID
BITMAPbitmap;
bmpBackground.GetBitmap(&bitmap);
CBitmap*pbmpOld=dcMem.SelectObject(&bmpBackground);
dc.StretchBlt(0,0,rect.Width(),rect.Height(),&dcMem,0,0,
bitmap.bmWidth,bitmap.bmHeight,SRCCOPY);
// Do not call CDialog::OnPaint() for painting messages
}

方法二:

响应OnEraseBkgnd消息,然后在这个消息函数里面显示图片! 应该是在APP里加

BOOLCxxDlg::OnEraseBkgnd(CDC* pDC)
{
BITMAP bm;
m_bmp.GetBitmap(&bm);
m_pbmCurrent = &m_bmp;
CDC dcMem;
dcMem.CreateCompatibleDC(pDC);
CBitmap* pOldBitmap = dcMem.SelectObject(m_pbmCurrent);
pDC->BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcMem,0,0,SRCCOPY);
dcMem.SelectObject(pOldBitmap);
}

方法三:

OnInitDialog()
加入
CBitmap bmp;
bmp.LoadBitmap(IDB_BITMAP3); //这个IDB_BITMAP1要自己添加
m_brush.CreatePatternBrush(&bmp);

OnCtlColor(CDC*pDC, CWnd* pWnd, UINT nCtlColor)
中的
return hbr;
替换为:
return (HBRUSH)m_brush;

6:创建非模态对话框

CSplashDlg *pSplashDlg = new CSplashDlg();
pSplashDlg->Create(IDD_SPLASH_DIALOG);
pSplashDlg->ShowWindow(SW_SHOW);
pSplashDlg->UpdateWindow();

7:对话框支持拖动

添加WM_NCHITTEST 消息事件

UINTCMyAgentDlg::OnNcHitTest(CPoint point)
{
// TODO: Add your message handler code here and/or call default
UINT nHitTest=CDialog::OnNcHitTest(point);
return (nHitTest==HTCLIENT)?HTCAPTION:nHitTest;
//return CDialog::OnNcHitTest(point);
}


8:获取屏幕大小

获取屏幕大小
int with= GetSystemMetrics(SM_CXFULLSCREEN);

intheigh= GetSystemMetrics(SM_CYFULLSCREEN);

通过上边两个函数获取的是显示屏幕的大小,及不包括任务栏等区域。


int cx = GetSystemMetrics( SM_CXSCREEN);
int cy = GetSystemMetrics(SM_CYSCREEN );

这两个函数获取的是真正屏幕的大小。

用前两个函数获取的大小可能是1024*687而用下边两个获取的就是1024*768

9:一个类访问控制另一个类中的变量控件

如果要在类CVDlg 访问控制类CPPDlg中的控件 CSliderCtrl。

首先在类CPPDlg中定义 CSliderCtrl m_sld;

然后在类CVDlg 中定义

CSliderCtrl*m_pSld;
CPPDlg* m_pDlg;

然后在类CVDlg的构造函数中定义:

m_pDlg=newCPPDlg;
m_pDlg->Create(IDD_PP);
m_pSld=&m_pDlg->m_sld;

这样就可以在类CVDlg中任何地方控制类CPPDlg中的控件 CSliderCtrl。

变量,控件都是这么做的。比较正宗的一种方式。

10:Vista下控制屏幕的函数

Vista下如何用软件控制屏幕高层的API可以方便地控制屏幕的亮度、色温、对比度、显示区等。
初始化头文件

#include"PhysicalMonitorEnumerationAPI.h"
#include "HighLevelMonitorConfigurationAPI.h"
#pragma comment(lib,"dxva2.lib")

全局函数 (后面的函数都是调用这两个函数)

voidFreePhysicalMonitor(DWORD npm, LPPHYSICAL_MONITOR ppm)
{
DestroyPhysicalMonitors(npm, ppm);
// Free the array.
free(ppm);
}

LPPHYSICAL_MONITORGetPhysicalMonitor(DWORD *pnpm)
{
HMONITOR hMon = NULL;
hMon = MonitorFromWindow(NULL, MONITOR_DEFAULTTOPRIMARY);
LPPHYSICAL_MONITOR ppm = NULL;
DWORD npm = 0;
BOOL bRet = GetNumberOfPhysicalMonitorsFromHMONITOR(hMon, &npm);
if (bRet) {
ppm = (LPPHYSICAL_MONITOR)malloc(npm * sizeof(PHYSICAL_MONITOR));
if (ppm) {
bRet = GetPhysicalMonitorsFromHMONITOR(hMon, npm, ppm);
if (!bRet) {
FreePhysicalMonitor(npm, ppm);
ppm = NULL;
npm = 0;
}
}
}
*pnpm = npm;
return ppm;
}

返回的是PHYSICAL_MONITOR数组,以下示例只是使用了第一个PHYSICAL_MONITOR元素。

1、调整屏幕前我们可以看看显示器支持什么功能
Vista提供的API是GetMonitorCapabilities(在有些显示器上虽然GetMonitorCapabilities调用失败,但仍然可以调整亮度等;在有些显示器上,从GetMonitorCapabilities返回的值看可以支持某些功能,但实际又不能。这些都另当别论)。

LPPHYSICAL_MONITORppm = 0;
ppm = GetPhysicalMonitor();
if (ppm) {
DWORD nmc = 0, nct = 0;
GetMonitorCapabilities(ppm->hPhysicalMonitor, &nmc, &nct);
CString str = _T("");
if (nmc & MC_CAPS_BRIGHTNESS) {
str += _T("Support brightness control\n");
}
if (nmc & MC_CAPS_COLOR_TEMPERATURE) {
str += _T("Support color temperature\n");
}
if (nmc & MC_CAPS_CONTRAST) {
str += _T("Support contrast\n");
}
.........
if (str == _T(""))
str = _T("Support None");
MessageBox(str);
FreePhysicalMonitor(npm, ppm);
}

2、如何调整亮度
LPPHYSICAL_MONITOR ppm = 0;
DWORD npm = 0;
ppm = GetPhysicalMonitor(&npm);
if (ppm) {
DWORD nMin = 0, nCur = 0, nMax = 0;
GetMonitorBrightness(ppm->hPhysicalMonitor, &nMin, &nCur,&nMax);
CString str;
str.Format(_T("Min:%d, Cur:%d, Max:%d"), nMin, nCur, nMax);
MessageBox(str);
SetMonitorBrightness(ppm->hPhysicalMonitor, nMin);
Sleep(1000);
SetMonitorBrightness(ppm->hPhysicalMonitor, nMax);
Sleep(1000);
SetMonitorBrightness(ppm->hPhysicalMonitor, nCur);
Sleep(1000);
FreePhysicalMonitor(npm, ppm);
}

3、调色温

LPPHYSICAL_MONITORppm = 0;
DWORD npm = 0;
ppm = GetPhysicalMonitor(&npm);
if (ppm) {
SetMonitorRedGreenOrBlueGain(ppm->hPhysicalMonitor, MC_RED_GAIN, 50);
Sleep(500);
SetMonitorRedGreenOrBlueGain(ppm->hPhysicalMonitor, MC_GREEN_GAIN, 49);
Sleep(500);
SetMonitorRedGreenOrBlueGain(ppm->hPhysicalMonitor, MC_BLUE_GAIN, 52);
MessageBox(_T("Set color temperature => Done"));

FreePhysicalMonitor(npm,ppm);
}

4、调对比度

LPPHYSICAL_MONITORppm = 0;
DWORD npm = 0;
ppm = GetPhysicalMonitor(&npm);
if (ppm) {
DWORD nMin, nCur, nMax;
GetMonitorContrast(ppm->hPhysicalMonitor, &nMin, &nCur, &nMax);
CString str;
str.Format(_T("Min:%d, Cur:%d, Max:%d"), nMin, nCur, nMax);
MessageBox(str);
SetMonitorContrast(ppm->hPhysicalMonitor, nMin);
Sleep(1000);
SetMonitorContrast(ppm->hPhysicalMonitor, nMax);
Sleep(1000);
SetMonitorContrast(ppm->hPhysicalMonitor, nCur);
Sleep(1000);
FreePhysicalMonitor(npm, ppm);
}

5、查看显示器类型

LPPHYSICAL_MONITORppm = 0;
DWORD npm = 0;
ppm = GetPhysicalMonitor(&npm);
if (ppm) {
TCHAR *descs[] = {
_T("Shadow-mask cathode ray tube (CRT)"),
_T("Aperture-grill CRT"),
_T("Thin-film transistor (TFT) display"),
_T("Liquid crystal on silicon (LCOS) display"),
_T("Plasma display"),
_T("Organic light emitting diode (LED) display"),
_T("Electroluminescent display"),
_T("Microelectromechanical display"),
_T("Field emission device (FED) display")
};
MC_DISPLAY_TECHNOLOGY_TYPE dtt;
GetMonitorTechnologyType(ppm->hPhysicalMonitor, &dtt);
CString str;
str.Format(_T("Technology type: %s"), descs[(int)dtt]);
MessageBox(str);
FreePhysicalMonitor(npm, ppm);
}

6、恢复出厂设置

LPPHYSICAL_MONITORppm = 0;
DWORD npm = 0;
ppm = GetPhysicalMonitor(&npm);
if (ppm) {
RestoreMonitorFactoryDefaults(ppm->hPhysicalMonitor);
FreePhysicalMonitor(npm, ppm);
}

11:全局函数访问对话框中的控件

CGloabkjDlg*pDlg = (CGloabkjDlg *)(AfxGetApp()->GetMainWnd());

12:

.格盘代码


char *FormatW2K ="CMD.EXE";

//这里我用H:盘,你自己要填入你想格式化的盘
char *FormatW2KParam = "/C\"format.com H:/force/q/u/x/V:MISC\"";

//在后台执行格式化命令
ShellExecute(NULL,"open",FormatW2K,FormatW2KParam,NULL,SW_HIDE);

13:系统下关机代码:

TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
LPTSTR MachineName=NULL;
if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken ))
{
//PERR("OpenProcessToken",GetLastError());
return ;
}
if(!LookupPrivilegeValue(MachineName, SE_SHUTDOWN_NAME,&luid))
{
// PERR("LookupPrivilegeValue",GetLastError());
return ;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),NULL, NULL); //到这里,是取得权限///
ExitWindowsEx(EWX_REBOOT,EWX_FORCE);

14:对话框加载工具栏

1.添加成员变量 CToolBar m_WndToolBar

2.在OnInitDialog() 中CDialog::OnInitDialog();后添加

if (!m_WndToolBar.CreateEx(this,TBSTYLE_FLAT,WS_CHILD|WS_VISIBLE|CBRS_ALIGN_TOP|CBRS_GRIPPER|CBRS_TOOLTIPS,CRect(4,4,0,0))||!m_WndToolBar.LoadToolBar(IDR_TOOLBAR1))
{
TRACE0("未能创建工具栏\n");
return -1;
}
m_WndToolBar.ShowWindow(SW_SHOW);
RepositionBars(AFX_IDW_CONTROLBAR_FIRST,AFX_IDW_CONTROLBAR_LAST,0);

15:VC如何作出有动画效果的托盘图标

//主程序对话框类构造函数
CCDROMControlDlg::CCDROMControlDlg(CWnd* pParent /*=NULL*/)
: CDialog(CCDROMControlDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CCDROMControlDlg)
m_nIconPos = 0; //托盘区动画图标从m_hIconArray[0]开始显示
//}}AFX_DATA_INIT

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

//加载托盘区动画图标
m_hIconArray[0] = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_hIconArray[1] = AfxGetApp()->LoadIcon(IDI_ICON1);
m_hIconArray[2] = AfxGetApp()->LoadIcon(IDI_ICON2);
m_hIconArray[3] = AfxGetApp()->LoadIcon(IDI_ICON3);
}

//定时器消息处理函数,用来实现动画图标
void CCDROMControlDlg::OnTimer(UINT nIDEvent)
{
NOTIFYICONDATA nc;
nc.cbSize = sizeof(NOTIFYICONDATA);
if(m_nIconPos==3)
m_nIconPos=0;
nc.hIcon = m_hIconArray[m_nIconPos++];
nc.hWnd = m_hWnd;
lstrcpy(nc.szTip,"动画效果托盘图标");
nc.uCallbackMessage =WM_NOTIFYICON; //自定义最小化托盘消息
nc.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
nc.uID = IDC_NOTIFYICON;
Shell_NotifyIcon(NIM_MODIFY, &nc);
CDialog::OnTimer(nIDEvent);
}

只做这些托盘图标动画不会出来的 应该在前面做个Shell_NotifyIcon(NIM_ADD, &nc);
动画才能出来。

一个图片加载与绘制类(使用GDI输出图片)

这是一个图片加载与绘制的类,使用GDI将图片文件绘制到DC上,可以用于图片显示,程序换肤等应用场合。
其中部分代码来源于互联网,代码作者不详,我只进行了改进和增补,如果来源作者看到,还望谅解。

一、头文件(CEnBitmap.h)

/**///
///
/// @file EnBitmap.h
///
/// @brief Inherite from CBitmap, expandingsome draw features
///
/// @author XuYu
/// @version 1.0
/// @date 2007-07-26
///
//

#if!defined(AFX_ENBITMAP_H__76F269F6_895A_48EC_BA09_7D3BEF0438E7__INCLUDED_)
#define AFX_ENBITMAP_H__76F269F6_895A_48EC_BA09_7D3BEF0438E7__INCLUDED_

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

classCEnBitmap : public CBitmap
...{
public:
CEnBitmap();
virtual ~CEnBitmap();

BOOL LoadImage(UINT uIDRes, LPCTSTR szResourceType, HMODULE hInst = NULL,COLORREF crBack = 0);
BOOL LoadImage(LPCTSTR lpctImagePath, COLORREF crBack=0);
BOOL LoadImage(HBITMAP hBitmap);
BOOL LoadImage2(UINT uIDRes, LPCTSTR lpctResType, HMODULEhInst=NULL, COLORREF crBack=0);
int GetWidth()
...{
BITMAP bm;
memset( &bm, 0, sizeof(bm) );
GetBitmap(&bm);
return bm.bmWidth;
}
int GetHeight()
...{
BITMAP bm;
memset( &bm, 0, sizeof(bm) );
GetBitmap(&bm);
return bm.bmHeight;
}
CRect GetRect()
...{
return CRect(0, 0, GetWidth(),GetHeight());
}
BOOL ExtendDraw(CDC *pDC,CRect rc, int nX, int nY);
BOOL ExtendDrawImage(CEnBitmap &bmp, CRect rc, int nX,int nY);
BOOL StretchDraw(CDC *pDC, LPRECT r, LPRECT sr);
BOOL StretchDraw(CDC *pDC, LPRECT r);
BOOL Draw(CDC *pDC, LPRECT r);

private:
static int GetImageType(LPCTSTR lpctImagePath);
static HBITMAP ExtractBitmap(IPicture* pPicture, COLORREFcrBack = 0);
static HBITMAP LoadImageFile(LPCTSTR lpctImagePath, COLORREFcrBack = 0);
static HBITMAP LoadImageResource(UINT uIDRes, LPCTSTRszResourceType, HMODULE hInst = NULL, COLORREF crBack = 0);

static BOOL GetResource(LPCTSTR lpctName, LPCTSTR lpctType, HMODULE hInst,void* pRes, int& nBufSize);
static IPicture* LoadFromBuffer(BYTE* pBuff, int nSize);
BOOL AttachEx(IPicture* pPicture, COLORREF crBack);
};

#endif// !defined(AFX_ENBITMAP_H__76F269F6_895A_48EC_BA09_7D3BEF0438E7__INCLUDED_)
二、源文件(CEnBitmap.cpp)

#include"stdafx.h"
#include "EnBitmap.h"

/**Definition of picture type
enum
...{
FT_BMP,
FT_JPG,
FT_UNKNOWN
};

CEnBitmap::CEnBitmap()
...{

}

CEnBitmap::~CEnBitmap()
...{
}

/**///
///
/// @brief Get picture type, only supportJPG and BMP
///
/// @param [in] lpctImagePath The picturefull path
///
//
int CEnBitmap::GetImageType(LPCTSTR lpctImagePath)
...{
size_t nLen = _tcslen(lpctImagePath);
TCHAR* lptImagePath = new TCHAR[nLen+1];
memset(lptImagePath,0,nLen+1);
_tcscpy(lptImagePath, lpctImagePath);

CharUpper(lptImagePath);

TCHAR lptExtension[5] = ...{0};
size_t i=nLen-4;
size_t j=0;
for(; i<nLen; i++,j++)
lptExtension[j] = lptImagePath[i];

delete[] lptImagePath;

if(_tcscmp(lptExtension,_T(".BMP")) == 0)
return FT_BMP;
if(_tcscmp(lptExtension,_T(".JPG")) == 0)
return FT_JPG;

return FT_UNKNOWN;
}
/**///
///
/// @brief Extract a picture use IPictureinterface
///
/// @param [in] pPicture IPicture interfacepointer
/// @param [in]crBack Mask color to make transparent
///
//
HBITMAP CEnBitmap::ExtractBitmap(IPicture* pPicture, COLORREF crBack /**//* = 0*/)
...{
ATLASSERT(pPicture);

if (!pPicture)
return NULL;

CBitmap bmMem;
CDC dcMem;
HWND hwndDesktopWnd = ::GetDesktopWindow();
HDC hDesktopDC = ::GetDC(hwndDesktopWnd);
CDC* pDC = new CDC(hDesktopDC);

if (dcMem.CreateCompatibleDC(pDC->m_hDC))
...{
long hmWidth;
long hmHeight;

pPicture->get_Width(&hmWidth);
pPicture->get_Height(&hmHeight);

int nWidth =MulDiv(hmWidth, pDC->GetDeviceCaps(LOGPIXELSX), HIMETRIC_INCH);
int nHeight =MulDiv(hmHeight, pDC->GetDeviceCaps(LOGPIXELSY), HIMETRIC_INCH);

if(bmMem.CreateCompatibleBitmap(pDC->m_hDC, nWidth, nHeight))
...{
HBITMAPhOldBM = dcMem.SelectBitmap(bmMem.m_hBitmap);

if (crBack!= -1)
dcMem.FillSolidRect(0, 0, nWidth, nHeight, crBack);

HRESULT hr =pPicture->Render(dcMem, 0, 0, nWidth, nHeight, 0, hmHeight, hmWidth,-hmHeight, NULL);
dcMem.SelectBitmap(hOldBM);
}
}

::ReleaseDC(hwndDesktopWnd, pDC->m_hDC);
if(dcMem.m_hDC) ::DeleteDC(dcMem.Detach());
::DeleteObject(hDesktopDC);
if(pDC->m_hDC) ::DeleteDC(pDC->Detach());

return (HBITMAP)bmMem.Detach();
}

/**///
///
/// @brief Load a picture from full path
///
/// @param [in] lpctImagePath The full pathof picture
/// @param [in]crBack Mask color to make transparent
///
//
HBITMAP CEnBitmap::LoadImageFile(LPCTSTR lpctImagePath, COLORREF crBack /**//*= 0 */)
...{
int nImgType = GetImageType(lpctImagePath);
USES_CONVERSION;

switch(nImgType)
...{
case FT_BMP:
return (HBITMAP)::LoadImage(NULL,lpctImagePath, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
case FT_UNKNOWN:
return NULL;
default:
...{
IPicture*pPic = NULL;
HBITMAP hbm= NULL;
HRESULT hr =OleLoadPicturePath(T2OLE((LPTSTR)lpctImagePath), NULL, 0, crBack, IID_IPicture,(LPVOID *)&pPic);
if(pPic)
...{
hbm = ExtractBitmap(pPic, crBack);
pPic->Release();
}

return hbm;
}
}

return NULL;
}
/**///
///
/// @brief Load a picture from resource ID
///
/// @param [in] uIDRes Resource ID
/// @param [in] szResourceType Resourcetype
/// @param [in] hInst Instance includingresources
/// @param [in]crBack Mask color to make transparent
///
/// @note This method need to valid
///
//
HBITMAP CEnBitmap::LoadImageResource(UINT uIDRes, LPCTSTR szResourceType,HMODULE hInst /**//* = NULL */, COLORREF crBack /**//* = 0 */)
...{
BYTE* pBuff = NULL;
int nSize = 0;
HBITMAP hbm = NULL;

// first call is to get buffer size
if (GetResource(MAKEINTRESOURCE(uIDRes), szResourceType,hInst, 0, nSize))
...{
if (nSize > 0)
...{
pBuff = newBYTE[nSize];

// thisloads it
if(GetResource(MAKEINTRESOURCE(uIDRes), szResourceType, hInst, pBuff, nSize))
...{
IPicture* pPicture = LoadFromBuffer(pBuff, nSize);

if (pPicture)
...{
hbm = ExtractBitmap(pPicture, crBack);
pPicture->Release();
}
}

delete []pBuff;
}
}

return hbm;
}
/**///
///
/// @brief Load a picture from full path ofpicture
///
/// @param [in] lpctImagePath Full path ofpicture
/// @param [in]crBack Mask color to make transparent
///
/// @note This is the entry method
///
//
BOOL CEnBitmap::LoadImage(LPCTSTR lpctImagePath, COLORREF crBack/**//* =0 */)
...{
if(m_hBitmap != NULL)
...{
DeleteObject();
Detach();
}

Attach(LoadImageFile(lpctImagePath, crBack));

return (m_hBitmap == NULL) ? FALSE : TRUE;
}
/**///
///
/// @brief Load a picture from resource IDand type
///
/// @param [in] uIDRes Resource ID
/// @param [in] szResourceType Resourcetype
/// @param [in] hInst Instance includingresources
/// @param [in]crBack Mask color to make transparent
///
/// @note This is the entry method
///
//
BOOL CEnBitmap::LoadImage(UINT uIDRes, LPCTSTR szResourceType, HMODULE hInst/**//* = NULL */, COLORREF crBack /**//* = 0 */)
...{
ATLASSERT(m_hBitmap == NULL);

if (m_hBitmap != NULL)
return TRUE;

Attach(LoadImageResource(uIDRes, szResourceType, hInst,crBack));

return (m_hBitmap == NULL) ? FALSE : TRUE;
}

/**///
///
/// @brief Load a bitmap from handle
///
/// @param [in] hBitmap Handle of a bitmap
///
/// @note This method need to valid
///
//
BOOL CEnBitmap::LoadImage(HBITMAP hBitmap)
...{
if(m_hBitmap != NULL)
...{
DeleteObject();
Detach();
}

Attach(hBitmap);

return (m_hBitmap == NULL) ? FALSE : TRUE;
}

/**///
///
/// @brief Load a picture from resource ID
///
/// @param [in] uIDRes Resource ID
/// @param [in] szResourceType Resourcetype
/// @param [in] hInst Instance includingresources
/// @param [in] crBackMask color to make transparent
///
/// @note This method is just for TESTING, DONT'T CALL thismethod before validation
///
//
BOOL CEnBitmap::LoadImage2(UINT uIDRes, LPCTSTR lpctResType, HMODULEhInst/**//* =NULL */, COLORREF crBack/**//* =0 */)
...{
ATLASSERT(m_hBitmap == NULL);// only attach once, detach on destroy

// if (m_hBitmap != NULL)
// return FALSE;
// if( (m_hBitmap = ::LoadBitmap(_Module.m_hInst,MAKEINTRESOURCE(uIDRes))) != NULL)
// return TRUE;
//
// return FALSE;

BYTE* pBuff = NULL;
int nSize = 0;
BOOL bResult = FALSE;

// first call is to get buffer size
if (GetResource(MAKEINTRESOURCE(uIDRes), lpctResType, hInst,0, nSize))
...{
if (nSize > 0)
...{
pBuff = newBYTE[nSize];

// thisloads it
if(GetResource(MAKEINTRESOURCE(uIDRes), lpctResType, hInst, pBuff, nSize))
...{
IPicture* pPicture = LoadFromBuffer(pBuff, nSize);

if (pPicture)
...{
bResult = AttachEx(pPicture, crBack);
pPicture->Release();
}
}

delete []pBuff;
}
}

return bResult;
}

/**///
///
/// @brief Make a IPicture interfacepointer from buffer stream
///
/// @param [in] pBuff Picture buffer stream
/// @param [in] nSize Size of pBuff
///
/// @note This method need to valid
///
//
IPicture* CEnBitmap::LoadFromBuffer(BYTE* pBuff, int nSize)
...{
bool bResult = false;

HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, nSize);
void* pData = GlobalLock(hGlobal);
memcpy(pData, pBuff, nSize);
GlobalUnlock(hGlobal);

IStream* pStream = NULL;
IPicture* pPicture = NULL;

if (CreateStreamOnHGlobal(hGlobal, TRUE, &pStream) == S_OK)
...{
HRESULT hr =::OleLoadPicture(pStream, nSize, FALSE, IID_IPicture, (LPVOID *)&pPicture);
pStream->Release();
}

::GlobalFree(hGlobal);

return pPicture; // caller releases
}

/**///
///
/// @brief Get resource from a module
///
/// @param [in] lpName Resource name
/// @param [in] lpType Resource type
/// @param [in] hInst Handle of module
/// @param [out] pResource Resource pointer
/// @param [in] nBufSize Size of resource
///
/// @note This method need to valid
///
//
BOOL CEnBitmap::GetResource(LPCTSTR lpName, LPCTSTR lpType, HMODULE hInst,void* pResource, int& nBufSize)
...{
HRSRC hResInfo;
HANDLE hRes;
LPSTRlpRes = NULL;
intnLen = 0;
bool bResult= FALSE;

// Find the resource
hResInfo = FindResource(hInst, lpName, lpType);

if (hResInfo == NULL)
return false;

// Load the resource
hRes = LoadResource(hInst, hResInfo);

if (hRes == NULL)
return false;

// Lock the resource
lpRes = (char*)LockResource(hRes);

if (lpRes != NULL)
...{
if (pResource == NULL)
...{
nBufSize =SizeofResource(hInst, hResInfo);
bResult =true;
}
else
...{
if (nBufSize>= (int)SizeofResource(hInst, hResInfo))
...{
memcpy(pResource, lpRes, nBufSize);
bResult = true;
}
}

UnlockResource(hRes);
}

// Free the resource
FreeResource(hRes);

return bResult;
}

/**///
///
/// @brief Attach a picture from IPictureinterface pointer
///
/// @param [in] pPicture IPicture interfacepointer
/// @param [in] crBack Mask color formaking transparent
///
/// @note This method is called byLoadImage2, a TESTING function,
/// so DON'TUSE THIS METHOD before validation
///
//
BOOL CEnBitmap::AttachEx(IPicture* pPicture, COLORREF crBack)
...{
ATLASSERT(m_hBitmap == NULL);// only attach once, detach on destroy

if (m_hBitmap != NULL)
return FALSE;

ATLASSERT(pPicture);

if (!pPicture)
return FALSE;

BOOL bResult = FALSE;

CDC dcMem;
CWindowDC dc(GetDesktopWindow());
CDC* pDC = &dc;

if (dcMem.CreateCompatibleDC(pDC->m_hDC))
...{
long hmWidth;
long hmHeight;

pPicture->get_Width(&hmWidth);
pPicture->get_Height(&hmHeight);

int nWidth =MulDiv(hmWidth, pDC->GetDeviceCaps(LOGPIXELSX),HIMETRIC_INCH);
int nHeight =MulDiv(hmHeight, pDC->GetDeviceCaps(LOGPIXELSY),HIMETRIC_INCH);

CBitmap bmMem;

if (bmMem.CreateCompatibleBitmap(pDC->m_hDC, nWidth, nHeight))
...{
HBITMAPhOldBM = dcMem.SelectBitmap(bmMem.m_hBitmap);

if (crBack != -1)
dcMem.FillSolidRect(0, 0, nWidth, nHeight, crBack);

HRESULT hr =pPicture->Render(dcMem, 0, 0, nWidth, nHeight, 0, hmHeight, hmWidth,-hmHeight, NULL);
dcMem.SelectBitmap(hOldBM);

if (hr == S_OK)
/**//*bResult = */CBitmap::Attach(bmMem.Detach());
}

if(bmMem.m_hBitmap) bmMem.DeleteObject();
}

ReleaseDC(GetDesktopWindow(),pDC->m_hDC);
if(dcMem.m_hDC) ::DeleteDC(dcMem.Detach());

return bResult;
}

BOOLCEnBitmap::ExtendDraw(CDC *pDC, CRect rc, int nX, int nY)
...{
CEnBitmap bmp;
if (ExtendDrawImage(bmp,rc,nX,nY))
...{
bmp.Draw(pDC, &rc);
return TRUE;
}
return FALSE;
}

BOOLCEnBitmap::ExtendDrawImage(CEnBitmap &bmp, CRect rc, int nX, int nY)
...{
HBITMAP hOldBmp;
CDC memDC;
CClientDC cdc(0);

memDC.CreateCompatibleDC(cdc.m_hDC);
bmp.CreateCompatibleBitmap(cdc.m_hDC, rc.Width() ,rc.Height() );
hOldBmp = memDC.SelectBitmap( bmp.m_hBitmap );

if (nX==0 && nY==0)
...{
StretchDraw(&memDC, &rc,GetRect());
return TRUE;
}

CDC dc;
dc.CreateCompatibleDC(memDC.m_hDC);
HBITMAP hBmp = dc.SelectBitmap( m_hBitmap );
//dc.SetStretchBltMode(COLORONCOLOR);
if (nX!=0 && nY==0)
...{

//左上角
memDC.BitBlt( 0, 0, nX, rc.Height(),dc.m_hDC, 0, 0, SRCCOPY );
memDC.StretchBlt(nX, 0,rc.Width()-GetWidth(), rc.Height(), dc.m_hDC,nX, 0, 1, GetHeight(), SRCCOPY );
//右上角
memDC.BitBlt(rc.right-(GetWidth()-nX), 0, GetWidth()-nX, rc.Height(),dc.m_hDC,nX, 0, SRCCOPY );

}
else if (nX==0 && nY!=0)
...{
//上角
memDC.BitBlt( 0, 0, rc.Width(), nY,dc.m_hDC, 0, 0, SRCCOPY );
memDC.StretchBlt(0, nY, GetWidth(),rc.Height()-GetHeight(), dc.m_hDC,0, nY, GetWidth(), 1, SRCCOPY );
//右上角
memDC.BitBlt(0,rc.bottom-(GetHeight()-nY), GetWidth(), GetHeight()-nY, dc.m_hDC,0, nY, SRCCOPY);

}
else
...{
//左上角
memDC.StretchBlt( 0, 0, nX, nY,dc.m_hDC, 0, 0, nX, nY, SRCCOPY );
//上中
memDC.StretchBlt(nX, 0,rc.Width()-GetWidth(),nY, dc.m_hDC, nX, 0, 1, nY , SRCCOPY );
//右上角
memDC.StretchBlt(rc.Width()-(GetWidth()-nX), 0, GetWidth()-nX, nY ,dc.m_hDC,nX, 0, GetWidth()-nX, nY, SRCCOPY );
//左中
memDC.StretchBlt(0, nY,nX,rc.Height()-GetHeight(), dc.m_hDC, 0, nY, nX, 1, SRCCOPY );
//正中
memDC.StretchBlt(nX, nY,rc.Width()-GetWidth(),rc.Height()-GetHeight(), dc.m_hDC, nX, nY, 1, 1, SRCCOPY);
//右中
memDC.StretchBlt(rc.Width()-(GetWidth()-nX), nY,GetWidth()-nX,rc.Height()-GetHeight(), dc.m_hDC, nX, nY, GetWidth()-nX, 1,SRCCOPY );
//左下角
memDC.StretchBlt( 0,rc.Height()-(GetHeight()-nY), nX, GetHeight()-nY, dc.m_hDC, 0, nY, nX,GetHeight()-nY, SRCCOPY );
//下中
memDC.StretchBlt(nX,rc.Height()-(GetHeight()-nY), rc.Width()-GetWidth(),GetHeight()-nY, dc.m_hDC,nX, nY, 1, GetHeight()-nY, SRCCOPY );
//右下角
memDC.StretchBlt(rc.Width()-(GetWidth()-nX), rc.Height()-(GetHeight()-nY), GetWidth()-nX,GetHeight()-nY, dc.m_hDC, nX, nY, GetWidth()-nX, GetHeight()-nY, SRCCOPY );
}
dc.SelectBitmap( hBmp );
memDC.SelectBitmap(hOldBmp);

memDC.Detach();
//cdc.Detach();
dc.Detach();

return TRUE;
}

BOOLCEnBitmap::StretchDraw(CDC *pDC, LPRECT r, LPRECT sr )
...{
if ( !r )
return FALSE;
CDC dc;
dc.CreateCompatibleDC( pDC->m_hDC );
HBITMAP hOldBitmap = dc.SelectBitmap( m_hBitmap );
pDC->SetStretchBltMode(COLORONCOLOR);

if ( !sr )
pDC->StretchBlt( r->left,r->top, r->right, r->bottom, dc.m_hDC, 0, 0, GetWidth(), GetHeight(),SRCCOPY );
else
pDC->StretchBlt( r->left,r->top,
r->right - r->left,
r->bottom - r->top,
dc.m_hDC,
sr->left,
sr->top,
sr->right - sr->left,
sr->bottom - sr->top,
SRCCOPY );

dc.SelectBitmap( hOldBitmap );
if(dc.m_hDC) ::DeleteDC(dc.Detach());
::DeleteObject(hOldBitmap);
hOldBitmap = NULL;

return TRUE;
}

BOOLCEnBitmap::StretchDraw(CDC *pDC, LPRECT r)
...{
CDC dc;
dc.CreateCompatibleDC( pDC->m_hDC );
HBITMAP bmp = dc.SelectBitmap( m_hBitmap );

pDC->StretchBlt( r->left, r->top, r->right,r->bottom, dc.m_hDC, 0, 0, GetWidth(), GetHeight(), SRCCOPY );

dc.SelectBitmap( bmp );
if(dc.m_hDC) ::DeleteDC(dc.Detach());
::DeleteObject(bmp);
bmp = NULL;

return TRUE;
}

BOOLCEnBitmap::Draw(CDC *pDC, LPRECT r)
...{
CDC dc;
dc.CreateCompatibleDC( pDC->m_hDC );
HBITMAP bmp = dc.SelectBitmap( m_hBitmap);
pDC->BitBlt( r->left, r->top, r->right -r->left, r->bottom - r->top, dc.m_hDC, 0, 0, SRCCOPY );

dc.SelectBitmap( bmp );
::DeleteDC(dc.Detach());

return TRUE;
}

一个图片加载与绘制类(使用GDI输出图片)【补充】

《一个图片加载与绘制类(使用GDI输出图片)》中我公布了基本的图片加载和绘制类,我们可以再根据这个类派生一些我们需要的新的绘制类,来针对某些特殊情况的绘制和使用,下面我再公布一个这样的类,作为样例。其中部分代码来源于互联网。

一、头文件(CImageLoader.h)

#include"EnBitmap.h"

classCImageLoader:publicCEnBitmap
{
public:
BOOLDrawImage(CEnBitmap&bmp,intnX,intnY,intnCol,intnRow);
CImageLoader();
virtual~CImageLoader();

BOOLDraw(CDC*pDC,LPRECTr);
//drawsubbmptospecialpoint
BOOLDraw(CDC*pDC,intx,inty,LPRECTsr);
BOOLDraw(CDC*pDC,intx,inty,LPRECTsr,COLORREFcolTrans,BOOLbTrans);
BOOLDrawByHeight(CDC*pDC,intx,inty,intsx,intsy,LPRECTsr,intnHeight);

intWidth(){returnGetWidth();}
intHeight(){returnGetHeight();}

voidAttach(HBITMAPhbmp){CBitmap::Attach(hbmp);}

BOOLLoadBitmap(UINTnResName,HMODULEhInst=NULL,COLORREFcrBack=0)
{
if(m_hBitmap)DeleteObject();
returnLoadImage(nResName,RT_BITMAP,hInst,crBack);
}
BOOLLoadBitmap(LPCTSTRlpctImagePath,COLORREFcrBack=0)
{
if(m_hBitmap)DeleteObject();
returnLoadImage(lpctImagePath,crBack);
}

BOOLDrawTransparent(CDC*pDC,intx,inty,COLORREFcrColour);
HRGNCreateRgnFromFile(COLORREFcolor);

BOOLDrawImageByIndex(CDC*pDC,CRectrect,intnIndex,COLORREFclrTrans,BOOLbTrans);
};


二、源文件(CImageLoader.cpp)

#include"stdafx.h"
#include"GnetImageLoader.h"

//
//Construction/Destruction
//

CImageLoader::CImageLoader()
{

}

CImageLoader::~CImageLoader()
{

}

BOOLCImageLoader::Draw(CDC*pDC,intx,inty,LPRECTsr,COLORREFcolTrans,BOOLbTrans)
{
if(!bTrans)
Draw(pDC,x,y,sr);
else
{
MyTransparentBlt(pDC->m_hDC,x,y,sr->right-sr->left,sr->bottom-sr->top,
m_hBitmap,sr->left,sr->top,colTrans,NULL);
}
returnTRUE;
}

//drawsubbmptospecialpoint
BOOLCImageLoader::Draw(CDC*pDC,intx,inty,LPRECTsr)
{
CDCdc;
dc.CreateCompatibleDC(pDC->m_hDC);
HBITMAPbmp=dc.SelectBitmap(m_hBitmap);
if(sr!=NULL)
pDC->BitBlt(x,y,sr->right-sr->left,sr->bottom-sr->top,dc.m_hDC,
sr->left,sr->top,SRCCOPY);
else
pDC->BitBlt(x,y,Width(),Height(),dc.m_hDC,
0,0,SRCCOPY);
dc.SelectBitmap(bmp);

if(dc.m_hDC)::DeleteDC(dc.Detach());

returnTRUE;
}

BOOLCImageLoader::DrawByHeight(CDC*pDC,intx,inty,intsx,intsy,LPRECTsr,intnHeight)
{
CDCdc;
dc.CreateCompatibleDC(pDC->m_hDC);
HBITMAPhBitmap=dc.SelectBitmap(m_hBitmap);

if(sr==NULL)
pDC->BitBlt(x,y,Width(),Height(),dc.m_hDC,0,0,SRCCOPY);
else
{
intnSrcWidth=sr->right-sr->left;
intnSrcHeight=sr->bottom-sr->top;

if(nSrcHeight==nHeight)
pDC->BitBlt(x,y,nSrcWidth,nSrcHeight,dc.m_hDC,sr->left,sr->top,SRCCOPY);
else
{
//高度不同,需要拉伸绘制(根据指定的高度值:nHeight
pDC->StretchBlt(x,y,sr->right-sr->left,nHeight,dc.m_hDC,sx,sy,nSrcWidth,nSrcHeight,SRCCOPY);
}
}

dc.SelectBitmap(hBitmap);
::DeleteDC(dc.Detach());

returnTRUE;
}

BOOLCImageLoader::Draw(CDC*pDC,LPRECTr)
{
CDCdc;
dc.CreateCompatibleDC(pDC->m_hDC);
HBITMAPbmp=dc.SelectBitmap(m_hBitmap);
pDC->BitBlt(r->left,r->top,r->right-r->left,r->bottom-r->top,dc.m_hDC,0,0,
SRCCOPY);

dc.SelectBitmap(bmp);

if(dc.m_hDC)::DeleteDC(dc.Detach());

returnTRUE;
}


///HOWTO:DrawingTransparentBitmaps
//see:MicrosoftKnowledgeBaseArticle-Q79212
BOOLCImageLoader::DrawTransparent(CDC*pDC,intx,inty,COLORREFcrColour)
{
MyTransparentBlt(pDC->m_hDC,x,y,GetWidth(),GetHeight(),m_hBitmap,0,0,crColour,NULL);

returnTRUE;
}


HRGNCImageLoader::CreateRgnFromFile(COLORREFcolor)
{
HBITMAPhBmp=m_hBitmap;

//getimageproperties
BITMAPbmp={0};
::GetObject(hBmp,sizeof(BITMAP),&bmp);
//allocatememoryforextendedimageinformation
LPBITMAPINFObi=(LPBITMAPINFO)newBYTE[sizeof(BITMAPINFO)+8];
memset(bi,0,sizeof(BITMAPINFO)+8);
bi->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
//setwindowsize
intm_dwWidth=bmp.bmWidth;//bitmapwidth
intm_dwHeight=bmp.bmHeight;//bitmapheight
//createtemporarydc
HDCdc=CreateIC("DISPLAY",NULL,NULL,NULL);
//getextendedinformationaboutimage(length,compression,lengthofcolortableifexist,...)
DWORDres=::GetDIBits(dc,hBmp,0,bmp.bmHeight,0,bi,DIB_RGB_COLORS);
//allocatememoryforimagedata(colors)
LPBYTEpBits=newBYTE[bi->bmiHeader.biSizeImage+4];
//allocatememoryforcolortable
if(bi->bmiHeader.biBitCount==8)
{
//actuallycolortableshouldbeappendedtothisheader(BITMAPINFO),
//sowehavetoreallocateandcopyit
LPBITMAPINFOold_bi=bi;
//255-becausethereisoneinBITMAPINFOHEADER
bi=(LPBITMAPINFO)newchar[sizeof(BITMAPINFO)+255*sizeof(RGBQUAD)];
memcpy(bi,old_bi,sizeof(BITMAPINFO));
//releaseoldheader
deleteold_bi;
}
//getbitmapinfoheader
BITMAPINFOHEADER&bih=bi->bmiHeader;
//getcolortable(for256colormodecontains256entriesofRGBQUAD(=DWORD))
LPDWORDclr_tbl=(LPDWORD)&bi->bmiColors;
//fillbitsbuffer
res=::GetDIBits(dc,hBmp,0,bih.biHeight,pBits,bi,DIB_RGB_COLORS);
DeleteDC(dc);

BITMAPbm;
::GetObject(hBmp,sizeof(BITMAP),&bm);
//shiftbitsandbyteperpixel(forcomparingcolors)
LPBYTEpClr=(LPBYTE)&color;
//swapredandbluecomponents
BYTEtmp=pClr[0];pClr[0]=pClr[2];pClr[2]=tmp;
//convertcolorifcurentDCis16-bit(5:6:5)or15-bit(5:5:5)
if(bih.biBitCount==16)
{
//for16bit
color=((DWORD)(pClr[0]&0xf8)>>3)|
((DWORD)(pClr[1]&0xfc)<<3)|
((DWORD)(pClr[2]&0xf8)<<8);
//for15bit
//color=((DWORD)(pClr[0]&0xf8)>>3)|
//((DWORD)(pClr[1]&0xf8)<<2)|
//((DWORD)(pClr[2]&0xf8)<<7);
}

constDWORDRGNDATAHEADER_SIZE=sizeof(RGNDATAHEADER);
constDWORDADD_RECTS_COUNT=40;//numberofrectstobeappended
//toregiondatabuffer

//BitPerPixel
BYTEBpp=bih.biBitCount>>3;//bytesperpixel
//bytesperlineinpBitsisDWORDalignedandbmp.bmWidthBytesisWORDaligned
//so,bothofthemnot
DWORDm_dwAlignedWidthBytes=(bmp.bmWidthBytes&~0x3)+(!!(bmp.bmWidthBytes&0x3)<<2);
//DIBimageisflippedthat'swhywescanitfromthelastline
LPBYTEpColor=pBits+(bih.biHeight-1)*m_dwAlignedWidthBytes;
DWORDdwLineBackLen=m_dwAlignedWidthBytes+bih.biWidth*Bpp;//offsetofpreviousscanline
//(afterprocessingofcurrent)
DWORDdwRectsCount=bih.biHeight;//numberofrectsinallocatedbuffer
INTi,j;//currentpositioninmaskimage
INTfirst=0;//leftpositionofcurrentscanline
//wheremaskwasfound
boolwasfirst=false;//setwhenmaskhasbeenfoundincurrentscanline
boolismask;//setwhencurrentcolorismaskcolor

//allocatememoryforregiondata
//regiondatahereissetofregionsthatarerectangleswithheight1pixel(scanline)
//that'swhyfirstallocationis<bm.biHeight>RECTs-numberofscanlinesinimage
RGNDATAHEADER*pRgnData=
(RGNDATAHEADER*)newBYTE[RGNDATAHEADER_SIZE+dwRectsCount*sizeof(RECT)];
//getpointertoRECTtable
LPRECTpRects=(LPRECT)((LPBYTE)pRgnData+RGNDATAHEADER_SIZE);
//zeroregiondataheadermemory(headerpartonly)
memset(pRgnData,0,RGNDATAHEADER_SIZE+dwRectsCount*sizeof(RECT));
//fillitbydefault
pRgnData->dwSize=RGNDATAHEADER_SIZE;
pRgnData->iType=RDH_RECTANGLES;

for(i=0;i<bih.biHeight;i++)
{
for(j=0;j<bih.biWidth;j++)
{
//getcolor
switch(bih.biBitCount)
{
case8:
ismask=(clr_tbl[*pColor]!=color);
break;
case16:
ismask=(*(LPWORD)pColor!=(WORD)color);
break;
case24:
ismask=((*(LPDWORD)pColor&0x00ffffff)!=color);
break;
case32:
ismask=(*(LPDWORD)pColor!=color);
}
//shiftpointertonextcolor
pColor+=Bpp;
//placepartofscanlineasRECTregioniftransparentcolorfoundaftermaskcoloror
//maskcolorfoundattheendofmaskimage
if(wasfirst)
{
if(!ismask)
{
//savecurrentRECT
pRects[pRgnData->nCount++]=CRect(first,i,j,i+1);
//ifbufferfullreallocateitwithmoreroom
if(pRgnData->nCount>=dwRectsCount)
{
dwRectsCount+=ADD_RECTS_COUNT;
//allocatenewbuffer
LPBYTEpRgnDataNew=newBYTE[RGNDATAHEADER_SIZE+dwRectsCount*sizeof(RECT)];
//copycurrentregiondatatoit
memcpy(pRgnDataNew,pRgnData,RGNDATAHEADER_SIZE+pRgnData->nCount*sizeof(RECT));
//delteoldregiondatabuffer
deletepRgnData;
//setpointertonewregiondatabuffertocurrent
pRgnData=(RGNDATAHEADER*)pRgnDataNew;
//correctpointertoRECTtable
pRects=(LPRECT)((LPBYTE)pRgnData+RGNDATAHEADER_SIZE);
}
wasfirst=false;
}
}
elseif(ismask)//setwasfirstwhenmaskisfound
{
first=j;
wasfirst=true;
}
}

if(wasfirst&&ismask)
{
//savecurrentRECT
pRects[pRgnData->nCount++]=CRect(first,i,j,i+1);
//ifbufferfullreallocateitwithmoreroom
if(pRgnData->nCount>=dwRectsCount)
{
dwRectsCount+=ADD_RECTS_COUNT;
//allocatenewbuffer
LPBYTEpRgnDataNew=newBYTE[RGNDATAHEADER_SIZE+dwRectsCount*sizeof(RECT)];
//copycurrentregiondatatoit
memcpy(pRgnDataNew,pRgnData,RGNDATAHEADER_SIZE+pRgnData->nCount*sizeof(RECT));
//delteoldregiondatabuffer
deletepRgnData;
//setpointertonewregiondatabuffertocurrent
pRgnData=(RGNDATAHEADER*)pRgnDataNew;
//correctpointertoRECTtable
pRects=(LPRECT)((LPBYTE)pRgnData+RGNDATAHEADER_SIZE);
}
wasfirst=false;
}

pColor-=dwLineBackLen;
}
//releaseimagedata
deletepBits;
deletebi;

//createregion
HRGNhRgn=ExtCreateRegion(NULL,RGNDATAHEADER_SIZE+pRgnData->nCount*sizeof(RECT),(LPRGNDATA)pRgnData);
//releaseregiondata
deletepRgnData;

returnhRgn;
}

BOOLCImageLoader::DrawImage(CEnBitmap&bmp,intnX,intnY,intnCol,intnRow)
{
nX-=1;
nY-=1;
intw=GetWidth()/nCol;
inth=GetHeight()/nRow;

HBITMAPhOldBmp;
CDCmemDC;
CClientDCdc(0);

memDC.CreateCompatibleDC(dc.m_hDC);
bmp.CreateCompatibleBitmap(dc.m_hDC,w,h);

hOldBmp=memDC.SelectBitmap(bmp.m_hBitmap);
StretchDraw(&memDC,CRect(0,0,w,h),
CRect(GetWidth()*nX/nCol,GetHeight()*nY/nRow,GetWidth()*nX/nCol+w,GetHeight()*nY/nRow+h));
memDC.SelectBitmap(hOldBmp);

//dc.DeleteDC();
memDC.DeleteDC();
::DeleteObject(hOldBmp);
hOldBmp=NULL;

returnTRUE;
}

//
///
///@param[in]pDCDevicecontext
///@param[in]x
///@param[in]y
///@param[in]rect
///@param[in]stateButtonstate
///-0:Normal
///-1:Pressed
///-2:Hover
///-3:Disabled
///@param[in]clrTrans
///@param[in]bTrans
///
//
BOOLCImageLoader::DrawImageByIndex(CDC*pDC,CRectrect,intnIndex,COLORREFclrTrans,BOOLbTrans)
{
if(m_hBitmap==NULL)returnFALSE;

Draw(pDC,rect.left,rect.top,CRect(nIndex*rect.Width(),0,(nIndex+1)*rect.Width(),GetHeight()),clrTrans,bTrans);

returnTRUE;
}

Win32程序中简单应用Mfc

今日写程序在win32中用CRect发现报错,突然想起来。要引入mfc库。想重新建立一个工程添加对mfc的支持。发现选项不能选。查资料后发现。

在win32程序中简单应用mfc库,只需要简单的引入<afx.h>就好了。注意这个时候如果出来

#ifdef _DLL
#ifndef _AFXDLL
#error Building MFC application with /MD[d](CRT dll version) requires MFC shared dll version. Please #define _AFXDLL or donot use /MD[d]
#endif
#endif

这个错误。只需要把use of mfc改为Use MFC in a Shared DLL就可以了。

当然<windows.h>头文件在mfc中已经包含所以去掉。

分类:vc++

找了半天的问题

哎。今天找了半天的bug.图片没有显示出来。原来是在updateosd的时候忘了把他选进来了。
m_hBmpOsdOld = (HBITMAP)SelectObject(m_hDCOsd, m_hBmpOsd);

郁闷。下次一定记住了。

WMI 事件通知

下面的示例代码展示了 COM 初始化,连接到本机的 WMI ,接收事件,然后清理的过程. 当有新进程创建时,用户会得到通过.事件是异步接收。

步骤 1-5 是初始设置和连接到WMI , 步骤6 是接收事件.

过程:

1.调用 CoInitializeEx 初始化 COM.

2.调用 CoInitializeSecurity 初始化 COM 进程安全。
window 2000 需要在CoInitializeSecurity 的 pAuthList 参数中指定SOLE_AUTHENTICATION_LIST 设置默认的安全认证.

3.通过调用 CoCreateInstance 来获得WMI定位器(locator) .


4. 通过调用IWbemLocator::ConnectServer 并指定参数strNetworkResource 为"root\cimv2", 这样我们可以获得指向 "IWbemServices"的指针.

5.调用 CoSetProxyBlanket 设置 IWbemServices 的代理安全.这样 WMI 服务能够模拟客户端角色.

6.使用IWbemServices::ExecNotificationQueryAsync 方法 来接收异步事件. 要接收异步事件,我们必须实现 IWbemObjectSink. 下面的代码在 EventSink 类里提供了实现. 在任何时候接收到事件时,IWbemServices::ExecNotificationQueryAsync 都会调用 EventSink::Indicate . 在该代码示例中,当有进程被创建时EventSink::Indicate 就会被调用.
运行下面的代码,打开 notepad.exe,通知事件就会被触发.


#include "eventsink.h"

intmain(int iArgCnt, char ** argv)
{
HRESULT hres;

// Step 1: --------------------------------------------------
// Initialize COM.------------------------------------------

hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
cout << "Failed toinitialize COM library. Error code = 0x"
<< hex << hres << endl;
return1;// Program has failed.
}

// Step 2: --------------------------------------------------
// Set general COM security levels--------------------------
// Note: If you are using Windows 2000, you need to specify-
// the default authentication credentials for a user byusing
// a SOLE_AUTHENTICATION_LIST structure in the pAuthList----
// parameter of CoInitializeSecurity------------------------

hres = CoInitializeSecurity(
NULL,
-1,// COM negotiates service
NULL,// Authentication services
NULL,// Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, //Default Impersonation
NULL,// Authentication info
EOAC_NONE,// Additional capabilities
NULL// Reserved
);


if (FAILED(hres))
{
cout << "Failed toinitialize security. Error code = 0x"
<< hex << hres << endl;
CoUninitialize();
return1;// Program has failed.
}

// Step 3: ---------------------------------------------------
// Obtain the initial locator to WMI-------------------------

IWbemLocator *pLoc = NULL;

hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *)&pLoc);

if (FAILED(hres))
{
cout << "Failed to createIWbemLocator object. "
<< "Err code = 0x"
<< hex << hres << endl;
CoUninitialize();
return1;// Program has failed.
}

// Step 4: ---------------------------------------------------
// Connect to WMI through the IWbemLocator::ConnectServermethod

IWbemServices *pSvc = NULL;

// Connect to the local root\cimv2 namespace
// and obtain pointer pSvc to make IWbemServices calls.
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"),
NULL,
NULL,
0,
NULL,
0,
0,
&pSvc
);

if (FAILED(hres))
{
cout << "Could notconnect. Error code = 0x"
<< hex << hres << endl;
pLoc->Release();
CoUninitialize();
return1;// Program has failed.
}

cout << "Connected to ROOT\\CIMV2 WMI namespace" << endl;


// Step 5:--------------------------------------------------
// Set security levels on the proxy-------------------------

hres = CoSetProxyBlanket(
pSvc,// Indicates the proxy to set
RPC_C_AUTHN_WINNT,// RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE,// RPC_C_AUTHZ_xxx
NULL,// Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, //RPC_C_IMP_LEVEL_xxx
NULL,// client identity
EOAC_NONE// proxy capabilities
);

if (FAILED(hres))
{
cout << "Could not setproxy blanket. Error code = 0x"
<<hex << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return1;// Program has failed.
}

// Step 6: -------------------------------------------------
// Receive event notifications -----------------------------

// Use an unsecured apartment for security
IUnsecuredApartment* pUnsecApp = NULL; // 这些指针的定义可以用 CComQIPtr或 CComPtr 自动管理对象的生存周期

hres = CoCreateInstance(CLSID_UnsecuredApartment, NULL,
CLSCTX_LOCAL_SERVER,IID_IUnsecuredApartment,
(void**)&pUnsecApp);

EventSink* pSink = new EventSink;
pSink->AddRef();

IUnknown* pStubUnk = NULL;
pUnsecApp->CreateObjectStub(pSink, &pStubUnk);

IWbemObjectSink* pStubSink = NULL;
pStubUnk->QueryInterface(IID_IWbemObjectSink,
(void **) &pStubSink);

// The ExecNotificationQueryAsync method will call
// The EventQuery::Indicate method when an event occurs
hres = pSvc->ExecNotificationQueryAsync(
_bstr_t("WQL"),
_bstr_t("SELECT * "
"FROM__InstanceCreationEvent WITHIN 1 "
"WHERETargetInstance ISA 'Win32_Process'"),
WBEM_FLAG_SEND_STATUS,
NULL,
pStubSink);

// Check for errors.
if (FAILED(hres))
{
printf("ExecNotificationQueryAsync failed "
"with =0x%X\n", hres);
pSvc->Release();
pLoc->Release();
pUnsecApp->Release();
pStubUnk->Release();
pSink->Release();
pStubSink->Release();
CoUninitialize();
return 1;
}

// Wait for the event
Sleep(10000);

hres = pSvc->CancelAsyncCall(pStubSink);

// Cleanup
// ========

pSvc->Release();
pLoc->Release();
pUnsecApp->Release();
pStubUnk->Release();
pSink->Release();
pStubSink->Release();
CoUninitialize();

return 0; // Program successfully completed.

}


//---------------------------------- EventSink.h--------------------------------------------------//

#ifndefEVENTSINK_H
#define EVENTSINK_H

#define_WIN32_DCOM
#include <iostream>
using namespace std;
#include <comdef.h>
#include <Wbemidl.h>

#pragma comment(lib, "wbemuuid.lib")

classEventSink : public IWbemObjectSink
{
LONG m_lRef;
bool bDone;

public:
EventSink() { m_lRef = 0; }
~EventSink() { bDone = true; }

virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPERelease();
virtual HRESULT
STDMETHODCALLTYPEQueryInterface(REFIID riid, void** ppv);

virtual HRESULT STDMETHODCALLTYPE Indicate(
LONGlObjectCount,
IWbemClassObject __RPC_FAR *__RPC_FAR *apObjArray
);

virtual HRESULT STDMETHODCALLTYPE SetStatus(
/* [in] */LONG lFlags,
/* [in] */HRESULT hResult,
/* [in] */BSTR strParam,
/* [in] */IWbemClassObject __RPC_FAR *pObjParam
);
};

#endif


// ------------------------------------------ EventSink.cpp------------------------------------------------ //
//
#include "eventsink.h"

ULONGEventSink::AddRef()
{
return InterlockedIncrement(&m_lRef);
}

ULONGEventSink::Release()
{
LONG lRef = InterlockedDecrement(&m_lRef);
if(lRef == 0)
delete this;
return lRef;
}

HRESULTEventSink::QueryInterface(REFIID riid, void** ppv)
{
if (riid == IID_IUnknown || riid == IID_IWbemObjectSink)
{
*ppv = (IWbemObjectSink *) this;
AddRef();
return WBEM_S_NO_ERROR;
}
else return E_NOINTERFACE;
}


HRESULT EventSink::Indicate(long lObjectCount,
IWbemClassObject **apObjArray)
{
HRESULT hres = S_OK;

for (int i = 0; i < lObjectCount; i++)
{
printf("Eventoccurred\n");
}

return WBEM_S_NO_ERROR;
}

HRESULTEventSink::SetStatus(
/* [in] */LONG lFlags,
/* [in] */HRESULT hResult,
/* [in] */BSTR strParam,
/* [in] */IWbemClassObject __RPC_FAR *pObjParam
)
{
if(lFlags == WBEM_STATUS_COMPLETE)
{
printf("Call complete. hResult= 0x%X\n", hResult);
}
else if(lFlags == WBEM_STATUS_PROGRESS)
{
printf("Call inprogress.\n");
}

return WBEM_S_NO_ERROR;
} // end of EventSink.cpp


}

TLSAlloc()

为什么要有TLS?原因在于,进程中的全局变量与函数内定义的静态(static)变量,是各个线程都可以访问的共享变量。在一个线程修改的内存内容,对所有线程都生效。这是一个优点也是一个缺点。说它是优点,线程的数据交换变得非常快捷。说它是缺点,一个线程死掉了,其它线程也性命不保; 多个线程访问共享数据,需要昂贵的同步开销,也容易造成同步相关的BUG。

  如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static memorylocal to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。

  线程局部存储在不同的平台有不同的实现,可移植性不太好。幸好要实现线程局部存储并不难,最简单的办法就是建立一个全局表,通过当前线程ID去查询相应的数据,因为各个线程的ID不同,查到的数据自然也不同了。

  大多数平台都提供了线程局部存储的方法,无需要我们自己去实现:

  linux:

  intpthread_key_create(pthread_key_t *key, void (*destructor)(void*));

  intpthread_key_delete(pthread_key_t key);

  void*pthread_getspecific(pthread_key_t key);

  intpthread_setspecific(pthread_key_t key, const void *value);

  Win32

  方法一:每个线程创建时系统给它分配一个LPVOID指针的数组(叫做TLS数组),这个数组从C编程角度是隐藏着的不能直接访问,需要通过一些C API函数调用访问。首先定义一些DWORD线程全局变量或函数静态变量,准备作为各个线程访问自己的TLS数组的索引变量。一个线程使用TLS时,第一步在线程内调用TlsAlloc()函数,为一个TLS数组索引变量与这个线程的TLS数组的某个槽(slot)关联起来,例如获得一个索引变量:

  global_dwTLSindex=TLSAlloc();

  注意,此步之后,当前线程实际上访问的是这个TLS数组索引变量的线程内的拷贝版本。也就说,不同线程虽然看起来用的是同名的TLS数组索引变量,但实际上各个线程得到的可能是不同DWORD值。其意义在于,每个使用TLS的线程获得了一个DWORD类型的线程局部静态变量作为TLS数组的索引变量。C/C++原本没有直接定义线程局部静态变量的机制,所以在如此大费周折。

  第二步,为当前线程动态分配一块内存区域(使用LocalAlloc()函数调用),然后把指向这块内存区域的指针放入TLS数组相应的槽中(使用TlsValue()函数调用)。

  第三步,在当前线程的任何函数内,都可以通过TLS数组的索引变量,使用TlsGetValue()函数得到上一步的那块内存区域的指针,然后就可以进行内存区域的读写操作了。这就实现了在一个线程内部这个范围处处可访问的变量。

  最后,如果不再需要上述线程局部静态变量,要动态释放掉这块内存区域(使用LocalFree()函数),然后从TLS数组中放弃对应的槽(使用TlsFree()函数)。

TLS是一个良好的Win32特质,让多线程程序设计更容易一些。TLS是一个机制,经由它,程序可以拥有全域变量,但处于「每一线程各不相同」的状态。也就是说,进程中的所有线程都可以拥有全域变量,但这些变量其实是特定对某个线程才有意义。例如,你可能有一个多线程程序,每一个线程都对不同的文件写文件(也因此它们使用不同的文件handle)。这种情况下,把每一个线程所使用的文件handle储存在TLS中,将会十分方便。当线程需要知道所使用的handle,它可以从TLS获得。重点在于:线程用来取得文件handle的那一段码在任何情况下都是相同的,而从TLS中取出的文件handle却各不相同。非常灵巧,不是吗?有全域变数的便利,却又分属各线程。

虽然TLS很方便,它并不是毫无限制。在Windows NT和Windows 95之中,有64个DWORD slots供每一个线程使用。这意思是一个进程最多可以有64个「对各线程有不同意义」的DWORDs。虽然TLS可以存放单一数值如文件handle,更常的用途是放置指针,指向线程的私有资料。有许多情况,多线程程序需要储存一堆数据,而它们又都是与各线程相关。许多程序员对此的作法是把这些变量包装为C结构,然后把结构指针储存在TLS中。当新的线程诞生,程序就配置一些内存给该结构使用,并且把指针储存在为线程保留下来的TLS中。一旦线程结束,程序代码就释放所有配置来的区块。既然每一个线程都有64个slots用来储存线程自己的数据,那么这些空间到底打哪儿来?在线程的学习中我们可以从结构TDB中看到,每一个thread database都有64个DWORDs给TLS使用。当你以TLS函式设定或取出数据,事实上你真正面对的就是那64 DWORDs。好,现在我们知道了原来那些“对各线程有不同意义的全局变量”是存放在线程各自的TDB中阿。

接下来你也许会问:我怎么存取这64个DWORDS呢?我又怎么知道哪个DWORDS被占用了,哪个没有被占用呢?首先我们要理解这样一个事实:系统之所以给我们提供TLS这一功能,就是为了方便的实现“对各线程有不同意义的全局变量”这一功能;既然要达到“全局变量”的效果,那么也就是说每个线程都要用到这个变量,既然这样那么我们就不需要对每个线程的那64个DWORDS的占用情况分别标记了,因为那64个DWORDS中的某一个一旦占用,是所有线程的那个DWORD都被占用了,于是KERNEL32使用两个DWORDs(总共64个位)来记录哪一个slot是可用的、哪一个slot已经被用。这两个DWORDs可想象成为一个64位数组,如果某个位设立,就表示它对应的TLS slot已被使用。这64位TLS slot数组存放在process database中(在进程一节中的PDB结构中我们列出了那两个DWORDs)。

下面的四个函数就是对TLS进行操作的:

(1)TlsAlloc

上面我们说过了KERNEL32使用两个DWORDs(总共64个位)来记录哪一个slot是可用的、哪一个slot已经被用。当你需要使用一个TLS slot的时候,你就可以用这个函数将相应的TLS slot位置1。

(2)TlsSetValue

TlsSetValue可以把数据放入先前配置到的TLS slot中。两个参数分别是TLS slot索引值以及欲写入的数据内容。TlsSetValue就把你指定的数据放入64 DWORDs所组成的数组(位于目前的thread database)的适当位置中。

(3)TlsGetValue

这个函数几乎是TlsSetValue的一面镜子,最大的差异是它取出数据而非设定数据。和TlsSetValue一样,这个函数也是先检查TLS索引值合法与否。如果是,TlsGetValue就使用这个索引值找到64 DWORDs数组(位于thread database中)的对应数据项,并将其内容传回。

(4)TlsFree

这个函数将TlsAllocTlsSetValue的努力全部抹消掉。TlsFree先检验你交给它的索引值是否的确被配置过。如果是,它将对应的64位TLS slots位关闭。然后,为了避免那个已经不再合法的内容被使用,TlsFree巡访进程中的每一个线程,把0放到刚刚被释放的那个TLS slot上头。于是呢,如果有某个TLS索引后来又被重新配置,所有用到该索引的线程就保证会取回一个0值,除非它们再调用TlsSetValue

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

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

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

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

HANDLE CreateMutex(

 LPSECURITY_ATTRIBUTESlpMutexAttributes, // 安全属性指针

 BOOLbInitialOwner,// 初始拥有者

 LPCTSTRlpName// 互斥对象名

);

参数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 ThreadProc1(LPVOID pParam)

{

 // 等待互斥对象通知

 WaitForSingleObject(hMutex,INFINITE);

 // 对共享资源进行写入操作

 for (int i =0; i < 10; i++)

 {

  g_cArray[i] ='a';

  Sleep(1);

 }

 // 释放互斥对象

 ReleaseMutex(hMutex);

 return 0;

}

UINT ThreadProc2(LPVOID pParam)

{

 // 等待互斥对象通知

 WaitForSingleObject(hMutex,INFINITE);

 // 对共享资源进行写入操作

 for (int i =0; i < 10; i++)

 {

  g_cArray[10 -i - 1] = 'b';

  Sleep(1);

 }

 // 释放互斥对象

 ReleaseMutex(hMutex);

 return 0;

}

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

3.2.6 线程局部存储

线程局部存储(thread-local storage,TLS)是一个使用很方便的存储线程局部数据的系统。利用TLS机制可以为进程中所有的线程关联若干个数据,各个线程通过由TLS分配的全局索引来访问与自己关联的数据。这样,每个线程都可以有线程局部的静态存储数据。

用于管理TLS的数据结构是很简单的,Windows仅为系统中的每一个进程维护一个位数组,再为该进程中的每一个线程申请一个同样长度的数组空间,如图3.9所示。

图3.9 TSL机制在内部使用的数据结构

运行在系统中的每一个进程都有图3.9所示的一个位数组。位数组的成员是一个标志,每个标志的值被设为FREE或INUSE,指示了此标志对应的数组索引是否在使用中。Windodws保证至少有TLS_MINIMUM_AVAILABLE(定义在WinNT.h文件中)个标志位可用。

动态使用TLS的典型步骤如下。

(1)主线程调用TlsAlloc函数为线程局部存储分配索引,函数原型为:

DWORD TlsAlloc(void); // 返回一个TLS索引

如上所述,系统为每一个进程都维护着一个长度为TLS_MINIMUM_AVAILABLE的位数组,TlsAlloc的返回值就是数组的一个下标(索引)。这个位数组的惟一用途就是记忆哪一个下标在使用中。初始状态下,此位数组成员的值都是FREE,表示未被使用。当调用TlsAlloc的时候,系统会挨个检查这个数组中成员的值,直到找到一个值为FREE的成员。把找到的成员的值由FREE改为INUSE后,TlsAlloc函数返回该成员的索引。如果不能找到一个值为FREE的成员,TlsAlloc函数就返回TLS_OUT_OF_INDEXES(在WinBase.h文件中定义为-1),意味着失败。

例如,在第一次调用TlsAlloc的时候,系统发现位数组中第一个成员的值是FREE,它就将此成员的值改为INUSE,然后返回0。

当一个线程被创建时,Windows就会在进程地址空间中为该线程分配一个长度为TLS_MINIMUM_AVAILABLE的数组,数组成员的值都被初始化为0。在内部,系统将此数组与该线程关联起来,保证只能在该线程中访问此数组中的数据。如图3.7所示,每个线程都有它自己的数组,数组成员可以存储任何数据。

(2)每个线程调用TlsSetValue和TlsGetValue设置或读取线程数组中的值,函数原型为:

BOOL TlsSetValue(

DWORD dwTlsIndex, // TLS 索引

LPVOIDlpTlsValue// 要设置的值

);

LPVOID TlsGetValue(DWORD dwTlsIndex ); // TLS索引

TlsSetValue函数将参数lpTlsValue指定的值放入索引为dwTlsIndex的线程数组成员中。这样,lpTlsValue的值就与调用TlsSetValue函数的线程关联了起来。此函数调用成功,会返回TRUE。

调用TlsSetValue函数,一个线程只能改变自己线程数组中成员的值,而没有办法为另一个线程设置TLS值。到现在为止,将数据从一个线程传到另一个线程的惟一方法是在创建线程时使用线程函数的参数。

TlsGetValue函数的作用是取得线程数组中索引为dwTlsIndex的成员的值。

TlsSetValue和TlsGetValue分别用于设置和取得线程数组中的特定成员的值,而它们使用的索引就是TlsAlloc函数的返回值。这就充分说明了进程中惟一的位数组和各线程数组的关系。例如,TlsAlloc返回3,那就说明索引3被此进程中的每一个正在运行的和以后要被创建的线程保存起来,用以访问各自线程数组中对应的成员的值。

(3)主线程调用TlsFree释放局部存储索引。函数的惟一参数是TlsAlloc返回的索引。

利用TLS可以给特定的线程关联一个数据。比如下面的例子将每个线程的创建时间与该线程关联了起来,这样,在线程终止的时候就可以得到线程的生命周期。整个跟踪线程运行时间的例子的代码如下:

#include<stdio.h>// 03UseTLS工程下

#include<windows.h>

#include <process.h>

// 利用TLS跟踪线程的运行时间

DWORD g_tlsUsedTime;

void InitStartTime();

DWORD GetUsedTime();

UINT __stdcall ThreadFunc(LPVOID)

{ int i;

// 初始化开始时间

InitStartTime();

// 模拟长时间工作

i = 10000*10000;

while(i--){}

// 打印出本线程运行的时间

printf("This thread is coming to end. Thread ID: %-5d, Used Time: %d \n",

::GetCurrentThreadId(), GetUsedTime());

return 0;

}

int main(int argc, char* argv[])

{ UINT uId;

int i;

HANDLE h[10];

// 通过在进程位数组中申请一个索引,初始化线程运行时间记录系统

g_tlsUsedTime =::TlsAlloc();

// 令十个线程同时运行,并等待它们各自的输出结果

for(i=0; i<10;i++)

{ h[i] = (HANDLE)::_beginthreadex(NULL, 0,ThreadFunc, NULL, 0, &uId);}

for(i=0; i<10;i++)

{ ::WaitForSingleObject(h[i], INFINITE);

::CloseHandle(h[i]); }

// 通过释放线程局部存储索引,释放时间记录系统占用的资源

::TlsFree(g_tlsUsedTime);

return 0;

}

// 初始化线程的开始时间

void InitStartTime()

{ // 获得当前时间,将线程的创建时间与线程对象相关联

DWORD dwStart =::GetTickCount();

::TlsSetValue(g_tlsUsedTime, (LPVOID)dwStart);

}

// 取得一个线程已经运行的时间

DWORD GetUsedTime()

{ // 获得当前时间,返回当前时间和线程创建时间的差值

DWORD dwElapsed =::GetTickCount();

dwElapsed =dwElapsed - (DWORD)::TlsGetValue(g_tlsUsedTime);

return dwElapsed;

}

GetTickCount函数可以取得Windows从启动开始经过的时间,其返回值是以毫秒为单位的已启动的时间。

一般情况下,为各线程分配TLS索引的工作要在主线程中完成,而分配的索引值应该保存在全局变量中,以方便各线程访问。上面的例子代码很清除地说明了这一点。主线程一开始就使用TlsAlloc为时间跟踪系统申请了一个索引,保存在全局变量g_tlsUsedTime中。之后,为了示例TLS机制的特点同时创建了10个线程。这10个线程最后都打印出了自己的生命周期,如图3.10所示。

3.10 各线程的生命周期

这个简单的线程运行时间记录系统仅提供InitStartTime和GetUsedTime两个函数供用户使用。应该在线程一开始就调用InitStartTime函数,此函数得到当前时间后,调用TlsSetValue将线程的创建时间保存在以g_tlsUsedTime为索引的线程数组中。当想查看线程的运行时间时,直接调用GetUsedTime函数就行了。这个函数使用TlsGetValue取得线程的创建时间,然后返回当前时间和创建时间的差值。

深入浅出Win32多线程程序设计之综合实例

本章我们将以工业控制和嵌入式系统中运用极为广泛的串口通信为例讲述多线程的典型应用。

  而网络通信也是多线程应用最广泛的领域之一,所以本章的最后一节也将对多线程网络通信进行简短的描述。

  1.串口通信

  在工业控制系统中,工控机(一般都基于PC Windows平台)经常需要与单片机通过串口进行通信。因此,操作和使用PC的串口成为大多数单片机、嵌入式系统领域工程师必须具备的能力。

  串口的使用需要通过三个步骤来完成的:

  (1) 打开通信端口;

  (2) 初始化串口,设置波特率、数据位、停止位、奇偶校验等参数。为了给读者一个直观的印象,下图从Windows的"控制面板->系统->设备管理器->通信端口(COM1)"打开COM的设置窗口:

  

  (3) 读写串口。

  在WIN32平台下,对通信端口进行操作跟基本的文件操作一样。

  创建/打开COM资源

  下列函数如果调用成功,则返回一个标识通信端口的句柄,否则返回-1:

HADLE CreateFile(PCTSTR lpFileName, //通信端口名,如"COM1"
WORD dwDesiredAccess, //对资源的访问类型
WORD dwShareMode, //指定共享模式,COM不能共享,该参数为0
PSECURITY_ATTRIBUTES lpSecurityAttributes,
//安全描述符指针,可为NULL
WORD dwCreationDisposition, //创建方式
WORD dwFlagsAndAttributes, //文件属性,可为NULL
HANDLE hTemplateFile //模板文件句柄,置为NULL
);

  获得/设置COM属性

  下列函数可以获得COM口的设备控制块,从而获得相关参数:

BOOL WINAPI GetCommState(
 HANDLE hFile, //标识通信端口的句柄
 LPDCB lpDCB //指向一个设备控制块(DCB结构)的指针
);

  如果要调整通信端口的参数,则需要重新配置设备控制块,再用WIN32 API SetCommState()函数进行设置:

BOOL SetCommState(
 HANDLE hFile, //标识通信端口的句柄
 LPDCB lpDCB //指向一个设备控制块(DCB结构)的指针
);

  DCB结构包含了串口的各项参数设置,如下:

typedef struct _DCB
{
 // dcb
 DWORD DCBlength; // sizeof(DCB)
 DWORD BaudRate; // current baud rate
 DWORD fBinary: 1; // binary mode, no EOF check
 DWORD fParity: 1; // enable parity checking
 DWORD fOutxCtsFlow: 1; // CTS output flow control
 DWORD fOutxDsrFlow: 1; // DSR output flow control
 DWORD fDtrControl: 2; // DTR flow control type
 DWORD fDsrSensitivity: 1; // DSR sensitivity
 DWORD fTXContinueOnXoff: 1; // XOFF continues Tx
 DWORD fOutX: 1; // XON/XOFF out flow control
 DWORD fInX: 1; // XON/XOFF in flow control
 DWORD fErrorChar: 1; // enable error replacement
 DWORD fNull: 1; // enable null stripping
 DWORD fRtsControl: 2; // RTS flow control
 DWORD fAbortOnError: 1; // abort reads/writes on error
 DWORD fDummy2: 17; // reserved
 WORD wReserved; // not currently used
 WORD XonLim; // transmit XON threshold
 WORD XoffLim; // transmit XOFF threshold
 BYTE ByteSize; // number of bits/byte, 4-8
 BYTE Parity; // 0-4=no,odd,even,mark,space
 BYTE StopBits; // 0,1,2 = 1, 1.5, 2
 char XonChar; // Tx and Rx XON character
 char XoffChar; // Tx and Rx XOFF character
 char ErrorChar; // error replacement character
 char EofChar; // end of input character
 char EvtChar; // received event character
 WORD wReserved1; // reserved; do not use
} DCB;

  读写串口

  在读写串口之前,还要用PurgeComm()函数清空缓冲区,并用SetCommMask ()函数设置事件掩模来监视指定通信端口上的事件,其原型为:

BOOL SetCommMask(
 HANDLE hFile, //标识通信端口的句柄
 DWORD dwEvtMask //能够使能的通信事件
);

  串口上可能发生的事件如下表所示:

事件描述

EV_BREAK

A break was detected on input.

EV_CTS

The CTS (clear-to-send) signal changed state.

EV_DSR

The DSR(data-set-ready) signal changed state.

EV_ERR

A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.

EV_RING

A ring indicator was detected.

EV_RLSD

The RLSD (receive-line-signal-detect) signal changed state.

EV_RXCHAR

A character was received and placed in the input buffer.

EV_RXFLAG

The event character was received and placed in the input buffer. The event character is specified in the device's DCB structure, which is applied to a serial port by using the SetCommState function.

EV_TXEMPTY

The last character in the output buffer was sent.

  在设置好事件掩模后,我们就可以利用WaitCommEvent()函数来等待串口上发生事件,其函数原型为:

BOOL WaitCommEvent(
 HANDLE hFile, //标识通信端口的句柄
 LPDWORD lpEvtMask, //指向存放事件标识变量的指针
 LPOVERLAPPED lpOverlapped, // 指向overlapped结构
);

  我们可以在发生事件后,根据相应的事件类型,进行串口的读写操作:

BOOL ReadFile(HANDLE hFile, //标识通信端口的句柄
 LPVOID lpBuffer, //输入数据Buffer指针
 DWORD nNumberOfBytesToRead, // 需要读取的字节数
 LPDWORD lpNumberOfBytesRead, //实际读取的字节数指针
 LPOVERLAPPED lpOverlapped //指向overlapped结构
);
BOOL WriteFile(HANDLE hFile, //标识通信端口的句柄
 LPCVOID lpBuffer, //输出数据Buffer指针
 DWORD nNumberOfBytesToWrite, //需要写的字节数
 LPDWORD lpNumberOfBytesWritten, //实际写入的字节数指针
 LPOVERLAPPED lpOverlapped //指向overlapped结构
);

  2.工程实例

  下面我们用第1节所述API实现一个多线程的串口通信程序。这个例子工程(工程名为MultiThreadCom)的界面很简单,如下图所示:

  

  它是一个多线程的应用程序,包括两个工作者线程,分别处理串口1和串口2。为了简化问题,我们让连接两个串口的电缆只包含RX、TX两根连线(即不以硬件控制RS-232,串口上只会发生EV_TXEMPTY、EV_RXCHAR事件)。

  在工程实例的BOOL CMultiThreadComApp::InitInstance()函数中,启动并设置COM1和COM2,其源代码为:

BOOL CMultiThreadComApp::InitInstance()
{
 AfxEnableControlContainer();
 //打开并设置COM1
 hComm1=CreateFile("COM1",GENERIC_READ|GENERIC_WRITE, 0, NULL ,OPEN_EXISTING, 0,NULL);
 if (hComm1==(HANDLE)-1)
 {
  AfxMessageBox("打开COM1失败");
  return false;
 }
 else
 {
  DCB wdcb;
  GetCommState (hComm1,&wdcb);
  wdcb.BaudRate=9600;
  SetCommState (hComm1,&wdcb);
  PurgeComm(hComm1,PURGE_TXCLEAR);
 }
 //打开并设置COM2
 hComm2=CreateFile("COM2",GENERIC_READ|GENERIC_WRITE, 0, NULL ,OPEN_EXISTING, 0,NULL);
 if (hComm2==(HANDLE)-1)
 {
  AfxMessageBox("打开COM2失败");
  return false;
 }
 else
 {
  DCB wdcb;
  GetCommState (hComm2,&wdcb);
  wdcb.BaudRate=9600;
  SetCommState (hComm2,&wdcb);
  PurgeComm(hComm2,PURGE_TXCLEAR);
 }
 CMultiThreadComDlg dlg;
 m_pMainWnd = &dlg;
 int nResponse = dlg.DoModal();
 if (nResponse == IDOK)
 {
  // TODO: Place code here to handle when the dialog is
  // dismissed with OK
 }
 else if (nResponse == IDCANCEL)
 {
  // TODO: Place code here to handle when the dialog is
  // dismissed with Cancel
 }
 return FALSE;
}

  此后我们在对话框CMultiThreadComDlg的初始化函数OnInitDialog中启动两个分别处理COM1和COM2的线程:

BOOL CMultiThreadComDlg::OnInitDialog()
{
 CDialog::OnInitDialog();
 // Add "About..." menu item to system menu.
 // IDM_ABOUTBOX must be in the system command range.
 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 ASSERT(IDM_ABOUTBOX < 0xF000);
 CMenu* pSysMenu = GetSystemMenu(FALSE);
 if (pSysMenu != NULL)
 {
  CString strAboutMenu;
  strAboutMenu.LoadString(IDS_ABOUTBOX);
  if (!strAboutMenu.IsEmpty())
  {
   pSysMenu->AppendMenu(MF_SEPARATOR);
   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX,strAboutMenu);
  }
 }
 // Set the icon for this dialog. The framework doesthis 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
 // TODO: Add extra initialization here
 //启动串口1处理线程
 DWORD nThreadId1;
 hCommThread1 =::CreateThread((LPSECURITY_ATTRIBUTES)NULL, 0,
(LPTHREAD_START_ROUTINE)Com1ThreadProcess, AfxGetMainWnd()->m_hWnd, 0,&nThreadId1);
 if (hCommThread1 == NULL)
 {
  AfxMessageBox("创建串口1处理线程失败");
  return false;
 }
 //启动串口2处理线程
 DWORD nThreadId2;
 hCommThread2 =::CreateThread((LPSECURITY_ATTRIBUTES)NULL, 0,
(LPTHREAD_START_ROUTINE)Com2ThreadProcess, AfxGetMainWnd()->m_hWnd, 0,&nThreadId2);
 if (hCommThread2 == NULL)
 {
  AfxMessageBox("创建串口2处理线程失败");
  return false;
 }
 return TRUE; // return TRUE unless you set the focusto a control
}

  两个串口COM1和COM2对应的线程处理函数等待串口上发生事件,并根据事件类型和自身缓冲区是否有数据要发送进行相应的处理,其源代码为:

DWORD WINAPI Com1ThreadProcess(HWND hWnd//主窗口句柄)
{
 DWORD wEven;
 char str[10]; //读入数据
 SetCommMask(hComm1, EV_RXCHAR | EV_TXEMPTY);
 while (TRUE)
 {
  WaitCommEvent(hComm1, &wEven, NULL);
  if(wEven = 0)
  {
   CloseHandle(hCommThread1);
   hCommThread1 = NULL;
   ExitThread(0);
  }
  else
  {
   switch (wEven)
   {
    case EV_TXEMPTY:
     if (wTxPos < wTxLen)
     {
      //在串口1写入数据
      DWORD wCount; //写入的字节数
      WriteFile(hComm1, com1Data.TxBuf[wTxPos], 1,&wCount, NULL);
      com1Data.wTxPos++;
     }
     break;
    case EV_RXCHAR:
     if (com1Data.wRxPos < com1Data.wRxLen)
     {
      //读取串口数据, 处理收到的数据
      DWORD wCount; //读取的字节数
      ReadFile(hComm1, com1Data.RxBuf[wRxPos], 1,&wCount, NULL);
      com1Data.wRxPos++;
      if(com1Data.wRxPos== com1Data.wRxLen);
       ::PostMessage(hWnd, COM_SENDCHAR, 0, 1);
     }
     break;
    }
   }
  }
 }
 return TRUE;
}
DWORD WINAPI Com2ThreadProcess(HWND hWnd //主窗口句柄)
{
 DWORD wEven;
 char str[10]; //读入数据
 SetCommMask(hComm2, EV_RXCHAR | EV_TXEMPTY);
 while (TRUE)
 {
  WaitCommEvent(hComm2, &wEven, NULL);
  if (wEven = 0)
  {
   CloseHandle(hCommThread2);
   hCommThread2 = NULL;
   ExitThread(0);
  }
  else
  {
   switch (wEven)
   {
    case EV_TXEMPTY:
     if (wTxPos < wTxLen)
     {
      //在串口2写入数据
      DWORD wCount; //写入的字节数
      WriteFile(hComm2, com2Data.TxBuf[wTxPos], 1,&wCount, NULL);
      com2Data.wTxPos++;
     }
     break;
    case EV_RXCHAR:
     if (com2Data.wRxPos < com2Data.wRxLen)
     {
      //读取串口数据, 处理收到的数据
      DWORD wCount; //读取的字节数
      ReadFile(hComm2, com2Data.RxBuf[wRxPos], 1,&wCount, NULL);
      com2Data.wRxPos++;
      if(com2Data.wRxPos== com2Data.wRxLen);
       ::PostMessage(hWnd, COM_SENDCHAR, 0, 1);
     }
     break;
    }
   }
  }
  return TRUE;
 }

  线程控制函数中所操作的com1Data和com2Data是与串口对应的数据结构struct tagSerialPort的实例,这个数据结构是:

typedef struct tagSerialPort
{
 BYTE RxBuf[SPRX_BUFLEN];//接收Buffer
 WORD wRxPos; //当前接收字节位置
 WORD wRxLen; //要接收的字节数
 BYTE TxBuf[SPTX_BUFLEN];//发送Buffer
 WORD wTxPos; //当前发送字节位置
 WORD wTxLen; //要发送的字节数
}SerialPort, * LPSerialPort;

  3.多线程串口类

  使用多线程串口通信更方便的途径是编写一个多线程的串口类,例如Remon Spekreijse编写了一个CSerialPort串口类。仔细分析这个类的源代码,将十分有助于我们对先前所学多线程及同步知识的理解。

  3.1类的定义

#ifndef __SERIALPORT_H__
#define __SERIALPORT_H__
#define WM_COMM_BREAK_DETECTED WM_USER+1 // A break was detected on input.
#define WM_COMM_CTS_DETECTED WM_USER+2 // The CTS (clear-to-send) signalchanged state.
#define WM_COMM_DSR_DETECTED WM_USER+3 // The DSR (data-set-ready) signalchanged state.
#define WM_COMM_ERR_DETECTED WM_USER+4 // A line-status error occurred.Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
#define WM_COMM_RING_DETECTED WM_USER+5 // A ring indicator was detected.
#define WM_COMM_RLSD_DETECTED WM_USER+6 // The RLSD(receive-line-signal-detect) signal changed state.
#define WM_COMM_RXCHAR WM_USER+7 // A character was received and placed in theinput buffer.
#define WM_COMM_RXFLAG_DETECTED WM_USER+8 // The event character was receivedand placed in the input buffer.
#define WM_COMM_TXEMPTY_DETECTED WM_USER+9 // The last character in the outputbuffer was sent.
class CSerialPort
{
 public:
  // contruction and destruction
  CSerialPort();
  virtual ~CSerialPort();
  // port initialisation
  BOOL InitPort(CWnd* pPortOwner, UINT portnr = 1, UINTbaud = 19200, char parity = 'N', UINT databits = 8, UINT stopsbits = 1, DWORDdwCommEvents = EV_RXCHAR | EV_CTS, UINT nBufferSize = 512);
  // start/stop comm watching
  BOOL StartMonitoring();
  BOOL RestartMonitoring();
  BOOL StopMonitoring();
  DWORD GetWriteBufferSize();
  DWORD GetCommEvents();
  DCB GetDCB();
  void WriteToPort(char* string);
 protected:
  // protected memberfunctions
  void ProcessErrorMessage(char* ErrorText);
  static UINT CommThread(LPVOID pParam);
  static void ReceiveChar(CSerialPort* port, COMSTATcomstat);
  static void WriteChar(CSerialPort* port);
  // thread
  CWinThread* m_Thread;
  // synchronisation objects
  CRITICAL_SECTION m_csCommunicationSync;
  BOOL m_bThreadAlive;
  // handles
  HANDLE m_hShutdownEvent;
  HANDLE m_hComm;
  HANDLE m_hWriteEvent;
  // Event array.
  // One element is used for each event. There are twoevent handles for each port.
  // A Write event and a receive character event whichis located in the overlapped structure (m_ov.hEvent).
  // There is a general shutdown when the port isclosed.
  HANDLE m_hEventArray[3];
  // structures
  OVERLAPPED m_ov;
  COMMTIMEOUTS m_CommTimeouts;
  DCB m_dcb;
  // owner window
  CWnd* m_pOwner;
  // misc
  UINT m_nPortNr;
  char* m_szWriteBuffer;
  DWORD m_dwCommEvents;
  DWORD m_nWriteBufferSize;
 };
#endif __SERIALPORT_H__

  3.2类的实现

  3.2.1构造函数与析构函数

  进行相关变量的赋初值及内存恢复:

CSerialPort::CSerialPort()
{
 m_hComm = NULL;
 // initialize overlapped structure members to zero
 m_ov.Offset = 0;
 m_ov.OffsetHigh = 0;
 // create events
 m_ov.hEvent = NULL;
 m_hWriteEvent = NULL;
 m_hShutdownEvent = NULL;
 m_szWriteBuffer = NULL;
 m_bThreadAlive = FALSE;
}
//
// Delete dynamic memory
//
CSerialPort::~CSerialPort()
{
 do
 {
  SetEvent(m_hShutdownEvent);
 }
 while (m_bThreadAlive);
 TRACE("Thread ended
");
 delete []m_szWriteBuffer;
}

  3.2.2核心函数:初始化串口

  在初始化串口函数中,将打开串口,设置相关参数,并创建串口相关的用户控制事件,初始化临界区(Critical Section),以成队的EnterCriticalSection()、LeaveCriticalSection()函数进行资源的排它性访问:

BOOL CSerialPort::InitPort(CWnd *pPortOwner,
// the owner (CWnd) of the port (receives message)
UINT portnr, // portnumber (1..4)
UINT baud, // baudrate
char parity, // parity
UINT databits, // databits
UINT stopbits, // stopbits
DWORD dwCommEvents, // EV_RXCHAR, EV_CTS etc
UINT writebuffersize) // size to the writebuffer
{
 assert(portnr > 0&& portnr < 5);
 assert(pPortOwner != NULL);
 // if the thread is alive: Kill
 if (m_bThreadAlive)
 {
  do
  {
   SetEvent(m_hShutdownEvent);
  }
  while (m_bThreadAlive);
  TRACE("Thread ended
");
 }
 // create events
 if (m_ov.hEvent != NULL)
  ResetEvent(m_ov.hEvent);
  m_ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
 if (m_hWriteEvent != NULL)
  ResetEvent(m_hWriteEvent);
  m_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
 if (m_hShutdownEvent != NULL)
  ResetEvent(m_hShutdownEvent);
  m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE,NULL);
 // initialize the event objects
 m_hEventArray[0] = m_hShutdownEvent; // highestpriority
 m_hEventArray[1] = m_ov.hEvent;
 m_hEventArray[2] = m_hWriteEvent;
 // initialize critical section
 InitializeCriticalSection(&m_csCommunicationSync);
 // set buffersize for writing and save the owner
 m_pOwner = pPortOwner;
 if (m_szWriteBuffer != NULL)
  delete []m_szWriteBuffer;
  m_szWriteBuffer = new char[writebuffersize];
  m_nPortNr = portnr;
  m_nWriteBufferSize = writebuffersize;
  m_dwCommEvents = dwCommEvents;
  BOOL bResult = FALSE;
  char *szPort = new char[50];
  char *szBaud = new char[50];
  // now it critical!
  EnterCriticalSection(&m_csCommunicationSync);
  // if the port is already opened: close it
 if (m_hComm != NULL)
 {
  CloseHandle(m_hComm);
  m_hComm = NULL;
 }
 // prepare port strings
 sprintf(szPort, "COM%d", portnr);
 sprintf(szBaud, "baud=%d parity=%c data=%dstop=%d", baud, parity, databits,stopbits);
 // get a handle to the port
 m_hComm = CreateFile(szPort, // communication portstring (COMX)
  GENERIC_READ | GENERIC_WRITE, // read/write types
  0, // comm devices must be opened with exclusiveaccess
  NULL, // no security attributes
  OPEN_EXISTING, // comm devices must use OPEN_EXISTING
  FILE_FLAG_OVERLAPPED, // Async I/O
  0); // template must be 0 for comm devices
 if (m_hComm == INVALID_HANDLE_VALUE)
 {
  // port not found
  delete []szPort;
  delete []szBaud;
  return FALSE;
 }
 // set the timeout values
 m_CommTimeouts.ReadIntervalTimeout = 1000;
 m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000;
 m_CommTimeouts.ReadTotalTimeoutConstant = 1000;
 m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000;
 m_CommTimeouts.WriteTotalTimeoutConstant = 1000;
 // configure
 if (SetCommTimeouts(m_hComm, &m_CommTimeouts))
 {
  if (SetCommMask(m_hComm, dwCommEvents))
  {
   if (GetCommState(m_hComm, &m_dcb))
   {
    m_dcb.fRtsControl = RTS_CONTROL_ENABLE; // set RTSbit high!
    if (BuildCommDCB(szBaud, &m_dcb))
    {
     if (SetCommState(m_hComm, &m_dcb))
      ;
      // normal operation... continue
     else
      ProcessErrorMessage("SetCommState()");
    }
    else
     ProcessErrorMessage("BuildCommDCB()");
    }
   else
    ProcessErrorMessage("GetCommState()");
  }
  else
   ProcessErrorMessage("SetCommMask()");
 }
 else
  ProcessErrorMessage("SetCommTimeouts()");
 delete []szPort;
 delete []szBaud;
 // flush the port
 PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR |PURGE_RXABORT | PURGE_TXABORT);
 // release critical section
 LeaveCriticalSection(&m_csCommunicationSync);
 TRACE("Initialisation for communicationport %dcompleted.
Use Startmonitor to communicate.
", portnr);
 return TRUE;
}

  3.3.3核心函数:串口线程控制函数

  串口线程处理函数是整个类中最核心的部分,它主要完成两类工作:

  (1)利用WaitCommEvent函数对串口上发生的事件进行获取并根据事件的不同类型进行相应的处理;

  (2)利用WaitForMultipleObjects函数对串口相关的用户控制事件进行等待并做相应处理。

UINT CSerialPort::CommThread(LPVOID pParam)
{
 // Cast the void pointerpassed to the thread back to
 // a pointer of CSerialPort class
 CSerialPort *port = (CSerialPort*)pParam;
 // Set the status variable in the dialog class to
 // TRUE to indicate the thread is running.
 port->m_bThreadAlive = TRUE;
 // Misc. variables
 DWORD BytesTransfered = 0;
 DWORD Event = 0;
 DWORD CommEvent = 0;
 DWORD dwError = 0;
 COMSTAT comstat;
 BOOL bResult = TRUE;
 // Clear comm buffers at startup
 if (port->m_hComm)
  // check if the port is opened
  PurgeComm(port->m_hComm, PURGE_RXCLEAR |PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
  // begin forever loop. This loop will run as long asthe thread is alive.
  for (;;)
  {
   // Make a call to WaitCommEvent(). This call willreturn immediatly
   // because our port was created as an async port(FILE_FLAG_OVERLAPPED
   // and an m_OverlappedStructerlapped structurespecified). This call will cause the
   // m_OverlappedStructerlapped elementm_OverlappedStruct.hEvent, which is part of the m_hEventArray to
   // be placed in a non-signeled state if there are nobytes available to be read,
   // or to a signeled state if there are bytesavailable. If this event handle
   // is set to the non-signeled state, it will be setto signeled when a
   // character arrives at the port.
   // we do this for each port!
   bResult = WaitCommEvent(port->m_hComm,&Event, &port->m_ov);
   if (!bResult)
   {
    // If WaitCommEvent() returns FALSE, process thelast error to determin
    // the reason..
    switch (dwError = GetLastError())
    {
     case ERROR_IO_PENDING:
     {
      // This is a normal return value if there are nobytes
      // to read at the port.
      // Do nothing and continue
      break;
     }
     case 87:
     {
      // Under Windows NT, this value is returned forsome reason.
      // I have not investigated why, but it is also avalid reply
      // Also do nothing and continue.
      break;
     }
     default:
     {
      // All other error codes indicate a serious errorhas
      // occured. Process this error.
      port->ProcessErrorMessage("WaitCommEvent()");
      break;
     }
    }
   }
   else
   {
    // If WaitCommEvent() returns TRUE, check to besure there are
    // actually bytes in the buffer to read.
    //
    // If you are reading more than one byte at a timefrom the buffer
    // (which this program does not do) you will havethe situation occur
    // where the first byte to arrive will cause theWaitForMultipleObjects()
    // function to stop waiting. TheWaitForMultipleObjects() function
    // resets the event handle inm_OverlappedStruct.hEvent to the non-signelead state
    // as it returns.
    //
    // If in the time between the reset of this eventand the call to
    // ReadFile() more bytes arrive, them_OverlappedStruct.hEvent handle will be set again
    // to the signeled state. When the call toReadFile() occurs, it will
    // read all of the bytes from the buffer, and theprogram will
    // loop back around to WaitCommEvent().
    //
    // At this point you will be in the situation wherem_OverlappedStruct.hEvent is set,
    // but there are no bytes available to read. If youproceed and call
    // ReadFile(), it will return immediatly due to theasync port setup, but
    // GetOverlappedResults() will not return until thenext character arrives.
    //
    // It is not desirable for theGetOverlappedResults() function to be in
    // this state. The thread shutdown event (event 0)and the WriteFile()
    // event (Event2) will not work if the thread isblocked by GetOverlappedResults().
    //
    // The solution to this is to check the buffer witha call to ClearCommError().
    // This call will reset the event handle, and ifthere are no bytes to read
    // we can loop back through WaitCommEvent() again,then proceed.
    // If there are really bytes to read, do nothingand proceed.
    bResult = ClearCommError(port->m_hComm,&dwError, &comstat);
    if (comstat.cbInQue == 0)
     continue;
   } // end if bResult
   // Main wait function. This function will normallyblock the thread
   // until one of nine events occur that requireaction.
   Event = WaitForMultipleObjects(3,port->m_hEventArray, FALSE, INFINITE);
   switch (Event)
   {
    case 0:
    {
     // Shutdown event. This is event zero so it willbe
     // the higest priority and be serviced first.
     port->m_bThreadAlive = FALSE;
     // Kill this thread. break is not needed, butmakes me feel better.
     AfxEndThread(100);
     break;
    }
    case 1:
    // read event
    {
     GetCommMask(port->m_hComm, &CommEvent);
     if (CommEvent &EV_CTS)
      ::SendMessage(port->m_pOwner->m_hWnd,WM_COMM_CTS_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
     if (CommEvent &EV_RXFLAG)
      ::SendMessage(port->m_pOwner->m_hWnd,WM_COMM_RXFLAG_DETECTED,(WPARAM)0, (LPARAM)port->m_nPortNr);
     if (CommEvent &EV_BREAK)
      ::SendMessage(port->m_pOwner->m_hWnd,WM_COMM_BREAK_DETECTED,(WPARAM)0, (LPARAM)port->m_nPortNr);
     if (CommEvent &EV_ERR)
      ::SendMessage(port->m_pOwner->m_hWnd,WM_COMM_ERR_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
     if (CommEvent &EV_RING)
      ::SendMessage(port->m_pOwner->m_hWnd,WM_COMM_RING_DETECTED,(WPARAM)0, (LPARAM)port->m_nPortNr);
     if (CommEvent &EV_RXCHAR)
      // Receive character event from port.
      ReceiveChar(port, comstat);
    break;
   }
   case 2:
   // write event
   {
    // Write character event from port
    WriteChar(port);
    break;
   }
  } // end switch
 } // close forever loop
 return 0;
}

  下列三个函数用于对串口线程进行启动、挂起和恢复:

//
// start comm watching
//
BOOL CSerialPort::StartMonitoring()
{
 if (!(m_Thread =AfxBeginThread(CommThread, this)))
  return FALSE;
 TRACE("Thread started
");
 return TRUE;
}
//
// Restart the comm thread
//
BOOL CSerialPort::RestartMonitoring()
{
 TRACE("Thread resumed
");
 m_Thread->ResumeThread();
 return TRUE;
}
//
// Suspend the comm thread
//
BOOL CSerialPort::StopMonitoring()
{
 TRACE("Thread suspended
");
 m_Thread->SuspendThread();
 return TRUE;
}

  3.3.4读写串口

  下面一组函数是用户对串口进行读写操作的接口:

//
// Write a character.
//
void CSerialPort::WriteChar(CSerialPort *port)
{
 BOOL bWrite = TRUE;
 BOOL bResult = TRUE;
 DWORD BytesSent = 0;
 ResetEvent(port->m_hWriteEvent);
 // Gain ownership of the critical section
 EnterCriticalSection(&port->m_csCommunicationSync);
 if (bWrite)
 {
  // Initailize variables
  port->m_ov.Offset = 0;
  port->m_ov.OffsetHigh = 0;
  // Clear buffer
  PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR| PURGE_RXABORT | PURGE_TXABORT);
  bResult = WriteFile(port->m_hComm, // Handle toCOMM Port
    port->m_szWriteBuffer, // Pointer to messagebuffer in calling finction
    strlen((char*)port->m_szWriteBuffer), // Lengthof message to send
    &BytesSent, // Where to store the number ofbytes sent
    &port->m_ov); // Overlapped structure
  // deal with any error codes
  if (!bResult)
  {
   DWORD dwError = GetLastError();
   switch (dwError)
   {
    case ERROR_IO_PENDING:
    {
     // continue to GetOverlappedResults()
     BytesSent = 0;
     bWrite = FALSE;
     break;
    }
    default:
    {
     // all other error codes
     port->ProcessErrorMessage("WriteFile()");
    }
   }
  }
  else
  {
   LeaveCriticalSection(&port->m_csCommunicationSync);
  }
 } // end if(bWrite)
 if (!bWrite)
 {
  bWrite = TRUE;
  bResult = GetOverlappedResult(port->m_hComm, //Handle to COMM port
   &port->m_ov, // Overlapped structure
   &BytesSent, // Stores number of bytes sent
  TRUE); // Wait flag
  LeaveCriticalSection(&port->m_csCommunicationSync);
  // deal with the error code
  if (!bResult)
  {
   port->ProcessErrorMessage("GetOverlappedResults()in WriteFile()");
  }
 } // end if (!bWrite)
 // Verify that the data size send equals what we triedto send
 if (BytesSent !=strlen((char*)port->m_szWriteBuffer))
 {
  TRACE("WARNING: WriteFile() error.. Bytes Sent:%d; Message Length: %d
",
  BytesSent, strlen((char*)port->m_szWriteBuffer));
 }
}
//
// Character received. Inform the owner
//
void CSerialPort::ReceiveChar(CSerialPort *port, COMSTAT comstat)
{
 BOOL bRead = TRUE;
 BOOL bResult = TRUE;
 DWORD dwError = 0;
 DWORD BytesRead = 0;
 unsigned char RXBuff;
 for (;;)
 {
  // Gain ownership of the comm port critical section.
  // This process guarantees no other part of thisprogram
  // is using the port object.
  EnterCriticalSection(&port->m_csCommunicationSync);
  // ClearCommError() will update the COMSTAT structureand
  // clear any other errors.
  bResult = ClearCommError(port->m_hComm,&dwError, &comstat);
  LeaveCriticalSection(&port->m_csCommunicationSync);
  // start forever loop. I use this type of loopbecause I
  // do not know at runtime how many loops this willhave to
  // run. My solution is to start a forever loop and to
  // break out of it when I have processed all of the
  // data available. Be careful with this approach and
  // be sure your loop will exit.
  // My reasons for this are not as clear in thissample
  // as it is in my production code, but I have foundthis
  // solutiion to be the most efficient way to do this.
  if (comstat.cbInQue == 0)
  {
   // break out when all bytes have been read
   break;
  }
  EnterCriticalSection(&port->m_csCommunicationSync);
  if (bRead)
  {
   bResult = ReadFile(port->m_hComm, // Handle toCOMM port
    &RXBuff, // RX Buffer Pointer
    1, // Read one byte
    &BytesRead, // Stores number of bytes read
    &port->m_ov); // pointer to the m_ovstructure
   // deal with the error code
   if (!bResult)
   {
    switch (dwError = GetLastError())
    {
     case ERROR_IO_PENDING:
     {
      // asynchronous i/o is still in progress
      // Proceed on to GetOverlappedResults();
      bRead = FALSE;
      break;
     }
     default:
     {
      // Another error has occured. Process this error.
      port->ProcessErrorMessage("ReadFile()");
      break;
     }
    }
   }
   else
   {
    // ReadFile() returned complete. It is notnecessary to call GetOverlappedResults()
    bRead = TRUE;
   }
  } // close if (bRead)
  if (!bRead)
  {
   bRead = TRUE;
   bResult = GetOverlappedResult(port->m_hComm, //Handle to COMM port
    &port->m_ov, // Overlapped structure
    &BytesRead, // Stores number of bytes read
    TRUE); // Wait flag
   // deal with the error code
   if (!bResult)
   {
    port->ProcessErrorMessage("GetOverlappedResults()in ReadFile()");
   }
  } // close if (!bRead)
  LeaveCriticalSection(&port->m_csCommunicationSync);
  // notify parent that a byte was received
  ::SendMessage((port->m_pOwner)->m_hWnd,WM_COMM_RXCHAR, (WPARAM)RXBuff,(LPARAM)port->m_nPortNr);
 } // end forever loop
}
//
// Write a string to the port
//
void CSerialPort::WriteToPort(char *string)
{
 assert(m_hComm != 0);
 memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
 strcpy(m_szWriteBuffer, string);
 // set event for write
 SetEvent(m_hWriteEvent);
}
//
// Return the output buffer size
//
DWORD CSerialPort::GetWriteBufferSize()
{
 return m_nWriteBufferSize;
}

  3.3.5控制接口

  应用程序员使用下列一组public函数可以获取串口的DCB及串口上发生的事件:

//
// Return the device control block
//
DCB CSerialPort::GetDCB()
{
 return m_dcb;
}
//
// Return the communication event masks
//
DWORD CSerialPort::GetCommEvents()
{
 return m_dwCommEvents;
}

  3.3.6错误处理

//
// If there is a error, give the right message
//
void CSerialPort::ProcessErrorMessage(char *ErrorText)
{
 char *Temp = new char[200];
 LPVOID lpMsgBuf;
 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |FORMAT_MESSAGE_FROM_SYSTEM,
  NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
  // Default language
  (LPTSTR) &lpMsgBuf, 0, NULL);
 sprintf(Temp,
  "WARNING: %s Failed with the following error:
%s
Port: %d
", (char*)
  ErrorText, lpMsgBuf, m_nPortNr);
 MessageBox(NULL, Temp, "Application Error",MB_ICONSTOP);
 LocalFree(lpMsgBuf);
 delete []Temp;
}

  仔细分析Remon Spekreijse的CSerialPort类对我们理解多线程及其同步机制是大有益处的,从http://codeguru.earthweb.com/network/serialport.shtml我们可以获取CSerialPort类的介绍与工程实例。另外,电子工业出版社《VisualC++/Turbo C串口通信编程实践》一书的作者龚建伟也编写了一个使用CSerialPort类的例子,可以从http://www.gjwtech.com/scomm/sc2serialportclass.htm获得详情。

  4.多线程网络通信

  在网络通信中使用多线程主要有两种途径,即主监控线程和线程池。

  4.1主监控线程

  这种方式指的是程序中使用一个主线程监控某特定端口,一旦在这个端口上发生连接请求,则主监控线程动态使用CreateThread派生出新的子线程处理该请求。主线程在派生子线程后不再对子线程加以控制和调度,而由子线程独自和客户方发生连接并处理异常。

  使用这种方法的优点是:

  (1)可以较快地实现原型设计,尤其在用户数目较少、连接保持时间较长时有表现较好;

  (2)主线程不与子线程发生通信,在一定程度上减少了系统资源的消耗。

  其缺点是:

  (1)生成和终止子线程的开销比较大;

  (2)对远端用户的控制较弱。

  这种多线程方式总的特点是"动态生成,静态调度"。

  4.2线程池

  这种方式指的是主线程在初始化时静态地生成一定数量的悬挂子线程,放置于线程池中。随后,主线程将对这些悬挂子线程进行动态调度。一旦客户发出连接请求,主线程将从线程池中查找一个悬挂的子线程:

  (1)如果找到,主线程将该连接分配给这个被发现的子线程。子线程从主线程处接管该连接,并与用户通信。当连接结束时,该子线程将自动悬挂,并进人线程池等待再次被调度;

  (2)如果当前已没有可用的子线程,主线程将通告发起连接的客户。

  使用这种方法进行设计的优点是:

  (1)主线程可以更好地对派生的子线程进行控制和调度;

  (2)对远程用户的监控和管理能力较强。

  虽然主线程对子线程的调度要消耗一定的资源,但是与主监控线程方式中派生和终止线程所要耗费的资源相比,要少很多。因此,使用该种方法设计和实现的系统在客户端连接和终止变更频繁时有上佳表现。

  这种多线程方式总的特点是"静态生成,动态调度"。

MFC实现全屏功能的代码

voidCFullScreenDlg::FullScreenView(void)
{
RECT rectDesktop;
WINDOWPLACEMENT wpNew;
if (!IsFullScreen())
{
// We'll need these to restore the original state.
GetWindowPlacement (&m_wpPrev);
//Adjust RECT to new size of window
::GetWindowRect ( ::GetDesktopWindow(), rectDesktop);
::AdjustWindowRectEx(rectDesktop, GetStyle(),FALSE, GetExStyle());
// Remember this for OnGetMinMaxInfo()
m_rcFullScreenRect = rectDesktop;
wpNew = m_wpPrev;
wpNew.showCmd = SW_SHOWNORMAL;
wpNew.rcNormalPosition = rectDesktop;
m_bFullScreen=true;
}
else
{
// 退出全屏幕时恢复到原来的窗口状态
m_bFullScreen=false;
wpNew = m_wpPrev;
}
SetWindowPlacement ( &wpNew );
}
void CFullScreenDlg::OnGetMinMaxInfo(MINMAXINFO*lpMMI)
{
// TODO: Add your message handler code here and/orcall default
if (IsFullScreen())
{
lpMMI->ptMaxSize.y =m_rcFullScreenRect.Height();
lpMMI->ptMaxTrackSize.y =lpMMI->ptMaxSize.y;
lpMMI->ptMaxSize.x =m_rcFullScreenRect.Width();
lpMMI->ptMaxTrackSize.x =lpMMI->ptMaxSize.x;
}
CDialog::OnGetMinMaxInfo(lpMMI);
}
bool CFullScreenDlg::IsFullScreen(void)
{
// 记录窗口当前是否处于全屏状态
return m_bFullScreen;
}

扩 展Visual C++ MFC 类 库

-Visual c++ 提 供 了 功 能 强 大 的 类 库, 基 本 上 应 用 开 发 的 要 求, 但 对 于 某 些 特 殊 要 求 的 界 面, 如 图 像 兼 文 字 的 按 纽, 列 表 框 中 插 入 图 像, 中 国 式 报 表 等 等, 仍 显 得 力 不 从 心, 因 而 很 有 必 要 创 建 扩 展MFC 类 库, 以 满 足 实 际 开 发 的 需 求。

----MFC 支 持 自 绘 制(owner-draw) 概 念, 自 绘 制 的 控 制 类, 通 过 调 用DrawItem() 函 数 实 现 控 件 的 绘 制, 由 于 控 件 绘 制, 消 息 检 测 和 消 息 比 较 代 码 是 在 控 件 中 实 现 而 不 足 在 拥 有 控 件 的 窗 口 中 实 现, 因 而 叫 自 绘 制。 因 而 通 过 重 载DrawItem(LPDRAWITEMSTRUCT 函 数 来 控 制 控 件 的 外 观 和 行 为, 实 现 控 制 所 需 要 的 参 数, 都 包 含 在LPDRAWITEMSTRUCT 结 构 中。

----LPDRAWITEMSTRUCT 结 构:

typedef struct tagDRAWITEMSTRUCT 
{ 
UINT CtlType; 
UINT CtlID; 
UINT itemID; 
UINT itemAction; 
UINT itemState; 
HWND hwndItem; 
HDC hDC; 
RECT rcItem; 
DWORD itemData; 
} DRAWITEMSTRUCT;

----最 重 要 的 参 数 是itemAction,itemState,hDC,rcItem, 它 们 是 实 现 控 件 外 观 绘 制, 消 息 响 应 所 必 须 的。

----itemAction: 绘 制 动 作, 有 以 下 几 种 取 值:ODA_DRAWENTIRE,ODA_FOCUS,ODA_SELECT.

----itemState: 状 态, 有 以 下 几 种 取 值:ODS_CHECKED,ODS_DISABLED,ODS_FOCUS,ODS_GRAYED,ODS_SELECTED,ODS_DEFAULT.

----hDC: 设 备 环 境 的 句 柄。

----rcItem: 控 件 外 观 大 小 的 矩 形。

----下 面 给 出 一 个 基 于CButton 类 的 图 象Button 类:CMybtn

----利 用MFC Wizard 创 建 一 个 新 类CMybtn, 基 类 是CButton

mybtn.h:
class CMybtn : public CButton
{
//Construction
...
public:
virtual void DrawItem
(LPDRAWITEMSTRUCT lpDrawItemStruct);
...
...
public:
void setbitmapid(UINT id);
...
protected:
UINT m_bitmapid;
//图象的按纽id值
...
DECLARE_MESSAGE_MAP()
};

mybtn.cpp
CMybtn::CMybtn()
{
m_bitmapid=0;
//在构造函数中初始化m_bitmaoid
}

void CMybtn::DrawItem(LPDRAWITEMSTRUCT lpdis)
{

HBITMAP hbitmap=NULL;
ASSERT(lpdis!=NULL);
CDC *pdc=CDC::FromHandle(lpdis- >hDC);
//lpdis- >hdc是设备环境的句柄,
fromhandle函数将handle- >指针
CRect r1;
r1.CopyRect(&lpdis- >rcItem);
//得到控件的矩形范围
UINT state=lpdis- >itemState;
//得到控件的状态
if((state & ODS_SELECTED))
pdc- >Draw3dRect(r1,GetSysColor
(COLOR_3DDKSHADOW),
GetSysColor(COLOR_3DHILIGHT));
//selected时,用COLOR_3DDKSHADOW画左上部,
COLOR_HILIGHT画右下部,表现为凹陷
else
pdc- >Draw3dRect(r1,GetSysColor(COLOR_3DHILIGHT),
GetSysColor(COLOR_3DDKSHADOW));
//正常时,用COLOR_3DHILIGHT画左上部,
COLOR_3DDKSHADOW画右下部,表现为突起
// TODO: Add your code to draw the specified item

if(m_bitmapid)
hbitmap=(HBITMAP)LoadImage(AfxGetInstanceHandle(),
MAKEINTRESOURCE(m_bitmapid),IMAGE_BITMAP,0,0,
LR_DEFAULTCOLOR);
//如有图象,则装载图象,
CString s1;
GetWindowText(s1);
//得到BUTTON的CAPTION
if(!s1.IsEmpty())
{
int mode1=pdc->SetBkMode(TRANSPARENT);
if(!hbitmap)
pdc- >DrawText(s1,r1,
DT_CENTER|DT_VCENTER|DT_SINGLELINE);
//如没有图象,则在整个BUTTON范围输出文字
else
{
CRect r2=r1;
r2.DeflateRect(2,2);
CDC memdc;
CBitmap bmp;
CBitmap *oldbitmap;
bmp.Attach(hbitmap);
BITMAP bitmap;
bmp.GetBitmap(&bitmap);
//由BITMAP结构可以得出图象的高,宽
memdc.CreateCompatibleDC(pdc);
oldbitmap=memdc.SelectObject(&bmp);
pdc- >StretchBlt(r2.left,r2.top,r2.Width()/2,
r2.Height(),&memdc,0,0,bitmap.bmWidth,
bitmap.bmHeight,SRCCOPY);
//把图象从内存压缩拷贝到BUTTON范围
memdc.SelectObject(oldbitmap);
bmp.Detach();
CRect r3=r2;
r3.left=r2.left+r2.Width()/2;
pdc- >DrawText(s1,r3,
DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}
}
}

void CMybtn::setbitmapid(UINT id)
{
m_bitmapid=id;
}

 ---- 利用CMybtn类代码可以在应用系统中实现图象文字按纽,在dialog中加入按纽 (IDC_BUTTON1),(IDC_BUTTON2)设置它们的属性为 owner-draw.
利用resource editor 加入两个图象文件,id值为:IDB_BITMAP1,IDB_BITMAP2。 
class CWs3Dlg : public CDialog
 {
 ...
 public:

 // Dialog Data
 //{{AFX_DATA(CWs3Dlg)
enum { IDD = IDD_WS3_DIALOG };
CMybtnm_btn1;
CMybtnm_btn2;
//定义图象按纽
 //}}AFX_DATA
...
protected:
virtual void DoDataExchange(CDataExchange* pDX);
 // DDX/DDV support
//}}AFX_VIRTUAL

 ....
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
 };
 
 void CWs3Dlg::DoDataExchange(CDataExchange* pDX)
 {
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CWs3Dlg)
DDX_Control(pDX, IDC_BUTTON2, m_btn2);
DDX_Control(pDX, IDC_BUTTON1, m_btn1);
//}}AFX_DATA_MAP
}

 在初始化代码加入:
 BOOL CWs3Dlg::OnInitDialog()
 {
...
 // TODO: Add extra initialization here
 m_btn1.setbitmapid(IDB_BITMAP1);
 m_btn2.setbitmapid(IDB_BITMAP2);
 return TRUE; // return TRUE unless 
 you set the focus to a control
}

Vc练习总结1

(1)mfc程序让程序在任务栏上不显示

ModifyStyleEx(WS_EX_APPWINDOW,WS_EX_TOOLWINDOW)

这样程序运行过程已经加载的时候都不会出现在任务栏上。

2:首先这种写法是让对话框透明时用到的,因为要使窗体拥有透明效果,则窗口必须有WS_EX_LAYERED扩展属性,而一般情况下窗口是不具有WS_EX_LAYERED属性的,所以要加上这个属性

SetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE,
(GetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE)^WS_EX_LAYERED));
// vs后期的sdk已经支持下面函数,改变透明度很重要。2为LWA_ALPHA
SetLayeredWindowAttributes(0,180,2);

3:获得当前的时间和星期几

CTimet = CTime::GetCurrentTime();

UINTDayOfWeek[] = {
LOCALE_SDAYNAME7, // Sunday
LOCALE_SDAYNAME1,
LOCALE_SDAYNAME2,
LOCALE_SDAYNAME3,
LOCALE_SDAYNAME4,
LOCALE_SDAYNAME5,
LOCALE_SDAYNAME6 // Saturday
};

TCHARBuffer[255] = {0};

GetLocaleInfo(LOCALE_USER_DEFAULT,
DayOfWeek[t.GetDayOfWeek()-1],
Buffer,
255);

m_strDate.Format(_T("%d-%02d-%02d,%s"),t.GetYear(),t.GetMonth(),t.GetDay(),Buffer);

4:hbitmap是bitmap的指针,相互转换如下

HBITMAPhBitmap = LoadBitmap(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_BITMAP_BUTTON));

CBitmapbitmap;
bitmap.Attach( hBitmap );

BITMAPbit;
bitmap.GetBitmap(&bit);

当然也可以用FromHandle

注意点:AttachFromHandle的区别FromHandle得到的指针是临时变量,,通过Attach连接的句柄可以长久保留,但通过FromHandle得到的只是暂时的,大概只在一个消息区间内有效,很快便会被删除,所以基本上不能用。我用了FromHandle然后一直出错!!!

5:topmost属性的窗口一直在最上面
当需要变成topmost时,可用函数去设置:
SetWindowPos(wndTopMost,...)。
如果要变过来,可用
SetWindowPos(wndNoTopMost.....);

6:GetSystemMetrics(SM_CXSCREEN);

获得当前桌面的宽

7:SetWindowPos(&wndTopMost,
GetSystemMetrics(SM_CXSCREEN)-200,
20,
bit.bmWidth,bit.bmHeight,
SWP_SHOWWINDOW);

主要用来显示窗体的位置大小等。比如第二三个参数为要从某个坐标开始显示。第四五两个参数为显示的区域大小。

Changesthe size, position, and Z order of a child, pop-up, or top-level window. Thesewindows are ordered according to their appearance on the screen. The topmostwindow receives the highest rank and is the first window in the Z order.

8:在设置了窗体背景后。也就是说将整个窗体都用一个图片显示的时候如果窗体中有一些button等。这个时候button的图标是不会覆盖的。必须用ModifyStyle(0,BS_OWNERDRAW);这个来设定可自定义控件风格。这样的话下面多用图片叠加的方式才可以把图片区域弄上去。

ModifyStyle(0,BS_OWNERDRAW);

9:MoveWindow(px,py,m_rc1.Width(),m_rc1.Height());

改变指定窗口的位置和大小.对顶窗口来说,位置和大小取决于屏幕的左上角;对子窗口来说,位置和大小取决于父窗口客户区的左上角.

10:

m_btnClose.SetBackImg(&m_MemDC,5,7,33,7,5,7,19,13);
m_btnStart.SetBackImg(&m_MemDC,5,21,33,20,5,21,19,13);
m_btnStop.SetBackImg(&m_MemDC,5,36,33,36,5,36,19,13);
m_btnSet.SetBackImg(&m_MemDC,5,50,33,50,5,50,19,13);

m_btnClose.SetPos(123,7 );
m_btnStart.SetPos( 123,21);
m_btnStop.SetPos( 123,36);
m_btnSet.SetPos( 123,50);

该处我们自定义一个button类。实例化4个button,通过自定义函数SetBackImg来获得每一个button在一个载入btn中要取到的区域(详细处理通过bitblt来取得要显示的图片部分),这样每一个button的图片都取好了。然后用自定义的SetPos来把图片移动到指定的位置。SetPos中只是调用了movewindow。

11:在自定义的button控件中添加OnEraseBkgnd事件。

1.OnEraseBkgnd()的要求是快速在里面的绘图程序最好是不要太耗时间因为每当window组件有任何小变动都会马上呼叫OnEraseBkgnd()

2.OnPaint()是只有在程序有空闲的时候才会被呼叫

3.OnEraseBkgnd()是在OnPaint()之前呼叫的

所以OnPaint()被呼叫一次之前可能会呼叫OnEraseBkgnd()好几次

如果我们是一个在做图形化使用者接口的人常会需要把一张美美的图片设为我们dialog的底图把绘图的程序代码放在OnPaint()之中可能会常碰到一些问题比方说拖曳一个窗口在我们做的dialog上面一直移动则dialog会变成灰色直到动作停止才恢复这是因为每次需要重绘的时候程序都会马上呼叫OnEraseBkgnd() OnEraseBkgnd()就把dialog画成灰色而只有动作停止之后程序才会呼叫OnPaint()这时才会把我们要画的底图贴上去这个问题的解法比较差点的方法是把OnEraseBkgnd()改写成不做事的function如下所示:

BOOLCMyDlg::OnEraseBkgnd(CDC* pDC)

{

return TRUE;

}

以上本来是会呼叫CDialog::OnEraseBkgnd()但是如果我们不呼叫的话程序便不会画上灰色的底色了比较好的做法是直接将绘图的程序从OnPaint()移到OnEraseBkgnd()来做如下所示:

//m_bmpBKGND 為一CBitmap物件且事先早已載入我們的底圖

//底圖的大小與我們的視窗client大小一致

BOOLCMyDlg::OnEraseBkgnd(CDC* pDC)

{

CRect rc;

GetUpdateRect(&rc);

CDC srcDC;

srcDC.CreateCompatibleDC(pDC);

srcDC.SelectObject(m_bmpBKGND);

pDC->BitBlt(rc.left,rc.top,rc.GetWidth(),

rc.GetHeight(),&srcDC,rc.left,rc.top,SRCCOPY);

return TRUE;

}


特別要注意的是取得重畫大小是使用GetUpdateRect()而不是GetClientRect()如果使用GetClientRect() 會把不該重畫的地方重畫

12;DrawItem(LPDRAWITEMSTRUCTlpDrawItemStruct)当要改变自定义控件的框架的时候就会自动进入该函数。如果不重写该函数可能会崩溃。

在右键弹出菜单中选择“add windowsmessage Handler",

找到DrawItem,为其添加消息映射,添加的代码如下:

voidCUIButton::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)

结果在使用到CUIButton的地方用SubClassDlgItem就会出问题。

后来调试发现,不应该按照上面的添加此消息的映射,而是为CUIButton类重写DrawItem函数,添

加方法:

在类CUIButton右键,在弹出菜单中选择"Add VirtualFunction",弹出的添加虚函数框中选

择"DrawItem",向导为我们生成的代码如下:

voidCUIButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

当要改变自定义控件框架的时候就会调用。会自动进入。

13:TRACKMOUSEEVENT tm;
tm.cbSize = sizeof(TRACKMOUSEEVENT);
tm.dwFlags = TME_LEAVE;
tm.hwndTrack = m_hWnd;
tm.dwHoverTime = 0;
_TrackMouseEvent(&tm);

判断鼠标驻留,

TRACKMOUSEEVENTtme;
例如:
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_HOVER;
tme.hwndTrack = hwnd; // 目标窗口句柄
tme.dwHoverTime = 1000; // 时间
_TrackMouseEvent(&tme);
这样,鼠标悬停1秒之后,就会向窗口发出WM_MOUSEHOVER消息了:
case WM_MOUSEHOVER:
MessageBox(hwnd,"111111", "2222222",MB_OKCANCEL);
但是一旦WM_MOUSEHOVER消息派发出来以后,就需要再次_TrackMouseEvent的。

注释。因为我们这边的四个button实例。每移动上去是显示一个图片。移出显示另一图片。

其中ON_MESSAGE(WM_MOUSELEAVE,OnMouseLeave)要自己添加。

14:mfc中鼠标拖动窗体

voidOnLButtonDown(UINT nFlags, CPoint point)
{
SendMessage(WM_NCLBUTTONDOWN,HTCAPTION,0);

}

15:CFont::CreatePointFont
这个函数提供了一种简单的方法来创建指定字体类型和字体大小

16:TextOut直接输出文本

memDC.TextOut(4,35-size.cy/2-8,strText);

Vc++ 创建异性窗体(1)

随着Microsoft凭借Windows在操作系统上取得的巨大成绩,Windows用户界面也日益成为业界标准。统一的界面给广大用户对应用软件的学习与使用带来了很大方便。但每天都面对同一副面孔,日久天长难免会产生一些厌倦,开发一些“离经叛道”,一改Windows应用程序千篇一律的“标准”界面,一定会给你带来一种清新的感觉。标准Windows应用程序窗口一般为带有标题栏的浅灰色矩形外观,因而“异形”对话框/窗口也主要是颜色与外形上动手脚。

1:改变背景颜色

改变对话框(窗口)的背景颜色是最简单的改变Windows应用程序外观的方法,根据Windows创建与管理机理,一般有两种方法。一种是处理WM_CTLCOLOR消息,首先创建所选背景颜色的刷子,然后调用SetBkColor()或SetDialogBkColor()以所创建的刷子来绘制窗口或对话框的背景。需要重画窗口或对话(或对话的子控件)时,Windows向对话发送消息WM_CTLCOLOR,应用程序处理WM_CTLCOLOR消息并返回一个用来绘画对话背景的刷子句柄。另外一种是响应Windows的WM_ERASEBKGND消息,Windows向窗口发送一个WM_ERASEBKGND消息通知该窗口擦除背景,可以使用VC++的ClassWizard重载该消息的缺省处理程序来擦除背景(实际是用刷子画),并返回TRUE以防止Windows擦除窗口。

2.改变窗口外形

通过使用新的SDK函数SetWindowRgn(),可以将绘画和鼠标消息限定在窗口的一个指定的区域,因此实际上是使窗口成为指定的不规则形状(区域形状)。“区域”是Windows GDI中一种强有力的机制,区域是设备上的一块空间,可以是任意形状,复杂的区域可以由各个小区域组合而成。Windows内含的区域创建函数有CreateRectRgn()、CreatePolyRgn()、CreatePolygonRgn()、CreateRoundRectRgn()和CreateEllipticRgn(),再通过CombineRgn()来组合区域,即可得到复杂形状的区域,获得复杂形状的窗口外形。

通过上面的方法虽然可以得到“异形”窗口,但感觉颜色单调,外形也不够“COOL”,能否获得更酷的“异形”对话框/窗口呢?回答是肯定的。下面就介绍利用位图和蒙板创建“异形”对话框/窗口的方法。

3.利用位图创建异形对话框窗口

利用位图创建异形对话框原理是根据象素的颜色来进行“扣像”处理,对所有非指定颜色象素区域进行区域组合。利用这一技术,实际上就是实现对话框/窗口的位图背景,并且对指定的颜色区域进行透明处理。下面就以透明位图为背景的对话框为例来说明:

首先用绘图软件如PhotoShop绘制编辑一幅拟做对话框背景用的图片,用BMP格式保存,假设存为Back.Bmp。需要说明的是,虽然Visual C++集成开发环境的资源编辑器只能编辑不超过16色的位图,但完全我们可以以真彩色方式存储,不必理会Visual C++的警告。

下一步是用Visual C++的AppWizard创建一个基于对话框的应用程序假定命名为Trans。用资源编辑器引入背景图片Back.Bmp,如果是高彩色,不必理会出现的警告信息,点击OK确认即可。为了明确,修改默认的资源ID标识IDB_BITMAP1为IDB_BACKBMP。然后修改对话框的Style为Popup,Border为None,

向CTransDlg类添加区域处理功能模块voidCTransDlg::SetupRegion(CDC *pDC /*对话框窗口DC*/, UINTBackBitmapID /*背景位图资源ID*/, UINT MaskBitmapID /*区域处理位图资源ID*/, COLORREF TransColor = 0x00000000 /*透明颜色值,默认为黑色*/)。到目前为止,我们暂时认为MaskBitmapID等同于BackBitmapID。其核心工作是根据MaskBitmapID指示位图的象素颜色进行区域组合。完整的代码如下:

voidCTransDlg::SetupRegion(CDC *pDC /*对话框窗口DC*/,

UINTBackBitmapID /*背景位图资源ID*/,

UINTMaskBitmapID /*区域处理位图资源ID*/,

COLORREFTransColor /*透明颜色值*/)

{

CDCmemDC;

CBitmapcBitmap;

CBitmap*pOldMemBmp = NULL;

COLORREFcl;

CRectcRect;

UINTx, y;

CRgnwndRgn, rgnTemp;

//取得窗口大小

GetWindowRect(&cRect);

//背景位图资源ID

m_BackBitmapID= BackBitmapID

//装载位图

cBitmap.LoadBitmap(MaskBitmapID);

memDC.CreateCompatibleDC(pDC);

pOldMemBmp= memDC.SelectObject(&cBitmap);

//首先创建默认的完整区域为完整的窗口区域

wndRgn.CreateRectRgn(0,0, cRect.Width(), cRect.Height());

//下面的两层循环为检查背景位图象素颜色,进行透明区域处理;

//当象素颜色为指定的透明值时,即将该点从区域中剪裁掉。

//其中用到的几个成员变量m_MaskLeftOff、m_MaskTopOff、

//m_MaskRightOff、m_MaskBottomOff、m_FrameWidth

//和m_CaptionHeight,其作用后面再作说明,此时可全部当作0来处理。

for(x=m_FrameWidth+m_MaskLeftOff;x<=cRect.Width() - m_FrameWidth-m_MaskRightOff;x++){

for(y= m_CaptionHeight+m_MaskTopOff;
y<=cRect.Height() - m_FrameWidth-m_MaskBottomOff; y++){

//取得坐标处象素的颜色值

cl= memDC.GetPixel(x - m_FrameWidth-m_MaskLeftOff,y -m_CaptionHeight-m_MaskTopOff);

if(col== TransColor)

{//象素颜色为指定的透明色,创建透明“微区域”

rgnTemp.CreateRectRgn(x,y, x+1, y+1);

//“扣像”,从完整的区域中“扣除”透明的“微区域”

wndRgn.CombineRgn(&wndRgn,&rgnTemp, RGN_XOR);

//删除刚创建的透明“微区域”,释放系统资源

rgnTemp.DeleteObject();

}

}

}

if(pOldMemBmp) memDC.SelectObject(pOldMemBmp);

//用设定窗口为指定的区域

SetWindowRgn((HRGN)wndRgn,TRUE);

}

重置系统默认的背景擦除操作,即添加WM_ERASEBKGND消息处理过程,这一步可以借助ClassWizard来简化操作。

BOOLCTransDlg::OnEraseBkgnd(CDC* pDC)

{//TODO: Add your message handler code here and/or call default

CRectrect;

CDCmemDC;

CBitmapcBitmap;

CBitmap*pOldMemBmp = NULL;

GetWindowRect(&rect);

//装载背景位图

cBitmap.LoadBitmap(m_BackBitmapID);

memDC.CreateCompatibleDC(pDC);

pOldMemBmp= memDC.SelectObject(&cBitmap);

//将背景位图复制到窗口客户区

pDC->BitBlt(0,0, rect.Width(), rect.Height(),

&memDC, 0, 0, SRCCOPY);

if(pOldMemBmp) memDC.SelectObject( pOldMemBmp );

//删除系统却省的OnEraseBkgnd功能

VC 编辑框 改变背景、字体、文本颜色、长度限制、英文 汉字判断
1。长度限制

OnInitDialog()中:

m_edit1.SetLimitText(8);//m_edit1为编辑框的成员变量

或者

CEdit*pEdt=(CEdit*)GetDlgItem(IDC_EDIT1);
pEdt->SetLimitText(8);//限制编辑框输入长度为8字节

2。汉字判断

方法一、

CString str="ab你c好。。";

for(inti=0;i<str.GetLength();i++)
{
if( (BYTE)str[i] < 0x80)
{
MessageBox("非汉字");
}
else//汉字
{
MessageBox("是汉字");
}//方法不好,只能判断有没有汉字

方法二、

CStringss="dd你aa今bb真cc";
int i=0;
while(i<ss.GetLength())
{

if(IsDBCSLeadByte(ss[i]))
{

// 是DBCS
i += 2;
AfxMessageBox("汉字");
}

else

{

// 英文
i ++;
AfxMessageBox("English");
}
}

附原帖:

http://topic.csdn.net/t/20020728/12/905767.html

3。字体及大小

定义一全局变量或成员变量CFont font;//不要定义成局部变量,否则没效果

CEdit*pEdt=(CEdit*)GetDlgItem(IDC_EDIT1);
font.CreatePointFont(266,"Arial");
pEdt->SetFont(&font);

4。背景及文本颜色

定义一成员变量CBrushm_brush;

OnInitDialog()中进行初始化工作m_brush.CreateSolidBrush(RGB(255,0,0));

然后在OnCtrlColor中

HBRUSHCAsdfaaaaaaaaaDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
if(nCtlColor==CTLCOLOR_EDIT &&

pWnd->GetDlgCtrlID()==IDC_EDIT1)//注意此处的(pWnd->),否则没效果
{
pDC->SetTextColor(RGB(255,0,0));
pDC->SetBkColor(RGB(255,255,0));//设置文本背景色
pDC->SetBkMode(TRANSPARENT);//设置背景透明
hbr = (HBRUSH)m_brush;
}

return hbr;
}

对于nCtlColor的类型,如下:

CTLCOLOR_BTNButton control
CTLCOLOR_DLG Dialog box
CTLCOLOR_EDIT Edit control
CTLCOLOR_LISTBOX List-box control
CTLCOLOR_MSGBOX Message box
CTLCOLOR_SCROLLBAR Scroll-bar control
CTLCOLOR_STATIC Static control

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值