我们截获函数执行最直接的目的就是为函数增添功能,修改返回值,或者为调试以及性能测试加入附加的代码,或者截获函数的输入输出作研究,破解使用。通过访 问源代码,我们可以轻而易举的使用重建(Rebuilding)操作系统或者应用程序的方法在它们中间插入新的功能或者做功能扩展。然而,在今天这个商业 化的开发世界里,以及在只有二进制代码发布的系统中,研究人员几乎没有机会可以得到源代码。本文主要讨论Detour在Windows二进制PE文件基础 上的API截获技术。对于Linux平台,作这件事情将会非常的简单,由于最初的操作系统设计者引入了LD_PRELOAD。如果你设置 LD_PRELOAD=mylib.so ,那么应用程序在载入 dll时,会先查看mylib.so的符号表,在relocation 的时候会优先 使用mylib.so 里的 symbol 。假如你在mylib.so里有个printf() ,那么这个printf就会替代libc的 printf。 而在mylib.so里的这个printf可以直接访问 libc.so里的printf函数指针来获得真正的 printf的入口地 址。 这样,所有的dll的API HOOK在loader加载dll的时候就已经完成,非常自然,和平台相关的部分全部交给loader去处理。
一、 Detour开发库:
简介
Detours是一个在x86平台上截获任意Win32函数调用的工具库。中断代码可以在运行时动态加载。Detours使用一个无条件转移指令来替换目 标函数的最初几条指令,将控制流转移到一个用户提供的截获函数。而目标函数中的一些指令被保存在一个被称为“trampoline” (译注:英文意为蹦 床,杂技)的函数中,在这里我觉得翻译成目标函数的部分克隆/拷贝比较贴切。这些指令包括目标函数中被替换的代码以及一个重新跳转到目标函数的无条件分 支。而截获函数可以替换目标函数,或者通过执行“trampoline”函数的时候将目标函数作为子程序来调用的办法来扩展功能。
Detours是执行时被插入的。内存中的目标函数的代码不是在硬盘上被修改的,因而可以在一个很好的粒度上使得截获二进制函数的执行变得更容易。例如, 一个应用程序执行时加载的DLL中的函数过程可以被插入一段截获代码(detoured),与此同时,这个DLL还可以被其他应用程序按正常情况执行(译 注:也就是按照不被截获的方式执行,因为DLL二进制文件没有被修改,所以发生截获时不会影响其他进程空间加载这个DLL)。不同于DLL的重新链接或者 静态重定向,Detours库中使用的这种中断技术确保不会影响到应用程序中的方法或者系统代码对目标函数的定位。
如果其他人为了调试或者在内部使用其他系统检测手段而试图修改二进制代码,Detours将是一个可以普遍使用的开发包。据我所知,Detours是第一 个可以在任意平台上将未修改的目标代码作为一个可以通过“trampoline”调用的子程序来保留的开发包。而以前的系统在逻辑上预先将截获代码放到目 标代码中,而不是将原始的目标代码做为一个普通的子程序来调用。我们独特的“trampoline”设计对于扩展现有的软件的二进制代码是至关重要的。
出于使用基本的函数截获功能的目的,Detours同样提供了编辑任何DLL导入表的功能,达到向存在的二进制代码中添加任意数据节表的目的,向一个新进 程或者一个已经运行着的进程中注入一个DLL。一旦向一个进程注入了DLL,这个动态库就可以截获任何Win32函数,不论它是在应用程序中或者在系统库 中。
基本原理
1. WIN32进程的内存管理
众所周知,WINDOWS NT实现了虚拟存储器,每一WIN32进程拥有4GB的虚存空间, 关于WIN32进程的虚存结构及其操作的具体细节请参阅WIN32 API手册, 以下仅指出与Detours相关的几点:
(1) 进程要执行的指令也放在虚存空间中
(2) 可以使用QueryProtectEx函数把存放指令的页面的权限更改为可读可写可执行,再改写其内容,从而修改正在运行的程序
(3) 可以使用VirtualAllocEx从一个进程为另一正运行的进程分配虚存,再使用 QueryProtectEx函数把页面的权限更改为可读可写可执行,并把要执行的指令以二进制机器码的形式写入,从而为一个正在运行的进程注入任意的代码 。
2. 拦截WIN32 API的原理
Detours定义了三个概念:
(1) Target函数:要拦截的函数,通常为Windows的API。
(2) Trampoline函数:Target函数的部分复制品。因为Detours将会改写Target函数,所以先把Target函数的前5个字节复制保存好,一方面仍然保存Target函数的过程调用语义,另一方面便于以后的恢复。
(3) Detour 函数:用来替代Target函数的函数。
Detours在Target函数的开头加入JMP Address_of_ Detour_ Function指令(共5个字节)把对Target函数 的调用引导到自己的Detour函数, 把Target函数的开头的5个字节加上JMP Address_of_ Target _ Function+ 5共10个字节作为Trampoline函数。请参考下面的图1和图2。
(图1:Detour函数的过程)
![](/images/cppblog_com/wanghaiguang/crack_01_thumb.jpg)
(图2: Detour函数的调用过程)
![](/images/cppblog_com/wanghaiguang/crack_02_thumb.jpg)
说明:
目标函数:
目标函数的函数体(二进制)至少有5个字节以上。按照微软的说明文档Trampoline函数的函数体是拷贝前5个字节加一个无条件跳转指令的话(如果没 有特殊处理不可分割指令的话),那么前5个字节必须是完整指令,也就是不能第5个字节和第6个字节是一条不可分割的指令,否则会造成Trampoline 函数执行错误,一条完整的指令被硬性分割开来,造成程序崩溃。对于第5字节和第6个字节是不可分割指令需要调整拷贝到杂技函数(Trampoline)的 字节个数,这个值可以查看目标函数的汇编代码得到。此函数是目标函数的修改版本,不能在Detour函数中直接调用,需要通过对Trampoline函数 的调用来达到间接调用。
Trampoline函数:
此函数默认分配了32个字节,函数的内容就是拷贝的目标函数的前5个字节,加上一个JMP Address_of_ Target _ Function+5指令,共10个字节。
此函数仅供您的Detour函数调用,执行完前5个字节的指令后再绝对跳转到目标函数的第6个字节继续执行原功能函数。
Detour函数:
此函数是用户需要的截获API的一个模拟版本,调用方式,参数个数必须和目标函数相一致。如目标函数是__stdcall,则Detour函数声明也必须 是__stdcall,参数个数和类型也必须相同,否则会造成程序崩溃。此函数在程序调用目标函数的第一条指令的时候就会被调用(无条件跳转过来的),如 果在此函数中想继续调用目标函数,必须调用Trampoline函数(Trampoline函数在执行完目标函数的前5个字节的指令后会无条件跳转到目标 函数的5个字节后继续执行),不能再直接调用目标函数,否则将进入无穷递归(目标函数跳转到Detour函数,Detour函数又跳转到目标函数的递归, 因为目标函数在内存中的前5个字节已经被修改成绝对跳转)。通过对Trampoline函数的调用后可以获取目标函数的执行结果,此特性对分析目标函数非 常有用,而且可以将目标函数的输出结果进行修改后再传回给应用程序。
Detour提供了向运行中的应用程序注入Detour函数和在二进制文件基础上注入Detour函数两种方式。本章主要讨论第二种工作方式。通过 Detours提供的开发包可以在二进制EXE文件中添加一个名称为Detour的节表,如下图3所示,主要目的是实现PE加载器加载应用程序的时候会自 动加载您编写的Detours DLL,在Detours Dll中的DLLMain中完成对目标函数的Detour。
(图3)
![](/images/cppblog_com/wanghaiguang/crack_03_thumb.jpg)
二、 Detours提供的截获API的相关接口
Detours的提供的API 接口可以作为一个共享DLL给外部程序调用,也可以作为一个静态Lib链接到您的程序内部。
Trampoline函数可以动态或者静态的创建,如果目标函数本身是一个链接符号,使用静态的trampoline函数将非常简单。如果目标函数不能在链接时可见,那么可以使用动态trampoline函数。
要使用静态的trampoline函数来截获目标函数,应用程序生成trampoline的时候必须使用
DETOUR_TRAMPOLINE宏。DETOUR_TRAMPOLINE有两个输入参数:trampoline的原型和目标函数的名字。
注意,对于正确的截获模型,包括目标函数,trampoline函数,以及截获函数都必须是完全一致的调用形式,包括参数格式和调用约定。当通过 trampoline函数调用目标函数的时候拷贝正确参数是截获函数的责任。由于目标函数仅仅是截获函数的一个可调用分支(截获函数可以调用 trampoline函数也可以不调用),这种责任几乎就是一种下意识的行为。
使用相同的调用约定可以确保寄存器中的值被正确的保存,并且保证调用堆栈在截获函数调用目标函数的时候能正确的建立和销毁。
可以使用DetourFunctionWithTrampoline函数来截获目标函数。这个函数有两个参数:trampoline函数以及截获函数的指针。因为目标函数已经被加到trampoline函数中,所有不需要在参数中特别指定。
我们可以使用DetourFunction函数来创建一个动态的trampoline函数,它包括两个参数:一个指向目标函数的指针和一个截获函数的指针。DetourFunction分配一个新的trampoline函数并将适当的截获代码插入到目标函数中去。
当目标函数不是很容易使用的时候,DetourFindFunction函数可以找到那个函数,不管它是DLL中导出的函数,或者是可以通过二进制目标函数的调试符号找到。
DetourFindFunction接受两个参数:库的名字和函数的名字。如果DetourFindFunction函数找到了指定的函数,返回该函数 的指针,否则将返回一个NULL指针。DetourFindFunction会首先使用Win32函数LoadLibrary 和 GetProcAddress来定位函数,如果函数没有在DLL的导出表中找到,DetourFindFunction将使用ImageHlp库来搜索有 效的调试符号(译注:这里的调试符号是指Windows本身提供的调试符号,需要单独安装,具体信息请参考Windows的用户诊断支持信息)。 DetourFindFunction返回的函数指针可以用来传递给DetourFunction以生成一个动态的trampoline函数。
我们可以调用DetourRemoveTrampoline来去掉对一个目标函数的截获。
注意,因为Detours中的函数会修改应用程序的地址空间,请确保当加入截获函数或者去掉截获函数的时候没有其他线程在进程空间中执行,这是程序员的责任。一个简单的方法保证这个时候是单线程执行就是在加载Detours库的时候在DllMain中呼叫函数。
三、 使用Detours实现对API的截获的两种方法
建立一个MFC对话框工程,在对话框的OK按钮的单击事件中加入对MessageBoxA函数的调用,编译后的程序名称MessageBoxApp,效果如图。
![](/images/cppblog_com/wanghaiguang/crack_04_thumb.jpg)
(图4)
静态方法
建立一个Dll工程,名称为ApiHook,这里以Visual C++6.0开发环境,以截获ASCII版本的MessageBoxA函数来说明。在Dll的工程加入:
DETOUR_TRAMPOLINE(int WINAPI Real_Messagebox(HWND hWnd ,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType), ::MessageBoxA);
生成一个静态的MessageBoxA的Trampoline函数,在Dll工程中加入目标函数的Detour函数:
int WINAPI MessageBox_Mine( HWND hWnd ,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType)
{
CString tmp= lpText;
tmp+=” 被Detour截获”;
return Real_Messagebox(hWnd,tmp,lpCaption,uType);
// return ::MessageBoxA(hWnd,tmp,lpCaption,uType); //Error
}
在Dll入口函数中的加载Dll事件中加入:
DetourFunctionWithTrampoline((PBYTE)Real_Messagebox, (PBYTE)MessageBox_Mine);
在Dll入口函数中的卸载Dll事件中加入:
DetourRemove((PBYTE)Real_Messagebox, (PBYTE)MessageBox_Mine);
动态方法
建立一个Dll工程,名称为ApiHook,这里以Visual C++6.0开发环境,以截获ASCII版本的MessageBoxA函数来说明。在Dll的工程加入:
//声明MessageBoxA一样的函数原型
typedef int (WINAPI * MessageBoxSys)( HWND hWnd ,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType);
//目标函数指针
MessageBoxSys SystemMessageBox=NULL;
//Trampoline函数指针
MessageBoxSys Real_MessageBox=NULL;
在Dll工程中加入目标函数的Detour函数:
int WINAPI MessageBox_Mine( HWND hWnd ,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType)
{
CString tmp= lpText;
tmp+=” 被Detour截获”;
return Real_Messagebox(hWnd,tmp,lpCaption,uType);
// return ::MessageBoxA(hWnd,tmp,lpCaption,uType); //Error
}
在Dll入口函数中的加载Dll事件中加入:
SystemMessageBox=(MessageBoxSys)DetourFindFunction("user32.dll","MessageBoxA");
if(SystemMessageBox==NULL)
{
return FASLE;
}
Real_MessageBox=(MessageBoxSys)DetourFunction((PBYTE)SystemMessageBox, (PBYTE)MessageBox_Mine);
在Dll入口函数中的卸载Dll事件中加入:
DetourRemove((PBYTE)Real_Messagebox, (PBYTE)MessageBox_Mine);
重写二进制可执行文件
使用Detours自带的SetDll.exe重写二进制可执行文件,可以在需要截获的程序中加入一个新的Detours的PE节表。对于本文就是新建一个批处理文件调用SetDll.exe。
@echo off
if not exist MessageBoxApp.exe (
echo 请将文件解压到MessageBoxApp.exe的安装目录, 然后执行补丁程序
) else (
setdll /d:ApiHook.dll MessageBoxApp.exe
)
Pause
调用后使用depends.exe(微软VC6.0开发包的工具之一)观察MessageBoxApp.exe前后变化, 可以看到Setdll已经重写MessageBoxApp.exe
成功,加入了对ApiHook.dll的依赖关系。
![](/images/cppblog_com/wanghaiguang/crack_05_thumb.jpg)
(执行SetDll.exe前) (执行SetDll.exe后)
执行SetDll.exe重写后的MessageBoxApp.exe,点击确定后可以看到结果如下:
至此,MessageBoxApp.exe对MessageBoxA函数的调用已经被截获,弹出的对话框内容已经明显说明这一点。
![](/images/cppblog_com/wanghaiguang/crack_06_thumb.jpg)
本文转自:http://www.cnblogs.com/flying_bat/archive/2008/04/18/1159996.html
posted @
2012-11-02 14:32 王海光 阅读(627) |
评论 (0) |
编辑 收藏
这是对C++高效编程的一个总结, 很有指导作用.一、#include “filename.h”和#include <filename.h>的区别#include “filename.h”是指编译器将从当前工作目录上开始查找此文件#include <filename.h>是指编译器将从标准库目录中开始查找此文件二、头文件的作用加强安全检测通过头文件可能方便地调用库功能,而不必关心其实现方式三、* , &修饰符的位置
int *i,j; // better for read i = new int(0); j = 0; int *&y = i; // pointer's reference对于*和&修饰符,为了避免误解,最好将修饰符紧靠变量名四、if语句不要将布尔变量与任何值进行比较,那会很容易出错的。整形变量必须要有类型相同的值进行比较浮点变量最好少比相等,可以通过求差与较小的数比较指针变量要和NULL进行比较,不要和布尔型和整形比较五、const和#define的比较const有数据类型,#define没有数据类型个别编译器中const可以进行调试,#define不可以进行调试在类中定义常量有两种方式1、 在类在声明常量,但不赋值,在构造函数初始化表中进行赋值;(常量和引用类型的成员变量必须通过初始化列表来初始化赋值)2、 用枚举代替const常量。六、C++函数中值的传递方式有三种方式:值传递(Pass by value)、指针传递(Pass by pointer)、引用传递(Pass by reference)void fun(char c) //pass by valuevoid fun(char *str) //pass by pointervoid fun(char &str) //pass by reference如果输入参数是以值传递的话,最好使用引用传递代替,因为引用传递省去了临时对象的构造和析构函数的返回类型不能省略,就算没有也要加个void七、函数体中的指针或引用常量不能被返回
Char *func(void)
{
char str[]=”Hello Word”;
//这个是不能被返回的,因为str是个指定变量,不是一般的值,函数结束后会被注销掉
return str;
}
函数体内的指针变量并不会随着函数的消亡而自动释放八、一个内存拷贝函数的实现体
void *memcpy(void *pvTo,const void *pvFrom,size_t size)
{
assert((pvTo!=NULL)&&(pvFrom!=NULL));
byte *pbTo=(byte*)pvTo; //防止地址被改变
byte *pbFrom=(byte*)pvFrom;
while (size-- >0)
pbTo++ = pbForm++;
return pvTo;
}
九、内存的分配方式分配方式有三种,请记住,说不定那天去面试的时候就会有人问你这问题1、 静态存储区,是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量。(程序编译后运行时包含code和data两部分,其中data即为静态存储区分配,程序一开始运行便分配整个data的东东)2、 栈上分配,函数内的局部变量就是从这分配的,但分配的内存容易有限。3、 堆上分配,也称动态分配,如我们用new,malloc分配内存,用delete,free来释放的内存。十、内存分配的注意事项用new或malloc分配内存时,必须要对此指针赋初值。用delete 或free释放内存后,必须要将指针指向NULL不能修改指向常量的指针数据十一、内容复制与比较
//数组……
char a[]=”Hello Word!”;
char b[10];
strcpy(b,a);
if (strcmp(a,b)==0)
{}
//指针……
char a[]=”Hello Word!”;
char *p;
p=new char[strlen(a)+1];
strcpy(p,a);
if (strcmp(p,a)==0)
{}
十二、sizeof的问题记住一点,C++无法知道指针所指对象的大小,指针的大小永远为4字节
char a[]=”Hello World!”
char *p=a;
count<<sizeof(a)<<end; //12字节
count<<sizeof(p)<<endl; //4字节
而且,在函数中,数组参数退化为指针,所以下面的内容永远输出为4
void fun(char a[1000])
{
count<<sizeof(a)<<endl; //输出4而不是1000
}
十三、关于指针1、 指针创建时必须被初始化2、 指针在free 或delete后必须置为NULL3、 指针的长度都为4字节4、释放内存时,如果是数组指针,必须要释放掉所有的内存,如
char *p=new char[100];
strcpy(p,”Hello World”);
delete []p; //注意前面的[]号
p=NULL;
5、数组指针的内容不能超过数组指针的最大容易。如:
char *p=new char[5];
strcpy(p,”Hello World”); //报错 目标容易不够大
delete []p; //注意前面的[]号
p=NULL;
十四、关于malloc/free 和new /deletemalloc/free 是C/C+的内存分配符,new /delete是C++的内存分配符。注意:malloc/free是库函数,new/delete是运算符malloc/free不能执行构造函数与析构函数,而new/delete可以new/delete不能在C上运行,所以malloc/free不能被淘汰两者都必须要成对使用C++中可以使用_set_new_hander函数来定义内存分配异常的处理十五、C++的特性C++新增加有重载(overload),内联(inline),Const,Virtual四种机制重载和内联:即可用于全局函数,也可用于类的成员函数;Const和Virtual:只可用于类的成员函数;重载:在同一类中,函数名相同的函数。由不同的参数决定调用那个函数。函数可要不可要Virtual关键字。和全局函数同名的函数不叫重载。如果在类中调用同名的全局函数,必须用全局引用符号::引用。覆盖是指派生类函数覆盖基类函数:函数名相同;参数相同;基类函数必须有Virtual关键字;不同的范围(派生类和基类)。隐藏是指派生类屏蔽了基类的同名函数相同1、 函数名相同,但参数不同,此时不论基类有无Virtual关键字,基类函数将被隐藏。2、 函数名相同,参数也相同,但基类无Virtual关键字(有就是覆盖),基类函数将被隐藏。内联:inline关键字必须与定义体放在一起,而不是单单放在声明中。Const:const是constant的缩写,“恒定不变”的意思。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。1、 参数做输入用的指针型参数,加上const可防止被意外改动。2、 按值引用的用户类型做输入参数时,最好将按值传递的改为引用传递,并加上const关键字,目的是为了提高效率。数据类型为内部类型的就没必要做这件事情;如:将void Func(A a) 改为void Func(const A &a)。而void func(int a)就没必要改成void func(const int &a);3、 给返回值为指针类型的函数加上const,会使函数返回值不能被修改,赋给的变量也只能是const型变量。如:函数const char*GetString(void); char *str=GetString()将会出错。而const char *str=GetString()将是正确的。4、 Const成员函数是指此函数体内只能调用Const成员变量,提高程序的键壮性。如声明函数 int GetCount(void) const;此函数体内就只能调用Const成员变量。Virtual:虚函数:派生类可以覆盖掉的函数,纯虚函数:只是个空函数,没有函数实现体;十六、extern“C”有什么作用?Extern “C”是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数。这是因为C++编译后库中函数名会变得很长,与C生成的不一致,造成C++不能直接调用C函数,加上extren “c”后,C++就能直接调用C函数了。Extern “C”主要使用正规DLL函数的引用和导出 和 在C++包含C函数或C头文件时使用。使用时在前面加上extern “c” 关键字即可。十七、构造函数与析构函数派生类的构造函数应在初始化表里调用基类的构造函数;派生类和基类的析构函数应加Virtual关键字。不要小看构造函数和析构函数,其实编起来还是不容易。
#include <iostream.h>
class Base
{
public:
virtual ~Base() { cout<< "~Base" << endl ; }
};
class Derived : public Base
{
public:
virtual ~Derived() { cout<< "~Derived" << endl ; }
};
void main(void)
{
Base * pB = new Derived; // upcast
delete pB;
}
输出结果为:~Derived~Base如果析构函数不为虚,那么输出结果为~Base十八、#IFNDEF/#DEFINE/#ENDIF有什么作用仿止该头文件被重复引用
评论
# re: C++高效编程忠告 2007-07-19 19:21
闲来无聊,无聊一下:
2 头文件的作用
这是由于历史原因造成的。
头文件主要用于存放接口声明,以便不同的c文件共享函数声明。
到了c++中,已经造成一种妨碍了。
3 * & 的位置
这个位置放哪,虽然要紧,但更要紧的是,遵守一个变量一行定义。这样就不会出现错误,也很好读。如:
int * i;
int j;
4 if
仍然是历史原因造成的。c标准中没有真正的bool类型(c++98和c99加了),而是用int来代替,这就造成了麻烦。所以新写的程序要避免使用BOOL,而用bool来替代。
如果是旧标准的编译器,仍然要注意BOOL其实是int这个要点。
5 const #define enum
这几个各有用途,有时候也不能互代。使用时,优先顺序 enum const #define。看具体要求,不一定非要怎么怎么样。
6 参数传递
用指针或用引用,倒不定要要争个谁好谁坏。
实际上,用指针还更容易让人明白传进去的是指针。而引用则不然。引用的引入,在c++中是为了解决运算符重载的问题。
不过,不管用哪个,倒是建议优先采用 const T* 或const T&,这种常量指针或常亘引用。这不仅关系程序优化,也关系到代码的质量。
8 memcpy
除非没有类似的库函数,否则不要重新写一个。要知道,绝大多数编译器会对这些库函数作特殊优化。这是手工编码无法做到的。
11 字串
建议不要使用strcpy,strcmp类似的函数,应该改为strncpy, strncmp等函数。目前新型的编译器会认为strxxx函数是非法的(被淘汰的),建议用strnxxx,或者编译器提供的更安全的版本。
13 指针的大小
与编译器及系统平台有关。大小从2/4/8/16各种可能性都有.不要认死了。
14 关于new/delete 与malloc/free
你用了类似class的关键字后,就注定不兼容于c了。因此,用了c++的东西,就不要去用mallco/free,除非特殊情况。
同理,不使用c++的东西,也决不要去用new /delete.
否则,你的跨语言或跨平台的想法就打水漂了。还容易出错。
15 const 函数
const不一定只能引用const成员。它可以使用任何成员,只是默认情况不能改变成员的值而已。如果成员被定义为mutable(c++98),则不限制。
18 #ifdef
说的太绝对了。而且没有任何说明如何做。
事实上#ifdef能做的事还是挺多的。 回复 更多评论
# re: C++高效编程忠告 2007-07-19 21:44
恩,楼上说的很好,可见C++的功底之强,学习中。。。 回复 更多评论
# re: C++高效编程忠告 2007-07-20 09:33
三、* , &修饰符的位置
int* & rY = pI; // ptr's ref
用int*表示指针更清楚。
int *i,j;
我更倾向于这样分开写:
int * pI;
int j;
六、C++函数中值的传递方式
“如果输入参数是以值传递的话,最好使用引用传递代替”
应该是“如果输入参数是常量值的话,最好使用常量引用传递代替”。
但是简单常量就不需要引用传递。
十二、sizeof的问题
一般没有这样定义函数的:
void fun(char a[1000])
而是void fun(char * p, int len) 回复 更多评论
# re: C++高效编程忠告 2007-07-20 09:47
“头文件到了c++中,已经造成一种妨碍了”?
能说明一下吗?
头文件在C++中还是必须的吧?
回复 更多评论
# re: C++高效编程忠告 2007-07-20 12:00
@金庆
就是必须的,所以才是一种妨碍。
目前C++有3种编程:c, 类, 模板。
其中,头文件对于c来说,并不成问题,甚至还带来许多便利。
但是对于类,和模板,特别是模板来说,麻烦就大了。
对于类来说,封装性和实现隐藏是一个很重要的概念,但大多数情况下,许多人都不容易做到这一点。.h文件中会有许多私有的数据成员,甚至还有不少的函数实现(如inline函数),从而对封装性和实现隐藏造成破坏,造成编译依赖,进一步造成编译器的复杂度提高。最终为了避开这些问题,将会造成库设计要求的提高。
至于模板风格的编程就更不得了,目前几乎没有支持分离模板的编译器。况且即便分离了,又能怎么样?最终的结果是,大多的模板风格的程序,所有的代码都在.h文件中,.cpp反而只剩了一句话:#include "xxx.h",没了。
回复 更多评论
# re: C++高效编程忠告[未登录] 2007-07-22 21:18
对目前的编译器都不支持模板的分离,都必须在。h中实现。 回复 更多评论
本文转自:http://www.cppblog.com/mzty/archive/2007/07/19/28359.html
posted @
2012-10-25 15:20 王海光 阅读(453) |
评论 (0) |
编辑 收藏
功能代码:
1 /*========================================================================
2 功能:获取文件版本信息.
3 说明:要使用此函数必需在程序中加入
4 #pragma comment(lib, "Version.lib ")
5 ----------------------------------------------------------------------------
6 参数:lpszFileName=程序文件名,如果为空则是获取当前运行程序的版本信息
7 Reference From : http://topic.csdn.net/t/20050204/10/3774845.html
8 ==========================================================================*/
9 CString GetProductVersion(LPCTSTR lpszFileName)
10 {
11 CString strVersion;
12 TCHAR szSrcfn[MAX_PATH];
13 if(lpszFileName == NULL)
14 {
15 if (!::GetModuleFileName(NULL, szSrcfn, sizeof(szSrcfn)))
16 {
17 printf("Function <GetModuleFileName> unsuccessful!, ErrorCode:%d\n", ::GetLastError());
18 return "Error";
19 }
20 }
21 else
22 {
23 lstrcpy(szSrcfn, lpszFileName);
24 }
25
26 if (!CFileFind().FindFile(lpszFileName))
27 {
28 printf("%s is not Exist!\n", lpszFileName);
29 return "Error";
30 }
31
32 BOOL bRet;
33 DWORD dwVerHnd = 0;
34 DWORD dwVerInfoSize = ::GetFileVersionInfoSize(szSrcfn, &dwVerHnd);
35 if(!dwVerInfoSize)
36 {
37 printf("Function <GetFileVersionInfo> unsuccessful!, ErrorCode:%d\n", ::GetLastError());
38 return "Error";
39 }
40
41 HANDLE hMem;
42 LPVOID lpvMem;
43 unsigned int uInfoSize = 0;
44 VS_FIXEDFILEINFO * pFileInfo;
45
46 hMem = ::GlobalAlloc(GMEM_MOVEABLE, dwVerInfoSize);
47 lpvMem = ::GlobalLock(hMem);
48 bRet = ::GetFileVersionInfo(szSrcfn, dwVerHnd, dwVerInfoSize, lpvMem);
49 if (!bRet)
50 {
51 printf("Function <GetFileVersionInfo> unsuccessful!, ErrorCode:%d\n", ::GetLastError());
52 return "Error";
53 }
54
55 bRet = ::VerQueryValue(lpvMem, (LPTSTR)_T( "\\"), (LPVOID*)&pFileInfo, &uInfoSize);
56 if (!bRet)
57 {
58 printf("Function <VerQueryValue> unsuccessful!\n");
59 return "Error";
60 }
61
62 WORD nVer[4];
63 nVer[0] = HIWORD(pFileInfo-> dwProductVersionMS);
64 nVer[1] = LOWORD(pFileInfo-> dwProductVersionMS);
65 nVer[2] = HIWORD(pFileInfo-> dwProductVersionLS);
66 nVer[3] = LOWORD(pFileInfo-> dwProductVersionLS);
67 strVersion.Format(_T( "%d.%d.%d.%d "), nVer[0], nVer[1], nVer[2], nVer[3]);
68
69 ::GlobalUnlock(hMem);
70 ::GlobalFree(hMem);
71
72 return strVersion;
73 }
调用代码:
1 LPCTSTR sFilePath = argv[1];
2 CString sFileVersion = GetProductVersion(sFilePath);
3 if (sFileVersion.CompareNoCase("Error") == 0)
4 {
5 printf("Get File Version unsuccessful!\n");
6 }
7 else
8 {
9 printf("Version : %s\n", sFileVersion);
10 printf("Get File Version successful!\n");
11 }
posted @
2012-10-25 13:43 王海光 阅读(1186) |
评论 (0) |
编辑 收藏
摘要: C++内存泄露的检测(一) 一Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法。主要使用函数:_CrtDumpMemoryLeaks();二 实例
#define _CRTDBG_MAP_ALLOC //输出更详细的report#include <stdlib.h>#incl...
阅读全文
posted @
2012-10-24 13:17 王海光 阅读(565) |
评论 (0) |
编辑 收藏
将文字传送到剪贴簿 |
|
让我们想像把一个ANSI字串传送到剪贴簿上,并且我们已经有了指向这个字串的指标(pString)。现在希望传送这个字串的iLength字元,这些字元可能以NULL结尾,也可能不以NULL结尾。
首先,通过使用GlobalAlloc来配置一个足以储存字串的记忆体块,其中还包括一个终止字元NULL:
hGlobal = GlobalAlloc (GHND | GMEM_SHARE, iLength + 1) ;
如果未能配置到记忆体块,hGlobal的值将为NULL 。如果配置成功,则锁定这块记忆体,并得到指向它的一个指标:
pGlobal = GlobalLock (hGlobal) ;
将字串复制到记忆体块中:
for (i = 0 ; i < wLength ; i++)
*pGlobal++ = *pString++ ;
由於GlobalAlloc的GHND旗标已使整个记忆体块在配置期间被清除为零,所以不需要增加结尾的NULL 。以下叙述为记忆体块解锁:
现在就有了表示以NULL结尾的文字所在记忆体块的记忆体代号。为了把它送到剪贴簿中,打开剪贴簿并把它清空:
OpenClipboard (hwnd) ;
EmptyClipboard () ;
利用CF_TEXT识别字把记忆体代号交给剪贴簿,关闭剪贴簿:
SetClipboardData (CF_TEXT, hGlobal) ;
CloseClipboard () ;
工作告一段落。 |
GlobalAlloc 及其它 |
|
从用户的角度来看,WIN32的内存管理是非常简单和明了的。每一个应用程序都有自己独立的4G地址空间,这种内存模式叫做“平坦”型地址模式,所有的段寄存器或描述符都指向同样的起始地址,所有的地址偏移都是32位的长度,这样一个应用程序无须变换选择符就可以存取自己的多达4G的地址空间。这种内存管理模式是非常简洁而便于管理的,而且我们再不用和那些令人讨厌的“near”和“far”指针打交道了。在W16下有两种主要类型的API:全局和局部。“全局”的API 分配在其他的段中,这样从内存角度来看他们是一些“far”(远)函数或者叫远过程调用,“局部”API只要和进程的堆打交道,所以把它们叫做“near”(近)函数或者近过程调用。而在WIN32中,这两种内存模式是相同的,无论您调用GlobalAlloc还是LocalAlloc,结果都是一样。
至于分配和使用内存的过程都是一样的:
调用GlobalAlloc函数分配一块内存,该函数会返回分配的内存句柄。 调用GlobalLock函数锁定内存块,该函数接受一个内存句柄作为参数,然后返回一个指向被锁定的内存块的指针。 您可以用该指针来读写内存。 调用GlobalUnlock函数来解锁先前被锁定的内存,该函数使得指向内存块的指针无效。 调用GlobalFree函数来释放内存块。您必须传给该函数一个内存句柄。 在WIN32中您也可以用“Local”替代内存分配API函数带有“Global”字样的函数中的“Global”,也即用LocalAlloc、LocalLock等。 在调用函数GlobalAlloc时使用GMEM_FIXED标志位可以更进一步简化操作。使用了该标志后,Global/LocalAlloc返回的是指向已分配内存的指针而不是句柄,这样也就不用调用Global/LocalLock来锁定内存了,释放内存时只要直接调用Global/LocalFree就可以了。 |
句柄vs指针 |
|
句柄是一种指向指针的指针。我们知道,所谓指针是一种内存地址。应用程序启动后,组成这 个程序的各对象是住留在内存的。如果简单地理解,似乎我们只要获知这个内存的首地址,那么就可以随时用这个地址 访问对象。但是,如果您真的这样认为,那么您就大错特错了。我们知道,Windows是一 个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化 了。如果地址总是如此变化,我们该到哪里去找该对象呢?为了解决这个问题,Windows操作系统为各应用程序腾出一些内存储地址,用来专门 登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的。Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时(Unload)又释放给系统。句柄地址(稳定)→记载着对象在内存中的地址→对象在内存中的地址(不稳定)→实际对象。但是,必须注意的是程序每次从新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确不一样的。假如我们把进入电影院看电影看成 是一个应用程序的启动运行,那么系统给应用程序分配的句柄总是不一样,这和每次电 影院售给我们的门票总是不同的一个座位是一样的道理。
|
Debug |
|
某年,某月,某日。 为某一个大型程序,增加一个大型功能。编译,运行,死机。
跟踪之,居然死在了如下语句: CString str; 而且还极不稳定,这次调试死在n行,下次调试死在m行。但都是和内存申请有关。(由于程序很大,其中频繁地申请和释放内存,多处使用new和CString)
猜测:一定是内存不够啦,遂在某处调用函数得到当前剩余的物理内存数量并使用MessageBox显示。报告曰:自由物理内存还有100多M。鼠标按下OK键,程序居然不死了。恩???
删除MessageBox()函数—死!加上MessageBox()函数—不死!再删除–死,再加上–不死。晕倒!
捏呆呆郁闷不知道多少时间后,灵光闪烁……把多处的new/delete改写为GlobalAlloc()/GlobalFree(),一切OK。
事后原因分析:使用new和CString,频繁申请,释放内存,一定产生零碎内存块。当使用MessageBox的时候,系统接管程序的运行(因为它在等待着你按OK按纽),它这时候开始回收合并这些零碎的内存块。这样程序就没有问题了。而函数GlobalAlloc()/GlobalFree()本身就有回收合并零碎内存的功能。
友情提示:在频繁使用new,CString的场合,建议把某些(大)数据块的申请用GlobalAlloc替换。
c++异常处理 |
|
#include<fstream.h> #include<iostream.h> #include<stdlib.h> void main() { ifstream source("c:\abc.txt"); //打开文件 char line[128]; try //定义异常 {if (source.fail()) throw "txt"; //抛掷异常 } catch(char * s) //定义异常处理 { cout<<"error opening the file "<<s<<endl; exit(1); } while(!source.eof()) { source.getline(line, sizeof(line)); cout<<line<<endl;} source.close(); }
/////////////////////////////////////////////////////////// | | |
C++开发中常见问题 |
|
1,简述VC6下如何进行程序的调试。
在主菜单"Build"中,有一个Start Build的子菜单,它下面包含了Go菜单(快捷键为F5),选择后,程序将从当前语句进入调试运行,直到遇到断点或程序结束。
将鼠标移动到要调试的代码行,单击鼠标右键选择“Insert/Remove Breakpoint”,或者按下F9,可以在该行上添加断点,此时断点代码行前面出现一个棕色的圈,再次选择将清除断点。进入调试状态后,Debug菜单将取代Build菜单出现在菜单栏中,它下面包含常用的调试操作,如Step Over,单步运行并不跟踪到调用的函数内部;其他还包括Step Into,Step Out, Stop Debugging等调试方法。
2, 简述在VC6建立的工程中后缀为.cpp,.h,.rc,.dsp,.dsw的文件的作用是什么?
.cpp是源程序代码C++文件
.h是包含函数声明和变量定义的头文件
.rc是定义资源的资源脚本文件
.dsp是工程文件,记录当前工程的有关信息
.dsw是工作区文件,一个工作区可能包含一个或多个工程
3, 已知一个对话框上有一个编辑框控件,ID为IDC_EDIT1,为其关联了CEdit类型的变量m_edit1,使用两种方法,说明如何改变编辑框内部的文本为"Hello",写出程序代码的片断。
第一种方法:m_edit1.SetSel(0,-1);
m_edit1.ReplaceSel("Hello");
第二种方法:SetWindowText("Hello");
4, 简述使用Windows API编写的一个基本的Windows应用程序框架的结构。
Windows API编写的基本应用程序框架至少应该包含程序入口函数WinMain和窗口函数WndProc。在主函数WinMain里面包含窗口类的定义和注册,窗口的创建和显示以及消息循环。
5, 消息在Windows中的数据类型是什么,它有哪些成员变量,各有什么含义
消息的数据类型是MSG,它是一个结构体,其成员变量主要包括hwnd,表示消息的窗口句柄;message代表消息的类型;wParam和lParam包含消息的附加信息,随不同的消息有所不同。
6, Windows的鼠标消息的长参数lParam与字参数wParam的含义是什么
鼠标消息的长参数lParam的低字节包含了鼠标光标位置的x坐标值,lParam的高字节包含了鼠标光标位置的y坐标值;字参数wParam内包含了指示当前按下的各种虚键状态的值。
7, 说明使用一个非模态对话框的注意问题和用到的Windows API函数
使用一个非模态对话框应该注意一定要在样式中包含WS_VISIBLE才能正常显示;创建对话框使用CreateDialog函数;消息循环部分应该使用IsDialogMessage过滤消息;关闭对话框使用函数DestroyWindow。
8, 简述在MFC应用程序中UpdateData函数的作用及其参数含义与使用场合。
UpdateData只有一个BOOL类型的参数,UpdateData(FALSE)一般用于对话框控件连接的变量值刷新屏幕显示;UpdateData(TRUE)用于获取屏幕数据到对话框控件连接的变量中。
9, 列举列表框控件能够接受的三个消息类型,并说明其作用
LB_ADDSTRING用于在列表框中加入一项字符串;LB_DIR用于在列表框中列出指定文件;LB_GETTEXT用于获取指定项的文本。
10, 在一个对话框上添加了三个单选按钮,要使它们之间自动实现互斥,应该注意什么问题,在VC环境下如何操作?
要实现一组单选按钮的自动互斥,应该让它们的控件ID值连续,并设置第一个单选按钮的Group属性,其他的不设。
11, 简述由一个文档类派生自己的文档类,并实现文档的存取需要哪些步骤。
首先为每一个文档类型从CDocument派生一个相应的文档类;然后为该文档类添加成员变量以保存数据;最后重载Serialize成员函数以实现文档数据的串行化。
12, 列举视图类(CView)的三个子类,并简要说明其作用。
CScrollView类提供视图的滚动显示;CEditView类支持在视图中的文本编辑操作;CHtmlView类支持在视图中显示和操作html文件。
13, Visual C++ 6.0如何进入调试状态,在调试状态下能够显示哪些调试窗口,列举三个,其作用分别是什么?
启动调试后,在View菜单的Debug Window子菜单下可以打开一些辅助调试的窗口
Watch:显示察看当前语句和前面语句中变量值的窗口
Call Stack:显示察看调用堆栈的窗口
Memory:显示察看内存中内容的窗口
14, 说明位图资源的创建及显示过程的步骤,并给出相应的Windows API函数名。
首先定义位图句柄HBITMAP hBitmap;第二步使用LoadBitMap加载位图;第三步,调用CreateCompatibleDC向系统申请内存设备环境句柄,并调用函数SelectObject把位图选入内存设备环境;第四步,调用BitBlt函数将位图从内存设备环境输出到指定的窗口设备环境中,从而实现显示位图。
15, 如何获取字体句柄从而实现字体的输出,并给出相应的Windows API函数名。
首先定义字体句柄变量HFONT hF;然后调用函数GetStockObject获取系统的字体句柄,或者调用CreateFont得到自定义的字体句柄;最后调用SelectObject把字体句柄选入设备环境。
16, 列举三种按钮的类型,并说明其作用和创建方法之间的不同之处。
常用的按钮有普通按钮、单选按钮、复选框,和组框。普通按钮作用是帮助用户触发指定动作;单选按钮一般各选项之间存在互斥性;复选框用来显示一组选项供用户选择,各选项之间不存在互斥;组框主要用于把控件分成不同的组并加以说明.
17, 要使一个静态控件显示一个位图并能接受用户输入,应该注意什么问题。
要使静态控件显示位图,必须设定其风格包含SS_BITMAP,并在创建静态控件窗口,即调用CreateWindow时指定并加载位图;要使静态控件能够接收用户输入,必须设定其风格包含SS_NOTIFY。
VC学习笔记 |
|
VC学习笔记1:按钮的使能与禁止
用ClassWizard的Member Variables为按钮定义变量,如:m_Button1; 则 m_Button1.EnableWindow(true); 使按钮处于允许状态 m_Button1.EnableWindow(false); 使按钮被禁止,并变灰显示
VC学习笔记2:控件的隐藏与显示
用CWnd类的函数BOOL ShowWindow(int nCmdShow)可以隐藏或显示一个控件。
例1: CWnd *pWnd; pWnd = GetDlgItem( IDC_EDIT1 ); //获取控件指针,IDC_EDIT为控件ID号 pWnd->ShowWindow( SW_HIDE ); //隐藏控件
例2: CWnd *pWnd; pWnd = GetDlgItem( IDC_EDIT1 ); //获取控件指针,IDC_EDIT为控件ID号 pWnd->ShowWindow( SW_SHOW ); //显示控件
以上方法常用于动态生成控件,虽说用控件的Create函数可以动态生成控件,但这种控件很不好控制,所以用隐藏、显示方法不失为一种替代手段。
VC学习笔记3:改变控件的大小和位置
用CWnd类的函数MoveWindow()或SetWindowPos()可以改变控件的大小和位置。
void MoveWindow(int x,int y,int nWidth,int nHeight); void MoveWindow(LPCRECT lpRect); 第一种用法需给出控件新的坐标和宽度、高度; 第二种用法给出存放位置的CRect对象; 例: CWnd *pWnd; pWnd = GetDlgItem( IDC_EDIT1 ); //获取控件指针,IDC_EDIT1为控件ID号 pWnd->MoveWindow( CRect(0,0,100,100) ); //在窗口左上角显示一个宽100、高100的编辑控件
SetWindowPos()函数使用更灵活,多用于只修改控件位置而大小不变或只修改大小而位置不变的情况: BOOL SetWindowPos(const CWnd* pWndInsertAfter,int x,int y,int cx,int cy,UINT nFlags); 第一个参数我不会用,一般设为NULL; x、y控件位置;cx、cy控件宽度和高度; nFlags常用取值: SWP_NOZORDER:忽略第一个参数; SWP_NOMOVE:忽略x、y,维持位置不变; SWP_NOSIZE:忽略cx、cy,维持大小不变; 例: CWnd *pWnd; pWnd = GetDlgItem( IDC_BUTTON1 ); //获取控件指针,IDC_BUTTON1为控件ID号 pWnd->SetWindowPos( NULL,50,80,0,0,SWP_NOZORDER | SWP_NOSIZE ); //把按钮移到窗口的(50,80)处 pWnd = GetDlgItem( IDC_EDIT1 ); pWnd->SetWindowPos( NULL,0,0,100,80,SWP_NOZORDER | SWP_NOMOVE ); //把编辑控件的大小设为(100,80),位置不变 pWnd = GetDlgItem( IDC_EDIT1 ); pWnd->SetWindowPos( NULL,0,0,100,80,SWP_NOZORDER ); //编辑控件的大小和位置都改变 以上方法也适用于各种窗口。
VC学习笔记4:什么时候设定视中控件的初始尺寸?
我在CFormView的视中加入了一个编辑控件,在运行时使它充满客户区,当窗口改变大小时它也跟着改变。 改变控件尺寸可以放在OnDraw()函数中,也可放在CalcWindowRect()函数中,当窗口尺寸发生变化时,它们都将被执行,且CalcWindowRect()函数先于OnDraw()函数,下例是在CalcWindowRect()函数中修改控件尺寸。 重载VIEW类的CalcWindowRect函数,把设定控件的尺寸的语句加入这个函数中。 例: void CMyEditView::CalcWindowRect(LPRECT lpClientRect, UINT nAdjustType) { // TODO: Add your specialized code here and/or call the base class
CFrameWnd *pFrameWnd=GetParentFrame(); //获取框架窗口指针
CRect rect; pFrameWnd->GetClientRect(&rect); //获取客户区尺寸
CWnd *pEditWnd=GetDlgItem(IDC_MYEDIT); //获取编辑控件指针,IDC_MYEDIT为控件ID号 pEditWnd->SetWindowPos(NULL,0,0,rect.right,rect.bottom-50,SWP_NOMOVE | SWP_NOZORDER); //设定控件尺寸,bottom-50是为了让出状态条位置。
CFormView::CalcWindowRect(lpClientRect, nAdjustType); }
VC学习笔记5:单选按钮控件(Ridio Button)的使用
一、对单选按钮进行分组: 每组的第一个单选按钮设置属性:Group,Tabstop,Auto;其余按钮设置属性Tabstop,Auto。
如: Ridio1、Ridio2、Ridio3为一组,Ridio4、Ridio5为一组
设定Ridio1属性:Group,Tabstop,Auto 设定Ridio2属性:Tabstop,Auto 设定Ridio3属性:Tabstop,Auto
设定Ridio4属性:Group,Tabstop,Auto 设定Ridio5属性:Tabstop,Auto
二、用ClassWizard为单选控件定义变量,每组只能定义一个。如:m_Ridio1、m_Ridio4。
三、用ClassWizard生成各单选按钮的单击消息函数,并加入内容:
void CWEditView::OnRadio1() { m_Ridio1 = 0; //第一个单选按钮被选中 }
void CWEditView::OnRadio2() { m_Ridio1 = 1; //第二个单选按钮被选中 }
void CWEditView::OnRadio3() { m_Ridio1 = 2; //第三个单选按钮被选中 }
void CWEditView::OnRadio4() { m_Ridio4 = 0; //第四个单选按钮被选中 }
void CWEditView::OnRadio5() { m_Ridio4 = 1; //第五个单选按钮被选中 }
四、设置默认按钮: 在定义控件变量时,ClassWizard在构造函数中会把变量初值设为-1,只需把它改为其它值即可。 如: //{{AFX_DATA_INIT(CWEditView) m_Ridio1 = 0; //初始时第一个单选按钮被选中 m_Ridio4 = 0; //初始时第四个单选按钮被选中 //}}AFX_DATA_INIT
VC学习笔记6:旋转控件(Spin)的使用
当单击旋转控件上的按钮时,相应的编辑控件值会增大或减小。其设置的一般步骤为: 一、在对话框中放入一个Spin控件和一个编辑控件作为Spin控件的伙伴窗口, 设置Spin控件属性:Auto buddy、Set buddy integer、Arrow keys 设置文本控件属性:Number
二、用ClassWizard为Spin控件定义变量m_Spin,为编辑控件定义变量m_Edit,定义时注意要把m_Edit设置为int型。
三、在对话框的OnInitDialog()函数中加入语句: BOOL CMyDlg::OnInitDialog() { CDialog::OnInitDialog(); m_Spin.SetBuddy( GetDlgItem( IDC_EDIT1 ) ); //设置编辑控件为Spin控件的伙伴窗口 m_Spin.SetRange( 0, 10 ); //设置数据范围为0-10 return TRUE; }
四、用ClassWizard为编辑控件添加EN_CHANGE消息处理函数,再加入语句: void CMyDlg::OnChangeEdit1() { m_Edit = m_Spin.GetPos(); //获取Spin控件当前值 }
OK!
VC学习笔记7:程序结束时保存文件问题
在文档-视图结构中,用串行化自动保存文件在各种VC书上都有介绍。现在的问题是我不使用串行化,而是自己动手保存,当点击窗口的关闭按钮时,如何提示并保存文档。
用ClassWizard在文档类(CxxDoc)中添加函数CanCloseFrame(),再在其中加入保存文件的语句就可以了。 注:要保存的数据应放在文档类(CxxDoc)或应用程序类(CxxApp)中,不要放在视图类中。
例: //退出程序 BOOL CEditDoc::CanCloseFrame(CFrameWnd* pFrame) { CFile file; if(b_Flag) //b_Flag为文档修改标志,在修改文档时将其置为True { int t; t=::MessageBox(NULL,"文字已经改变,要存盘吗?","警告", MB_YESNOCANCEL | MB_ICONWARNING); //弹出提示对话框 if(t==0 || t==IDCANCEL) return false; if(t==IDYES) { CString sFilter="Text File(*.txt)|*.txt||"; CFileDialog m_Dlg(FALSE,"txt",NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,(LPCTSTR)sFilter,NULL); //定制文件对话框
int k=m_Dlg.DoModal(); //弹出文件对话框 if(k==IDCANCEL || k==0) return false; m_PathName=m_Dlg.GetPathName(); //获取选择的文件路径名 file.Open(m_PathName,CFile::modeCreate | CFile::modeWrite); file.Write(m_Text,m_TextLen); //数据写入文件 file.Close(); } } return CDocument::CanCloseFrame(pFrame); }
VC学习笔记8:UpdateData()
对于可以接收数据的控件,如编辑控件来说,UpdateData()函数至关重要。当控件内容发生变化时,对应的控件变量的值并没有跟着变化,同样,当控件变量值变化时,控件内容也不会跟着变。 UpdateData()函数就是解决这个问题的。
UpdateData(true);把控件内容装入控件变量 UpdateData(false);用控件变量的值更新控件
如:有编辑控件IDC_EDIT1,对应的变量为字符串m_Edit1, 1、修改变量值并显示在控件中: m_Edit1 = _T("结果为50"); UpdateData(false); 2、读取控件的值到变量中: 用ClassWizard为IDC_EDIT1添加EN_CHANGE消息处理函数, void CEditView::OnChangeEdit1() { UpdateData(true); }
| |
VC实现BMP位图文件结构及平滑缩放 |
|
|
|
|
|
用普通方法显示BMP位图,占内存大,速度慢,在图形缩小时,失真严重,在低颜色位数的设备上显示高颜色位数的图形图形时失真大。本文采用视频函数显示BMP位图,可以消除以上的缺点。 |
一、BMP文件结构
1. BMP文件组成
BMP文件由文件头、位图信息头、颜色信息和图形数据四部分组成。
2. BMP文件头
BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息。
其结构定义如下:
typedef struct tagBITMAPFILEHEADER
{
WORDbfType; // 位图文件的类型,必须为BM
DWORD bfSize; // 位图文件的大小,以字节为单位
WORDbfReserved1; // 位图文件保留字,必须为0
WORDbfReserved2; // 位图文件保留字,必须为0
DWORD bfOffBits; // 位图数据的起始位置,以相对于位图
// 文件头的偏移量表示,以字节为单位
} BITMAPFILEHEADER; |
3. 位图信息头
BMP位图信息头数据用于说明位图的尺寸等信息。
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; // 本结构所占用字节数
LONGbiWidth; // 位图的宽度,以像素为单位
LONGbiHeight; // 位图的高度,以像素为单位
WORD biPlanes; // 目标设备的级别,必须为1
WORD biBitCount// 每个像素所需的位数,必须是1(双色),
// 4(16色),8(256色)或24(真彩色)之一
DWORD biCompression; // 位图压缩类型,必须是 0(不压缩),
// 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
DWORD biSizeImage; // 位图的大小,以字节为单位
LONGbiXPelsPerMeter; // 位图水平分辨率,每米像素数
LONGbiYPelsPerMeter; // 位图垂直分辨率,每米像素数
DWORD biClrUsed;// 位图实际使用的颜色表中的颜色数
DWORD biClrImportant;// 位图显示过程中重要的颜色数
} BITMAPINFOHEADER; |
4. 颜色表
颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下:
typedef struct tagRGBQUAD {
BYTErgbBlue;// 蓝色的亮度(值范围为0-255)
BYTErgbGreen; // 绿色的亮度(值范围为0-255)
BYTErgbRed; // 红色的亮度(值范围为0-255)
BYTErgbReserved;// 保留,必须为0
} RGBQUAD;
颜色表中RGBQUAD结构数据的个数有biBitCount来确定:
当biBitCount=1,4,8时,分别有2,16,256个表项;
当biBitCount=24时,没有颜色表项。
位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader; // 位图信息头
RGBQUAD bmiColors[1]; // 颜色表
} BITMAPINFO; |
5. 位图数据
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数:
当biBitCount=1时,8个像素占1个字节;
当biBitCount=4时,2个像素占1个字节;
当biBitCount=8时,1个像素占1个字节;
当biBitCount=24时,1个像素占3个字节;
Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充,一个扫描行所占的字节数计算方法:
DataSizePerLine= (biWidth* biBitCount+31)/8;
// 一个扫描行所占的字节数
DataSizePerLine= DataSizePerLine/4*4; // 字节数必须是4的倍数
位图数据的大小(不压缩情况下):
DataSize= DataSizePerLine* biHeight; |
二、BMP位图一般显示方法
1. 申请内存空间用于存放位图文件 GlobalAlloc(GHND,FileLength);
2. 位图文件读入所申请内存空间中 LoadFileToMemory( mpBitsSrc,mFileName);
3. 在OnPaint等函数中用创建显示用位图
用CreateDIBitmap()创建显示用位图,用CreateCompatibleDC()创建兼容DC, 用SelectBitmap()选择显示位图。
4. 用BitBlt或StretchBlt等函数显示位图
5. 用DeleteObject()删除所创建的位图
以上方法的缺点是: 1)显示速度慢; 2) 内存占用大; 3) 位图在缩小显示时图形失真大,(可通过安装字体平滑软件来解决); 4) 在低颜色位数的设备上(如256显示模式)显示高颜色位数的图形(如真彩色)图形失真严重。
三、BMP位图缩放显示
用DrawDib视频函数来显示位图,内存占用少,速度快,而且还可以对图形进行淡化(Dithering)处理。淡化处理是一种图形算法,可以用来在一个支持比图像所用颜色要少的设备上显示彩色图像。BMP位图显示方法如下:
1. 打开视频函数DrawDibOpen(),一般放在在构造函数中
2. 申请内存空间用于存放位图文件
GlobalAlloc(GHND,FileLength); |
3. 位图文件读入所申请内存空间中
LoadFileToMemory( mpBitsSrc,mFileName); |
4. 在OnPaint等函数中用DrawDibRealize(),DrawDibDraw()显示位图
5. 关闭视频函数DrawDibClose(),一般放在在析构函数中
以上方法的优点是: 1)显示速度快; 2) 内存占用少; 3) 缩放显示时图形失真小,4) 在低颜色位数的设备上显示高颜色位数的图形图形时失真小; 5) 通过直接处理位图数据,可以制作简单动画。 四、CViewBimap类编程要点
1. 在CViewBimap类中添加视频函数等成员
HDRAWDIB m_hDrawDib; // 视频函数
HANDLEmhBitsSrc; // 位图文件句柄(内存)
LPSTR mpBitsSrc; // 位图文件地址(内存)
BITMAPINFOHEADER *mpBitmapInfo; // 位图信息头 |
2. 在CViewBimap类构造函数中添加打开视频函数
m_hDrawDib= DrawDibOpen(); |
3. 在CViewBimap类析构函数中添加关闭视频函数
if( m_hDrawDib != NULL)
{
DrawDibClose( m_hDrawDib);
m_hDrawDib = NULL;
} |
4. 在CViewBimap类图形显示函数OnPaint中添加GraphicDraw()
voidCViewBitmap::OnPaint()
{
CPaintDC dc(this); // device context for painting
GraphicDraw( );
}
voidCViewBitmap::GraphicDraw( void )
{
CClientDC dc(this); // device context for painting
BITMAPFILEHEADER *pBitmapFileHeader;
ULONG bfoffBits= 0;
CPoint Wid;
// 图形文件名有效 (=0 BMP)
if( mBitmapFileType < ID_BITMAP_BMP ) return;
// 图形文件名有效 (=0 BMP)
// 准备显示真彩位图
pBitmapFileHeader= (BITMAPFILEHEADER *) mpBitsSrc;
bfoffBits= pBitmapFileHeader->bfOffBits;
// 使用普通函数显示位图
if( m_hDrawDib == NULL || mDispMethod == 0)
{
HBITMAP hBitmap=::CreateDIBitmap(dc.m_hDC,
mpBitmapInfo, CBM_INIT, mpBitsSrc+bfoffBits,
(LPBITMAPINFO) mpBitmapInfo,DIB_RGB_COLORS);
// 建立位图
HDC hMemDC=::CreateCompatibleDC(dc.m_hDC);// 建立内存
HBITMAP hBitmapOld= SelectBitmap(hMemDC, hBitmap); // 选择对象
// 成员CRect mDispR用于指示图形显示区域的大小.
// 成员CPoint mPos用于指示图形显示起始位置坐标.
if( mPos.x > (mpBitmapInfo- >biWidth - mDispR.Width() ))
mPos.x= mpBitmapInfo->biWidth - mDispR.Width() ;
if( mPos.y > (mpBitmapInfo- >biHeight- mDispR.Height()))
mPos.y= mpBitmapInfo- >biHeight- mDispR.Height();
if( mPos.x < 0 ) mPos.x= 0;
if( mPos.y < 0 ) mPos.y= 0;
if( mFullViewTog == 0)
{
// 显示真彩位图
::BitBlt(dc.m_hDC,0,0, mDispR.Width(), mDispR.Height(),
hMemDC,mPos.x,mPos.y, SRCCOPY);
} else {
::StretchBlt(dc.m_hDC,0,0, mDispR.Width(), mDispR.Height(),
hMemDC,0,0, mpBitmapInfo- >biWidth, mpBitmapInfo-
>biHeight, SRCCOPY);
}
// 结束显示真彩位图
::DeleteObject(SelectObject(hMemDC,hBitmapOld));
// 删 除 位 图
} else {
// 使用视频函数显示位图
if( mPos.x > (mpBitmapInfo- >biWidth - mDispR.Width() ))
mPos.x= mpBitmapInfo- >biWidth - mDispR.Width() ;
if( mPos.y > (mpBitmapInfo- >biHeight- mDispR.Height()))
mPos.y= mpBitmapInfo- >biHeight- mDispR.Height();
if( mPos.x < 0 ) mPos.x= 0;
if( mPos.y < 0 ) mPos.y= 0;
// 显示真彩位图
DrawDibRealize( m_hDrawDib, dc.GetSafeHdc(), TRUE);
if( mFullViewTog == 0)
{
Wid.x= mDispR.Width();
Wid.y= mDispR.Height();
// 1:1 显示时, 不能大于图形大小
if( Wid.x > mpBitmapInfo- >biWidth )
Wid.x = mpBitmapInfo- >biWidth;
if( Wid.y > mpBitmapInfo- >biHeight)
Wid.y = mpBitmapInfo- >biHeight;
DrawDibDraw( m_hDrawDib, dc.GetSafeHdc()
, 0, 0, Wid.x, Wid.y,
mpBitmapInfo, (LPVOID) (mpBitsSrc+bfoffBits),
mPos.x, mPos.y, Wid.x, Wid.y, DDF_BACKGROUNDPAL);
} else {
DrawDibDraw( m_hDrawDib, dc.GetSafeHdc(),
0, 0, mDispR.Width(), mDispR.Height(),
mpBitmapInfo, (LPVOID) (mpBitsSrc+bfoffBits),
0, 0, mpBitmapInfo- >biWidth, mpBitmapInfo- >biHeight,
DDF_BACKGROUNDPAL);
}
}
return;
} |
五、使用CViewBimap类显示BMP位图
1. 在Visual C++5.0中新建一个名称为mymap工程文件,类型为MFC AppWizard[exe]。在编译运行通过后,在WorkSpace(如被关闭,用Alt_0打开)点击ResourceView,点击Menu左侧的+符号展开Menu条目,双击IDR_MAINFRAME条目,进入菜单资源编辑,在'“查看(V)”下拉式菜单(英文版为View下拉式菜单)的尾部添加“ViewBitmap”条目,其ID为ID_VIEW_BITMAP。
2. 在Visual C++5.0中点击下拉式菜单Project- >Add To project- >Files...,将Bitmap0.h和Bitmap0.cpp添加到工程文件中。
3. 在Visual C++5.0中按Ctrl_W进入MFC ClassWizard,选择类名称为CMainFrame,ObjectIDs: ID_VIEW_BITMAP,Messages选择Command,然后点击Add Fucction按钮,然后输入函数名为OnViewBimap。在添加OnViewBimap后,在Member functions: 中点击OnViewBimap条目,点击Edit Code按钮编辑程序代码。代码如下:
void CMainFrame::OnViewBitmap()
{
// TODO: Add your command handler code here
CViewBitmap *pViewBitmap= NULL;
pViewBitmap= new CViewBitmap( "BITMAP.BMP", this);
pViewBitmap- >ShowWindow( TRUE);
} |
并在该程序的头部添加#include "bitmap0.h",然后编译运行。
4. 找一个大一点的真彩色的BMP位图,将它拷贝到BITMAP.BMP中。
5. 运行时,点击下拉式菜单“查看(V)- >ViewBitmap”(英文版为View- > ViewBitmap)即可显示BITMAP.BMP位图。
六、CViewBimap类功能说明
1. 在客户区中带有水平和垂直滚动条。在位图大小大于显示客户区时,可以使用滚动条;在位图大小小于显示客户区或全屏显示时,滚动条无效。
2. 在客户区中底部带有状态条。状态条中的第一格为位图信息,第二格为位图显示方法,可以是使用普通函数或使用视频函数。在第二格区域内点击鼠标,可在两者之间接换。第三格为位图显示比例,可以是1;1显示或全屏显示。在第三格区域内点击鼠标,可在两者之间接换。在全屏显示时,如果位图比客户区小,则对位图放大; 如果位图比客户区大,则对位图缩小。
3. 支持文件拖放功能。可以从资源管理器中拖动一个位图文件到客户区,就可以显示该位图。
程序调试通过后,可以找一个较大的真彩色位图或调整客户区比位图小,在全屏显示方式下,比较使用普通函数与使用视频函数的差别。可以看出,位图放大时两者差别不大,但在位图缩小时,两者差别明显; 使用视频函数时位图失真小,显示速度快。
还可以从控制面板中将屏幕显示方式从真彩色显示模式切换到256色显示模式,再比较使用普通函数与使用视频函数显示同一个真彩色位图的差别。现在可以体会到使用视频函数的优越性了吧。
在全屏显示时,位图的xy方向比例不相同,如要保持相同比例,可在显示程序中加以适当调整即可,读者可自行完成.
| |
v
本文转自:
http://www.cppblog.com/mzty/archive/2006/05/29/7794.html
posted @
2012-10-24 11:04 王海光 阅读(1118) |
评论 (0) |
编辑 收藏
摘要: 一、打开CD-ROM mciSendString("Set cdAudio door open wait",NULL,0,NULL); 二、关闭CD_ROM mciSendString("Set cdAudio door closed wait",NULL,0,NULL);&n...
阅读全文
posted @
2012-10-24 10:49 王海光 阅读(654) |
评论 (0) |
编辑 收藏
C++函数后面后加到关键字throw(something)限制,是对这个函数的异常安全性作出限制。void f() throw() 表示f不允许抛出任何异常,即f是异常安全的。void f() throw(...) 表示f可以抛出任何形式的异常。void f() throw(exceptionType); 表示f只能抛出exceptionType类型的异常。引别人的一个笑话:throw() 大概会说:“噢,不管你抛什么,就是不准抛。。”throw(...) 呵呵一笑,满脸慈祥:“抛吧抛吧,尽情地抛吧。。。”throw(type) 一听急了:“那可不行,要抛也只能抛我的香烟头,否则要是不小心把俺祖传的金戒指抛掉就太亏了。。。”关于C++的异常传递有三种方法:1.传值(by value)传值的过程中会产生临时对象的拷贝,不能解决多态的问题,如下:myexception继承exception,但是但确无法被正确的调用myexception的方法,造成对异常对象的切割。
1 class myexception:public exception{
2 public:
3 virtual const char* what() throw();
4 };
5 const char* myexception::what(){
6 return "myException";
7 }
8 class A{
9 public:
10 A(){}
11 void f() throw(){
12 throw myexception();
13 }
14 };
15 int main(){
16 A a;
17 try{
18 a.f();
19 }catch(exception exc){
20 cout<<exc.what();
21 }
22 }
运行结果:UnKnown exceptions程序执行是会调用exception的what方法,而不是myexception的what方法。2.传指针(by pointer)指针可以实现多态,但往往会将临时对象的地址作为指针传出去,出现悬挂指针错误。如果在堆上分配内存空间,又往往不知道何时删除对象,出现to be or not to be的错误。
结果显示:myException
1 class myexception:public exception{
2 public:
3 virtual const char * what() const;
4 };
5 const char* myexception::what() const{
6 return "myException";
7 }
8 class A{
9 public:
10 A(){}
11 void f() throw(){
12 throw new myexception();
13 }
14 };
15 int main(){
16 A a;
17 try{
18 a.f();
19 }catch(exception* pexc){
20 cout<<pexc->what();
21 delete pexc;
22 }
23 }
3.传引用(by reference)
传引用是最好的方法,可以克服前面的两个问题。
程序结果显示:myException
1 class myexception:public exception{
2 public:
3 virtual const char * what() const;
4 };
5 const char* myexception::what() const{
6 return "myException";
7 }
8 class A{
9 public:
10 A(){}
11 void f() throw(){
12 throw myexception();
13 }
14 };
15 int main(){
16 A a;
17 try{
18 a.f();
19 }catch(exception& exc){
20 cout<<exc.what();
21 }
22 }
本文转自:
http://www.cnblogs.com/CUCmehp/archive/2009/01/12/1374320.html
posted @
2012-10-24 10:20 王海光 阅读(540) |
评论 (0) |
编辑 收藏
摘要: IOCP模型总结IOCP(I/O Completion Port,I/O完成端口)是性能最好的一种I/O模型。它是应用程序使用线程池处理异步I/O请求的一种机制。在处理多个并发的异步I/O请求时,以往的模型都是在接收请求是创建一个线程来应答请求。这样就有很多的线程并行地运行在系统中。而这些线程都是可运行的,Windows内核花费大量的时间在进行线程的上下文切换,并没有多少时间花在线程运行上。再加上...
阅读全文
posted @
2012-10-23 14:46 王海光 阅读(737) |
评论 (0) |
编辑 收藏
摘要: 一、什么是异常处理
一句话:异常处理就是处理程序中的错误。
二、为什么需要异常处理,以及异常处理的基本思想
C++之父Bjarne Stroustrup在《The C++ Programming Language》中讲到:...
阅读全文
posted @
2012-10-22 17:30 王海光 阅读(542) |
评论 (0) |
编辑 收藏
SYSTEM权限启动应用程序时,无法访问HKEY_CURRENT_USER下的注册表,可以同修改HKEY_USERS下的注册表项来实现设置默认打印机。
HKEY_USERS根键中保存的是默认用户(.DEFAULT)、当前登录用户(如ws)与软件(Software)的信息
HKEY_CURRENT_USER根键中保存的信息(当前用户的子键信息)与HKEY_USERS\.Default分支中所保存的信息是相同的,
任何对HKEY_CURRENT_USER根键中的信息的修改都会导致对HKEY_USERS\.Default中子键信息的修改,反之也是如此。
1 //Reference from:http://topic.csdn.net/u/20111201/07/b5092cbe-bf0a-40a3-a0f4-b7e596fc00e6.html
2 BOOL CCommonFun::GetRegeditSetDefaultPrinter(const CString &sUserId, const CString &sDefaultPrinter)
3 {
4 const int MAX_LEG = 256 * sizeof(TCHAR);
5 HKEY hKey;
6 DWORD dwRegNum = MAX_LEG;
7 TCHAR regBufferName[MAX_LEG] = {0};
8 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"),NULL, KEY_READ, &hKey) == ERROR_SUCCESS )
9 {
10 if(RegQueryInfoKey(hKey, NULL, NULL, NULL, &dwRegNum, NULL, NULL, NULL, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
11 {
12 for(int i = 0; i < (int)dwRegNum; i++)
13 {
14 DWORD dwRegSize = MAX_LEG;
15 RegEnumKeyEx(hKey, i, regBufferName, &dwRegSize, NULL, NULL, NULL, NULL);
16 DWORD dwType;
17 HKEY hSubKey;
18 if(RegOpenKeyEx(hKey, regBufferName, NULL, KEY_READ, &hSubKey) == ERROR_SUCCESS)
19 {
20 TCHAR regBufferValue[MAX_LEG] = {0};
21 dwRegSize = MAX_LEG;
22 RegQueryValueEx(hSubKey, _T("ProfileImagePath"), 0, &dwType, (LPBYTE)regBufferValue, &dwRegSize);
23 CString displayName = regBufferValue;
24 displayName = displayName.Mid(displayName.ReverseFind('\\') + 1, displayName.GetLength());
25 if(displayName.CompareNoCase(sUserId) == 0)
26 {
27 CString sPrinterRegPath;
28 sPrinterRegPath.Format("%s%s", regBufferName,"\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows");
29
30 HKEY hUserKey;
31 if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_USERS, sPrinterRegPath, 0, KEY_READ|KEY_WRITE|KEY_SET_VALUE,
32 &hUserKey))
33 {
34 LOG("打开打印机注册表项%s失败", sPrinterRegPath);
35 return FALSE;
36 }
37
38 TCHAR regDefaultValue[MAX_LEG] = {0};
39 dwRegSize = MAX_LEG;
40 RegQueryValueEx(hUserKey, _T("Device"), 0, &dwType, (LPBYTE)regDefaultValue, &dwRegSize);
41 CString sPrinterInfo = regDefaultValue;
42 sPrinterInfo = sPrinterInfo.Mid(sPrinterInfo.Find(","), sPrinterInfo.GetLength());
43 CString sDefaultInfo;
44 sDefaultInfo.Format("%s%s", sDefaultPrinter, sPrinterInfo);
45
46 if(ERROR_SUCCESS != RegSetValueEx(hUserKey, TEXT("Device"), 0, REG_SZ, (LPBYTE)(LPCTSTR)sDefaultInfo, sDefaultInfo.GetLength()))
47 {
48 LOG("写入注册表%s键值%s失败", sPrinterRegPath, sDefaultInfo);
49 RegCloseKey(hUserKey);
50 return FALSE;
51 }
52 RegCloseKey(hUserKey);
53 }
54 }
55 else
56 {
57 return FALSE; //打开键失败
58 }
59 RegCloseKey(hSubKey);
60 }
61 }
62 }
63 else
64 {
65 return FALSE; //打开键失败
66 }
67 RegCloseKey(hKey);
68 return TRUE;
69 }
posted @
2012-10-18 14:22 王海光 阅读(897) |
评论 (0) |
编辑 收藏