欢迎访问scmcopew的博客
Report风格的ListCtrl控件,当鼠标点击时,如何确定被点击单元的Item和subItem?
问题: Report风格的ListCtrl控件,当鼠标点击时,如何确定被点击单元的Item和subItem?
MFC消息循环机制
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 *
C#和C++区别汇总
在程序中直接使用关键字作为标识符是错误的,但是可以在关键字前面加上@前缀,这个字符串也成了一个合法的标识符。
C#中一般有2种类型的字符串常数:常规字符串和逐字字符串。
常规字符串:"My teacher is a good person"
逐字字符串:在常规字符串前加@符号 比如:@"My teacher is a good person"
区别在于:逐字字符串中的每个字符都代表它本来的意思,也就是逐字字符串是不能用转义字符的,而常规字符串是可以用转义字符的。
C#中的小数型数值类型decimal,可以精确到28或29位,为了满足一些财政计算的需要
在C#中一些类型永远只能是值类型,而另一些类型也永远只能是引用类型,不像在C++中可以用&符号把某一种类型定义为引用类型
在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与之类似),而且运行结果正确。
注册表知识
为了克服上述这些问题,在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的注册表的结构和重要的信息,这对于我们修改注册表是非常有用的。
如何检测一个文件是否存在?
用Visual C++操作INI文件
回调函数
使用回调函数实际上就是在调用某个函数(通常是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,尽管两者有相同的返回值和参数列
ASSERT()是干什么用的