`
isiqi
  • 浏览: 16004195 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

20个VC开发小技巧

阅读更多
//一、打开CD-ROM

mciSendString("Set cdAudio door open wait",NULL,0,NULL);

//二、关闭CD_ROM

mciSendString("Set cdAudio door closed wait",NULL,0,NULL);

//三、关闭计算机

OSVERSIONINFO OsVersionInfo; //包含操作系统版本信息的数据结构
OsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&OsVersionInfo); //获取操作系统版本信息
if(OsVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
{
//Windows98,调用ExitWindowsEx()函数重新启动计算机
DWORD dwReserved;
ExitWindowsEx(EWX_REBOOT,dwReserved); //可以改变第一个参数,实现注销用户、
//关机、关闭电源等操作
// 退出前的一些处理程序
}

//四、重启计算机

typedef int (CALLBACK *SHUTDOWNDLG)(int); //显示关机对话框函数的指针
HINSTANCE hInst = LoadLibrary("shell32.dll"); //装入shell32.dll
SHUTDOWNDLG ShutDownDialog; //指向shell32.dll库中显示关机对话框函数的指针
if(hInst != NULL)
{
//获得函数的地址并调用之
ShutDownDialog = (SHUTDOWNDLG)GetProcAddress(hInst,(LPSTR)60);

(*ShutDownDialog)(0);
}

//五、枚举所有字体

LOGFONT lf;
lf.lfCharSet = DEFAULT_CHARSET; // Initialize the LOGFONT structure
strcpy(lf.lfFaceName,"");
CClientDC dc (this);
// Enumerate the font families
::EnumFontFamiliesEx((HDC) dc,&lf, (FONTENUMPROC) EnumFontFamProc,(LPARAM) this,0);
//枚举函数
int CALLBACK EnumFontFamProc(LPENUMLOGFONT lpelf,
LPNEWTEXTMETRIC lpntm,DWORD nFontType,long lparam)

{
// Create a pointer to the dialog window
CDay7Dlg* pWnd = (CDay7Dlg*) lparam;
// add the font name to the list box
pWnd ->m_ctlFontList.AddString(lpelf ->elfLogFont.lfFaceName);
// Return 1 to continue font enumeration
return 1;
}

//其中m_ctlFontList是一个列表控件变量

//六、一次只运行一个程序实例,如果已运行则退出

if( FindWindow(NULL,"程序标题")) exit(0);

//七、得到当前鼠标所在位置

CPoint pt;
GetCursorPos(&pt); //得到位置

//八、上下文菜单事件触发事件:OnContextMenu事件

//九、显示和隐藏程序菜单

CWnd *pWnd=AfxGetMainWnd();
if(b_m) //隐藏菜单
{
pWnd->SetMenu(NULL);
pWnd->DrawMenuBar();
b_m=false;
}
else
{
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME); ////显示菜单 也可改变菜单项
pWnd->SetMenu(&menu);
pWnd->DrawMenuBar();
b_m=true;
menu.Detach();
}

//十、获取可执行文件的图标

HICON hIcon=::ExtractIcon(AfxGetInstanceHandle(),_T("NotePad.exe"),0);
if (hIcon &&hIcon!=(HICON)-1)
{
pDC->DrawIcon(10,10,hIcon);

}
DestroyIcon(hIcon);

//十一、窗口自动靠边程序演示

BOOL AdjustPos(CRect* lpRect)
{//自动靠边
int iSX=GetSystemMetrics(SM_CXFULLSCREEN);
int iSY=GetSystemMetrics(SM_CYFULLSCREEN);
RECT rWorkArea;
BOOL bResult = SystemParametersInfo(SPI_GETWORKAREA, sizeof(RECT), &rWorkAre
a, 0);
CRect rcWA;
if(!bResult)
{//如果调用不成功就利用GetSystemMetrics获取屏幕面积
rcWA=CRect(0,0,iSX,iSY);
}
else
rcWA=rWorkArea;
int iX=lpRect->left;
int iY=lpRect->top;

if(iX < rcWA.left + DETASTEP && iX!=rcWA.left)
{//调整左
//pWnd->SetWindowPos(NULL,rcWA.left,iY,0,0,SWP_NOSIZE);
lpRect->OffsetRect(rcWA.left-iX,0);
AdjustPos(lpRect);
return TRUE;
}
if(iY < rcWA.top + DETASTEP && iY!=rcWA.top)
{//调整上
//pWnd->SetWindowPos(NULL ,iX,rcWA.top,0,0,SWP_NOSIZE);
lpRect->OffsetRect(0,rcWA.top-iY);
AdjustPos(lpRect);
return TRUE;
}
if(iX + lpRect->Width() > rcWA.right - DETASTEP && iX !=rcWA.right-lpRect->Width())
{//调整右
//pWnd->SetWindowPos(NULL ,rcWA.right-rcW.Width(),iY,0,0,SWP_NOSIZE);
lpRect->OffsetRect(rcWA.right-lpRect->right,0);
AdjustPos(lpRect);
return TRUE;
}
if(iY + lpRect->Height() > rcWA.bottom - DETASTEP && iY !=rcWA.bottom-lpRect
->Height())
{//调整下
//pWnd->SetWindowPos(NULL ,iX,rcWA.bottom-rcW.Height(),0,0,SWP_NOSIZE);
lpRect->OffsetRect(0,rcWA.bottom-lpRect->bottom);
return TRUE;
}
return FALSE;
}
//然后在ONMOVEING事件中使用所下过程调用

CRect r=*pRect;
AdjustPos(&r);
*pRect=(RECT)r;

//十二、给系统菜单添加一个菜单项给系统菜单添加一个菜单项需要进行下述三个步骤:
//首先,使用Resource Symbols对话(在View菜单中选择Resource Symbols...可以显
//示该对话)定义菜单项ID,该ID应大于0x0F而小于0xF000;
//其次,调用CWnd::GetSystemMenu获取系统菜单的指针并调用CWnd:: Appendmenu将菜单
//项添加到菜单中。下例给系统菜单添加两个新的

int CMainFrame:: OnCreate (LPCREATESTRUCT lpCreateStruct)
{
//…
//Make sure system menu item is in the right range.
ASSERT(IDM_MYSYSITEM<0xF000);
//Get pointer to system menu.
CMenu* pSysMenu=GetSystemMenu(FALSE);
ASSERT_VALID(pSysMenu);
//Add a separator and our menu item to system menu.
CString StrMenuItem(_T ("New menu item"));
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_MYSYSITEM, StrMenuItem);
//…
}

//十三、运行其它程序

//1、运行EMAIL或网址

char szMailAddress[80];
strcpy(szMailAddress,"mailto:netvc@21cn.com");
ShellExecute(NULL, "open", szMailAddress, NULL, NULL, SW_SHOWNORMAL);

//2、运行可执行程序
WinExec("notepad.exe",SW_SHOW); //运行计事本

//十四、动态增加或删除菜单

//1、 增加菜单
//添加
CMenu *mainmenu;
mainmenu=AfxGetMainWnd()->GetMenu(); //得到主菜单
(mainmenu->GetSubMenu (0))->AppendMenu (MF_SEPARATOR);//添加分隔符
(mainmenu->GetSubMenu (0))->AppendMenu(MF_STRING,ID_APP_ABOUT,
_T("Always on &Top")); //添加新的菜单项
DrawMenuBar(); //重画菜单
//2、 删除菜单
//删除
CMenu *mainmenu;
mainmenu=AfxGetMainWnd()->GetMenu(); //得到主菜单

CString str ;
for(int i=(mainmenu->GetSubMenu (0))->GetMenuItemCount()-1;i>=0;i--) //取得菜单的项数。
{
(mainmenu->GetSubMenu (0))->GetMenuString(i,str,MF_BYPOSITION);
//将指定菜单项的标签拷贝到指定的缓冲区。MF_BYPOSITION的解释见上。
if(str=="Always on &Top") //如果是刚才我们增加的菜单项,则删除。
{
(mainmenu->GetSubMenu (0))->DeleteMenu(i,MF_BYPOSITION);
break;
}
}

//十五、改变应用程序的图标静态更改:

//修改图标资源IDR_MAINFRAME。它有两个图标,一个是16*16的,另一个是32*32的,注意要一起修改。

//动态更改: 向主窗口发送WM_SETICON消息.代码如下:
HICON hIcon=AfxGetApp()->LoadIcon(IDI_ICON);
ASSERT(hIcon);
AfxGetMainWnd()->SendMessage(WM_SETICON,TRUE,(LPARAM)hIcon);

//十六、另一种改变窗口标题的方法

//使用语句 CWnd* m_pCWnd = AfxGetMainWnd( ),然后,再以如下形式调用SetWindowText()函数:
SetWindowText( *m_pCWnd,(LPCTSTR)m_WindowText);// m_WindowText可以是一个CString类的变量。

//十七、剪切板上通过增强元文件拷贝图像数据下面代码拷贝通过元文件拷贝图像数据到任何应用程序,
//其可以放置在CView派生类的函数中。

CMetaFileDC * m_pMetaDC = new CMetaFileDC();
m_pMetaDC->CreateEnhanced(GetDC(),NULL,NULL,"whatever");
//draw meta file
//do what ever you want to do: bitmaps, lines, text...
//close meta file dc and prepare for clipboard;
HENHMETAFILE hMF = m_pMetaDC->CloseEnhanced();
//copy to clipboard
OpenClipboard();
EmptyClipboard();
::SetClipboardData(CF_ENHMETAFILE,hMF);
CloseClipboard();

//DeleteMetaFile(hMF);
delete m_pMetaDC;

//十八、剪切板上文本数据的传送把文本放置到剪接板上:

CString source;
//put your text in source
if(OpenClipboard())
{
HGLOBAL clipbuffer;
char * buffer;
EmptyClipboard();
clipbuffer = GlobalAlloc(GMEM_DDESHARE, source.GetLength()+1);
buffer = (char*)GlobalLock(clipbuffer);
strcpy(buffer, LPCSTR(source));
GlobalUnlock(clipbuffer);
SetClipboardData(CF_TEXT,clipbuffer);
CloseClipboard();
}

//从剪接板上获取文本:

char * buffer;
if(OpenClipboard())
{
buffer = (char*)GetClipboardData(CF_TEXT);
//do something with buffer here
//before it goes out of scope
}
CloseClipboard();

//十九、将捕捉屏幕图像到剪切版中

void CShowBmpInDlgDlg::OnCutScreen()
{
ShowWindow(SW_HIDE);
RECT r_bmp={0,0,::GetSystemMetrics(SM_CXSCREEN),
::GetSystemMetrics(SM_CYSCREEN)};
HBITMAP hBitmap;
hBitmap=CopyScreenToBitmap(&r_bmp);

//hWnd为程序窗口句柄
if (OpenClipboard())
{
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();
}
ShowWindow(SW_SHOW);
}
HBITMAP CShowBmpInDlgDlg::CopyScreenToBitmap(LPRECT lpRect)
{
//lpRect 代表选定区域
{
HDC hScrDC, hMemDC;
// 屏幕和内存设备描述表
HBITMAP hBitmap, hOldBitmap;
// 位图句柄
int nX, nY, nX2, nY2;
// 选定区域坐标
int nWidth, nHeight;
// 位图宽度和高度
int xScrn, yScrn;
// 屏幕分辨率

// 确保选定区域不为空矩形
if (IsRectEmpty(lpRect))
return NULL;
//为屏幕创建设备描述表
hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL);
//为屏幕设备描述表创建兼容的内存设备描述表
hMemDC = CreateCompatibleDC(hScrDC);
// 获得选定区域坐标
nX = lpRect->left;
nY = lpRect->top;
nX2 = lpRect->right;
nY2 = lpRect->bottom;
// 获得屏幕分辨率
xScrn = GetDeviceCaps(hScrDC, HORZRES);
yScrn = GetDeviceCaps(hScrDC, VERTRES);
//确保选定区域是可见的
if (nX<0)

nX = 0;
if (nY<0)
nY = 0;
if (nX2>xScrn)
nX2 = xScrn;
if (nY2>yScrn)
nY2 = yScrn;
nWidth = nX2 - nX;
nHeight = nY2 - nY;
// 创建一个与屏幕设备描述表兼容的位图
hBitmap = CreateCompatibleBitmap
(hScrDC, nWidth, nHeight);
// 把新位图选到内存设备描述表中
hOldBitmap =(HBITMAP)SelectObject(hMemDC, hBitmap);
// 把屏幕设备描述表拷贝到内存设备描述表中
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, nX, nY, SRCCOPY);
//得到屏幕位图的句柄
hBitmap = (HBITMAP)SelectObject(hMemDC, hOldBitmap);

//清除
DeleteDC(hScrDC);
DeleteDC(hMemDC);
// 返回位图句柄
return hBitmap;
}
}

//二十、如何将位图缩放显示在Static控件中

//在Staic控件内显示位图
void CShowBmpInDlgDlg::ShowBmpInStaic()
{
CBitmap hbmp;
HBITMAP hbitmap;
//将pStatic指向要显示的地方
CStatic *pStaic;
pStaic=(CStatic*)GetDlgItem(IDC_IMAGE);
//装载资源 MM.bmp是我的一个文件名,用你的替换
hbitmap=(HBITMAP)::LoadImage (::AfxGetInstanceHandle(),"MM.bmp",
IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION);

hbmp.Attach(hbitmap);
//获取图片格式
BITMAP bm;
hbmp.GetBitmap(&bm);
CDC dcMem;
dcMem.CreateCompatibleDC(GetDC());
CBitmap *poldBitmap=(CBitmap*)dcMem.SelectObject(hbmp);
CRect lRect;
pStaic->GetClientRect(&lRect);
//显示位图
pStaic->GetDC()->StretchBlt(lRect.left ,lRect.top ,lRect.Width(),lRect.Height(),
&dcMem,0 ,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
dcMem.SelectObject(&poldBitmap);
}
<!-- show the whole post, as we would normally do -->

VC中利用多线程技术实现线程之间的通信

<!-- <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"><rdf:Description rdf:about="http://fixedstar.itpub.net/post/7866/63027" dc:identifier="http://fixedstar.itpub.net/post/7866/63027" dc:title="VC中利用多线程技术实现线程之间的通信" trackback:ping="http://blog.itpub.net//trackback.php?id=63027" /></rdf:RDF> -->

fixedstar | 26 三月, 2006 12:03

当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本实例针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,分别进行探讨,并利用多线程技术进行线程之间的通信,实现了数字的简单排序。  

  一、 实现方法

  1、理解线程

  要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应于Visual C++中的CwinThread类对象。单独一个执行程序运行时,缺省地包含的一个主线程,主线程以函数地址的形式出现,提供程序的启动点,如main()或WinMain()函数等。当主线程终止时,进程也随之终止。根据实际需要,应用程序可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。

  一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。

  线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进程终止。工作线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CWinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。

  2、线程的管理和操作

  (一)线程的启动

  创建一个用户界面线程,首先要从类CwinThread产生一个派生类,同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。第二步是根据需要重载该派生类的一些成员函数如:ExitInstance()、InitInstance()、OnIdle()、PreTranslateMessage()等函数。最后调用AfxBeginThread()函数的一个版本:CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ) 启动该用户界面线程,其中第一个参数为指向定义的用户界面线程类指针变量,第二个参数为线程的优先级,第三个参数为线程所对应的堆栈大小,第四个参数为线程创建时的附加标志,缺省为正常状态,如为CREATE_SUSPENDED则线程启动后为挂起状态。

  对于工作线程来说,启动一个线程,首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1(),接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数,返回值赋给pThread变量的同时一并启动该线程来执行上面的Fun1()函数,其中Fun1是线程要运行的函数的名字,也既是上面所说的控制函数的名字,param是准备传送给线程函数Fun1的任意32位值,priority则是定义该线程的优先级别,它是预定义的常数,读者可参考MSDN。

  (二)线程的优先级

  以下的CwinThread类的成员函数用于线程优先级的操作:

int GetThreadPriority();
BOOL SetThradPriority()(int nPriority);
  上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。至于优先级设置所需的常数,自己参考MSDN就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置,CwinThread类没有提供相应的函数,但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass()来实现。

  (三)线程的悬挂和恢复

  CWinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendThread()用来悬挂线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。

  (四)结束线程

  终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行;可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。下面以第三种方法为例,给出部分代码:

////////////////////////////////////////////////////////////////
//////CtestView message handlers
/////Set to True to end thread
Bool bend=FALSE;//定义的全局变量,用于控制线程的运行;
//The Thread Function;
UINT ThreadFunction(LPVOID pParam)//线程函数
{
 while(!bend)
 {
  Beep(100,100);
  Sleep(1000);
 }
 return 0;
}
/////////////////////////////////////////////////////////////
CwinThread *pThread;
HWND hWnd;
Void CtestView::OninitialUpdate()
{
 hWnd=GetSafeHwnd();
 pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程
 pThread->m_bAutoDelete=FALSE;//线程为手动删除
 Cview::OnInitialUpdate();
}
////////////////////////////////////////////////////////////////
Void CtestView::OnDestroy()
{
 bend=TRUE;//改变变量,线程结束
 WaitForSingleObject(pThread->m_hThread,INFINITE);//等待线程结束
 delete pThread;//删除线程
 Cview::OnDestroy();
}
  3、线程之间的通信

  通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。

  (一) 利用用户定义的消息通信

  在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:#define WM_USERMSG WMUSER+100;在需要的时候,在一个线程中调用::PostMessage((HWND)param,WM_USERMSG,0,0)或CwinThread::PostThradMessage()来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束:

UINT ThreadFunction(LPVOID pParam)
{
 while(!bend)
 {
  Beep(100,100);
  Sleep(1000);
 }
 ::PostMessage(hWnd,WM_USERMSG,0,0);
 return 0;
}
////////WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam,
LPARAM lParam)
LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam)
{
 AfxMessageBox("Thread ended.");
 Retrun 0;
}
  上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。

  (二)用事件对象实现通信

  在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下:

////////////////////////////////////////////////////////////////////
Cevent threadStart ,threadEnd;
UINT ThreadFunction(LPVOID pParam)
{
 ::WaitForSingleObject(threadStart.m_hObject,INFINITE);
 AfxMessageBox("Thread start.");
 while(!bend)
 {
  Beep(100,100);
  Sleep(1000);
  Int result=::WaitforSingleObject(threadEnd.m_hObject,0);
  //等待threadEnd事件有信号,无信号时线程在这里悬停
  If(result==Wait_OBJECT_0)
   Bend=TRUE;
 }
 ::PostMessage(hWnd,WM_USERMSG,0,0);
 return 0;
}
/////////////////////////////////////////////////////////////
Void CtestView::OninitialUpdate()
{
 hWnd=GetSafeHwnd();
 threadStart.SetEvent();//threadStart事件有信号
 pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程
 pThread->m_bAutoDelete=FALSE;
 Cview::OnInitialUpdate();
}
////////////////////////////////////////////////////////////////
Void CtestView::OnDestroy()
{
 threadEnd.SetEvent();
 WaitForSingleObject(pThread->m_hThread,INFINITE);
 delete pThread;
 Cview::OnDestroy();
}
  运行这个程序,当关闭程序时,才显示提示框,显示"Thread ended"。
  4、线程之间的同步

  前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。

  (一) 临界区

  临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组组array[]操作,下面的代码说明了如何使用临界区对象:

#include "afxmt.h"
int array[10],destarray[10];
CCriticalSection Section;
UINT WriteThread(LPVOID param)
{
 Section.Lock();
 for(int x=0;x<10;x++)
  array[x]=x;
 Section.Unlock();
}
UINT ReadThread(LPVOID param)
{
 Section.Lock();
 For(int x=0;x<10;x++)
  Destarray[x]=array[x];
  Section.Unlock();
}
  上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。

  (二)互斥

  互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建一个CSingleLock或CMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:

#include "afxmt.h"
int array[10],destarray[10];
CMutex Section;

UINT WriteThread(LPVOID param)
{
 CsingleLock singlelock;
 singlelock (&Section);
 singlelock.Lock();
 for(int x=0;x<10;x++)
  array[x]=x;
 singlelock.Unlock();
}

UINT ReadThread(LPVOID param)
{
 CsingleLock singlelock;
 singlelock (&Section);
 singlelock.Lock();
 For(int x=0;x<10;x++)
  Destarray[x]=array[x];
  singlelock.Unlock();
}
  (三)信号量

  信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLock或CmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。

/////////////////////////////////////////////////////////////////////////
Csemaphore *semaphore;
Semaphore=new Csemaphore(2,2);
HWND hWnd=GetSafeHwnd();
AfxBeginThread(threadProc1,hWnd);
AfxBeginThread(threadProc2,hWnd);
AfxBeginThread(threadProc3,hWnd);
UINT ThreadProc1(LPVOID param)
{
 CsingleLock singelLock(semaphore);
 singleLock.Lock();
 Sleep(10000);
 ::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK);
 return 0;
}
UINT ThreadProc2(LPVOID param)
{
 CSingleLock singelLock(semaphore);
 singleLock.Lock();
 Sleep(10000);
 ::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK);
 return 0;
}

UINT ThreadProc3(LPVOID param)
{
 CsingleLock singelLock(semaphore);
 singleLock.Lock();
 Sleep(10000);
 ::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK);
 return 0;
}
  二、 编程步骤

  1、 启动Visual C++6.0,生成一个32位的控制台程序,将该程序命名为"sequence"

  2、 输入要排续的数字,声明四个子线程;

  3、 输入代码,编译运行程序。

三、 程序代码

//////////////////////////////////////////////////////////////////////////////////////
// sequence.cpp : Defines the entry point for the console application.
/*
主要用到的WINAPI线程控制函数,有关详细说明请查看MSDN;
线程建立函数:
HANDLE CreateThread(
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性结构指针,可为NULL;
 DWORD dwStackSize, // 线程栈大小,若为0表示使用默认值;
 LPTHREAD_START_ROUTINE lpStartAddress, // 指向线程函数的指针;
 LPVOID lpParameter, // 传递给线程函数的参数,可以保存一个指针值;
 DWORD dwCreationFlags, // 线程建立是的初始标记,运行或挂起;
 LPDWORD lpThreadId // 指向接收线程号的DWORD变量;
);

对临界资源控制的多线程控制的信号函数:

HANDLE CreateEvent(
 LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性结构指针,可为NULL;
 BOOL bManualReset, // 手动清除信号标记,TRUE在WaitForSingleObject后必须手动//调用RetEvent清除信号。若为 FALSE则在WaitForSingleObject
 //后,系统自动清除事件信号;
 BOOL bInitialState, // 初始状态,TRUE有信号,FALSE无信号;
 LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH;
 //如果遇到同名的其他信号量函数就会失败,如果遇
 //到同类信号同名也要注意变化;
);

HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性结构指针,可为NULL
 BOOL bInitialOwner, // 当前建立互斥量是否占有该互斥量TRUE表示占有,
 //这样其他线程就不能获得此互斥量也就无法进入由
 //该互斥量控制的临界区。FALSE表示不占有该互斥量
 LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH如果
 //遇到同名的其他信号量函数就会失败,
 //如果遇到同类信号同名也要注意变化;
);

//初始化临界区信号,使用前必须先初始化
VOID InitializeCriticalSection(
 LPCRITICAL_SECTION lpCriticalSection // 临界区变量指针
);

//阻塞函数
//如果等待的信号量不可用,那么线程就会挂起,直到信号可用
//线程才会被唤醒,该函数会自动修改信号,如Event,线程被唤醒之后
//Event信号会变得无信号,Mutex、Semaphore等也会变。
DWORD WaitForSingleObject(
 HANDLE hHandle, // 等待对象的句柄
 DWORD dwMilliseconds // 等待毫秒数,INFINITE表示无限等待
);
//如果要等待多个信号可以使用WaitForMutipleObject函数
*/

#include "stdafx.h"
#include "stdlib.h"
#include "memory.h"
HANDLE evtTerminate; //事件信号,标记是否所有子线程都执行完
/*
下面使用了三种控制方法,你可以注释其中两种,使用其中一种。
注意修改时要连带修改临界区PrintResult里的相应控制语句
*/
HANDLE evtPrint; //事件信号,标记事件是否已发生
//CRITICAL_SECTION csPrint; //临界区
//HANDLE mtxPrint; //互斥信号,如有信号表明已经有线程进入临界区并拥有此信号
static long ThreadCompleted = 0;
/*用来标记四个子线程中已完成线程的个数,当一个子线程完成时就对ThreadCompleted进行加一操作, 要使用InterlockedIncrement(long* lpAddend)和InterlockedDecrement(long* lpAddend)进行加减操作*/

//下面的结构是用于传送排序的数据给各个排序子线程
struct MySafeArray
{
 long* data;
 int iLength;
};

//打印每一个线程的排序结果
void PrintResult(long* Array, int iLength, const char* HeadStr = "sort");

//排序函数
unsigned long __stdcall BubbleSort(void* theArray); //冒泡排序
unsigned long __stdcall SelectSort(void* theArray); //选择排序
unsigned long __stdcall HeapSort(void* theArray); //堆排序
unsigned long __stdcall InsertSort(void* theArray); //插入排序
/*以上四个函数的声明必须适合作为一个线程函数的必要条件才可以使用CreateThread
建立一个线程。
(1)调用方法必须是__stdcall,即函数参数压栈顺序由右到左,而且由函数本身负责
栈的恢复, C和C++默认是__cdecl, 所以要显式声明是__stdcall
(2)返回值必须是unsigned long
(3)参数必须是一个32位值,如一个指针值或long类型
(4) 如果函数是类成员函数,必须声明为static函数,在CreateThread时函数指针有特殊的写法。如下(函数是类CThreadTest的成员函数中):
static unsigned long _stdcall MyThreadFun(void* pParam);
handleRet = CreateThread(NULL, 0, &CThreadTestDlg::MyThreadFun, NULL, 0, &ThreadID);
之所以要声明为static是由于,该函数必须要独立于对象实例来使用,即使没有声明实例也可以使用。*/

int QuickSort(long* Array, int iLow, int iHigh); //快速排序

int main(int argc, char* argv[])
{
 long data[] = {123,34,546,754,34,74,3,56};
 int iDataLen = 8;
 //为了对各个子线程分别对原始数据进行排序和保存排序结果
 //分别分配内存对data数组的数据进行复制
 long *data1, *data2, *data3, *data4, *data5;
 MySafeArray StructData1, StructData2, StructData3, StructData4;
 data1 = new long[iDataLen];
 memcpy(data1, data, iDataLen << 2); //把data中的数据复制到data1中
 //内存复制 memcpy(目标内存指针, 源内存指针, 复制字节数), 因为long的长度
 //为4字节,所以复制的字节数为iDataLen << 2, 即等于iDataLen*4
 StructData1.data = data1;
 StructData1.iLength = iDataLen;
 data2 = new long[iDataLen];
 memcpy(data2, data, iDataLen << 2);
 StructData2.data = data2;
 StructData2.iLength = iDataLen;
 data3 = new long[iDataLen];
 memcpy(data3, data, iDataLen << 2);
 StructData3.data = data3;
 StructData3.iLength = iDataLen;
 data4 = new long[iDataLen];
 memcpy(data4, data, iDataLen << 2);
 StructData4.data = data4;
 StructData4.iLength = iDataLen;
 data5 = new long[iDataLen];
 memcpy(data5, data, iDataLen << 2);
 unsigned long TID1, TID2, TID3, TID4;
 //对信号量进行初始化
 evtTerminate = CreateEvent(NULL, FALSE, FALSE, "Terminate");
 evtPrint = CreateEvent(NULL, FALSE, TRUE, "PrintResult");
 //分别建立各个子线程
 CreateThread(NULL, 0, &BubbleSort, &StructData1, NULL, &TID1);
 CreateThread(NULL, 0, &SelectSort, &StructData2, NULL, &TID2);
 CreateThread(NULL, 0, &HeapSort, &StructData3, NULL, &TID3);
 CreateThread(NULL, 0, &InsertSort, &StructData4, NULL, &TID4);
 //在主线程中执行行快速排序,其他排序在子线程中执行
 QuickSort(data5, 0, iDataLen - 1);
 PrintResult(data5, iDataLen, "Quick Sort");
 WaitForSingleObject(evtTerminate, INFINITE); //等待所有的子线程结束
 //所有的子线程结束后,主线程才可以结束
 delete[] data1;
 delete[] data2;
 delete[] data3;
 delete[] data4;
 CloseHandle(evtPrint);
 return 0;
}

/*
冒泡排序思想(升序,降序同理,后面的算法一样都是升序):从头到尾对数据进行两两比较进行交换,小的放前大的放后。这样一次下来,最大的元素就会被交换的最后,然后下一次
循环就不用对最后一个元素进行比较交换了,所以呢每一次比较交换的次数都比上一次循环的次数少一,这样N次之后数据就变得升序排列了*/
unsigned long __stdcall BubbleSort(void* theArray)
{
 long* Array = ((MySafeArray*)theArray)->data;
 int iLength = ((MySafeArray*)theArray)->iLength;
 int i, j=0;
 long swap;
 for (i = iLength-1; i >0; i--)
 {
  for(j = 0; j < i; j++)
  {
   if(Array[j] >Array[j+1]) //前比后大,交换
   {
    swap = Array[j];
    Array[j] = Array[j+1];
    Array[j+1] = swap;
   }
  }
 }
 PrintResult(Array, iLength, "Bubble Sort"); //向控制台打印排序结果
 InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
 if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完
 //若都执行完则设置程序结束信号量
 return 0;
}

/*选择排序思想:每一次都从无序的数据中找出最小的元素,然后和前面已经有序的元素序列的后一个元素进行交换,这样整个源序列就会分成两部分,前面一部分是已经排好序的有序序列,后面一部分是无序的,用于选出最小的元素。循环N次之后,前面的有序序列加长到跟源序列一样长,后面的无序部分长度变为0,排序就完成了。*/
unsigned long __stdcall SelectSort(void* theArray)
{
 long* Array = ((MySafeArray*)theArray)->data;
 int iLength = ((MySafeArray*)theArray)->iLength;
 long lMin, lSwap;
 int i, j, iMinPos;
 for(i=0; i < iLength-1; i++)
 {
  lMin = Array[i];
  iMinPos = i;
  for(j=i + 1; j <= iLength-1; j++) //从无序的元素中找出最小的元素
  {
   if(Array[j] < lMin)
   {
    iMinPos = j;
    lMin = Array[j];
   }
  }
  //把选出的元素交换拼接到有序序列的最后
  lSwap = Array[i];
  Array[i] = Array[iMinPos];
  Array[iMinPos] = lSwap;
 }
 PrintResult(Array, iLength, "Select Sort"); //向控制台打印排序结果
 InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
 if(ThreadCompleted == 4) SetEvent(evtTerminate);//检查是否其他线程都已执行完
 //若都执行完则设置程序结束信号量
 return 0;
}

/*堆排序思想:堆:数据元素从1到N排列成一棵二叉树,而且这棵树的每一个子树的根都是该树中的元素的最小或最大的元素这样如果一个无序数据集合是一个堆那么,根元素就是最小或最大的元素堆排序就是不断对剩下的数据建堆,把最小或最大的元素析透出来。下面的算法,就是从最后一个元素开始,依据一个节点比父节点数值大的原则对所有元素进行调整,这样调整一次就形成一个堆,第一个元素就是最小的元素。然后再对剩下的无序数据再进行建堆,注意这时后面的无序数据元素的序数都要改变,如第一次建堆后,第二个元素就会变成堆的第一个元素。*/
unsigned long __stdcall HeapSort(void* theArray)
{
 long* Array = ((MySafeArray*)theArray)->data;
 int iLength = ((MySafeArray*)theArray)->iLength;
 int i, j, p;
 long swap;
 for(i=0; i {
  for(j = iLength - 1; j>i; j--) //从最后倒数上去比较字节点和父节点
  {
   p = (j - i - 1)/2 + i; //计算父节点数组下标
   //注意到树节点序数跟数组下标不是等同的,因为建堆的元素个数逐个递减
   if(Array[j] < Array[p]) //如果父节点数值大则交换父节点和字节点
   {
    swap = Array[j];
    Array[j] = Array[p];
    Array[p] = swap;
   }
  }
 }
 PrintResult(Array, iLength, "Heap Sort"); //向控制台打印排序结果
 InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
 if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完
 //若都执行完则设置程序结束信号量
 return 0;
}

/*插入排序思想:把源数据序列看成两半,前面一半是有序的,后面一半是无序的,把无序的数据从头到尾逐个逐个的插入到前面的有序数据中,使得有序的数据的个数不断增大,同时无序的数据个数就越来越少,最后所有元素都会变得有序。*/
unsigned long __stdcall InsertSort(void* theArray)
{
 long* Array = ((MySafeArray*)theArray)->data;
 int iLength = ((MySafeArray*)theArray)->iLength;
 int i=1, j=0;
 long temp;
 for(i=1; i {
  temp = Array[i]; //取出序列后面无序数据的第一个元素值
  for(j=i; j>0; j--) //和前面的有序数据逐个进行比较找出合适的插入位置
  {
   if(Array[j - 1] >temp) //如果该元素比插入值大则后移
    Array[j] = Array[j - 1];
   else //如果该元素比插入值小,那么该位置的后一位就是插入元素的位置
    break;
  }
  Array[j] = temp;
 }
 PrintResult(Array, iLength, "Insert Sort"); //向控制台打印排序结果
 InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1
 if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完
  //若都执行完则设置程序结束信号量
 return 0;
}

/*快速排序思想:快速排序是分治思想的一种应用,它先选取一个支点,然后把小于支点的元素交换到支点的前边,把大于支点的元素交换到支点的右边。然后再对支点左边部分和右
边部分进行同样的处理,这样若干次之后,数据就会变得有序。下面的实现使用了递归
建立两个游标:iLow,iHigh;iLow指向序列的第一个元素,iHigh指向最后一个先选第一个元素作为支点,并把它的值存贮在一个辅助变量里。那么第一个位置就变为空并可以放置其他的元素。 这样从iHigh指向的元素开始向前移动游标,iHigh查找比支点小的元素,如果找到,则把它放置到空置了的位置(现在是第一个位置),然后iHigh游标停止移动,这时iHigh指向的位置被空置,然后移动iLow游标寻找比支点大的元素放置到iHigh指向的空置的位置,如此往复直到iLow与iHigh相等。最后使用递归对左右两部分进行同样处理*/

int QuickSort(long* Array, int iLow, int iHigh)
{
 if(iLow >= iHigh) return 1; //递归结束条件
 long pivot = Array[iLow];
 int iLowSaved = iLow, iHighSaved = iHigh; //保未改变的iLow,iHigh值保存起来
 while (iLow < iHigh)
 {
  while (Array[iHigh] >= pivot && iHigh >iLow) //寻找比支点大的元素
   iHigh -- ;
  Array[iLow] = Array[iHigh]; //把找到的元素放置到空置的位置
  while (Array[iLow] < pivot && iLow < iHigh) //寻找比支点小的元素
   iLow ++ ;
  Array[iHigh] = Array[iLow]; //把找到的元素放置到空置的位置
 }
 Array[iLow] = pivot; //把支点值放置到支点位置,这时支点位置是空置的
 //对左右部分分别进行递归处理
 QuickSort(Array, iLowSaved, iHigh-1);
 QuickSort(Array, iLow+1, iHighSaved);
 return 0;
}

//每一个线程都要使用这个函数进行输出,而且只有一个显示器,产生多个线程
//竞争对控制台的使用权。
void PrintResult(long* Array, int iLength, const char* HeadStr)
{
 WaitForSingleObject(evtPrint, INFINITE); //等待事件有信号
 //EnterCriticalSection(&csPrint); //标记有线程进入临界区
 //WaitForSingleObject(mtxPrint, INFINITE); //等待互斥量空置(没有线程拥有它)
 int i;
 printf("%s: ", HeadStr);
 for (i=0; i {
  printf("%d,", Array[i]);
  Sleep(100); //延时(可以去掉)
/*只是使得多线程对临界区访问的问题比较容易看得到
如果你把临界控制的语句注释掉,输出就会变得很凌乱,各个排序的结果会
分插间隔着输出,如果不延时就不容易看到这种不对临界区控制的结果
*/
 }
 printf("%dn", Array[i]);
 SetEvent(evtPrint); //把事件信号量恢复,变为有信号
}
  四、 小结

  对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本实例讲述了线程处理中经常遇到的问题,希望对读者朋友有一定的帮助,起到抛砖引玉的作用。
<!-- show the whole post, as we would normally do -->

VC常见数据类型转换详解

<!-- <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"><rdf:Description rdf:about="http://fixedstar.itpub.net/post/7866/63016" dc:identifier="http://fixedstar.itpub.net/post/7866/63016" dc:title="VC常见数据类型转换详解" trackback:ping="http://blog.itpub.net//trackback.php?id=63016" /></rdf:RDF> -->

fixedstar | 26 三月, 2006 11:10

刚接触VC编程的朋友往往对许多数据类型的转换感到迷惑不解,本文将介绍一些常用数据类型的使用。

我们先定义一些常见类型变量借以说明

int i = 100;
long l = 2001;
float f=300.2;
double d=12345.119;
char username[]="女侠程佩君";
char temp[200];
char *buf;
CString str;
_variant_t v1;
_bstr_t v2;

一、其它数据类型转换为字符串


短整型(int)
itoa(i,temp,10);///将i转换为字符串放入temp中,最后一个数字表示十进制
itoa(i,temp,2); ///按二进制方式转换
长整型(long)
ltoa(l,temp,10);


二、从其它包含字符串的变量中获取指向该字符串的指针


CString变量
str = "2008北京奥运";
buf = (LPSTR)(LPCTSTR)str;
BSTR类型的_variant_t变量
v1 = (_bstr_t)"程序员";
buf = _com_util::ConvertBSTRToString((_bstr_t)v1);

三、字符串转换为其它数据类型
strcpy(temp,"123");

短整型(int)
i = atoi(temp);
长整型(long)
l = atol(temp);
浮点(double)
d = atof(temp);

四、其它数据类型转换到CString
使用CString的成员函数Format来转换,例如:


整数(int)
str.Format("%d",i);
浮点数(float)
str.Format("%f",i);
字符串指针(char *)等已经被CString构造函数支持的数据类型可以直接赋值
str = username;

五、BSTR、_bstr_t与CComBSTR


CComBSTR、_bstr_t是对BSTR的封装,BSTR是指向字符串的32位指针。
char *转换到BSTR可以这样: BSTR b=_com_util::ConvertStringToBSTR("数据");///使用前需要加上头文件comutil.h
反之可以使用char *p=_com_util::ConvertBSTRToString(b);


六、VARIANT 、_variant_t 与 COleVariant


VARIANT的结构可以参考头文件VC98IncludeOAIDL.H中关于结构体tagVARIANT的定义。
对于VARIANT变量的赋值:首先给vt成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:
VARIANT va;
int a=2001;
va.vt=VT_I4;///指明整型数据
va.lVal=a; ///赋值

对于不马上赋值的VARIANT,最好先用Void VariantInit(VARIANTARG FAR* pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:

unsigned char bVal; VT_UI1
short iVal; VT_I2
long lVal; VT_I4
float fltVal; VT_R4
double dblVal; VT_R8
VARIANT_BOOL boolVal; VT_BOOL
SCODE scode; VT_ERROR
CY cyVal; VT_CY
DATE date; VT_DATE
BSTR bstrVal; VT_BSTR
IUnknown FAR* punkVal; VT_UNKNOWN
IDispatch FAR* pdispVal; VT_DISPATCH
SAFEARRAY FAR* parray; VT_ARRAY|*
unsigned char FAR* pbVal; VT_BYREF|VT_UI1
short FAR* piVal; VT_BYREF|VT_I2
long FAR* plVal; VT_BYREF|VT_I4
float FAR* pfltVal; VT_BYREF|VT_R4
double FAR* pdblVal; VT_BYREF|VT_R8
VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL
SCODE FAR* pscode; VT_BYREF|VT_ERROR
CY FAR* pcyVal; VT_BYREF|VT_CY
DATE FAR* pdate; VT_BYREF|VT_DATE
BSTR FAR* pbstrVal; VT_BYREF|VT_BSTR
IUnknown FAR* FAR* ppunkVal; VT_BYREF|VT_UNKNOWN
IDispatch FAR* FAR* ppdispVal; VT_BYREF|VT_DISPATCH
SAFEARRAY FAR* FAR* pparray; VT_ARRAY|*
VARIANT FAR* pvarVal; VT_BYREF|VT_VARIANT
void FAR* byref; VT_BYREF


_variant_t是VARIANT的封装类,其赋值可以使用强制类型转换,其构造函数会自动处理这些数据类型。
例如:
long l=222;
ing i=100;
_variant_t lVal(l);
lVal = (long)i;


COleVariant的使用与_variant_t的方法基本一样,请参考如下例子:
COleVariant v3 = "字符串", v4 = (long)1999;
CString str =(BSTR)v3.pbstrVal;
long i = v4.lVal;


七、其它

对消息的处理中我们经常需要将WPARAM或LPARAM等32位数据(DWORD)分解成两个16位数据(WORD),例如:
LPARAM lParam;
WORD loValue = LOWORD(lParam);///取低16位
WORD hiValue = HIWORD(lParam);///取高16位
对于16位的数据(WORD)我们可以用同样的方法分解成高低两个8位数据(BYTE),例如:
WORD wValue;
BYTE loValue = LOBYTE(wValue);///取低8位
BYTE hiValue = HIBYTE(wValue);///取高8位
<!-- show the whole post, as we would normally do -->

Source Insight:Linux源代码阅读的利器

<!-- <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"><rdf:Description rdf:about="http://fixedstar.itpub.net/post/7866/63015" dc:identifier="http://fixedstar.itpub.net/post/7866/63015" dc:title="Source Insight:Linux源代码阅读的利器 " trackback:ping="http://blog.itpub.net//trackback.php?id=63015" /></rdf:RDF> -->

fixedstar | 26 三月, 2006 11:04

阅读源代码是钻研技术的最佳手段,而Linux提供了一个庞大的源代码库,但是,由于缺乏良好的源代码阅读工具,使得阅读Linux源代码尤其是内核源代码十分困难,在本文中,笔者向大家推荐一个优秀的源代码阅读工具,并介绍了它的使用方法。

作为一个开放源代码的操作系统,Linux附带的源代码库使得广大爱好者有了一个广泛学习、深入钻研的机会,特别是Linux内核的组织极为复杂,同时,又不能像windows平台的程序一样,可以使用集成开发环境通过察看变量和函数,甚至设置断点、单步运行、调试等手段来弄清楚整个程序的组织结构,使得Linux内核源代码的阅读变得尤为困难。

当然Linux下的vim和emacs编辑程序并不是没有提供变量、函数搜索,彩色显示程序语句等功能。它们的功能是非常强大的。比如,vim和emacs就各自内嵌了一个标记程序,分别叫做ctag和etag,通过配置这两个程序,也可以实现功能强大的函数变量搜索功能,但是由于其配置复杂,linux附带的有关资料也不是很详细,而且,即使建立好标记库,要实现代码彩色显示功能,仍然需要进一步的配置(在另一片文章,我将会讲述如何配置这些功能),同时,对于大多数爱好者来说,可能还不能熟练使用vim和emacs那些功能比较强大的命令和快捷键。

为了方便的学习Linux源程序,我们不妨回到我们熟悉的window环境下,也算是“师以长夷以制夷”吧。但是在Window平台上,使用一些常见的集成开发环境,效果也不是很理想,比如难以将所有的文件加进去,查找速度缓慢,对于非Windows平台的函数不能彩色显示。于是笔者通过在互联网上搜索,终于找到了一个强大的源代码编辑器,它的卓越性能使得学习Linux内核源代码的难度大大降低,这便是Source Insight3.0,它是一个Windows平台下的共享软件,可以从http://www.sourceinsight.com/上边下载30天试用版本。也可以在http://www.pptel.net/index.php?option=com_remository&Itemid=67&func=fileinfo&parent=folder&filecatid=3由于Source Insight是一个Windows平台的应用软件,所以首先要通过相应手段把Linux系统上的程序源代码弄到Windows平台下,这一点可以通过在linux平台上将/usr/src目录下的文件拷贝到Windows平台的分区上,或者从网上光盘直接拷贝文件到Windows平台的分区来实现。

下面主要讲解如何使用Source Insight,考虑到阅读源程序的爱好者都有相当的软件使用水平,本文对于一些琐碎、人所共知的细节略过不提,仅介绍一些主要内容,以便大家能够很快熟练使用本软件,减少摸索的过程。

安装Source Insight并启动程序,可以进入图1界面。在工具条上有几个值得注意的地方,如图所示,图中内凹左边的是工程按钮,用于显示工程窗口的情况;右边的那个按钮按下去将会显示一个窗口,里边提供光标所在的函数体内对其他函数的调用图,通过点击该窗体里那些函数就可以进入该函数所在的地方。

图1 Source Insight界面图

图1 Source Insight界面图

由于Source Insight实质上是一个支持多种开发语言(java,c ,c++等等)的编辑器,只不过由于其查找、定位、彩色显示等功能的强大,而被我们当成源代码阅读工具使用。所以,为了有效的阅读源程序,首先必须选择功能菜单上的 “Project”选项的子菜单“New Project”新建一个项目,项目名称可以自由选定,当然也可以选择删除(Remove)一个项目。当删除一个项目的时候,并不删除原有的源代码文件,只是将该软件生成的那些工程辅助文件删除。设定之后,将会弹出一个对话框如图2,接受默认选择,如果,硬盘空间足够,可以将第一个复选框选上,该选项将会需要与源代码大致同等的空间来建立一个本地数据库以加快查找的速度。

图2 工程设置
图2 工程设置

点击“OK”按钮,接受选择后,将会有一个新的对话框弹出,在这个对话框里,可以选择将要阅读的文件加入工程,一种方式是通过在File Name中输入要阅读源代码文件的名称,点击“Add”按钮将其加入,也可以通过其中“Add All”和“Add Tree”两个按钮可以将选中目录的所有文件加入到工程中,其中“Add All”选项会提示加入顶层文件和递归加入所有文件两种方式,而“Add Tree”相当于“Add All”选项的递归加入所有文件,可以根据需要使用,就我来说,更喜欢“Add Tree”一些。由于该程序采用了部分打开文件的方式,没有用到的文件不会打开,所以,加入数千个文件也不用担心加入的文件超出程序的所能容忍的最大值,我就是采用“Add Tree”的方式将Linux2.4内核的四千五百九十一个文件加入的。

图3 添加文件
图3 添加文件

加入文件后,点击一个文件,可以出现使用界面,如图4所示,其中,右边的那个窗口(Linux Project,即工程窗口)缺省按照字母顺序列出当前工程中所有的文件。

图4 工作窗口
图4 工作窗口

点击一个文件就可以打开该文件,显示如图5所示,进入到右边的那个窗口分别可以以文件列表的方式,列出所有的文件,每个窗体下边有一排按钮,左边的窗口(21142.c)从左至右分别为:按字母顺序排列所有标记、按照文件中行数顺序排列标记、按照类型排列标记、浏览本地文件标记、标记窗口属性。右边的窗口(Linux Project)从左至右分别为:按字母顺序文件列表、显示文件夹、按照文件类型归类文件、全部文件的所有标记列表、按照标记类型归类标记、跳转到定义处、显示标记信息、浏览工程标记、查找函数调用、工程属性,其中全部文件的所有标记列表选项可能要一段时间抽取标记,同步到数据库去,如果开始选择了建立标记数据库,将会在今后节省同步时间,最有用的莫过于浏览标记信息和查找函数调用,前者可以通过“Jump”按钮在不同的地方查找同样的标志,还可以通过“Reference”按钮结合后者进行全局的标记查找。

图五

Reference功能是Source Insight的特色之一,它可以在速度极快的在整个工程中找到所有的标记,并且在该行程序的前边加上红色箭头的小按钮链接上。图6是一个Reference搜索后的结果,它可以有两种模式,一种集中显示结果,图6显示的就是这种模式,在这种模式下,可以通过前边的红色箭头小按钮进入另外一种模式,该标记的具体的所在处,也可以通过标记的具体所在处点击红色箭头小按钮进入警种模式,还可以通过工具条上的两个红色小箭头直接在第二种模式下前后移动,察看相应信息。它的这个强大的功能使得阅读Linux源程序有如神助。但是要注意的是,当进行了第二次“Reference”时,它会提示你将结果集附加在第一个结果集的后边还是取代第一个结果集。如果选择前者,不能对结果集根据前后两次搜索结果进行分类,然后在其子类里进行移动,只能在整个结果集里移动;如果,选择后者,结果集将会被替换为第二次搜索的结果,略微有些不方便。

图6 Reference的搜索结果
图6 Reference的搜索结果

当然,Source Insight 还提供了一些其他常见的便利。比如:右键菜单几乎包含了程序的所有功能,可以在编辑窗口为程序加上行号,还可以统计整个工程的程序行数,当然还有功能强大却用不上自动完成功能,似乎连它的30天试用期也是别有用心——可以迫使你尽可能快速的阅读源程序,其他一些技巧大家可以在使用过程中慢慢摸索。怎么样?爱好读源代码的朋友,不妨马上去下载一个,去开始我们的Linux内核探险之旅吧


<!-- show the whole post, as we would normally do -->

开始→运行→命令 集锦

<!-- <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"><rdf:Description rdf:about="http://fixedstar.itpub.net/post/7866/47719" dc:identifier="http://fixedstar.itpub.net/post/7866/47719" dc:title="开始→运行→命令 集锦" trackback:ping="http://blog.itpub.net//trackback.php?id=47719" /></rdf:RDF> -->

fixedstar | 05 十二月, 2005 23:27


winver---------检查Windows版本
wmimgmt.msc----打开windows管理体系结构(WMI)
wupdmgr--------windows更新程序
wscript--------windows脚本宿主设置
write----------写字板
winmsd---------系统信息
wiaacmgr-------扫描仪和照相机向导
winchat--------XP自带局域网聊天

mem.exe--------显示内存使用情况
Msconfig.exe---系统配置实用程序
mplayer2-------简易widnows media player
mspaint--------画图板
mstsc----------远程桌面连接
mplayer2-------媒体播放机
magnify--------放大镜实用程序
mmc------------打开控制台
mobsync--------同步命令

dxdiag---------检查DirectX信息
drwtsn32------ 系统医生
devmgmt.msc--- 设备管理器
dfrg.msc-------磁盘碎片
分享到:
评论

相关推荐

    20个VC开发小技巧txt'

    20个VC开发小技巧20个VC开发小技巧20个VC开发小技巧20个VC开发小技巧

    VC 开发20 个小技巧

    vc开发20个小技巧总结 重启计算机 打开计算机 打开cdrom 。。。。。。

    很实用的VC开发小技巧集锦

    很实用的VC++开发小技巧,汇集众多常见的问题的VC开发技巧

    VC开发小常识

    使用VC开发时候的一些小技巧,非常适用,初学者可以尝试一下

    VC小技巧经验总结和收集

    总结了在VC开发过程常用的一些知识点和技巧。这些小技巧简单实用,希望后来者少走些弯路。

    VC6.0中若干实用小技巧

    VC6.0中若干实用小技巧,VC6.0中若干实用小技巧,VC6.0中若干实用小技巧.

    VC若干小技巧.doc

    Visual C++6.0(5.0)开发工具功能非常强大,但是对于初学者来说,却有很多细节的问题需要注意。作者搜集整理了以下一些实用小技巧,希望对初学者有所帮助。

    vc中做大部分开发都有用的技巧

    在vc中开发的小技巧 这是我在多年的开发中总结出来的,供大家分享

    VC实用开发编程200例 part1

    VC开发中的小技巧文件,原书:《VC实用开发编程200例》的随书光盘。完美资源包。此为第一部分 第一部分:windows界面编程 第二部分:窗体界面 第三部分:图像处理 第四部分:多媒体 //第一部分 第五部分:网络与通信...

    [12]优秀程序员之路——C++开发经验及技巧大汇总.zip上位机开发VC串口学习资料源码下载

    [12]优秀程序员之路——C++开发经验及技巧大汇总.zip上位机开发VC串口学习资料源码下载[12]优秀程序员之路——C++开发经验及技巧大汇总.zip上位机开发VC串口学习资料源码下载[12]优秀程序员之路——C++开发经验及...

    VC技巧汇总2003

    包含了vc开发中经常用到的小技巧,常用函数等 是开发者不可缺少的手册

    VC与Labview、Matlab编程论文资料[2].rar

    这是VC与Labview、Matlab编程论文资料,全部为pdf格式,讲述有VC编程技巧、也有VC与Labview混合编程, VC与matlab混合编程,有365个,约500M,分成4个【独立】压缩包,如果想要全部的论文则需要下载全部压 缩包,...

    VC、C编程技巧收集中文版

    VC、C编程技巧收集,是一些htm格式的文档,比如一些基础的问题:如何学好VC、VC6中两个对话框的同时显示、利用C语言小程序来解决大问题、如何防止Edit框中的Password不被非法获娶在C++ Builder中编写控制面板应用...

    vc使用技巧集锦

    Visual C++6.0开发工具功能非常强大,但是对于初学者来说,却有很多细节的问题需要注意。作者搜集整理了以下一些实用小技巧,希望对初学者有所帮助。

    VC小技巧汇总之窗口技巧

    主要介绍了VC小技巧汇总之窗口技巧,功能非常实用,对于VC开发有一定借鉴价值,需要的朋友可以参考下

    VC小技巧汇总之对话框技巧

    主要介绍了VC小技巧汇总之对话框技巧,非常实用!对于进行VC开发有一定的参考借鉴价值,需要的朋友可以参考下

    VC小技巧汇总之控件技巧

    主要介绍了VC小技巧汇总之控件技巧,对于VC的开发很有借鉴价值,需要的朋友可以参考下

Global site tag (gtag.js) - Google Analytics