关于作者

用户名:scmcopew
笔名:scmcopew
地区:
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



访问统计:
文章个数:18
评论个数:3
留言条数:0




Powered by BlogDriver 2.1

cmshi的开发备忘录

 

欢迎访问scmcopew的博客

文章

Report风格的ListCtrl控件,当鼠标点击时,如何确定被点击单元的Item和subItem?

问题: Report风格的ListCtrl控件,当鼠标点击时,如何确定被点击单元的Item和subItem?

<>回答:

        POSITION pos;
    
CRect rect;
    
int iItem;
    
int iSubItem;
    
iItem=m_nList.GetNextItem(-1,LVNI_SELECTED);
    
POINT pt;
    
GetCursorPos(&pt);
    
GetWindowRect(rect);
    
pt.x-=rect.left;
    
pt.y-=rect.top;
    
    
for ( int i=0, j=0; ; i++)
    
{
    
j+=GetColumnWidth(i);
    
if ( pt.x < j )
    
break;
    
}
    
    
iSubItem = i;

- 作者: scmcopew 2005年07月5日, 星期二 20:07  回复(0) |  引用(0) 加入博采

MFC消息循环机制
Windows程序和DOS程序的主要不同点之一是:Windows程序是以事件为驱动、消息机制为基础。如何理解? 举了例子,当你CLICK Windows “开始”BUTTON时,为什么就会弹出一个菜单呢? 当你单击鼠标左键时,操作系统中与MOUSE相关的驱动程序在第一时间内得到这个信号[LBUTTONDOWN],然后它通知操作系统―――“嗨,鼠标左键被单击了!”,操作系统得到这一信号后,马上要判断――用户单击鼠标左键,这是针对哪个窗口呢?如何判断?这很简单!当前状态中,具有焦点的窗口[或控件]就是了[这里当然是“开始”BUTTON了]。然后操作系统马上向这个窗口发送一条消息到这个窗口所在进程的消息队列,消息内容应是消息本身的代号、附加参数、窗口句柄…等等了。那么,只有操作系统才有资格发送消息至某一窗口的消息队列吗?不然,其它程序也有资格。你可以在你的程序中调用:SendMessage 、PostMessage。这样,被单击的窗口得到了一条由操作系统发送的包含CLICK的消息,操作系统已经暂时不再管窗口的任何事,因为它还要忙于处理其它事务。你的程序得到一条消息后如何做呢?Windows对于你在“开始”BUTTON上的单击事件做出如下反映:弹出一菜单。可是,得到消息到做出反映这一过程是如何实现的呢?这就是本文讨论的主要内容[当然只是针对MFC了]。 我首先简要谈一下SDI,然后会花更多文字描述模式对话框。 对于SDI窗口,你的应用程序类的InitInstance()大约如下: BOOL CEx06aApp::InitInstance() { …………… CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CEx06aDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CEx06aView)); AddDocTemplate(pDocTemplate); CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); if (!ProcessShellCommand(cmdInfo)) return FALSE; m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); return TRUE; } 完成一些如动态生成相关文档、视,显示主框架窗口、处理参数行信息等工作。这些都是显示在你工程中的“明码”。我们现在把断点设置到return TRUE;一句,跟入MFC源码中,看看到底MFC内部做了什么。 程序进入SRC\WinMain.cpp,下一个大动作应是: nReturnCode = pThread->Run(); 各位看官注意了,重点来了。F11进入 int CWinApp::Run() { if (m_pMainWnd == NULL && AfxOleGetUserCtrl()) { // Not launched /Embedding or /Automation, but has no main window! TRACE0("Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application.\n"); AfxPostQuitMessage(0); } return CWinThread::Run(); } 再次F11进入: int CWinThread::Run() { ASSERT_VALID(this); // for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; // acquire and dispatch messages until a WM_QUIT message is received. for (;;) { // phase1: check to see if we can do idle work while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } // phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } ASSERT(FALSE); // not reachable } BOOL CWinThread::IsIdleMessage(MSG* pMsg) { // Return FALSE if the message just dispatched should _not_ // cause OnIdle to be run. Messages which do not usually // affect the state of the user interface and happen very // often are checked for. // redundant WM_MOUSEMOVE and WM_NCMOUSEMOVE if (pMsg->message == WM_MOUSEMOVE || pMsg->message == WM_NCMOUSEMOVE) { // mouse move at same position as last mouse move? if (m_ptCursorLast == pMsg->pt && pMsg->message == m_nMsgLast) return FALSE; m_ptCursorLast = pMsg->pt; // remember for next time m_nMsgLast = pMsg->message; return TRUE; } // WM_PAINT and WM_SYSTIMER (caret blink) return pMsg->message != WM_PAINT && pMsg->message != 0x0118; } 这是SDI处理消息的中心机构,但请注意,它觉对不是核心! 分析一下,在无限循环FOR内部又出现一个WHILE循环 while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } 这段代码是当你程序进程的消息队列中没有消息时,会调用OnIdle做一些后备工作, 临时对象在这里被删除。当然它是虚函数。其中的PeekMessage,是查看消息队列,如果有消息返回TRUE,如果没有消息返回FALSE,这里指定PM_NOREMOVE,是指查看过后不移走消息队列中刚刚被查看到的消息,也就是说这里的PeekMessage只起到一个检测作用,显然返回FALSE时[即没有消息],才会进入循环内部,执行OnIdle,当然了,你的OnIdle返回FLASE,会让程序不再执行OnIdle。你可能要问: 当bidle=0或消息队例中有消息时,程序又执行到哪了呢? do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); 看啊,又进入一个循环! 其中有个重要的函数,PumpMessage,内容如下: BOOL CWinThread::PumpMessage() { ASSERT_VALID(this); if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) { #ifdef _DEBUG if (afxTraceFlags & traceAppMsg) TRACE0("CWinThread::PumpMessage - Received WM_QUIT.\n"); m_nDisablePumpCount++; // application must die // Note: prevents calling message loop things in ’ExitInstance’ // will never be decremented #endif return FALSE; } #ifdef _DEBUG if (m_nDisablePumpCount != 0) { TRACE0("Error: CWinThread::PumpMessage called when not permitted.\n"); ASSERT(FALSE); } #endif #ifdef _DEBUG if (afxTraceFlags & traceAppMsg) _AfxTraceMsg(_T("PumpMessage"), &m_msgCur); #endif // process this message if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur)) { ::TranslateMessage(&m_msgCur); ::DispatchMessage(&m_msgCur); } return TRUE; } 如你所想,这才是MFC消息处理的核心基地[也是我个人认为的]。 GetMessage不同于PeekMessae,它是不得到消息不罢体,PeekMessage如果发现消息队列中没有消息会返回0,而GetMessage如果发现没有消息,等,直到有了消息,而且,GetMessage不同于PeekMessage,它会将消息移走[当然,PeekMessage也可以做到这点]。我想当你读了这个函数后,你应明白PreTranslateMessage函数的用法了吧[我比较喜欢在程序中充分利用这个函数]。 ::TranslateMessage(&m_msgCur); ::DispatchMessage(&m_msgCur); 将消息发送到窗口的处理函数[它是由窗口类指定的],之后的动作一直到你的程序做出反映的过程,你可以在《深入》一书中得到完美的解释。我们还是通过reurn TRUE;回到CWinThread::Run()中的Do{}while;循环。然后还是对IDLE的处理,即便刚才你的ONIDLE返回了FALSE,在这里你看到,你的程序还是有机会执行它的。然后又是利用PeekMessage检测消息队列: 如果有消息[这个消息不被移动的原因是因为它要为PumpMessage内的GetMessage所利用。]再次进入PumpMessage[叫它“消息泵”吧]。 如果没有消息,退出DO循环,但它还在FOR内部,所以又执行第一个While循环。 这是CwinThread::Run的一个执行过程。 不用担心退不出for(;;)如果你的消息队列中有一条WM_QUIT,会使GetMessage返回0,然后PumpMessage返回0而RUN()内部: if (!PumpMessage()) return ExitInstance();。 SDI就说到这,下面我来谈一下模式对话框。我分2种情况讨论: 一当你的工程以模式对话框为基础时[没父窗口,或为桌面]。 与SDI不同处在于,在应用程序类的InItInstance内部: BOOL CComboBoxApp::InitInstance() { AfxEnableControlContainer(); // Standard initialization // If you are not using these features and wish to reduce the size // of your final executable, you should remove from the following // the specific initialization routines you do not need. #ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL #else Enable3dControlsStatic(); // Call this when linking to MFC statically #endif this->m_nCmdShow = SW_HIDE; CComboBoxDlg 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 } // Since the dialog has been closed, return FALSE so that we exit the // application, rather than start the application’s message pump. return FALSE; } int nResponse = dlg.DoModal();一句使你的整个程序都在DoModal()内部进行。而且,你退出DoMal()时[你一定结束了你的对话框],InitInstance返回的是False,我们知道,这样,CwinThread::Run是不会执行的。 但对话框程序是在哪里进行消息处理的呢。 原来,dlg.DoModal()内部会调用CwinThread::RunModalLoop,它起到的作用和RUN()是一样的[当然内部有细小差别,请参考MSDN]!!! 第二种情况,你是在SDI[或其它]程序中调用Dlg.DoModal() 产生了一模式对话框[有父窗口]. 这又是如何运作的呢? 建了这样一个工程做为例子。 SDI,在View中处理LBUTTONDOWN: MyDLg.DoModal(); MyDLg内有按钮,以惫后用. 没有显示模式对话框前,消息处理一直在Cthread::Run()中进行. 你单击后,程序执行点进入DoModal()内部的RunModalLoop,这又是一个消息处理机制. 不过DoModal()中调用RunModalLoop,前会Disable掉它的父窗口.从RunModalLoop中出来后,再 Enable它. 模式对话框和非模式对话框都是通过调用CreateDialogIndirect()产生创建对话框.那它和非模式对话框区别是什么造成的呢? 1 模式对话框将父窗口DISABLE掉. 我原以为被Disable的窗口是不接收消息的.但后来我马上发现我是错的.但,为什么你对被Disable的窗口进行KeyBorad,Mouse动作时,窗口没反映呢,我想,这可能是操作系统从中搞的鬼.我在本文一开始,就写出操作系统向窗口发送消息的过程,我想当它发现目标窗口处理Disabled状态时,不会将消息发送给它,但这不能说窗口不接收消息,其它程序[或它本身]发送给它的消息还是可以接收并处理的. 2 模式对话框本身有消息处理机制 RunModalLoop. 对以上两点加以实验. 我在我的刚才建的项目中的模式对话框中加上一个BUTTON,其中加入如下代码: OnButton1() { GetParaent()->EnableWindow(1); } 单击,后我们发现,此时它已经不再表现为”模态”,我试着点击菜单,还是会作出正常反映. 我想这此消息[对于父窗口的,如:菜单动作]的处理也应是在模式对话框中的RunModalLoop中进行处理的吧[这点我不能确定]. 先写到这里吧,一点浊见.请大家批评. 2002-4-24 这篇文章对于初学windows编程以及正在使用windows编程的人来说,可以算是一篇较好的文章,在这里站长把它发表出来,

- 作者: scmcopew 2005年07月5日, 星期二 20:06  回复(0) |  引用(0) 加入博采

LPCTSTR是什么意思?

如何理解LPCTSTR,
L表示long指针, 这是为了兼容Windows 3.1等16位操作系统遗留下来的, 在win32中以及其他的32为操作系统中, long指针和near指针及far修饰符都是为了兼容的作用。没有实际意义。

P表示这是一个指针
C表示是一个常量
T在Win32环境中, 有一个_T宏, 这个宏用来表示你的字符是否使用UNICODE, 如果你的程序定义了UNICODE或者其他相关的宏, 那么这个字符或者字符串将被作为UNICODE字符串, 否则就是标准的ANSI字符串。
STR表示这个变量是一个字符串。

所以LPCTSTR就表示一个指向常固定地址的可以根据一些宏定义改变语义的字符串。
同样, LPCSTR就只能是一个ANSI字符串, 在程序中我们大部分时间要使用带T的类型定义。

LPCTSTR == const TCHAR *

- 作者: scmcopew 2005年06月29日, 星期三 00:12  回复(0) |  引用(0) 加入博采

C#和C++区别汇总

在程序中直接使用关键字作为标识符是错误的,但是可以在关键字前面加上@前缀,这个字符串也成了一个合法的标识符。

C#中一般有2种类型的字符串常数:常规字符串和逐字字符串。

常规字符串:"My teacher is a good person"

逐字字符串:在常规字符串前加@符号 比如:@"My teacher is a good person"

区别在于:逐字字符串中的每个字符都代表它本来的意思,也就是逐字字符串是不能用转义字符的,而常规字符串是可以用转义字符的。

C#中的小数型数值类型decimal,可以精确到28或29位,为了满足一些财政计算的需要

在C#中一些类型永远只能是值类型,而另一些类型也永远只能是引用类型,不像在C++中可以用&符号把某一种类型定义为引用类型

- 作者: scmcopew 2005年06月26日, 星期日 20:23  回复(0) |  引用(0) 加入博采

在VC++中访问和修改系统注册表

---- Windows95/98的注册表包含了Windows95/98的系统配置、PC机的硬件配置、WIN32
应用程序和用户的其他设置信息。注册表和INI文件不同,它是多层次的树状数据结
构,具有六个分支(根键),每个分支又由许多的键和键值组成,而每个键则代表一个
特定的配置项目。

---- 在Visual Basic中可以使用自带的注册表函数(如SaveSetting、GetSetting、
GetAllSettings和DeleteSetting)来访问系统的注册表。但遗憾的是,VB的这几个函
数只能在系统的注册表的固定位置进行工作,即只能在
\KEY_CURRENT_USER\Software\VB and VBA Program Settings下完成访问和修改操作。

---- 在实际的编程工作中,我们遇到了如何在Visual C++中对Windows95/98注册表整
个树状结构信息进行访问和修改的问题,如查询和修改注册表中用户姓名和公司名称的
有关信息。我们查询了许多资料,均未找到讨论在VC++中访问注册表的文章。通过一段
时间的编程实践,我们实现了在Visual C++中查询和修改系统注册表的有关信息。下面
将以一个实例说明具体的编程方法。

---- 在Visual C++ 6.0或5.0环境中新建一基于对话框的工程,我们设置了两个命令按
钮,名为“查询用户信息”和“修改用户信息”,用来查询和修改注册表中用户姓名和
公司名称。这里需要指出的是,用户的信息位于系统注册表中
\KEY_CURRENT_USER\Software\Microsoft\MS Setup (ACME)\User Info\ 的位置,键值
名DefName和DefCompany分别表示用户的姓名和用户公司的名称。

---- 1. 查询用户信息的代码

---- HKEY hKEY;//定义有关的 hKEY, 在查询结束时要关闭

LPCTSTR data_Set="Software\\
Microsoft\\MS Setup (ACME)\\User Info\\";

---- file://打开与路径 data_Set 相关的 hKEY,第一个参数为根键名称,第二个参
数表
---- file://示要访问的键的位置,第三个参数必须为0,KEY_READ表示以查询的方式

---- file://访问注册表,hKEY则保存此函数所打开的键的句柄

long ret0=(::RegOpenKeyEx
(HKEY_CURRENT_USER, data_Set, 0, KEY_READ,
&hKEY));

if(ret0!=ERROR_SUCCESS) //
如果无法打开hKEY,则终止程序的执行
{
MessageBox("错误: 无法打开有关的hKEY!");
return;
}

file://查询有关的数据 (用户姓名 username_Get)
LPBYTE username_Get=new BYTE[80];
DWORD type_1=REG_SZ ; DWORD cbData_1=80;

//hKEY为刚才RegOpenKeyEx()
函数所打开的键的句柄,"DefName"表示要查
//询的键值名,type_1表示查询数据的类型,
username_Get保存所查询的数据,
file://cbData_1表示预设置的数据长度
long ret1=::RegQueryValueEx(hKEY,
"DefName", NULL,&type_1, username_Get,
&cbData_1);

if(ret1!=ERROR_SUCCESS)
{
MessageBox("错误:
无法查询有关注册表信息!");
return;
}

// 查询有关的数据 (公司名 company_Get)
LPBYTE company_Get=new BYTE [80];
DWORD type_2=REG_SZ; DWORD cbData_2=80;

long ret2=::RegQueryValueEx(hKEY,
"DefCompany", NULL,&type_2,
company_Get, &cbData_2);

if(ret2!=ERROR_SUCCESS)
{
MessageBox("错误: 无法查询有关注册表信息!");
return;
}

// 将 username_Get 和 company_Get
转换为 CString 字符串, 以便显示输出
CString str_username=CString(username_Get);
CString str_company=CString(company_Get);

delete[] username_Get;
delete[] company_Get;

// 程序结束前要关闭已经打开的 hKEY
::RegCloseKey(hKEY);
……

---- 这样,上述程序执行完毕,字符串str_username和str_company则表示查询到的用
户的姓名和公司的名称,在VC++中便可用对话框的方式将其显示出来。
---- 2. 修改用户信息的代码(注意和上述的查询代码属于不同的函数体)

---- 在程序中我们先显示一个对话框,要求用户输入新的用户姓名和公司名称并按确
认键,将取得CString类型的有关字符串。要先将其转换为LPBYTE(即unsigned
char*)型的数据类型,以便后面的函数调用。下面是程序中用到的将CString型转换
为LPBYTE的转换函数:

LPBYTE CString_To_LPBYTE(CString str)
{
LPBYTE lpb=new BYTE[str.GetLength()+1];

for(int i=0; i< str.GetLength();
i++)lpb[i]=str[i];
lpb[str.GetLength()]=0;

return lpb;
}

以下则是具体的修改注册表用户信息的代码:

CString str_username, str_company;

…… file://通过对话框输入新的用户信息,
保存到str_username和str_company

file://定义有关的 hKEY, 在程序的最后要关闭
HKEY hKEY;
LPCTSTR data_Set="Software\\
Microsoft\\MS Setup (ACME)\\User Info\\";

file://打开与路径 data_Set 相关的hKEY,
KEY_WRITE表示以写的方式打开
long ret0=(::RegOpenKeyEx
(HKEY_CURRENT_USER,
data_Set, 0, KEY_WRITE, &hKEY));

if(ret0!=ERROR_SUCCESS)
{
MessageBox("错误: 无法打开有关的hKEY!");
return;
}

file://修改有关数据(用户姓名 username_Set),
要先将CString型转换为LPBYTE
LPBYTE username_Set=CString_
To_LPBYTE(str_username);

DWORD type_1=REG_SZ;
DWORD cbData_1=str_username.
GetLength()+1;

file://与RegQureyValueEx()类似,
hKEY表示已打开的键的句柄,"DefName"表示要
file://访问的键值名,username_Set
表示新的键值,type_1和cbData_1表示新值的
file://数据类型和数据长度
long ret1=::RegSetValueEx(hKEY,
"DefName", NULL,
type_1, username_Set, cbData_1);

if(ret1!=ERROR_SUCCESS)
{
MessageBox("错误: 无法修改有关注册表信息!");
return;
}

file://修改有关的数据 (公司名 company_Set)
LPBYTE company_Set=CString_
To_LPBYTE(str_company);
DWORD type_2=REG_SZ;
DWORD cbData_2=str_company.
GetLength()+1;


long ret2=::RegSetValueEx(hKEY,
"DefCompany", NULL,
type_2, company_Set, cbData_2);

if(ret2!=ERROR_SUCCESS)
{
MessageBox("错误:
无法修改有关注册表信息!");
return;
}

---- 执行上面的修改注册表的操作后,可打开注册表查看具体的数值,可以看到已经
成功地修改了有关的数据了。
---- 以上以一个实例讲述了如何在VC++中访问Windows98/95的系统注册表,我们可以
很方便查询及修改注册表的任何位置的有关信息。以上的程序在Visual C++ 6.0中已经
调试通过(Visual C++ 5.0与之类似),而且运行结果正确。

- 作者: scmcopew 2005年06月20日, 星期一 22:22  回复(0) |  引用(0) 加入博采

注册表知识
PC机及其操作系统的一个特点就是允许用户按照自己的要求对计算机系统的硬件和软件进行各种各样的配置。早期的图形操作系统,如Win3.x中,对软硬件工作环境的配置是通过对扩展名为.ini的文件进行修改来完成的,但INI文件管理起来很不方便,因为每种设备或应用程序都得有自己的INI文件,并且在网络上难以实现远程访问。

为了克服上述这些问题,在Windows 95及其后继版本中,采用了一种叫做“注册表”的数据库来统一进行管理,将各种信息资源集中起来并存储各种配置信息。按照这一原则,Windows各版本中都采用了将应用程序和计算机系统全部配置信息容纳在一起的注册表,用来管理应用程序和文件的关联、硬件设备说明、状态属性以及各种状态信息和数据等。

与INI文件不同的是

1.注册表采用了二进制形式登录数据;
2.注册表支持子键,各级子关键字都有自己的“键值”;
3.注册表中的键值项可以包含可执行代码,而不是简单的字串;
4.在同一台计算机上,注册表可以存储多个用户的特性。

注册表的特点有:
1.注册表允许对硬件、系统参数、应用程序和设备驱动程序进行跟踪配置,这使得修改某些设置后不用重新启动成为可能。
2.注册表中登录的硬件部分数据可以支持高版本Windows的即插即用特性。当Windows检测到机器上的新设备时,就把有关数据保存到注册表中,另外,还可以避免新设备与原有设备之间的资源冲突。
3.管理人员和用户通过注册表可以在网络上检查系统的配置和设置,使得远程管理得以实现。

我们在前面已经详细介绍了注册表的由来与基本结构。发现注册表比较复杂,但又安排得非常有条理,能有效地提高工作效率,为系统的维护提供了必要条件。由于注册表是一个二进制的配置数据库文件(Windows的命根子),因而,用户无法直接存取注册表。为了让高级用户能够编辑注册表,Windows2000提供了注册表编辑器“c”和“Regedt32”。对这种只使用Windows提供的注册表编辑器进行编辑的操作。

编辑器在安装Windows时已经被安装到硬盘中了,但是并未在“附件”程序组中建有快捷方式。用户如果需要使用注册表编辑器,可以在“运行”对话框内输入Regedt32或Regedit即可打开注册表编辑器,如图:













  或者在“命令提示符”中执行Regedt32.exe也可以进入注册表编辑器,
  如图:























  后面的内容将介绍注册表编辑器的使用方法,如创建删除主键、子键以及键值等,同时还将给出一些修改注册表的实例。

在Windows98的注册表中,所有的数据都是通过一种树状结构以键和子键的方式组织起来,就象我们的磁盘文件系统的目录结构一样。每个键都包含了一组特定的信息,每个键的键名都是和它所包含的信息相关联的。如果某个键包含了子键,则在注册表编辑器窗口中代表这个键的文件夹的左边将有“+”符号,以表示在这个文件夹中有更多的内容。如果这个文件夹被用户打开了,那么这个“+”就会变成“-”,我们可以象打开文件夹一样层层的打开注册表树,当然我们有时并不清楚我们要找的键在哪个目录分支下面,我们就得搜索相应的关键字。我们来看看注册表树最顶层的六个分支所分别代表的含义,这样我们在修改的时候就可以做到心中有数了。

 1.HKEY_CLASSES_ROOT
  管理文件系统。根据在Windows 98中安装的应用程序的扩展名,该根键指明其文件类型的名称,相应打开该文件所要调用的程序等等信息。

     2.HKEY_CURRENT_USER
  管理系统当前的用户信息。在这个根键中保存了本地计算机中存放的当前登录的用户信息,包括用户登录用户名和暂存的密码。在用户登录Windows 98时,其信息从HKEY_USERS中相应的项拷贝到HKEY_CURRENT_USER中。

     3.HKEY_LOCAL_MACHINE
  管理当前系统硬件配置。在这个根键中保存了本地计算机硬件配置数据,此根键下的子关键字包括在SYSTEM.DAT中,用来提供HKEY_LOCAL_MACHINE所需的信息,或者在远程计算机中可访问的一组键中。
  这个根键里面的许多子键与System.ini文件中设置项类似。

       4.HKEY_USERS
  管理系统的用户信息。在这个根键中保存了存放在本地计算机口令列表中的用户标识和密码列表。同时每个用户的预配置信息都存储在HKEY_USERS根键中。HKEY_USERS是远程计算机中访问的根键之一。

   5.HKEY_CURRENT_CONFIG
  管理当前用户的系统配置。在这个根键中保存着定义当前用户桌面配置(如显示器等等)的数据,该用户使用过的文档列表(MRU),应用程序配置和其他有关当前用户的Windows 98中文版的安装的信息。

 6.HKEY_DYN_DATA
  管理系统运行数据。在这个根键中保存了系统在运行时的动态数据,此数据在每次显示时都是变化的,因此,此根键下的信息没有放在注册表中。

      在Windows98 ⒉岜碇校峭ü妥蛹垂芾砀髦中畔ⅰM保⒃诓岜砝锩娴乃行畔⑹且愿髦中问降募迪钍荼4嫦吕础T谧⒉岜肀嗉鞯挠掖翱谥校4娴亩际歉髦旨迪钍荨<迪钣杉得⑹堇嘈秃图等糠肿槌桑涓袷轿骸凹得菏堇嘈停杭怠薄?nbsp;这些键值项数据可分为如下三种类型:
  1.字符串值(S)
  在Windows98的注册表中,表示文件的描述、硬件的标识等等信息一般都用字符串值。字符串值由字母和数字组成,它的最大长度不能超过255个字符。通过键、键值就组成了一种键值项数据,这就相当于Win.ini、Ssytem.ini文件中每个小节下面的设置行一样的道理。

  2.二进制值(B)
  在Windows的注册表中,二进制值是没有长度限制的,可以是任意个字节长。在注册表编辑器中,二进制以十六进制的方式显示出来。

  3.DWORD值(D)
  在Windwos98的注册表中,DWORD值是一个32位(双字节长)长度的数值。在注册表编辑器中,系统以十六进制的方式显示DWORD值。

   在修改中这么多子键并不一定都用得作,其中对我们最有用还是 HKEY_LOCAL_MACHINE和 HKEY_USERS这两个键下面的子键:
   我们先看看 HKEY_LOCAL_MACHINE键先面的几个重要的子键及其作用:
  1)HKEY_LOCAL_MACHINE\software\microsoft\windows\currentVersion\uninstall 保存Windows98系统中已经安装了的Windows应用程序卸载信息。

   2)HKEY_LOCAL_MACHINE\system\currentControl-Set\control\keyboard Layouts 保存Windows98中键盘使用的语言以及各种中文输入法的信息。
 
  3)HKEY_LOCAL_MACHINE\software\microsoft\windows\currentVersion\explorer\user shell folders 保存计算机中个人文件夹、收藏夹的路径。

  4)HKEY_LOCAL_MACHINE\system\CurrentControl-Set\services\class 保存控制面板-增添硬件设备-设备类型目录,全面管理你的硬件信息。

  5)HKEY_LOCAL_MACHINE\software\microsoft\win-dows\currentVersion\run 保存由控制面板设定的计算机启动时运行程序的名称,其图标显示在任务条右边。这也是我们经常修改和用到的一个目录。

  6)HKEY_LOCAL_MACHINE\software\microsoft\windows\currentVersion\Policies\Ratings 保存了IE的“安全”\“分级审查”中设置的口令(数据加密),若遗忘了口令,删除 Ratings 中的数据即可解决问题。

  7)HKEY_LOCAL_MACHINE\software\microsoft\windows\currentVersion\explorer\desktop\nameSpace 保存桌面中特殊的图标,如回收站、收件箱、网上邻居等等,你可以把它改得面目全非,人家都认不出来。

  先面我们再来看看另外一个重要的键HKEY_USERS下面的重要分支:
  1)HKEY_USERS\.Default\so..\microsoft\windows\current-Version\explorer\RunMRU保存“开始 \ 运行...”中运行的程序列表信息。清除文档菜单时该分支将被清空。

   2)HKEY_USERS\.Default\software\microsoft\internet explorer\typeURLs保存IE4.0浏览器地址栏中输入的URL地址列表信息。清除文档菜单时它也将被清空。

  3)HKEY_USERS\.Default\so..\microsoft\windows\current-Version\explorer\RecentDocs 保存最近使用的十五个(数目是可以修改的)文档的快捷方式,清除文档菜单时将被清空。

  4)HKEY_USERS\.default\software\microsoft\windows\currentVersion\applets 保存Windows98应用程序的记录数据信息。
  
    在上面我们大致介绍了Windows98的注册表的结构和重要的信息,这对于我们修改注册表是非常有用的。

- 作者: scmcopew 2005年06月20日, 星期一 22:19  回复(0) |  引用(0) 加入博采

如何检测一个文件是否存在?

ATLPath::FileExists
This function is an overloaded wrapper for PathFileExists
inline BOOL FileExists(
   const char* pszPath
);
inline BOOL FileExists(
   const wchar_t* pszPath
);
Remarks
See PathFileExists for details

PathFileExists Function

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

Determines whether a path to a file system object such as a file or directory is valid.

Syntax

BOOL PathFileExists(          LPCTSTR pszPath
);
Parameters

pszPath
[in] Pointer to a null-terminated string of maximum length MAX_PATH that contains the full path of the object to verify.
Return Value

Returns TRUE if the file exists, or FALSE otherwise. Call GetLastError for extended error information.

Remarks

This function tests the validity of the path. It works only on the local file system or on a remote drive that has been mounted to a drive letter. It returns FALSE if a mounted remote drive is out of service.

Example


Hide Example

#include
#include
#include "Shlwapi.h"

void main(void)
{
    // Valid file path name (file is there).
    char buffer_1[ ] = "C:\\TEST\\file.txt";
    char *lpStr1;
    lpStr1 = buffer_1;
   
    // Invalid file path name (file is not there).
    char buffer_2[ ] = "C:\\TEST\\file.doc";
    char *lpStr2;
    lpStr2 = buffer_2;
   
    // Return value from "PathFileExists".
    int retval;
   
    // Search for the presence of a file with a true result.
    retval = PathFileExists(lpStr1);
    if(retval == 1)
    {
        cout << "Search for the file path of : " << lpStr1 << endl;
        cout << "The file requested \"" << lpStr1 << "\" is a valid file" << endl;
        cout << "The return from function is : " << retval << endl;
    }
   
    else
    {
        cout << "\nThe file requested " << lpStr1 << " is not a valid file" << endl;
        cout << "The return from function is : " << retval << endl;
    }
   
    // Search for the presence of a file with a false result.
    retval = PathFileExists(lpStr2);
   
    if(retval == 1)
    {
        cout << "\nThe file requested " << lpStr2 << "is a valid file" << endl;
        cout << "Search for the file path of : " << lpStr2 << endl;
        cout << "The return from function is : " << retval << endl;
    }
    else
    {
        cout << "\nThe file requested \"" << lpStr2 << "\" is not a valid file" << endl;
        cout << "The return from function is : " << retval << endl;
    }
}

OUTPUT
==============
Search for the file path of : C:\TEST\file.txt
The file requested "C:\TEST\file.txt" is a valid file
The return from function is : 1

The file requested "C:\TEST\file.doc" is not a valid file
The return from function is : 0
Function Information

Minimum DLL Version shlwapi.dll version 4.71 or later
Custom Implementation No
Header shlwapi.h
Import library shlwapi.lib
Minimum operating systems Windows 2000, Windows NT 4.0 with Internet Explorer 4.0, Windows 98, Windows 95 with Internet Explorer 4.0
Unicode Implemented as ANSI and Unicode versions. 

- 作者: scmcopew 2005年06月17日, 星期五 20:05  回复(0) |  引用(0) 加入博采

用Visual C++操作INI文件
在我们写的程序当中,总有一些配置信息需要保存下来,以便完成程序的功能,最简单的办法就是将这些信息写入INI文件中,程序初始化时再读入.具体应用如下:

  一.将信息写入.INI文件中.

  1.所用的WINAPI函数原型为:

BOOL WritePrivateProfileString(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
LPCTSTR lpString,
LPCTSTR lpFileName
);

  其中各参数的意义:

   LPCTSTR lpAppName 是INI文件中的一个字段名.

   LPCTSTR lpKeyName 是lpAppName下的一个键名,通俗讲就是变量名.

   LPCTSTR lpString 是键值,也就是变量的值,不过必须为LPCTSTR型或CString型的.

   LPCTSTR lpFileName 是完整的INI文件名.

  2.具体使用方法:设现有一名学生,需把他的姓名和年龄写入 c:\stud\student.ini 文件中.

CString strName,strTemp;
int nAge;
strName="张三";
nAge=12;
::WritePrivateProfileString("StudentInfo","Name",strName,"c:\\stud\\student.ini");

  此时c:\stud\student.ini文件中的内容如下:

   [StudentInfo]
   Name=张三

  3.要将学生的年龄保存下来,只需将整型的值变为字符型即可:

strTemp.Format("%d",nAge);
::WritePrivateProfileString("StudentInfo","Age",strTemp,"c:\\stud\\student.ini");
 二.将信息从INI文件中读入程序中的变量.

  1.所用的WINAPI函数原型为:

DWORD GetPrivateProfileString(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
LPCTSTR lpDefault,
LPTSTR lpReturnedString,
DWORD nSize,
LPCTSTR lpFileName
);

  其中各参数的意义:

   前二个参数与 WritePrivateProfileString中的意义一样.

   lpDefault : 如果INI文件中没有前两个参数指定的字段名或键名,则将此值赋给变量.

   lpReturnedString : 接收INI文件中的值的CString对象,即目的缓存器.

   nSize : 目的缓存器的大小.

   lpFileName : 是完整的INI文件名.

  2.具体使用方法:现要将上一步中写入的学生的信息读入程序中.

CString strStudName;
int nStudAge;
GetPrivateProfileString("StudentInfo","Name","默认姓名",strStudName.GetBuffer(MAX_PATH),MAX_PATH,"c:\\stud\\student.ini");

  执行后 strStudName 的值为:"张三",若前两个参数有误,其值为:"默认姓名".

  3.读入整型值要用另一个WINAPI函数:

UINT GetPrivateProfileInt(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
INT nDefault,
LPCTSTR lpFileName
);

  这里的参数意义与上相同.使用方法如下:

nStudAge=GetPrivateProfileInt("StudentInfo","Age",10,"c:\\stud\\student.ini");
三.循环写入多个值,设现有一程序,要将最近使用的几个文件名保存下来,具体程序如下:

  1.写入:

CString strTemp,strTempA;
int i;
int nCount=6;
file://共有6个文件名需要保存
for(i=0;i {strTemp.Format("%d",i);
strTempA=文件名;
file://文件名可以从数组,列表框等处取得.
::WritePrivateProfileString("UseFileName","FileName"+strTemp,strTempA,
"c:\\usefile\\usefile.ini");
}
strTemp.Format("%d",nCount);
::WritePrivateProfileString("FileCount","Count",strTemp,"c:\\usefile\\usefile.ini");
file://将文件总数写入,以便读出.

  2.读出:

nCount=::GetPrivateProfileInt("FileCount","Count",0,"c:\\usefile\\usefile.ini");
for(i=0;i {strTemp.Format("%d",i);
strTemp="FileName"+strTemp;
::GetPrivateProfileString("CurrentIni",strTemp,"default.fil", strTempA.GetBuffer(MAX_PATH),MAX_PATH,"c:\\usefile\\usefile.ini");

file://使用strTempA中的内容.

}

  补充四点:
   1.INI文件的路径必须完整,文件名前面的各级目录必须存在,否则写入不成功,该函数返回 FALSE 值.
   2.文件名的路径中必须为 \\ ,因为在VC++中, \\ 才表示一个 \ .
   3.也可将INI文件放在程序所在目录,此时 lpFileName 参数为: ".\\student.ini".

//----------------------------------------------------------------------------------
/*
类名:CIni
版本:v2.0
最后更新:
v2.0
梦小孩于2004年2月14日情人节
加入高级操作的功能
v1.0
梦小孩于2003年某日
一般操作完成

类描述:
本类可以于.ini文件进行操作
*/

文件 1:

#pragma once

#include "afxTempl.h"

class CIni
{
private:
CString m_strFileName;
public:
CIni(CString strFileName):m_strFileName(strFileName)
{
}
public:
//一般性操作:
BOOL SetFileName(LPCTSTR lpFileName); //设置文件名
CString GetFileName(void); //获得文件名
BOOL SetValue(LPCTSTR lpSection, LPCTSTR lpKey, LPCTSTR lpValue,bool bCreate=true); //设置键值,bCreate是指段名及键名未存在时,是否创建。
CString GetValue(LPCTSTR lpSection, LPCTSTR lpKey); //得到键值.
BOOL DelSection(LPCTSTR strSection); //删除段名
BOOL DelKey(LPCTSTR lpSection, LPCTSTR lpKey); //删除键名


public:
//高级操作:
int GetSections(CStringArray& arrSection); //枚举出全部的段名
int GetKeyValues(CStringArray& arrKey,CStringArray& arrValue,LPCTSTR lpSection); //枚举出一段内的全部键名及值

BOOL DelAllSections();

};

文件 2:

#include "StdAfx.h"
#include "ini.h"

#define MAX_ALLSECTIONS 2048 //全部的段名
#define MAX_SECTION 260 //一个段名长度
#define MAX_ALLKEYS 6000 //全部的键名
#define MAX_KEY 260 //一个键名长度

BOOL CIni::SetFileName(LPCTSTR lpFileName)
{
CFile file;
CFileStatus status;

if(!file.GetStatus(lpFileName,status))
 return TRUE;

m_strFileName=lpFileName;
return FALSE;
}

CString CIni::GetFileName(void)
{
return m_strFileName;
}

BOOL CIni::SetValue(LPCTSTR lpSection, LPCTSTR lpKey, LPCTSTR lpValue,bool bCreate)
{
TCHAR lpTemp[MAX_PATH] ={0};

//以下if语句表示如果设置bCreate为false时,当没有这个键名时则返回TRUE(表示出错)
//!*&*none-value*&!* 这是个垃圾字符没有特别意义,这样乱写是防止凑巧相同。
if (!bCreate)
{
 GetPrivateProfileString(lpSection,lpKey,"!*&*none-value*&!*",lpTemp,MAX_PATH,m_strFileName);
 if(strcmp(lpTemp,"!*&*none-value*&!*")==0)
  return TRUE;
}

if(WritePrivateProfileString(lpSection,lpKey,lpValue,m_strFileName))
 return FALSE;
else
 return GetLastError();
}

CString CIni::GetValue(LPCTSTR lpSection, LPCTSTR lpKey)
{
DWORD dValue;
TCHAR lpValue[MAX_PATH] ={0};

dValue=GetPrivateProfileString(lpSection,lpKey,"",lpValue,MAX_PATH,m_strFileName);
return lpValue;
}

BOOL CIni::DelSection(LPCTSTR lpSection)
{
if(WritePrivateProfileString(lpSection,NULL,NULL,m_strFileName))
 return FALSE;
else
 return GetLastError();
}

BOOL CIni::DelKey(LPCTSTR lpSection, LPCTSTR lpKey)
{
if(WritePrivateProfileString(lpSection,lpKey,NULL,m_strFileName))
 return FALSE;
else
 return GetLastError();
}


int CIni::GetSections(CStringArray& arrSection)
{
/*
本函数基础:
GetPrivateProfileSectionNames - 从 ini 文件中获得 Section 的名称
如果 ini 中有两个 Section: [sec1] 和 [sec2],则返回的是 'sec1',0,'sec2',0,0 ,当你不知道 
ini 中有哪些 section 的时候可以用这个 api 来获取名称
*/
int i; 
int iPos=0; 
int iMaxCount;
TCHAR chSectionNames[MAX_ALLSECTIONS]={0}; //总的提出来的字符串
TCHAR chSection[MAX_SECTION]={0}; //存放一个段名。
GetPrivateProfileSectionNames(chSectionNames,MAX_ALLSECTIONS,m_strFileName);

//以下循环,截断到两个连续的0
for(i=0;i<MAX_ALLSECTIONS;i++)
{
 if (chSectionNames[i]==0)
  if (chSectionNames[i]==chSectionNames[i+1])
  break;
}

iMaxCount=i+1; //要多一个0号元素。即找出全部字符串的结束部分。
arrSection.RemoveAll();//清空原数组

for(i=0;i<iMaxCount;i++)
{
 chSection[iPos++]=chSectionNames[i];
 if(chSectionNames[i]==0)
 { 
  arrSection.Add(chSection);
  memset(chSection,0,MAX_SECTION);
  iPos=0;
 }

}

return (int)arrSection.GetSize();
}

int CIni::GetKeyValues(CStringArray& arrKey,CStringArray& arrValue, LPCTSTR lpSection)
{
/*
本函数基础:
GetPrivateProfileSection- 从 ini 文件中获得一个Section的全部键名及值名
如果ini中有一个段,其下有 "段1=值1" "段2=值2",则返回的是 '段1=值1',0,'段2=值2',0,0 ,当你不知道 
获得一个段中的所有键及值可以用这个。
*/
int i; 
int iPos=0;
CString strKeyValue;
int iMaxCount;
TCHAR chKeyNames[MAX_ALLKEYS]={0}; //总的提出来的字符串
TCHAR chKey[MAX_KEY]={0}; //提出来的一个键名

GetPrivateProfileSection(lpSection,chKeyNames,MAX_ALLKEYS,m_strFileName);

for(i=0;i<MAX_ALLKEYS;i++)
{
 if (chKeyNames[i]==0)
  if (chKeyNames[i]==chKeyNames[i+1])
  break;
}

iMaxCount=i+1; //要多一个0号元素。即找出全部字符串的结束部分。
arrKey.RemoveAll();//清空原数组
arrValue.RemoveAll();

for(i=0;i<iMaxCount;i++)
{
 chKey[iPos++]=chKeyNames[i];
 if(chKeyNames[i]==0)
 {
  strKeyValue=chKey;
  arrKey.Add(strKeyValue.Left(strKeyValue.Find("=")));
  arrValue.Add(strKeyValue.Mid(strKeyValue.Find("=")+1));
  memset(chKey,0,MAX_KEY);
  iPos=0;
 }

}

return (int)arrKey.GetSize();
}

BOOL CIni::DelAllSections()
{
int nSection;
CStringArray arrSection;
nSection=GetSections(arrSection);
for(int i=0;i<nSection;i++)
{
 if(DelSection(arrSection[i]))
  return GetLastError();
}
return FALSE;
}


使用方法:
CIni ini("c:\\a.ini");
int n;

/*获得值
TRACE("%s",ini.GetValue("段1","键1"));
*/

/*添加值
ini.SetValue("自定义段","键1","值");
ini.SetValue("自定义段2","键1","值",false);
*/

/*枚举全部段名
CStringArray arrSection;
n=ini.GetSections(arrSection);
for(int i=0;i<n;i++)
TRACE("%s\n",arrSection[i]);
*/

/*枚举全部键名及值
CStringArray arrKey,arrValue;
n=ini.GetKeyValues(arrKey,arrValue,"段1");
for(int i=0;i<n;i++)
TRACE("键:%s\n值:%s\n",arrKey[i],arrValue[i]);
*/

/*删除键值
ini.DelKey("段1","键1");
*/

/*删除段
ini.DelSection("段1");
*/

/*删除全部
ini.DelAllSections();
*/

- 作者: scmcopew 2005年06月17日, 星期五 19:49  回复(0) |  引用(0) 加入博采

回调函数

使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。至于如何定义回调函数,跟具体使用的API函数有关,一般在帮助中有说明回调函数的参数和返回值等。C++中一般要求在回调函数前加CALLBACK,这主要是说明该函数的调用方式。DialogBox的回调函数实际上是个窗口过程,用来处理所有消息。其定义为:
    BOOL CALLBACK DialogProc(
    
     HWND hwndDlg, // handle of dialog box
     UINT uMsg, // message
     WPARAM wParam, // first message parameter
     LPARAM lParam // second message parameter
     );
    在Win32 API中有详细说明。一般使用C++ Builder或MFC的往往没有使用SDK编程的经验,建议找一些SDK编程的书看一下,否则很难理解如何使用窗口过程。
    至于钩子函数,只是回调函数的一个特例。习惯上把与SetWindowsHookEx函数一起使用的回调函数称为钩子函数。也有人把利用VirtualQueryEx安装的函数称为钩子函数,不过这种叫法不太流行。

程序员常常需要实现回调。本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。

声明函数指针

    回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子:

void f();// 函数原型

上面的语句声明了一个函数,没有输入参数并返回void。那么函数指针的声明方法如下:

void (*) ();

    让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。注意本例中还没有创建指针变量-只是声明了变量类型。目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:

// 获得函数指针的大小
unsigned psize = sizeof (void (*) ()); 

// 为函数指针声明类型定义
typedef void (*pfv) ();

pfv是一个函数指针,它指向的函数没有输入参数,返回类行为void。使用这个类型定义名可以隐藏复杂的函数指针语法。

指针变量应该有一个变量名:

void (*p) (); //p是指向某函数的指针

    p是指向某函数的指针,该函数无输入参数,返回值的类型为void。左边圆括弧里星号后的就是指针变量名。有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。例如:

void func() 
{
/* do something */

p = func; 

p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。

传递回调函数的地址给调用者

    现在可以将p传递给另一个函数(调用者)- caller(),它将调用p指向的函数,而此函数名是未知的:

void caller(void(*ptr)())
{
ptr(); /* 调用ptr指向的函数 */ 
}
void func();
int main()
{
p = func; 
caller(p); /* 传递函数地址到调用者 */
}

    如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。

调用规范

    到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

    将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:

// 被调用函数是以int为参数,以int为返回值
__stdcall int callee(int); 

// 调用函数以函数指针为参数
void caller( __cdecl int(*ptr)(int)); 

// 在p中企图存储被调用函数地址的非法操作
__cdecl int(*p)(int) = callee; // 出错


    指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列

- 作者: scmcopew 2005年06月15日, 星期三 22:51  回复(0) |  引用(0) 加入博采

ASSERT()是干什么用的
ASSERT()是一个调试程序时经常使用的宏,在程序运行时它计算括号内的表达式,如果表达式为FALSE (0), 程序将报告错误,并终止执行。如果表达式不为0,则继续执行后面的语句。这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序以免导致严重后果,同时也便于查找错误。例如,变量n在程序中不应该为0,如果为0可能导致错误,你可以这样写程序:
    ......
    ASSERT( n != 0);
    k = 10/ n;
    ......
    ASSERT只有在Debug版本中才有效,如果编译为Release版本则被忽略。
    assert()的功能类似,它是ANSI C标准中规定的函数,它与ASSERT的一个重要区别是可以用在Release版本中。

- 作者: scmcopew 2005年06月15日, 星期三 21:07  回复(1) |  引用(0) 加入博采