VC环境下对函数调用的汇编分析【原创】
前沿:对于我们平常编程中常出现一些细节,如__stdcall和__cdecl编译器如何为我们处理,函数中变量以及new出来的变量到底存放于哪些地方,等等一些列问题。本文将和大家一起分析程序执行的汇编语言,通过对此过程掌握使自己在开发中熟悉并优化自己的代码。作者:天衣有缝,联系邮件:waterpub@mail.csdn.net,MSN:waterpub_cn@hotmail.com,我的QQ群3226292,转载请保留完整文档。
1.环境:我使用的开发环境是vc7.1,其release单步调试需要对项目属性作如下修改: “C++”--》“常规”--》“调试信息格式” 改为:“用于“编辑并继续”的程序数据库(/ZI)” “C++”--》“优化”--》“优化” 改为:禁用(/Od) 如果你是vc6环境,可如下修改release版属性: 选中Win32 Release然后 Project-》setting-》C/C++ -》Category-》General -》Optimization-》Disable(Debug) -》Debug Info-》Program DataBase -》Link---》Generate Debug Info打上钩
2.c语言代码,如下: /***开始*****************************************************/ #include "stdafx.h" int __cdecl add(int a, int b) { int c; c = a + b; return c; } int _tmain(int argc, _TCHAR* argv[]) { int iResult = add(123, 456); printf("\n************\n");
return 0; } /***结束*****************************************************/
3.程序分析过程,F10单步启动程序: /***开始*****************************************************/ int _tmain(int argc, _TCHAR* argv[]) { 00401020 push ebp 建立堆栈帧 00401021 mov ebp,esp 存入栈基地址,运行后EBP = 0012FEE4 (表明默认堆栈大小约为1兆) 00401023 sub esp,44h 空出一块堆栈区,不知道是干什么的,可有高人指点? ××××××××××××××××××××× 在一篇vc6的汇编调试文章中看到main中变量存储于此堆栈中,但是我在vc7调试发现iresult地址根本不在堆栈范围之内,不知作何解释 ××××××××××××××××××××× 00401026 push ebx 保护现场 00401027 push esi 00401028 push edi int iResult = add(123, 456); 00401029 push 1C8h 参数入栈 0040102E push 7Bh 00401030 call add (401000h) 执行函数调用,按F11跳到本文蓝色处 ××××××××××××××××××××× call指令详解: call指令是push和jmp的结合,先执行push eip将当前地址入栈(函数调用完毕需要用这个地址返回),然后调用jmp指令。因为普通指令无法对eip操作,所以在很多病毒程序中常有如下语句: call @@get_eip @@get_eip: pop ebp ;取得eip ××××××××××××××××××××× 00401035 add esp,8 释放123和456变量所占堆栈 00401038 mov dword ptr [iResult],eax 从eax取出计算结果 printf("\n************\n"); 下面是一个函数的测试 0040103B push offset string "\n************\n" (4060FCh) 变量地址入栈 00401040 call printf (401051h) 执行call调用函数 00401045 add esp,4 变量地址出栈
return 0; 00401048 xor eax,eax 使eax为0,eax就是返回给操作系统的值 } 0040104A pop edi 0040104B pop esi 0040104C pop ebx 恢复现场 0040104D mov esp,ebp 平衡堆栈 0040104F pop ebp 释放堆栈帧 00401050 ret 返回操作系统调用处
函数定义: int __cdecl add(int a, int b) { 00401000 push ebp 建立堆栈帧 00401001 mov ebp,esp 存入栈基地址 00401003 sub esp,44h 开辟变量使用的堆栈区,供函数内部变量使用 执行前ESP = 0012FE84,执行后ESP = 0012FE40 ××××××××××××××××××××× 此处可以打开内存0x0012FE8C,看到 7b 00 00 00 c8 01 00 00,这就是我们传入的123(0x0012fe8c处)和456(0x0012fe90处)变量 ××××××××××××××××××××× 00401006 push ebx 保护现场 00401007 push esi 00401008 push edi int c; c = a + b; 00401009 mov eax,dword ptr [a] 第一个参数,也就是[ebp+8] 0040100C add eax,dword ptr 第二个参数,也就是[ebp+c]
0040100F mov dword ptr [c],eax c变量在栈中,地址为0x0012fe80,就是变量堆栈区顶部 return c; 00401012 mov eax,dword ptr [c] 计算结果存入eax } 00401015 pop edi 回复现场 00401016 pop esi 00401017 pop ebx 00401018 mov esp,ebp 平衡堆栈,回收变量堆栈区 0040101A pop ebp 释放堆栈帧 0040101B ret 回到调用地址,读者从这里转到粉红色处接着看 /***结束*****************************************************/
4.关于__cdecl和__stdcall:(vc项目默认调用方式为__cdecl) 我们将上面的add函数改为__stdcall形式,执行过程如下,我在和上面__cdecl调用不同的地方将作出标记: /***开始*****************************************************/ int _tmain(int argc, _TCHAR* argv[]) { 00401020 push ebp 00401021 mov ebp,esp 00401023 sub esp,44h 00401026 push ebx 00401027 push esi 00401028 push edi int iResult = add(123, 456); 00401029 push 1C8h 0040102E push 7Bh 00401030 call add (401000h) 注意,这句话后面没有了add esp,8,原因:__stdcall调用方式的入栈参数在函数内部已经释放了,所以这句话也就不需要了。 00401035 mov dword ptr [iResult],eax printf("\n************\n"); 00401038 push offset string "\n************\n" (4060FCh) 0040103D call printf (40104Eh) 00401042 add esp,4
return 0; 00401045 xor eax,eax } 00401047 pop edi 00401048 pop esi 00401049 pop ebx 0040104A mov esp,ebp 0040104C pop ebp 0040104D ret
函数部分:
int __stdcall add(int a, int b) { 00401000 push ebp 00401001 mov ebp,esp 00401003 sub esp,44h 00401006 push ebx 00401007 push esi 00401008 push edi int c; c = a + b; 00401009 mov eax,dword ptr [a] 0040100C add eax,dword ptr 0040100F mov dword ptr [c],eax return c; 00401012 mov eax,dword ptr [c] } 00401015 pop edi 00401016 pop esi 00401017 pop ebx 00401018 mov esp,ebp 0040101A pop ebp 0040101B ret 8 这句话就是__stdcall调用方式的入栈参数 在函数内部释放的语句! /***结束*****************************************************/ 说明:对于函数的传值还是传址,大家在此之后自行分析,相信初学者可以看到很多细节方面的咚咚
5.全局变量的初始化: #include "stdafx.h" const char szName[]= "http://blog.csdn.net/waterpub"; class CTestClass { public: CTestClass() { printf("CTestClass::CTestClass()\n"); } ~CTestClass() { printf("CTestClass::~CTestClass()\n"); } }; const CTestClass tobject; int _tmain(int argc, _TCHAR* argv[]) { printf("\n************\n"); return 0; }
在这个程序中szname如何初始化的,为何没有执行到对应的反汇编语句? 因为main函数开始执行的时候,szname已经初始化了,所有我们运行不到这个地方。当用户启动这个exe程序的时候,进入C/C++ 运行时库代码(CRTStartup),由它初始化静态变量及全局变量,然后再转入main函数。
我们现在在上面绿色部分设置一个断点,然后运行,程序断在此处。按(Ctrl+Alt+C :VC7.1的快捷键,vc6有相应的菜单项),点到最下面调用函数,从此可以看出:程序启动时由操作系统执行“mainCRTStartup”函数,简化的代码我贴在下面了,一些很直观的英文没有翻译: /***开始*****************************************************/ int WinMainCRTStartup(void)
{ int initret; int mainret; OSVERSIONINFOA *posvi; int managedapp; posvi = (OSVERSIONINFOA *)_alloca(sizeof(OSVERSIONINFOA));//用这个函数避免了全局存储缓冲区的分配运行时检测
//操作系统版本相关的一些代码 posvi->dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); (void)GetVersionExA(posvi); _osplatform = posvi->dwPlatformId; _winmajor = posvi->dwMajorVersion; _winminor = posvi->dwMinorVersion; _osver = (posvi->dwBuildNumber) & 0x07fff; if ( _osplatform != VER_PLATFORM_WIN32_NT ) _osver |= 0x08000; _winver = (_winmajor << 8) + _winminor; //是否为托管程序 managedapp = check_managed_app();
#ifdef _MT if ( !_heap_init(1) ) /* 多线程用此方式初始化堆 */ #else /* _MT */ if ( !_heap_init(0) ) /* 单线程用此方式初始化堆 */ #endif /* _MT */ fast_error_exit(_RT_HEAPINIT); /* write message and die */
#ifdef _MT if( !_mtinit() ) /* initialize multi-thread */ fast_error_exit(_RT_THREAD); /* write message and die */ #endif /* _MT */
/* * Initialize the Runtime Checks stuff */ #ifdef _RTC _RTC_Initialize(); // 初始化runtime #endif /* _RTC */ /* * 下面是剩余的初始化代码(包括我的程序的全局变量的初始化就在这里了,呵呵) * 初始化完毕调用 main 或 WinMain(在try里面执行的) */
__try {
if ( _ioinit() < 0 ) /* io初始化,应该是输入输出吧,不太清楚 */ _amsg_exit(_RT_LOWIOINIT);
#ifdef WPRFLAG /* get wide cmd line info */ _wcmdln = (wchar_t *)__crtGetCommandLineW(); //取得运行参数的字符串
/* get wide environ info */ _wenvptr = (wchar_t *)__crtGetEnvironmentStringsW(); //取得环境变量的字符串
if ( _wsetargv() < 0 ) _amsg_exit(_RT_SPACEARG); if ( _wsetenvp() < 0 ) _amsg_exit(_RT_SPACEENV); #else /* WPRFLAG */ /* get cmd line info */ _acmdln = (char *)GetCommandLineA();
/* get environ info */ _aenvptr = (char *)__crtGetEnvironmentStringsA();
if ( _setargv() < 0 ) _amsg_exit(_RT_SPACEARG); if ( _setenvp() < 0 ) _amsg_exit(_RT_SPACEENV); #endif /* WPRFLAG */
initret = _cinit(TRUE); /* 全局变量初始化,找的就是这里!!! */ if (initret != 0) _amsg_exit(initret);
#ifdef _WINMAIN_
StartupInfo.dwFlags = 0; GetStartupInfo( &StartupInfo );
#ifdef WPRFLAG lpszCommandLine = _wwincmdln(); mainret = wWinMain( #else /* WPRFLAG */ lpszCommandLine = _wincmdln(); mainret = WinMain( #endif /* WPRFLAG */ GetModuleHandleA(NULL), NULL, lpszCommandLine, StartupInfo.dwFlags & STARTF_USESHOWWINDOW ? StartupInfo.wShowWindow : SW_SHOWDEFAULT ); #else /* _WINMAIN_ */
#ifdef WPRFLAG __winitenv = _wenviron; mainret = wmain(__argc, __wargv, _wenviron); #else /* WPRFLAG */ __initenv = _environ; mainret = main(__argc, __argv, _environ); // 就在这里调用了main,因为运行时代码在exe文件中,所以可以把main函数拿来调用(跟普通的函数没什么区别了,如果你看了win32汇编就知道main或winmain名字都不是定死的了)! #endif /* WPRFLAG */
#endif /* _WINMAIN_ */
if ( !managedapp ) exit(mainret);
_cexit();
} __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) ) // 异常就到这里来了,比如丢失了dll文件 { /* * Should never reach here */
mainret = GetExceptionCode();
if ( !managedapp ) _exit(mainret);
_c_exit();
} /* end of try - except */
return mainret; } /***结束*****************************************************/
6.win32的启动过程 既然console的程序我们分析出来了,win32的又有什么区别呢?区别还是有的(启动程序的核心代码都在crt0.c文件中),上面我把具体的分析方法阐述了一下,win32的分析就留给大家做好啦,:)
7.错误可能也有的,或者可以写的更好,但本菜也只有这个水平了,贻笑大方,悉请高手不吝指正。文章可能随时修改,如果你有什么问题或好的想法,到我的blog(http://blog.csdn.net/waterpub)上留言,不要在这里留了,我来的少,:)
8.深圳南山科技园科技工业大厦 2005-02-22 17:00:00
CNBIE BLOG --------------------------------------------------------------------------------
VC基于ActiveX组件的浏览器插件 原文:VC基于ActiveX组件的浏览器插件
插件是在不久的将来使你的浏览器不再平淡无奇的部分原因。你可以在浏览器设置时用插件加进定制的功能。大多数情况下,你会发现插件是免费的,而对插件提供的服务却不便宜。这对于程序员意味着什么呢?这就意味着,如果决定在自己的Web 站点加进某种特殊的功能,那么你得为其支付服务费。有时候,你可以先付一笔初始服务费,然后每年交一笔维持费用。从用户的观点来看,这意味着他们通常可以免费增强浏览器的功能,获得一个特定的因特网站点提供的附加功能。 本节列举了四种ActiveX插件,这些插件在现在或不久的将来会被见到。 在因特网站点上使用这些插件时,需要为其提供服务支持,我们总是假定购买这些服务是要花费一定代价的。与所列的厂商地址联系,以得到价格信息。 注释 某些插件取得了如此巨大的成功,以至于它们成了IE4.0 的一部分或者将被嵌入Windows 98。 VDOLive/VDOLive Tools/VDOPhone 因特网:http://www.clubvdo.net/clubvdo/Default.asp VDOLive的基本目标是让你在自己的浏览器上播放位于Web站点上的声音影像资料,它包括播放电影以及其它类型的动画的功能。新的VDOLive2.0版保证了更好的影视质量(每秒钟10-15帧)和影像大小(352x288)。它还包括一个有趣的想法:故事书模式。在这种模式下,影像仍然与声音同步。实际上,你将看到情节逐渐展开,这一点就像在故事书中看到的那样。 如果打算创建一个庞大的或者复杂的Web站点,你会想到购买 VDOLiveTools。VDOLive Tools包括压缩影视功能以及使Web站点界面友好所需的一切。它所用的AVI 格式与你以前在任何场合曾用过的AVI格式一样, 也就是说着你可以将其与其它编程工具一起使用,亦即在任何可能使用AVI文件的地方,均可使用该产品。 VDOPhone被设计为双向会谈产品,它通过浏览器工作,而不是一个单独使用的产品,这种灵活性意味着可以设计一个Web页面,在其中嵌入许多不同的功能, 包括定制的ActiveX组件。即便VDOPhone不像市场上有的产品那样功能齐全, 但它被设计成可与微软的网络会议软件一起工作。你可以实际召开一个网络会议,在会议期间,如果需要的话,可以提供白板这样的功能。VDOPhone完全依赖于流技术,这意味着,要应用该产品需要强大的硬件支持以得到优异的图像质量。当对方机器中没有安装影视捕获卡时,也可以将VDOPhone以只传送声音的模式使用。 Acrobat http://www.adobe.com/supportservice/custsupport/LIBRARY/44ae.htm Adobe的Acrobat Reader(阅读器)已经应用于多种场合。如果你是一位程序员,很可能某个时候在自己的机器上已经使用过Acrobat Reader, 该阅读器以书本的形式阅读帮助文件。Acrobat使用PDF(可携带文档格式)格式以显示设备支持的方式显示信息。 新的ActiveX 版本允许你用自己的浏览器观看 PDF文件, 而不是下载文件并且启动整个Acrobat reader。
CNBIE BLOG --------------------------------------------------------------------------------
VC开发数据库基础之ADO篇 (1) 原文:VC开发数据库基础之ADO篇 (1)
VC开发数据库基础之ADO篇 一、ADO简介 ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。 本文示例代码
二、基本流程 万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧! (1)初始化COM库,引入ADO库定义文件 (2)用Connection对象连接数据库 (3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 (4)使用完毕后关闭连接释放对象。
准备工作: 为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。 下面我们将详细介绍上述步骤并给出相关代码。 【1】COM库的初始化 我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
BOOL CADOTest1App::InitInstance() { AfxOleInit(); ......
【2】用#import指令引入ADO类型库 我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) #import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") 这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
几点说明: (1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改 (2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned
【3】创建Connection对象并连接数据库 首先我们需要添加一个指向Connection对象的指针: _ConnectionPtr m_pConnection; 下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。
BOOL CADOTest1Dlg::OnInitDialog() { CDialog::OnInitDialog(); HRESULT hr; try { hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 if(SUCCEEDED(hr)) { hr = m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 ///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为:Provider=Microsoft.Jet.OLEDB.3.51; } } catch(_com_error e)///捕捉异常 { CString errormessage; errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); AfxMessageBox(errormessage);///显示错误信息 }
在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型 HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ) ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权, Options可以是如下几个常量: adModeUnknown:缺省。当前的许可权未设置 adModeRead:只读 adModeWrite:只写 adModeReadWrite:可以读写 adModeShareDenyRead:阻止其它Connection对象以读权限打开连接 adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 adModeShareExclusive:阻止其它Connection对象打开连接 adModeShareDenyNone:允许其它程序或对象以任何权限建立连接
我们给出一些常用的连接方式供大家参考: (1)通过JET数据库引擎对ACCESS2000数据库的连接
m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);
(2)通过DSN数据源对任何支持ODBC的数据库进行连接: m_pConnection->Open("Data Source=adotest;UID=sa;PWD=;","","",adModeUnknown);
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
其中Server是SQL服务器的名称,DATABASE是库的名称
Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如: if(m_pConnection->State) m_pConnection->Close(); ///如果已经打开了连接则关闭它
【4】执行SQL命令并取得结果记录集 为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset; 并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset"); SQL命令的执行可以采用多种形式,下面我们一进行阐述。
(1)利用Connection对象的Execute方法执行SQL命令 Execute方法的原型如下所示: _RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一: adCmdText:表明CommandText是文本命令 adCmdTable:表明CommandText是一个表名 adCmdProc:表明CommandText是一个存储过程 adCmdUnknown:未知
CNBIE BLOG --------------------------------------------------------------------------------
VC开发小技巧20个 原文:VC开发小技巧20个
VC开发小技巧20个
一、打开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), &rWorkArea, 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);
lRect.NormalizeRect();
//显示位图
pStaic->GetDC()->StretchBlt(lRect.left ,lRect.top ,lRect.Width(),lRect.Height(),
&dcMem,0 ,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
dcMem.SelectObject(&poldBitmap);
}
CNBIE BLOG --------------------------------------------------------------------------------
VC快捷键: 原文:VC快捷键:
F1: 帮助
Ctrl+O :Open Ctrl+P :Print Ctrl+N :New Ctrl+Shift+F2 :清除所有书签 F2 :上一个书签 Shift+F2 :上一个书签 Alt+F2 :编辑书签 Ctrl+F2 :添加/删除一个书签 F12 :Goto definition Shift+F12 :Goto reference Ctrl+'Num+' :Displays the next symbol definition or reference Ctrl+'Num-' :Displays the previous symbol definition or reference Ctrl+J/K :寻找上一个/下一个预编译条件 Ctrl+Shift+J/K :寻找上一个/下一个预编译条件并将这一块选定 Ctrl+End :文档尾 Ctrl+Shift+End :选定从当前位置到文档尾 Ctrl+Home :文档头 Ctrl+Shift+Home :选定从当前位置到文档头 Ctrl+B/Alt+F9 :编辑断点 Alt+F3/Ctrl+F :查找 F3 :查找下一个 Shift+F3 :查找上一个 Ctrl+]/Ctrl+E :寻找下一半括弧 Ctrl+Shift+] :寻找下一半括弧并选定括弧之间的部分(包括括弧) Ctrl+Shift+E :寻找下一半括弧并选定括弧之间的部分(包括括弧) F4 :寻找下一个错误/警告位置 Shift+F4 :寻找上一个错误/警告位置 Shift+Home :选定从当前位置到行首 Shift+End :选定从当前位置到行尾 Ctrl+L :剪切当前行 Ctrl+Shift+L :删除当前行 Alt+Shift+T :交换当前行和上一行 Ctrl+Alt+T :Brings up the completion list box Shift+PageDown :选定从当前位置到下一页当前位置 Shift+PageUp :选定从当前位置到上一页当前位置 Ctrl+Shift+Space:显示函数参数的Tooltip Ctrl+Z/Alt+Backspace :Undo Ctrl+Shift+Z/Ctrl+Y :Redo F8 :当前位置变成选定区域的头/尾(再移动光标或者点鼠标就会选定) Ctrl+Shift+F8 :当前位置变成矩形选定区域的头/尾(再移动光标或者点鼠标就会选定) Alt+F8 :自动格式重排 Ctrl+G :Goto Ctlr+X/Shift+Del:Cut Ctrl+C/Ctrl+Ins :Copy Ctrl+V/Shift+Ins:Paste Ctrl+U :将选定区域转换成小写 Ctrl+Shift+U :将选定区域转换成大写 Ctrl+F8 :当前行变成选定区域的头/尾(再移动上下光标或者点鼠标就会选定多行) Ctrl+Shift+L :删除从当前位置到行尾 Ctrl+Shift+8 :将所有Tab变成`或者还原 Ctrl+T :显示变量类型 Ctrl+↑ :向上滚屏 Ctrl+↓ :向下滚屏 Ctrl+Del :删除当前单词的后半截(以光标为分割) Ctrl+Backspace :删除当前单词的前半截(以光标为分割) Ctrl+← :移到前一个单词 Ctrl+→ :移到后一个单词 Ctrl+Shift+← :选定当前位置到前一个单词 Ctrl+Shift+→ :选定当前位置到后一个单词 Ctrl+Shift+T :将本单词和上一个单词互换
Alt+0 :Workspace Window Alt+2 :Output Window Alt+3 :Watch Window Alt+4 :Variables Window Alt+5 :Registers Window Alt+6 :Memory Window Alt+7 :CallStack Window Alt+8 :Disassembly Window Ctrl+W :ClassWizard Alt+Enter :属性
Alt+F7 :Project Settings
F7 :Build Ctrl+F7 :Compile Ctrl+F5 :Run Ctrl+Break :Stops the build F5 :Go Ctrl+F10 :Run to cursor F11 :step into Alt+F10 :Apply codes changes Ctrl+F9 :Enable/Disable a breakpoint Alt+F11 :将 Memory Window 切换到下一种显示模式 Alt+Shift+F11 :将 Memory Window 切换到上一种显示模式 Ctrl+Shift+F9 :去掉所有断点 Ctrl+Shift+F5 :Restarts the program Ctrl+Shift+F10 :将当前行设为下一条指令执行的行 Alt+Num* :滚动到当前指令 Shift+F11 :跳出当前函数 F9 :断点 F10 :step over Shift+F5 :停止 Debugging Ctrl+F11 :Switches between the source view and the disassembly view for this instruction Alt+F12 :Queries on the selected object or current context
Alt+F6 :Toggles the docking feature for the window on/off Shift+Esc :隐藏窗口
Ctrl+Shift+G :? Ctrl+* :打开string table Ctrl+Space :? Ctrl+F3 :向下查找下一个 Ctrl+Shift+F3 :查找上一个 Ctrl+D :查找 Ctrl+I :向下查找下一个 Ctrl+Shift+I :查找上一个 F6 :?Activates the next pane Shift+F6 :?Activates the previous pane Ctrl+M :?Detects duplicate mnemonics in the resource Alt +O : 头文件与cpp文件的交互显示
CNBIE BLOG --------------------------------------------------------------------------------
VC里面如何拆分含汉字与字母的字符串 原文:VC里面如何拆分含汉字与字母的字符串
给别人的程序打补丁,出现了需要拆分含汉字,字母的字符串的情况,到网上搜到的都是同一段代码
"************* 截取字符串 ************** Function InterceptString(txt,length) txt=trim(txt) x = len(txt) y = 0 if x >= 1 then for ii = 1 to x if asc(mid(txt,ii,1)) < 0 or asc(mid(txt,ii,1)) >255 then "如果是汉字 y = y + 2 else y = y + 1 end if if y >= length then txt = left(trim(txt),ii) "字符串限长 exit for end if next InterceptString = txt else InterceptString = "" end if End Function
结果就是测试有些情况下拆分出现乱码,郁闷了好半天,终于发现是网上的这段到处转贴的代码是有错误的,其实这个错误很简单的,就是因为自己没有仔细检查一下就用结果,^_^,这次偶给更正了,希望以后看到的人不会郁闷了.
void CAaaView::OnButton1() { // TODO: Add your control notification handler code here CString ChargeItemName; CString aa = "9494858受得失测试585858585888d888888888888888"; int len=0; ChargeItemName=InterceptString(len,aa); AfxMessageBox(ChargeItemName);
len=ChargeItemName.GetLength(); ChargeItemName=aa.Mid(len); AfxMessageBox(ChargeItemName); } CString CAaaView::InterceptString(int qlen, CString strSource) { int len,i,y; CString sTemp,sreturn,ceshi;
strSource.TrimLeft();strSource.TrimRight(); len=strSource.GetLength(); y=0; sTemp=strSource.Right(len-qlen);
for(i=0;i<len;i++) { if(sTemp[y]<0 || sTemp[y]>255) y=y+2; else y=y+1; if(y>=26) break; } ceshi.Format("%d",y); AfxMessageBox(ceshi); sreturn=sTemp.Left(y);
return sreturn; }
CNBIE BLOG --------------------------------------------------------------------------------
VC如何实现透明窗口 原文:VC如何实现透明窗口 以图片为透明界面,下面的方法只适用WINDOWS2000和XP系统:
在对话框初始化函数中加入: #define LWA_COLORKEY ?0x00000001 #define WS_EX_LAYERED ?0x00080000
typedef BOOL (WINAPI *lpfnSetLayeredWindowAttributes)(HWND hWnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags); lpfnSetLayeredWindowAttributes SetLayeredWindowAttributes;
//设置成边缘透明 COLORREF maskColor=RGB(0,255,0); HMODULE hUser32 = GetModuleHandle("user32.dll"); SetLayeredWindowAttributes = (lpfnSetLayeredWindowAttributes)GetProcAddress(hUser32,"SetLayeredWindowAttributes"); SetWindowLong(GetSafeHwnd(), GWL_EXSTYLE, GetWindowLong(GetSafeHwnd(), GWL_EXSTYLE) │ WS_EX_LAYERED); ? ?SetLayeredWindowAttributes(GetSafeHwnd(), maskColor, 255, LWA_COLORKEY); FreeLibrary(hUser32);
其中maskcolor是透明颜色,也就是说把什么颜色区域设置成透明
CNBIE BLOG --------------------------------------------------------------------------------
vc入门宝典(一)(菜单) 原文:vc入门宝典(一)(菜单)
菜单
何志丹
菜单项属性说明: ID 标识菜单的唯一常量。 Caption 菜单项标题,“&" 后面的字符为快捷键,在菜单项后的字符将加下划线。 Separator 水平线,其它属性无效。 Pop_up 有子菜单 Grayed 无效,标题以灰色显示 Inactive 无效,标题正常显示 Checked 在标题前加一个对钩 break 为None时,使它和它的兄弟们一行或一列显示。 Help 只对最上层菜单项有效,使它及后面的最上层菜单移到窗口的右上角。 Prompt 当鼠标指向它时的提示信息 多文档应用程序除了生成IDR_MAINFRAME外,还生成标识符为IDR_xxxxTYPE,其中xxxx为应用程序名。它们分别对应无文档和有文档时。 一个菜单id可以在多个类有响应函数,但只会有一个响应。 我实验得出的结果,CChildFrame,CCMenuApp,CCMenuDoc,CCMenuView,CMainFrame(我的应用程序名为CMenu)的响应顺序为: 在IDR_MAINFRAME中,CMainFrame,CCMenuApp其它三个不响应. 在IDR_XXXXTYPE 中,CCMenuView,CCMenuDoc,CChildFrame,CCMenuApp,CMainFrame. ctrl+w打开ClassWizard,选好工程,类(最常选的是xxxView),在id中选择我们要修改的菜单项。 双击COMMAND(或UPDATE_COMMAND_UI)点确定就可以了。再在成员函数中双击我们刚刚加的函数,就可以编辑函数了。 当用户单击菜单时,我们刚刚编辑的函数会执行。 如果我们双击的是UPDATA_COMMAND_UI,则响应形式类似如下: void ... OnUpdate...(CCmdUI * pCmdUI) { pCmdUI->SetCheck(true);//在菜单项前加一个对钩 pCmdUI->Enable(true);// 使菜单项能够使用 } 因为此函数往往影响到它的外形,故在它的“父亲”或“祖父”被选中时就会执行。
CWnd类中与菜单有关的几个函数。 SetMeun(CMenu *pMenu); 修改窗口的菜单,为NULL,则表示删除。 常用的还有 GetMenu(); GetSystemMenu();
CMenu的一些函数。 AppendMenu()函数指定的菜单最后附加一个新菜单项,同时可以指定菜单项的相关情况,它有两个语法。 nFlag指定状态,可以是以下四组之一或相组合而成,还可以与MF_POPUP组合表示添加的是弹出式菜单。 MF_CHECKED,MF_CHECKED MF_DISABLED,MF_ENABLED,MF_GRAYED MF_STRING,MF_OWNERDRAW,MF_SEPARATOR,MF_BITMAP菜单项是字符串,自画型,分隔线,位图。 MF_MENUBARBREAK,MF_MENUBREAK nIDNewItem 指定菜单项的id. lpszNewItem指定菜单项的内容,与nFlag有关。为MF_OWNERDRAW时,该参数为数据指针,用来传送数据,系统在发送消息WM_MEASUREITEM和WM_DRAWITEM时将该数据存入参数 的(DRAWITEMSTRUCT结构)itemData域;nFlag为MF_STRING时该参数为菜单标题。 InsertMenu nFlags指定菜单项位置和状态,状态选项参见AppendMenu()函数,位置选项为MF_BYCOMMAND,MF_BYPOSITION. nPositin若为MF_BYCOMMAND,新菜单项插在指定菜单项之前;或为MF_BYPOSITION,该参数指定新菜单项的位置,为-1插到最后。 ModifyMenu()参数与InsertMenu类似。 DeleteMenu删除菜单项 RemoveMenu移去菜单项
设置和显示浮动菜单 BOOL TrackPopupMenu(UINT nFlags,int x,int y, CWnd *pWnd,LPCRECT = NULL); nFlag浮动式菜单坐标设定方式及鼠标操作方式,有效值如下: TPM_CENTERALIGN TPM_LEFTALIGN TPM_RIGHTALIGN TPM_LEFTBUTTON TPM_RIGHTBUTTON x,y浮动式菜单坐标 pWnd指定操作菜单的窗口 lpRect指定鼠标操作范围 在客户区单击左键就会弹出快捷菜单,方法二需要在资源编辑器中编辑一个新菜单,方法三必须有主菜单。 方法一: void CHeView::OnLButtonDown(UINT nFlags, CPoint point) { CMenu PopupMenu; PopupMenu.CreatePopupMenu(); PopupMenu.AppendMenu(MF_STRING,ID_FILE_NEW,"NEW.."); //...
ClientToScreen(&point); PopupMenu.TrackPopupMenu(TPM_CENTERALIGN|TPM_RIGHTBUTTON,point.x,point.y,this);
CView::OnLButtonDown(nFlags, point); }
方法二: void CHeView::OnLButtonDown(UINT nFlags, CPoint point) {
CMenu menu; menu.LoadMenu(IDR_DUMMY); CMenu *pMenu=menu.GetSubMenu(0); ASSERT(pMenu!=NULL);
ClientToScreen(&point); pMenu->TrackPopupMenu(TPM_CENTERALIGN|TPM_RIGHTBUTTON,point.x,point.y,this); CView::OnLButtonDown(nFlags, point); }
方法三: void CHeView::OnLButtonDown(UINT nFlags, CPoint point) { CWnd *pWnd=AfxGetApp()->GetMainWnd(); CMenu * pMenu=pWnd->GetMenu(); pMenu=pMenu->GetSubMenu(0); ASSERT(pMenu!=NULL);
ClientToScreen(&point); pMenu->TrackPopupMenu(TPM_CENTERALIGN|TPM_RIGHTBUTTON,point.x,point.y,this);
} 习题: 动态菜单,用户点击“更多菜单”,增加一些菜单项。 其实,自画菜单原理不难理解.AppendMenu的风格选自画,将自画用的信息(指针)强制转化成LPCTSTR,再重
载DrawItem就行了,注意自画用的信息不要提前delete了. 示例如下: COwnerMenu.h中 class CMenuItem { public: CString m_szText; COLORREF m_color; CMenuItem(CString szText,COLORREF color) { m_szText = szText; m_color = color; }
};
#include <afxtempl.h>
class COwnerMenu : public CMenu { public: void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); bool AppendMenu(UINT nIDNewItem,CString caption,COLORREF color); COwnerMenu();
CTypedPtrList<CPtrList,CMenuItem *> m_MenuList; virtual ~COwnerMenu();
};
COwnerMenu.cpp中 COwnerMenu::~COwnerMenu() { while(m_MenuList.GetCount()) { CMenuItem *pMenuItem = m_MenuList.GetHead(); delete pMenuItem; m_MenuList.RemoveHead(); } }
bool COwnerMenu::AppendMenu(UINT nIDNewItem, CString caption, COLORREF color) { CMenuItem * pMenuItem = new CMenuItem(caption,color); m_MenuList.AddTail(pMenuItem);
return CMenu::AppendMenu(MF_OWNERDRAW,nIDNewItem,(LPCTSTR)pMenuItem); }
void COwnerMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); pDC->SetTextColor(((CMenuItem*)lpDrawItemStruct->itemData)->m_color); pDC->TextOut(0,0,((CMenuItem*)lpDrawItemStruct->itemData)->m_szText);
}
在使用菜单的地方(不要忘记了加头文件): void COwnerMenuView::OnRButtonDown(UINT nFlags, CPoint point) { COwnerMenu menu; menu.CreatePopupMenu();
menu.AppendMenu(ID_1,"1",RGB(0,0,128)); ClientToScreen(&point);
menu.TrackPopupMenu(TPM_LEFTBUTTON|TPM_LEFTALIGN,point.x,point.y,this); CView::OnRButtonDown(nFlags, point); }
CNBIE BLOG --------------------------------------------------------------------------------
VC入门宝典三(String) 原文:VC入门宝典三(String)
CString:office" />
何志丹
主要内容:
1,主要函数的实现
2,常用函数
3.CString与char []的相互转换
4,将NULL字节放入CString中
vc中最主要函数不易理解。
CString::CString(char *p)
{
int n=strlen(p);
m_data = new char[n+1];
strcpy(m_data,p);
}
CString::CString(CString &other)
{
int n=strlen(other.m_data);
m_data = new char[n+1];
strcpy(m_data,other.m_data);
}
CString& CString::operator = (CString& other)
{
delete[] m_data;
int n=strlen(other.m_data);
m_data = new char[n+1];
strcpy(m_data,other.m_data);
return *this;
}
String::~String()
{
delete [] m_data;
}
Collate,Compare 与一个字符长指针所指的字符串进行比较,与strcmp相同,它们的区别是,分别调用_tcscoll,_tcsicmp。
Delete
int Delete( int nIndex, int nCount = 1 )
返回值是被删除前的字符串的长度,nIndex是第一个被删除的字符,nCount是一次删除几个字符。根据我实验得出的结果:当nCount>字符串的长度时会出错,当nCount过大,没有足够的字符删除时,此函数不执行。
FindOneOf
int FindOneOf( LPCTSTR lpszCharSet ) const;
此函数的功能是在查找lpszCharSet中的任意一个字符,查到一个就把位置返回,没有查到返回0。如:
CString str = "0123456789";
int x = str.FindOneOf("31");
x的值是1。
Find
int Find( TCHAR ch ) const;
int Find( LPCTSTR lpszSub ) const;
int Find( TCHAR ch, int nStart ) const;
int Find( LPCTSTR pstr, int nStart ) const;
返回值查找到的序号,ch待搜索的字符,lpszSub待搜索的字符子串,nStart 从那里开始搜索。如:
CString str = "0123456789";
int x = str.Find("34",4);
返回的值是-1.
GetAt
TCHAR GetAt( int nIndex ) const;
返回标号为nIndex的字符,你可以把字符串理解为一个数组,GetAt类似于[].注意nIndex的范围,如果不合适会有调试错误。
Insert
int Insert( int nIndex, TCHAR ch ) int Insert( int nIndex, LPCTSTR pstr ) 返回修改后的长度,nIndex字符(或字符串)插入后的标号。
Left
CString Left( int nCount ) const;
返回的字符串的前nCount个字符。
Right与Left类似
MakeLower ,MakeUpper改变字符的大小写
MakeReverse字符倒置,如:
CString str = "X0123456789";
str.MakeReverse();
str变为"9876543210X"
+=
const CString& operator +=( const CString& string ); const CString& operator +=( TCHAR ch ); const CString& operator +=( LPCTSTR lpsz );
将参数合并到自己身上。
如: CString str = "0123456789";
str+="ha";
str为"0123456789ha";
str[]
TCHAR operator []( int nIndex ) const;
象处理字符数组一样处理字符串。
注意是只读的。
CString str = "0123456789";
str[0]='x';
是错误的。
TrimLeft,TrimRight
void TrimLeft( );
void CString::TrimLeft( TCHAR chTarget );
void CString::TrimLeft( LPCTSTR lpszTargets );
void TrimRight( );
void CString::TrimRight( TCHAR chTarget );
void CString::TrimRight( LPCTSTR lpszTargets );
CString str = "\n\t a";
str.TrimLeft();
str为“a”;
如果没有参数,从左删除字符(\n\t空格等),至到遇到一个非此类字符.
当然你也可以指定删除那些字符.
如果指定的参数是字符串,那么遇上其中的一个字符就删除.
CString str = "abbcadbabcadb ";
str.TrimLeft("ab");
结果"cadbabcadb "
int CString::Remove( TCHAR ch );
ch删除的字符.
返回删除字符的个数,有多个时都会删除.
数组之间的相互转换>CString 与char []之间的转换.
char str[100] = ”str”;
CString sstr = “sstr”;
str.Format(“%s”,str);
str = LPCTSTR sstr;
strcpy(str,(LPCTSTR)sstr);
如果是赋值,则要:
CString s(_T("This is a test ")); LPTSTR p = s.GetBuffer(); // 在这里添加使用p的代码 if(p != NULL) *p = _T('\0'); s.ReleaseBuffer(); // 使用完后及时释放,以便能使用其它的CString成员函数
str的值变了.
将NULL字节放入CString中 1,CString str("abc\0""def", 7); str +="g"; int nLength = str.GetLength(); nLength为8. 2,CString str("My name is hedan!"); str.SetAt(5, 0); int nLength = str.GetLength(); 注意:不是所有的CString成员函数都可以,在使用时一定要小心。
实例:动态配置数据源 CString strDsn,strDBQ; strDsn = "DSN=test"; strDBQ.Format("DBQ=%s",strSourceMDBName); CString strConnect = strDsn + " " + strDBQ ; strConnect.SetAt(strDsn.GetLength(),'\0'); SQLConfigDataSource(NULL, ODBC_ADD_SYS_DSN, "Microsoft Access Driver (*.mdb)", strConnect);
|