| 個人檔案飘零风雨亭部落格清單 | 說明 |
|
|
3月14日 Symbol Paths(转)Symbol Paths(转)
http://msdn2.microsoft.com/en-us/library/ms680689(VS.85).aspx
The library uses the symbol search path to locate debug symbols (.dbg file) for .dll, .exe, and .sys files by appending "\symbols" and "\dll" or "\exe" or "\sys" to the path. For example, the typical location of symbol files for .dll files is c:\mysymbols\symbols\dll. For .exe files, the location is c:\mysymbols\symbols\exe. To specify where the symbol handler will search disk directories for symbol files, call the SymSetSearchPath function. Alternatively, you can specify a symbol search path in the UserSearchPath parameter of the SymInitialize function.
The UserSearchPath parameter in SymInitialize and the SearchPath parameter in SymSetSearchPath take a pointer to a null-terminated string that specifies a path, or series of paths separated by a semicolon. The symbol handler uses these paths to search for symbol files. If this parameter is specified as a non-null value, the symbol handler searches only the paths set by the application. If this parameter is NULL, the symbol handler first searches the current working directory of the application, then the system root directory (%windir%). If you set the _NT_SYMBOL_PATH or _NT_ALT_SYMBOL_PATH environment variable, the symbol handler searches for symbol files in the following order:
The current working directory of the application.
The _NT_SYMBOL_PATH environment variable. The _NT_ALT_SYMBOL_PATH environment variable. To retrieve the search paths, call the SymGetSearchPath function. The search path for program database (.pdb) files is different than the path for debug (.dbg) files. The algorithm is determined by the functionality of the symbol library. By default, Microsoft Visual C/C++ creates Microsoft format symbols, strips them from the image, and places them in a separate .pdb file. Typically, the .pdb file will be located in the directory that contains the executable image. Visual C/C++ embeds the absolute path to the .pdb file in the executable image. If the symbol handler cannot find the .pdb file in that location or if the .pdb file was moved to another directory, the symbol handler will locate the .pdb file using the search path described for .dbg files. 1月3日 fatal error C1900(备忘)fatal error C1900:
1>------ Build started: Project: sanguo, Configuration: Release Win32 ------
1>Linking... 1>fatal error C1900: Il mismatch between 'P1' version '20060201' and 'P2' version '20050411' 1>LINK : fatal error LNK1257: code generation failed 1>Build log was saved at "file://e:\project\sanguo\temp\release\BuildLog.htm" 1>sanguo - 1 error(s), 0 warning(s) 上网查了一下估计是链接库打了补丁,而我的没打,造成vc编译版本不一致 to fan:
i can't email to you because your setting's limit.
fatal error C1900 generate because your compile tools's version different from the lib's(which someone compiled it using another vc2005).
solve this problem, only need patch the same sp1 or sp2 for your vc2005(i.e. your program and the lib complier's patch must same)!
12月20日 关于keystate(备忘)1. GetKeyState(VK_CONTROL) & 0x80
2. GetAsyncKeyState(VK_LSHIFT) & 0x8000
3. BOOL GetKeyboardState(PBYTE lpKeyState); if(keystate[VK_RSHIFT]) { // +++ } 区别: 1:从windows消息队列中取得键盘消息,返回key status. 2:直接侦测键盘的硬件中断,返回key status. 3:当从windows消息队列中移除键盘消息时,才返回key status. new抛出异常的一种情况最近修改玩家反馈的bug(dmp文件) 有些内存错误很难查。 在一次无意中发现,在new中抛出异常的一种情况, 以后crash发现new异常时可查看是否上述情况:) 12月12日 程序与lua脚本数据交换(备忘)(这些是以前跟踪分析的,其实挺简单,只是不懂时用来跟踪一遍就完全明白了, 写在这里是为了备忘)
先给一下lua的值定义:
lua中的值:
TValue{ union{gc*,void*,int,bool}; //可回收对象,userlightdata, number, bool tt; //类别 }; 其中gc结构如下: GC{ gch; // gc head ts; // tstring u; // user data cl; // closure h; // table p; // proto uv; // upval th; // theard }; 一。脚本给程序传表格
以下代码摘自boswars,用来分析:
脚本如下: test( {Name = "sprite-mana", File = "general/mana2.png", Offset = {0, -1}, Size = {31, 4}} ) // {}中即是表格
程序如下
void test(lua_State *l)
{ lua_pushnil(l); while (lua_next(l, i + 1)) { key = LuaToString(l, -2); // key name if (!strcmp(key, "Name")) { name = LuaToString(l, -1); } else if (!strcmp(key, "File")) { File = LuaToString(l, -1); } else if (!strcmp(key, "Offset")) { if (!lua_istable(l, -1) || luaL_getn(l, -1) != 2) { LuaError(l, "incorrect argument"); } lua_rawgeti(l, -1, 1); // offsetX lua_rawgeti(l, -2, 2); // offsetY HotX = LuaToNumber(l, -2); HotY = LuaToNumber(l, -1); lua_pop(l, 2); // Pop offsetX and Y } else if (!strcmp(key, "Size")) { if (!lua_istable(l, -1) || luaL_getn(l, -1) != 2) { LuaError(l, "incorrect argument"); } lua_rawgeti(l, -1, 1); // Width lua_rawgeti(l, -2, 2); // Height Width = LuaToNumber(l, -2); Height = LuaToNumber(l, -1); lua_pop(l, 2); // Pop Width and Height } else { // Error. LuaError(l, "incorrect field '%s' for the DefineSprite." _C_ key); } lua_pop(l, 1); // pop the value; } } lua调用我们的函数void test(lua_State *l)后变成:
--- 表 我们需要先
lua_pushnil(l);(把nil作为key)后: --- nil --- 表 lua_next(l,1)后://1是表的位置,key则是上面的nil
--- value // 得到的value --- key // 得到的key --- 表 lua_rawgeti(l, -1, 1); //offsetX; 参数-1是table(value是一个表),
// 1是表的下标, 得到offsetX存在top,并inc top --- offsetX
--- value --- key --- 表 lua_rawgeti(l, -2, 1); //offsetY; 参数-2是table(value是一个表)(因为上面压入offsetX,所以变成-2了)
// 1是表的下标, 得到offsetX存在top,并inc top --- offsetY --- offsetX --- value --- key --- 表 使用
HotX = LuaToNumber(l, -2); HotY = LuaToNumber(l, -1);
保存得到的offsetX和offsetY 然后将它们出栈:
lua_pop(l, 2); // Pop offsetX and offsetY 变成: --- value --- key --- 表 再将value出栈:
lua_pop(l, 1); // pop the value; 变成: --- key --- 表 最后回到while函数继续调用
lua_next(l,1)://1是表的位置,key则是上面的key(上一次得到的key), //这样些函数数又将会得到下一个key和value放堆栈中 变成: --- 下一个value --- 下一个key --- 表 如此循环!
附:
这里有一篇类似文章参见:
使用lua_next()遍历表
二。程序生成表格给脚本:
以下代码也是从boswar拷过来的,挺简单就不分析了
static int CclFilteredListDirectory(lua_State *l, int type, int mask)
{ char directory[256]; const char *userdir; std::vector<FileList> flp; int n; int i; int j; int pathtype; LuaCheckArgs(l, 1);
userdir = lua_tostring(l, 1); n = strlen(userdir); pathtype = 0; // path relative to stratagus dir
if (n > 0 && *userdir == '~') { // path relative to user preferences directory pathtype = 1; } // security: disallow all special characters
if (strpbrk(userdir, ":*?\"<>|") != 0 || strstr(userdir, "..") != 0) { LuaError(l, "Forbidden directory"); } if (pathtype == 1) {
++userdir; sprintf(directory, "%s/%s", UserDirectory.c_str(), userdir); } else { sprintf(directory, "%s/%s", StratagusLibPath.c_str(), userdir); } lua_pop(l, 1); lua_newtable(l);
n = ReadDataDirectory(directory, NULL, flp); for (i = 0, j = 0; i < n; ++i) { if ((flp[i].type & mask) == type) { lua_pushnumber(l, j + 1); lua_pushstring(l, flp[i].name); lua_settable(l, 1); ++j; } delete[] flp[i].name; } return 1;
} 附:这个可参考:
Lua 语言和C集成调研小结
10月24日 汇编初学者问题合集(转)0 关于指令时间的问题
上次有兄弟问关于 指令时间的问题,答复查看 intel 手册是一个办法。
但很多人没有那个东东吧!,所以可以用另一招,在编译时加入 /Sc 选项:
ML /Fl /Sc Kinds.asm
还有有位兄弟问过 为什么 mov ax,offset table 比 lea ax,table 速度
要快?但到底快到什么程度,恐怕也没法感性认识。下面让偶们来
看看实际效果:
首先在源文件 Kinds.asm 中敲入:
data segment
tabledw? data ends code segment
assume cs:code,ds:data start: push ds sub ax,ax push ax mov ax,data
mov ds,ax mov ax,offset table
lea ax,table idiv table
retf
code ends end start 保存后,控制台中敲入:
ML /Fl /Sc Kinds.asm
完成后,在同一目录下用记事本打开 Kinds.lst 文件:
0000 datasegment
0000 0000 tabledw? 0002 dataends 0000 codesegment
assume cs:code,ds:data 0000start: 0000 10 1Epushds 0001 3 2B C0 subax,ax 0003 11 50 pushax 0004 4 B8 ---- Rmovax,data
0007 2 8E D8 movds,ax 0009 4 B8 0000 Rmovax,offset table
000C 8 8D 06 0000 R leaax,table 0010 177+ F7 3E 0000 R idivtable
0014 26 CB retf 0015 codeends endstart 可以清楚地看到 :
mov ds,ax 只需要 2个时钟周期
mov ax,offset table 需要 4个 lea ax,table 则需要 8 个 而 idiv table 更是夸张的用到了超过 177 个时钟周期。 是不是一目了然呢?呵呵!
1 debug中使用sal指令的问题 [问题]
在debug里面使用A指令,输入如下代码:
***************************
MOV AX,0ABC DEC AX AND AX,00FFH MOV CL,4 SAL AL,1 ***************************
当输入到 sal al,1 时提示error [回答]
shl 与 sal 作用是完全一样的,所以在编译的时候自动将
sal 转换成了 shl .使用sal dubug 不识别,换成shl就搞定了。 可以把上述代码编译成 EXE 文件,然后用debug 中 u 指令查看, 结果 sal 的地方 被换成 shl。
2 看似 ''不可能'' 的汇编问题
[问题]
怎样用一条指令把BX的内容加上123,放在AX里?
[回答]
猛一看起来好像不可能,通常的做法是:
add bx,123
mov ax,bx
这至少要用到两条指令~~~要是mips机构的系统就好了,因为其中有
3参数指令:
addx $1,$2,100 ----- $1=$2+100
那么没办法了么?不是的!
想一下 lea 指令 ,呵呵~~~看一下如下的指令:
lea ax,[bx+123]
lea 取变量的偏移放入 ax 中,[] 代表变量是间接寻址,他的地址就等于[]
中的值,即 bx+123,这样就达到了题目的目的。
3 用移位指令来代替乘法指令 大家都知道可以用移位指令来做形如 2,4,8 等2的整次幂的乘法,
但是非整次幂呢?比如 乘10。其实很简单:
36 * 10 = 36 * (8 + 2) = 36 * 8 + 36 * 2
即等于: 24h * 8 + 24h * 2 接下来不用我讲了吧,这一方法也可以进一步推广。 4 察看 debug 状态寄存器 of(溢出) df(方向) if(中断) sf(符号) zf(零) af(辅助进位) pf(奇偶) cf(进位) 为一的时候 ov(OVerflow) dn(DowN) ei(Enable Interrupt) ng(NeGtive) zr(ZeRo) ac(Auxiliary Carry) pe(Parity Even) cy(CarrY) 为零的时候 nv(Not oVerflow) up(UP) di(DIsable interrupt) pl(PLus) nz(Not Zero) na(Not Auxiliary) po(Parity Odd) nc(Not Carry) 5 关于简单汇编环境的搭建
如果是老鸟,则随心所欲自由选择。我在这里只是给新手一些我的建议。
我一向反对新手一上来(毫无汇编编译经验)就使用汇编的集成开发环境比如
radasm之类,这样不但容易出错,而且不能真正了解编译器和连接器的底
层命令行用法。因为汇编本身编译就已经很简单了,不像C++之类有那么多
优化的东东,你再套一个花里胡哨的集成环境,对初学者岂不很累?
但我认为用一个带颜色标记的编辑器却是有必要的。
(比如简单的几句用记事本就很好,复杂的我推荐使用editplus2(别忘了要
下载汇编的语法文件。)
总的来说整个汇编环境是这样的: 16位dos程序: masm6.1x or nasm + editplus2 32位windows程序: masm32v9.0 or nasm + editplus2 + 一个资源编辑器
(masm611下载地址: www.aogosoft.com,masm32下载地址
附, 上述的第2点解决初学者常碰到的下述问题: http://topic.csdn.net/t/20051008/15/4312328.html 上述也解决了这样一个问题: 楼主wjk302(影子传说)2005-10-08 15:17:37 在 其他开发语言 / 汇编语言 提问 lea指令在win32汇编中问题 ebp的值就是指向堆栈的地址对吧,那么lea eax,[ebp] 就是得到ebp指向的堆栈内容的偏移地址, 和寄存器ebp的值是相同的么? 还有就是offset命令是相对于文件头的偏移地址,还是相对于整个寻址空间的偏移地址? 1 楼mydo(侯佩|hopy|ks)回复于 2005-10-08 15:58:40 得分 5和寄存器ebp的值是相同的么? yes! Top 2 楼wjk302(影子传说)回复于 2005-10-08 16:09:46 得分 0 1.如果和和寄存器ebp的值是相同的,那么 lea eax,[ebp] 等价于 mov eax,ebp ???
2.offset命令是相对于文件头的偏移地址,还是相对于整个寻址空间的偏移地址??? Top 3 楼csdsjkk()回复于 2005-10-08 16:57:30 得分 5cpu 中没有文件的概念,当然是指地址空间了
Top 4 楼wjk302(影子传说)回复于 2005-10-08 17:07:04 得分 0 最后一点了 1.如果和和寄存器ebp的值是相同的,那么 lea eax,[ebp] 等价于 mov eax,ebp ???
Top 5 楼mydo(侯佩|hopy|ks)回复于 2005-10-08 17:21:24 得分 10请到我的blog参考 "看似 "不可能" 的汇编问题" 一章: http://hopy.blogchina.com/ 10月23日 在vc中调试汇编程序在vc中调试汇编程序: 实现方法参见转来的几篇文章: 1. 贴个用VC++在源码级下调试MASM32汇编程序的方法。。。大家交流。。 (转)
下面提供一种用VC++6.0调试的方法,好像比前两种更简单些。 方法如下: 至此,请随心所欲的用VC调试器的强大功能,调试你的Masm32程序吧! 附:我一般都是用个批处理(L_debug.bat)一下完成: Spring.W
2. 标 题: Myshell.asm的错误更正及在VC中融合masm32编译的方法 有朋友来信说:Myshell.asm用TestVC测试时候,报告找不到文件,出错原因如下: ;---------------------------------------------- 在此对各位朋友深表歉意! 另外: 方法如下: 如果是RELEASE模式,则输入: 在Outputs中输入: 如果是RELEASE模式,则输入: 如果您的没有把masm安装在c盘,则要作相应的修改。 之后,您可以直接对TestVC工程编译连接了。 再次谢谢各位朋友的来信! 3. 使用VC6调试器源码级调试win32汇编程序(转)
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=395823
0xC0000006: In page error这样的错误最近好些玩家的机器碰到0xC0000006: In page error这样的错误 如何解决. 注:该问题已解决. 方法是采用了ryan的提议:自己读取贴图文件再用d3d的内存创建贴图. 这样经验证再没收到用户返回 的此类crash
flywind 13:43:31 2. wow的: flywind 13:44:51 如果您检查安装日志文件,可能会看到与下列其中一项类似的项: 原因 flywind 13:46:36 张 13:47:14 张 13:47:32 flywind 13:47:34 flywind 13:47:47 flywind 13:49:18 After recieving some email messages, I go to open one and the message window is opened and the message is displayed. But then I get a error message saying an "Unhandled exeption has occured" and Eudora just hangs until I go into Windows Task Manager and end the task. I opened up the exception log and got the following flywind 13:50:04 张 13:51:04 flywind 13:51:22 flywind 13:51:27 张 13:52:12 flywind 13:52:23 flywind 13:52:35 flywind 13:53:29 flywind 13:53:59 flywind 13:55:15
10月18日 编译lua5.1debug版本编译lua5.1debug版本 ---- flipcode@msn.com 按win+R输入cmd运行控制台
1. 先运行设置vc的环境设置: E:\Microsoft Visual Studio .NET 2003\Common7\Tools\vsvars32.bat 2. 在lua-5.1目录下的etc找到luavc.bat, 执行即可. 另外如果需要编译debug版,可将luavc.bat修改如下再运行即可: cd src cl /Od /W3 /c /Zi /D "LUA_BUILD_AS_DLL" lua.c
优化-
10月12日 dx 程序crash问题war3在运行时立即按下win+L键,然后再登录回到windows继续时会crash!
directx sdk提供的程序也会! 我们的程序也一样. 结论: 估计XP下(其它系统未知)dx程序都会有这种情况... 我想知道碰到这种情况,有没有办法解决! OGRE的分辨率枚举类不错啊dx9的分辨率枚举总感觉有点点麻烦,我原来还用了ddraw来枚举,
参考了一下OGRE3D,发现原来是很轻松的事, 现在也改到dx9来枚举分辨率了
但是还是有个令人不太满意的地方: 见上一篇文章显存获取. IBM Rational PurifyPlusIBM Rational PurifyPlus 是个好东西,网上居然找到破解码 9月28日 使用Visual Leak Detector检测内存泄漏 (转)使用Visual Leak Detector检测内存泄漏 (转)
摘自: http://www.testage.net/AutoTest/Opentest/200609/896.htm 作者:dofty 文章来源:dofty的专栏 点击数: 更新时间:2006-9-8 初识Visual Leak Detector 灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题。当程序越来越复杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题。内存泄漏是最常见的内存问题之一。内存泄漏如果不是很严重,在短时间内对程序不会有太大的影响,这也使得内存泄漏问题有很强的隐蔽性,不容易被发现。然而不管内存泄漏多么轻微,当程序长时间运行时,其破坏力是惊人的,从性能下降到内存耗尽,甚至会影响到其他程序的正常运行。另外内存问题的一个共同特点是,内存问题本身并不会有很明显的现象,当有异常现象出现时已时过境迁,其现场已非出现问题时的现场了,这给调试内存问题带来了很大的难度。 Visual Leak Detector是一款用于Visual C++的免费的内存泄露检测工具。可以在http://www.codeproject.com/tools/visualleakdetector.asp 下载到。相比较其它的内存泄露检测工具,它在检测到内存泄漏的同时,还具有如下特点:
1、 可以得到内存泄漏点的调用堆栈,如果可以的话,还可以得到其所在文件及行号;
2、 可以得到泄露内存的完整数据;
3、 可以设置内存泄露报告的级别;
4、 它是一个已经打包的lib,使用时无须编译它的源代码。而对于使用者自己的代码,也只需要做很小的改动;
5、 他的源代码使用GNU许可发布,并有详尽的文档及注释。对于想深入了解堆内存管理的读者,是一个不错的选择。
可见,从使用角度来讲,Visual Leak Detector简单易用,对于使用者自己的代码,唯一的修改是#include Visual Leak Detector的头文件后正常运行自己的程序,就可以发现内存问题。从研究的角度来讲,如果深入Visual Leak Detector源代码,可以学习到堆内存分配与释放的原理、内存泄漏检测的原理及内存操作的常用技巧等。
本文首先将介绍Visual Leak Detector的使用方法与步骤,然后再和读者一起初步的研究Visual Leak Detector的源代码,去了解Visual Leak Detector的工作原理。
使用Visual Leak Detector(1.0) 下面让我们来介绍如何使用这个小巧的工具。 首先从网站上下载zip包,解压之后得到vld.h, vldapi.h, vld.lib, vldmt.lib, vldmtdll.lib, dbghelp.dll等文件。将.h文件拷贝到Visual C++的默认include目录下,将.lib文件拷贝到Visual C++的默认lib目录下,便安装完成了。因为版本问题,如果使用windows 2000或者以前的版本,需要将dbghelp.dll拷贝到你的程序的运行目录下,或其他可以引用到的目录。
接下来需要将其加入到自己的代码中。方法很简单,只要在包含入口函数的.cpp文件中包含vld.h就可以。如果这个cpp文件包含了stdafx.h,则将包含vld.h的语句放在stdafx.h的包含语句之后,否则放在最前面。如下是一个示例程序:
#include <vld.h>
void main()
{
…
}
接下来让我们来演示如何使用Visual Leak Detector检测内存泄漏。下面是一个简单的程序,用new分配了一个int大小的堆内存,并没有释放。其申请的内存地址用printf输出到屏幕上。
#include <vld.h>
#include <stdlib.h>
#include <stdio.h>
void f()
{
int *p = new int(0x12345678);
printf("p=%08x, ", p);
}
void main()
{
f();
}
编译运行后,在标准输出窗口得到:
p=003a89c0
在Visual C++的Output窗口得到:
WARNING: Visual Leak Detector detected memory leaks!
---------- Block 57 at 0x003A89C0: 4 bytes ---------- --57号块0x003A89C0地址泄漏了4个字节
Call Stack: --下面是调用堆栈
d:\test\testvldconsole\testvldconsole\main.cpp (7): f --表示在main.cpp第7行的f()函数
d:\test\testvldconsole\testvldconsole\main.cpp (14): main –双击以引导至对应代码处
f:\rtm\vctools\crt_bld\self_x86\crt\src\crtexe.c (586): __tmainCRTStartup
f:\rtm\vctools\crt_bld\self_x86\crt\src\crtexe.c (403): mainCRTStartup
0x7C816D4F (File and line number not available): RegisterWaitForInputIdle
Data: --这是泄漏内存的内容,0x12345678
78 56 34 12 xV4..... ........
Visual Leak Detector detected 1 memory leak.
第二行表示57号块有4字节的内存泄漏,地址为0x003A89C0,根据程序控制台的输出,可以知道,该地址为指针p。程序的第7行,f()函数里,在该地址处分配了4字节的堆内存空间,并赋值为0x12345678,这样在报告中,我们看到了这4字节同样的内容。
可以看出,对于每一个内存泄漏,这个报告列出了它的泄漏点、长度、分配该内存时的调用堆栈、和泄露内存的内容(分别以16进制和文本格式列出)。双击该堆栈报告的某一行,会自动在代码编辑器中跳到其所指文件的对应行。这些信息对于我们查找内存泄露将有很大的帮助。
这是一个很方便易用的工具,安装后每次使用时,仅仅需要将它头文件包含进来重新build就可以。而且,该工具仅在build Debug版的时候会连接到你的程序中,如果build Release版,该工具不会对你的程序产生任何性能等方面影响。所以尽可以将其头文件一直包含在你的源代码中。
Visual Leak Detector工作原理
下面让我们来看一下该工具的工作原理。 在这之前,我们先来看一下Visual C++内置的内存泄漏检测工具是如何工作的。Visual C++内置的工具CRT Debug Heap工作原来很简单。在使用Debug版的malloc分配内存时,malloc会在内存块的头中记录分配该内存的文件名及行号。当程序退出时CRT会在main()函数返回之后做一些清理工作,这个时候来检查调试堆内存,如果仍然有内存没有被释放,则一定是存在内存泄漏。从这些没有被释放的内存块的头中,就可以获得文件名及行号。
这种静态的方法可以检测出内存泄漏及其泄漏点的文件名和行号,但是并不知道泄漏究竟是如何发生的,并不知道该内存分配语句是如何被执行到的。要想了解这些,就必须要对程序的内存分配过程进行动态跟踪。Visual Leak Detector就是这样做的。它在每次内存分配时将其上下文记录下来,当程序退出时,对于检测到的内存泄漏,查找其记录下来的上下文信息,并将其转换成报告输出。
初始化
Visual Leak Detector要记录每一次的内存分配,而它是如何监视内存分配的呢?Windows提供了分配钩子(allocation hooks)来监视调试堆内存的分配。它是一个用户定义的回调函数,在每次从调试堆分配内存之前被调用。在初始化时,Visual Leak Detector使用_CrtSetAllocHook注册这个钩子函数,这样就可以监视从此之后所有的堆内存分配了。 如何保证在Visual Leak Detector初始化之前没有堆内存分配呢?全局变量是在程序启动时就初始化的,如果将Visual Leak Detector作为一个全局变量,就可以随程序一起启动。但是C/C++并没有约定全局变量之间的初始化顺序,如果其它全局变量的构造函数中有堆内存分配,则可能无法检测到。Visual Leak Detector使用了C/C++提供的#pragma init_seg来在某种程度上减少其它全局变量在其之前初始化的概率。根据#pragma init_seg的定义,全局变量的初始化分三个阶段:首先是compiler段,一般c语言的运行时库在这个时候初始化;然后是lib段,一般用于第三方的类库的初始化等;最后是user段,大部分的初始化都在这个阶段进行。Visual Leak Detector将其初始化设置在compiler段,从而使得它在绝大多数全局变量和几乎所有的用户定义的全局变量之前初始化。
记录内存分配
一个分配钩子函数需要具有如下的形式: int YourAllocHook( int allocType, void *userData, size_t size, int blockType, long requestNumber, const unsigned char *filename, int lineNumber);
就像前面说的,它在Visual Leak Detector初始化时被注册,每次从调试堆分配内存之前被调用。这个函数需要处理的事情是记录下此时的调用堆栈和此次堆内存分配的唯一标识——requestNumber。
得到当前的堆栈的二进制表示并不是一件很复杂的事情,但是因为不同体系结构、不同编译器、不同的函数调用约定所产生的堆栈内容略有不同,要解释堆栈并得到整个函数调用过程略显复杂。不过windows提供一个StackWalk64函数,可以获得堆栈的内容。StackWalk64的声明如下:
BOOL StackWalk64(
DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ); STACKFRAME64结构表示了堆栈中的一个frame。给出初始的STACKFRAME64,反复调用该函数,便可以得到内存分配点的调用堆栈了。 // Walk the stack.
while (count < _VLD_maxtraceframes) {
count++;
if (!pStackWalk64(architecture, m_process, m_thread, &frame, &context,
NULL, pSymFunctionTableAccess64, pSymGetModuleBase64, NULL)) {
// Couldn't trace back through any more frames.
break;
}
if (frame.AddrFrame.Offset == 0) {
// End of stack.
break;
}
// Push this frame's program counter onto the provided CallStack.
callstack->push_back((DWORD_PTR)frame.AddrPC.Offset);
}
那么,如何得到初始的STACKFRAME64结构呢?在STACKFRAME64结构中,其他的信息都比较容易获得,而当前的程序计数器(EIP)在x86体系结构中无法通过软件的方法直接读取。Visual Leak Detector使用了一种方法来获得当前的程序计数器。首先,它调用一个函数,则这个函数的返回地址就是当前的程序计数器,而函数的返回地址可以很容易的从堆栈中拿到。下面是Visual Leak Detector获得当前程序计数器的程序:
#if defined(_M_IX86) || defined(_M_X64)
#pragma auto_inline(off)
DWORD_PTR VisualLeakDetector::getprogramcounterx86x64 ()
{
DWORD_PTR programcounter;
__asm mov AXREG, [BPREG + SIZEOFPTR] // Get the return address out of the current stack frame
__asm mov [programcounter], AXREG // Put the return address into the variable we'll return
return programcounter;
}
#pragma auto_inline(on)
#endif // defined(_M_IX86) || defined(_M_X64)
得到了调用堆栈,自然要记录下来。Visual Leak Detector使用一个类似map的数据结构来记录该信息。这样可以方便的从requestNumber查找到其调用堆栈。分配钩子函数的allocType参数表示此次堆内存分配的类型,包括_HOOK_ALLOC, _HOOK_REALLOC, 和 _HOOK_FREE,下面代码是Visual Leak Detector对各种情况的处理。
switch (type) {
case _HOOK_ALLOC:
visualleakdetector.hookmalloc(request);
break;
case _HOOK_FREE:
visualleakdetector.hookfree(pdata);
break;
case _HOOK_REALLOC:
visualleakdetector.hookrealloc(pdata, request);
break;
default:
visualleakdetector.report("WARNING: Visual Leak Detector: in allochook(): Unhandled allocation type (%d).\n", type);
break;
}
这里,hookmalloc()函数得到当前堆栈,并将当前堆栈与requestNumber加入到类似map的数据结构中。hookfree()函数从类似map的数据结构中删除该信息。hookrealloc()函数依次调用了hookfree()和hookmalloc()。
检测内存泄露
前面提到了Visual C++内置的内存泄漏检测工具的工作原理。与该原理相同,因为全局变量以构造的相反顺序析构,在Visual Leak Detector析构时,几乎所有的其他变量都已经析构,此时如果仍然有未释放之堆内存,则必为内存泄漏。 分配的堆内存是通过一个链表来组织的,检查内存泄漏则是检查此链表。但是windows没有提供方法来访问这个链表。Visual Leak Detector使用了一个小技巧来得到它。首先在堆上申请一块临时内存,则该内存的地址可以转换成指向一个_CrtMemBlockHeader结构,在此结构中就可以获得这个链表。代码如下:
char *pheap = new char;
_CrtMemBlockHeader *pheader = pHdr(pheap)->pBlockHeaderNext;
delete pheap;
其中pheader则为链表首指针。
报告生成
前面讲了Visual Leak Detector如何检测、记录内存泄漏及其其调用堆栈。但是如果要这个信息对程序员有用的话,必须转换成可读的形式。Visual Leak Detector使用SymGetLineFromAddr64()及SymFromAddr()生成可读的报告。 // Iterate through each frame in the call stack.
for (frame = 0; frame < callstack->size(); frame++) {
// Try to get the source file and line number associated with
// this program counter address.
if (pSymGetLineFromAddr64(m_process,
(*callstack)[frame], &displacement, &sourceinfo)) {
...
}
// Try to get the name of the function containing this program
// counter address.
if (pSymFromAddr(m_process, (*callstack)[frame],
&displacement64, pfunctioninfo)) {
functionname = pfunctioninfo->Name;
}
else {
functionname = "(Function name unavailable)";
}
...
}
概括讲来,Visual Leak Detector的工作分为3步,首先在初始化注册一个钩子函数;然后在内存分配时该钩子函数被调用以记录下当时的现场;最后检查堆内存分配链表以确定是否存在内存泄漏并将泄漏内存的现场转换成可读的形式输出。有兴趣的读者可以阅读Visual Leak Detector的源代码。
总结
在使用上,Visual Leak Detector简单方便,结果报告一目了然。在原理上,Visual Leak Detector针对内存泄漏问题的特点,可谓对症下药——内存泄漏不是不容易发现吗?那就每次内存分配是都给记录下来,程序退出时算总账;内存泄漏现象出现时不是已时过境迁,并非当时泄漏点的现场了吗?那就把现场也记录下来,清清楚楚的告诉使用者那块泄漏的内存就是在如何一个调用过程中泄漏掉的。 Visual Leak Detector是一个简单易用内存泄漏检测工具。现在最新的版本是1.9a,采用了新的检测机制,并在功能上有了很多改进。读者不妨体验一下。 9月7日 UltraEdit32 & SciTE 挂接C编译器标题 UltraEdit32 & SciTE 挂接C编译器 选择自 uuzhang 的 Blog 关键字 UltraEdit32 & SciTE 挂接C编译器 出处 UltraEdit32 & SciTE 挂接C编译器 碎心竹 ----------------------------------------------------------------------------- UltraEdit32 是一种大家常用的源文件编辑查看工具 速度快 语法着色 ASCII码表 等功能很受欢迎 SciTE 是现在一些程序员喜欢的TE工具是在unix/linux
环境下制作的工具有其win32版本 而且是开源软件 其界面 作的很简单 编辑界面有种很特别的感觉 最主要就是他的启动速度 快 下面详细介绍一下 怎么用UE挂接 bcb5.5编译器
因为bcb5.5 是免费的 到处都有 而且非常小 还可以编译 win32程序 这两个工具下载安装都很简单 主要在设置上
编译器有一些设置 需要自己做 在borland主页上下载bcb5.5并解压后
在其目录/bin下面要手工创建两个配置文件 这个在其帮助文件中说得很明白 bcc32.cfg 和 ilink32.cfg 以我电脑为例 我解压到E盘 bcc5目录 在 e:\bcc5\bin\ 下 创建 bcc32.cfg 和 ilink32.cfg bcc32.cfg 内容如下:
-I"E:\bcc5\include" -L"E:\bcc5\lib" ilink32.cfg内容如下:
-L"E:\bcc5\Lib" 完成后
就可以配置 UE 和 SciTE 来挂接bcb5.5了 UE比较简单 在 高级 菜单 找到 工具栏配置 打开
首先是 编译 命令行填入: E:\bcc5\Bin\bcc32 -y -v -c -o"%n.obj" "%f" 工作目录填入: %p 菜单项名称填入: 编译 选项 点上 输出到列表框口 显示dos窗口 捕捉输出
点击插入 OK 完成一个
下一个 连接 菜单 这个要注意 因为dos程序和win32程序 连接命令是不同的 可以制作两个菜单 第一个dos连接
命令行填入:
E:\bcc5\Bin\ilink32 -v "%n.obj" import32.lib cw32.lib c0x32.obj /ap 工作目录填入: %p 菜单项名称填入: dos连接 选项 点上 输出到列表框口 显示dos窗口 捕捉输出
点击插入 然后是 win32连接
命令行填入: E:\bcc5\Bin\ilink32 -v "%n.obj" import32.lib cw32.lib c0w32.obj /aa 工作目录填入: %p 菜单项名称填入: win32连接 选项 点上 输出到列表框口 显示dos窗口 捕捉输出 点击插入 连接命令完成 下面作个运行命令 但是因为dos和win32在UE里面的运行还有一些不同 最好还是作两个 dos运行
命令行填入: %n.exe 工作目录填入: %p 菜单项名称填入: DOS运行 选项 点上 输出到列表框口 显示dos窗口 捕捉输出 点击插入 win32运行
命令行填入: %n.exe 工作目录填入: %p 菜单项名称填入: win32运行 选项 点上 windows程序 点击插入 好 UE还差最后一项 那就是清理临时文件 win32运行
命令行填入: del *.obj *.map *.ilc *.ild *.ilf *.ils *.tds 工作目录填入: %p 菜单项名称填入: 清除临时文件 选项 点上 输出到列表框口 显示dos窗口 捕捉输出 点击插入 大功告成 可以测试一下了
然后是稍微复杂的 SciTE
对 SciTE 也不知道该怎么说 反正我弄了N长时间 才弄明白 还是有点成就感 嘿嘿 不过还是有一些地方没弄明白 等学好编程 自己修改它 嘿嘿 反正是开源的 SciTE 的网址是 http://www.scintilla.org/SciTE.html 可以在它网站找到 中文的配置文件 弄好就是中文版的了 配置文件说的很明白 这里就不多说了 配置编译器 不多说啥 就是 直接复制过去好了
也不用研究怎么配置他的了 都是鹰语 主要是文件的选择 SciTE 的配置文件带了一堆 一开始还真有点不习惯 如果你下载的是完整的压缩版 非源程序版 解压缩之后能在其目录找到 cpp.properties 这个是C/C++等配制文件 打开之后 找到 cc= build= 从这开始粘贴下面内容 cc=E:\bcc5\Bin\bcc32 -y -v -c -o"$(FileDir)\$(FileName).obj" "$(FileDir)\$(FileNameExt)" build=E:\bcc5\Bin\ilink32 -v "$(FileDir)\$(FileName).obj" import32.lib cw32.lib c0x32.obj /ap buildw=E:\bcc5\Bin\ilink32 -v "$(FileDir)\$(FileName).obj" import32.lib cw32.lib c0w32.obj /aa command.compile.*.c=$(cc) command.build.*.c=$(build) command.go.*.c=$(FileName) command.compile.*.c=$(cc)
command.build.*.c=$(build) command.go.*.c=$(FileName) command.compile.*.cc=$(cc)
command.build.*.cc=$(build) command.go.*.cc=$(FileName) command.compile.*.cpp=$(cc)
command.build.*.cpp=$(build) command.go.*.cpp=$(FileName) command.compile.*.cxx=$(cc)
command.build.*.cxx=$(build) command.go.*.cxx=$(FileName) command.go.subsystem.*=2
command.build.*.h=make command.name.3.*.c=生成Win
command.3.*.c=$(buildw) command.subsystem.3.*.c=0 command.name.3.*.cpp=生成Win
command.3.*.cpp=$(buildw) command.subsystem.3.*.cpp=0 command.name.0.*=清除temp
command.0.*=$(FileDir)\dd.bat command.subsystem.0.*=0 上面的dd.bat 是自己手工创建的 SciTE好像是不支持del *.obj
之类的dos命令 只能作个批处理来输出temp文件 很麻烦 不知道有谁能很好的解决这个问题 这样就可以正常的编译了调试了 如果你下载的SciTE界面使用还有问题 不要怪SciTE有问题, SciTE的所有功能的是通过配制文件修改的 下面列举一些修改选项 点选项 打开全局设置文件 修改 tab
选项在 # Indentation 下面 tabsize=8 tab显示长度 indent.size=4 按tab填充长度 use.tabs=0 是非使用tab缩进 # Internationalisation 这下面有字体代码设置 如果你用的是win2000 只用设置code.page=936 就成了 别的系统好像还要改character.set= 这个看看中文配制文件上的说明就能懂了 toolbar.visible=1 工具栏打开
position.width=800 position.height=575 这两项能修改启动SciTE时的窗口大小根据你的分辨率调整 split.vertical=0 外部捕捉输出 的显示 1 右侧 0 下面 # Element styles 的下面 有一些选项能修改编辑界面的颜色 显示方式等 可是试着修改 ok了 可以测试了
2005.7.13 8月2日 C#中调用C++的dll的参数为指针类型的导出函数(包括二级指针的情况) (转) C#中调用C++的dll的参数为指针类型的导出函数(包括二级指针的情况) (转)
C#中调用C++的dll的参数为指针类型的导出函数(包括二级指针的情况)
严格来说这篇文章算不上C++范围的,不过还是挂了点边,还是在自己的blog中记录一下吧。 C++中使用指针是家常便饭了,也非常的好用,这也是我之所以喜欢C++的原因之一。但是在C#中就强调托管的概念了,指针就不用想了。本来如果就在C#的世界里面写代码,也还算舒服,但是万事万物总有联系,这不,现在公司的另外一个用C#作的项目就碰到问题了,要调用之前用C++写的一个DLL中的一些函数,很多函数的参数都是指针类型的,这下可麻烦咯,公司里做C#的都是刚起步,C++又只有我最熟悉,这项技术研究工作又光荣的落到我身上。
我对C#也不甚熟悉,所以也许我的方法不一定是最直接的,但是测试的结果是满足了这个调用需要了的。下面我就详细介绍一下。
使用unsafe、fix等关键字应该是能够实现的,但是他们项目组要求不用这个,所以我也没深入去试验。除了这个方法,应该来说是有两个思路的,第一个思路可能看起来比较直接,使用ref,ref这个关键字似乎有点特殊性,字面上理解似乎应该和C++中的引用类型相对应,不过似乎它还是有一定特殊性的,貌似以前看到过一篇文章说ref会自己去判断是引用类型还是指针,我尝试了一下,果然是可行的。但是对于有二级指针的情况ref也就不灵了~这就导出了我的另一个思路,使用Marshal。
下面我们还是代码说明问题:
以下是C++DLL中的代码片断,主要是使用到的两个结构的定义,以及导出函数TestFunction的定义。 C++ DLL中的代码片断 #pragma pack(push) #pragma pack(1) typedef struct EmmStruct { int len; } EMMSTRUCT, *LPEMMSTRUCT; typedef struct MyStruct {
int iParam; long size; LPEMMSTRUCT lpEmmStructArr; } MYSTRUCT, *LPMYSTRUCT; #pragma pack(pop) extern "C" void __declspec(dllexport) __stdcall TestFunction(LPMYSTRUCT lpMyStruct)
{ lpMyStruct->iParam = 100; lpMyStruct->size = 10; lpMyStruct->lpEmmStructArr = new EMMSTRUCT[lpMyStruct->size]; for(int i=0;i<lpMyStruct->size;i++) { lpMyStruct->lpEmmStructArr[i].len = i; } } 那么再来看看C#中调用的代码:
C#中调用的代码片断using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; //使用C#导入dll必须的 namespace csharptest
{ //StructLayout和FieldOffset这些设置不是必须的,只是为了防止对齐的问题最好加上,这样自己心里有数对齐到哪一位 [StructLayout(LayoutKind.Explicit)] public struct EmmStruct { [FieldOffset(0)] public int len; } [StructLayout(LayoutKind.Explicit)]
public struct MyStruct { [FieldOffset(0)] public int iParam; [FieldOffset(4)] public int size; [FieldOffset(8)] public IntPtr ptrEmmStruct; } class Program
{ // dll中导出函数的声明 [DllImport("dllforcsharp.dll", CallingConvention=CallingConvention.Winapi)] public extern static void TestFunction(IntPtr ptr); static void Main(string[] args)
{ try { MyStruct s = new MyStruct(); IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(s)); Marshal.StructureToPtr(s, ptr, false); TestFunction(ptr);
s = (MyStruct)Marshal.PtrToStructure(ptr, typeof(MyStruct));
EmmStruct ret;
for (int i = 0; i < s.size; i++) { IntPtr ptr2 = new IntPtr(s.ptrEmmStruct.ToInt32() + 4 * i); ret = (EmmStruct)Marshal.PtrToStructure(ptr2, typeof(EmmStruct)); } Marshal.FreeHGlobal(ptr);
} catch (Exception e) { string str = e.Message; } finally { } } } } 代码也不多,而且从字面的意思就能知道是干什么的了,所以我就没写注释。用这种方法就实现了参数中含有二级指针的情况。要注意的就是C#中的long和C++中不同,它占8字节。所以一般情况下C++中long的,C#里面用int或者int32就ok了。我自己对C#不是特别熟悉,所以可能也未能完全讲解清楚,甚至可能存在漏洞,有高人见到的话,可以指点指点。 7月27日 获取显卡相关信息获取显卡相关信息: void GetVideoCardInfo() pD3D = Direct3DCreate9(D3D_SDK_VERSION); //Direct3D9. D3DADAPTER_IDENTIFIER9 d3dAdapterIdentifier; DWORD m_dwNumAdapters = pD3D->GetAdapterCount(); SAFE_RELEASE( pD3D );
7月23日 遍历和查找外部程序 Tree-View 中的项目(转)遍历和查找外部程序 Tree-View 中的项目(转)
来自:http://www.vckbase.com/document/viewdoc/?id=1712 遍历和查找外部程序 Tree-View 中的项目
天津 赵春生
下载源代码
《金山词霸2002》中的附录收集了很多古诗,有时为了寻找一篇古诗,得找很久很久(俺文科很差)。观察其附录的结构,发现是个Tree-View控件,如果能查找里面的项目该有多好,可这个功能软件本身却并没有提供(不知道现在最新的版本是否已经提供了这个功能,如果没有,赶快加上吧,顺便奖励俺一套该产品的最新版,哈哈)……问题出来了:我们要编写一个程序,让她在外部程序中的Tree-View控件里,按用户指定的项目名称顺序查找其中的项目。
要查找首先得遍历,连范围都确定不好何谈查找?所以本篇分两部分进行讲解:第一部分解决遍历的问题;第二部分解决查找指定项目的问题。 第一部分:遍历外部程序Tree-View中的项目
一:程序说明:
如图一所示Tree-View控件的典型结构图,我们将按照图示的顺序来遍历其中的项目。
图一 翻阅SDK手册中关于Tree-View控件的相关章节,发现了几个有用的消息:
TVM_GETNEXTITEM:得到项目的句柄(参数:TVGN_ROOT得到根句柄,TVGN_NEXTVISIBLE得到下一个可见项目的句柄);
TVM_EXPAND:展开或折叠指定项目(参数:TVE_EXPAND展开指定项目); TVM_SELECTITEM:选中指定项目。 利用这些消息和SendMessage()函数,我们可以很容易写出遍历代码。 二:具体实践
在本文所提供的DEMO中,有一段将十六进制字符串转换成十进制无符号长整型的代码,作用是将用户输入的十六进制TV句柄值转换成十进制并存放在变量dec_sum中。此代码不列入本文讨论的范畴,大家不闲弱智的话就将就着用吧。下面是实现遍历功能的关键代码:
/* Tree-View Control_Demo_SeqShow 1.0 版
* 版权所有 (C) 2006 天津 赵春生 * 2006.08.28 * http://timw.yeah.net * http://timw.126.com * 本程序能顺序遍历TV控件中的所有项目。 * 代码在Win2000P+SP4 + VC6+SP6测试通过。 */ if(error==0)//如果在数据验证转换的过程中未出现错误(error==0时无错误)
{ //下面为核心部分:顺序显示(选中)指定Tree-View控件中的所有Item. hwnd=HWND(dec_sum);//得到转换后的数据 //得到根句柄 tvitem.hItem=(HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM,TVGN_ROOT, 0x0); ::SendMessage(hwnd, TVM_SELECTITEM,TVGN_CARET, (long)tvitem.hItem);//选中状态 while((long)tvitem.hItem) { //当此项目能展开时 while(::SendMessage(hwnd, TVM_EXPAND,TVE_EXPAND, (long)tvitem.hItem)) { //选择下一个可见项目 tvitem.hItem=(HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM,TVGN_NEXTVISIBLE, (long)tvitem.hItem); //选中状态
::SendMessage(hwnd, TVM_SELECTITEM,TVGN_CARET, (long)tvitem.hItem); continue; } //当不能再展开的时候,选择下一个可见项目
tvitem.hItem=(HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM,TVGN_NEXTVISIBLE, (long)tvitem.hItem); //选中状态
::SendMessage(hwnd, TVM_SELECTITEM,TVGN_CARET, (long)tvitem.hItem); } } //释放内存 CloseHandle(hwnd); //顺序显示(选中)完毕 三:TV_Demo_SeqShow的使用方法(图2):
图二
用SPY++的[Find Window]功能获得目标TV的句柄;
将句柄值输入到TV_Demo_SeqShow中的[Tree-View Control''s Handle:]; 点击[GO!]; 如果你把[Windows 资源管理器]中的[文件夹]作为目标,那你可要作好心理准备了……如果实在忍受不了这种刺激,干脆把管理器关掉就可以了。 第二部分:查找外部程序Tree-View中的项目
一:程序说明:
我们已经成功得对外部程序Tree-View中的项目进行了遍历,如果能在遍历的过程中读取每一个项目的名称,结合我们给定的项目名进行比较,那么查找某个项目的问题将变得易如反掌。由此可见:关键的问题是如何读取项目的名称。
读取项目的名称要发送TVM_GETITEM消息,由于该消息需要为LPARAM参数提供一个TV_ITEM结构的地址,在跨进程发送消息的前提下,为了使外部程序正常使用该内存地址,所以我们必须将TV_ITEM结构插入到目标进程的地址空间中去,代码如下: ptvitem=(TVITEM*)VirtualAllocEx(hProcess,NULL,sizeof(TVITEM),MEM_COMMIT,PAGE_READWRITE);//分配内存
WriteProcessMemory(hProcess,ptvitem,&tvitem,sizeof(TVITEM),NULL);//写入内存 在写入内存之前,要将TV_ITEM结构配置好: tvitem.mask=TVIF_TEXT;
tvitem.cchTextMax=512; tvitem.pszText=pItem; mask要设置成TVIF_TEXT,因为我们需要的是pszText的值;cchTextMax可以设置得稍微大一些,cchTextMax=512即可;hItem的值用来指定究竟哪个项目来接收TVM_GETITEM消息,该值在遍历的过程中动态获得;重要的是用来存放项目名称的缓冲区地址,即pszText参数的设置:和TV_ITEM结构一样,也要把她插入到目标进程的地址空间中去: pItem=(char*)VirtualAllocEx(hProcess,NULL,16,MEM_COMMIT,PAGE_READWRITE);
二:具体实践:
作为演示,下面的这段程序将在我们指定的Tree-View控件中查找我们需要的项目,在发现第一个部分匹配的项目后,程序将停止运行,不再进行查找操作。作为演示程序,程序并没有做速度上的优化,大家在具体应用的过程中可自行修改。程序找到目标后的效果图(图 三): /* Tree-View Control_Demo_SeqSearch 1.0 版
* 版权所有 (C) 2006 天津 赵春生 * 2006.08.28 * http://timw.yeah.net * http://timw.126.com * 本程序能按用户指定的项目名称顺序查找TV控件中的项目。 * 代码在Win2000P+SP4 + VC6+SP6测试通过。 */ if(error==0)//如果在数据验证转换的过程中未出现错误(error==0时无错误)
{ //下面为核心部分:按用户指定的项目名称顺序查找Tree-View控件中的Item. hwnd=HWND(dec_sum);//得到转换后的数据 GetWindowThreadProcessId(hwnd, &PID); hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,PID); if (!hProcess) MessageBox("获取进程句柄操作失败!","错误!"); else { ptvitem=(TVITEM*)VirtualAllocEx(hProcess, NULL, sizeof(TVITEM), MEM_COMMIT, PAGE_READWRITE); pItem=(char*)VirtualAllocEx(hProcess, NULL, 16, MEM_COMMIT, PAGE_READWRITE); if (!ptvitem) MessageBox("无法分配内存!","错误!"); else { MessageBox("本演示程序将按用户指定的项目名称顺序查找。","提示"); tvitem.mask=TVIF_TEXT; tvitem.cchTextMax=512; tvitem.pszText=pItem; //得到根句柄 tvitem.hItem=(HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_ROOT, 0x0); //选中状态
::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (long)tvitem.hItem); //将设置好的结构插入目标进程 WriteProcessMemory(hProcess, ptvitem, &tvitem, sizeof(TVITEM), NULL); //发送TVM_GETITEM消息
::SendMessage(hwnd, TVM_GETITEM, 0, (LPARAM)ptvitem); //获取pszText
ReadProcessMemory(hProcess, pItem, ItemBuf, 512, NULL); //MessageBox(ItemBuf,"ITEM TEXT");
if( strnicmp( ItemBuf, str_item_text, strlen(str_item_text) ) == 0) { MessageBox("已经找到!","恭喜"); Bingo=1; //如果根就是我们要找的目标,那么程序执行到这里就可以结束了。 tvitem.hItem=(HTREEITEM)0x0; } while((long)tvitem.hItem) { //当此项目能展开时 while(::SendMessage(hwnd, TVM_EXPAND, TVE_EXPAND, (long)tvitem.hItem)) { //选择下一个可见项目
tvitem.hItem=(HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM,TVGN_NEXTVISIBLE, (long)tvitem.hItem); //选中状态 ::SendMessage(hwnd, TVM_SELECTITEM,TVGN_CARET, (long)tvitem.hItem); //将设置好的结构插入目标进程
WriteProcessMemory(hProcess, ptvitem, &tvitem, sizeof(TVITEM), NULL); //发送TVM_GETITEM消息 ::SendMessage(hwnd, TVM_GETITEM, 0, (LPARAM)ptvitem); //获取pszText
ReadProcessMemory(hProcess, pItem, ItemBuf, 512, NULL); //MessageBox(ItemBuf,"ITEM TEXT");
if( strnicmp( ItemBuf, str_item_text, strlen(str_item_text) ) == 0) { MessageBox("已经找到!","恭喜"); Bingo=1; //如果发现我们要找的目标,那么程序执行到这里就可以结束了。
tvitem.hItem=(HTREEITEM)0x0; break; } continue; } if(Bingo!=1) { //当不能再展开的时候,选择下一个可见项目 tvitem.hItem=(HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM,TVGN_NEXTVISIBLE, (long)tvitem.hItem); //选中状态
::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (long)tvitem.hItem); //将设置好的结构插入目标进程
WriteProcessMemory(hProcess, ptvitem, &tvitem, sizeof(TVITEM), NULL); //发送TVM_GETITEM消息
::SendMessage(hwnd, TVM_GETITEM, 0, (LPARAM)ptvitem); ReadProcessMemory(hProcess, pItem, ItemBuf, 512, NULL);//获取pszText //MessageBox(ItemBuf,"ITEM TEXT"); if( strnicmp( ItemBuf, str_item_text, strlen(str_item_text) ) == 0) { MessageBox("已经找到!","恭喜"); Bingo=1; //如果发现我们要找的目标,那么程序执行到这里就可以结束了。
tvitem.hItem=(HTREEITEM)0x0; break; } } } } } } //释放内存 CloseHandle(hwnd); CloseHandle(hProcess); VirtualFreeEx(hProcess, ptvitem, 0, MEM_RELEASE); //顺序查找完毕 结束语
代码写得不够幽雅,大家见笑了。在此之前,类似的拙文我已经写了四篇,希望大家看完后能举一反三。谢谢。 如何查找项目在树控件通过其标签(转)如何查找项目在树控件通过其标签(转)
来自:http://support.microsoft.com/kb/155895/zh-cn 如何查找项目在树控件通过其标签 察看本文应用于的产品 注意:这篇文章是由无人工介入的自动的机器翻译系统翻译完成。这些文章是微软为不懂英语的用户提供的, 以使他们能够理解这些文章的内容。微软不保证机器翻译的正确度,也不对由于内容的误译或者客户对它的使用所引起的任何直接的, 或间接的可能的问题负责。 文章编号 : 155895 最后修改 : 2003年12月10日 修订 : 2.0 本页 概要
更多信息
示例代码
概要 " 树视图 " 公共控件没有任何内置方法用于搜索整个树, 或用于选择给定特定项标签时树中包含一个项。 本文提供代码返回给定特定标签以搜索时树中的任何项目位置。 GetItemByName() 函数采用树控件, HTREEITEM, 它指向该树以开始搜索和对要搜索字符串中项的窗口句柄。
回到顶端 更多信息
示例代码 #include <windows.h> #include <commctrl.h> // Note: If you have items with more than 50 characters
// of text, you'll need to increase this value. #define MAXTEXTLEN 50 HTREEITEM GetItemByName(HWND hWnd, HTREEITEM hItem,
LPCTSTR szItemName) { // If hItem is NULL, start search from root item. if (hItem == NULL) hItem = (HTREEITEM)SendMessage(hWnd, TVM_GETNEXTITEM, TVGN_ROOT, 0); while (hItem != NULL) { char szBuffer[MAXTEXTLEN+1]; TV_ITEM item; item.hItem = hItem;
item.mask = TVIF_TEXT | TVIF_CHILDREN; item.pszText = szBuffer; item.cchTextMax = MAXTEXTLEN; SendMessage(hWnd, TVM_GETITEM, 0, (LPARAM)&item); // Did we find it?
if (lstrcmp(szBuffer, szItemName) == 0) return hItem; // Check whether we have child items.
if (item.cChildren) { // Recursively traverse child items. HTREEITEM hItemFound, hItemChild; hItemChild = (HTREEITEM)SendMessage(hWnd, TVM_GETNEXTITEM,
TVGN_CHILD, (LPARAM)hItem); hItemFound = GetItemByName(hWnd, hItemChild, szItemName); // Did we find it?
if (hItemFound != NULL) return hItemFound; } // Go to next sibling item.
hItem = (HTREEITEM)SendMessage(hWnd, TVM_GETNEXTITEM, TVGN_NEXT, (LPARAM)hItem); } // Not found.
return NULL; } 例如, 以搜索整个树对单词 " JeffSmith " 和然后突出显示该单词, 您可以使用以下代码: 使用 Windows SDK: HTREEITEM hItem;
hItem = GetItemByName(hWndTreeCtrl, NULL, "Jeff"); if (hItem != NULL) SendMessage(hWndTreeCtrl, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem); 使用 MFC: HTREEITEM hItem = GetItemByName(m_TreeCtrl.GetSafeHwnd(), NULL, "Jeff"); if (hItem != NULL) m_TreeCtrl.SelectItem(hItem); 回到顶端 -------------------------------------------------------------------------------- 这篇文章中的信息适用于:
• Microsoft Visual C++ 2.2 • Microsoft Visual C++ 4.0 Standard Edition • Microsoft Visual C++ 4.1 Subscription • Microsoft Visual C++ 4.2 Enterprise Edition • Microsoft Visual C++ 4.2 Professional Edition 回到顶端
关键字: kbcode KB155895 KbMtzh kbmt
回到顶端
Microsoft和/或其各供应商对于为任何目的而在本服务器上发布的文件及有关图形所含信息的适用性,不作任何声明。 所有该等文件及有关图形均"依样"提供,而不带任何性质的保证。Microsoft和/或其各供应商特此声明,对所有与该等信息有关的保证和条件不负任何责任,该等保证和条件包括关于适销性、符合特定用途、所有权和非侵权的所有默示保证和条件。在任何情况下,在由于使用或运行本服务器上的信息所引起的或与该等使用或运行有关的诉讼中,Microsoft和/或其各供应商就因丧失使用、数据或利润所导致的任何特别的、 7月19日 vc++的LNK2005错误(转)[转者: 之前转过另一篇也是这种错误的不错文章,可以找来看看。]
VC++的LNK2005错误 (转)
摘自:http://blog.csdn.net/vcleaner/archive/2005/02/01/276278.aspx 编程中经常能遇到LNK2005错误——重复定义错误,其实LNK2005错误并不是一个很难解决的错误。弄清楚它形成的原因,就可以轻松解决它了。 造成LNK2005错误主要有以下几种情况:
1. 重复定义全局变量。可能存在两种情况:
A、 对于一些初学编程的程序员,有时候会以为需要使用全局变量的地方就可以使用定义申明一下。其实这是错误的,全局变量是针对整个工程的。正确的应该是在一个CPP文件中定义如下:int g_Test;那么在使用的CPP文件中就应该使用:extern int g_Test即可,如果还是使用int g_Test,那么就会产生LNK2005错误,一般错误错误信息类似:AAA.obj error LNK2005 int book c?book@@3HA already defined in BBB.obj。切记的就是不能给变量赋值否则还是会有LNK2005错误。
这里需要的是“声明”,不是“定义”!根据C++标准的规定,一个变量是声明,必须同时满足两个条件,否则就是定义:
(1)声明必须使用extern关键字;(2)不能给变量赋初值
所以,下面的是声明:
extern int a;
下面的是定义
int a; int a = 0; extern int a =0;
B、对于那么编程不是那么严谨的程序员,总是在需要使用变量的文件中随意定义一个全局变量,并且对于变量名也不予考虑,这也往往容易造成变量名重复,而造成LNK2005错误。
2. 头文件的包含重复。往往需要包含的头文件中含有变量、函数、类的定义,在其它使用的地方又不得不多次包含之,如果头文件中没有相关的宏等防止重复链接的措施,那么就会产生LNK2005错误。解决办法是在需要包含的头文件中做类似的处理:#ifndef MY_H_FILE //如果没有定义这个宏 #define MY_H_FILE //定义这个宏
……. //头文件主体内容
…….
#endif
上面是使用宏来做的,也可以使用预编译来做,在头文件中加入:
#pragma once
//头文件主体
3. 使用第三方的库造成的。这种情况主要是C运行期函数库和MFC的库冲突造成的。具体的办法就是将那个提示出错的库放到另外一个库的前面。另外选择不同的C函数库,可能会引起这个错误。微软和C有两种C运行期函数库,一种是普通的函数库:LIBC.LIB,不支持多线程。另外一种是支持多线程的:msvcrt.lib。如果一个工程里,这两种函数库混合使用,可能会引起这个错误,一般情况下它需要MFC的库先于C运行期函数库被链接,因此建议使用支持多线程的msvcrt.lib。所以在使用第三方的库之前首先要知道它链接的是什么库,否则就可能造成LNK2005错误。如果不得不使用第三方的库,可以尝试按下面所说的方法修改,但不能保证一定能解决问题,前两种方法是微软提供的:
A、 选择VC菜单Project->Settings->Link->Catagory选择Input,再在Ignore libraries 的Edit栏中填入你需要忽略的库,如:Nafxcwd.lib;Libcmtd.lib。然后在Object/library Modules的Edit栏中填入正确的库的顺序,这里需要你能确定什么是正确的顺序,呵呵,God bless you!
B、 选择VC菜单Project->Settings->Link页,然后在Project Options的Edit栏中输入/verbose:lib,这样就可以在编译链接程序过程中在输出窗口看到链接的顺序了。
C、 选择VC菜单Project->Settings->C/C++页,Catagory选择Code Generation后再在User Runtime libraray中选择MultiThread DLL等其他库,逐一尝试。
关于编译器的相关处理过程,参考:
这就是我所遇到过的LNK2005错误的几种情况,肯定还有其他的情况也可能造成这种错误,所以我不希望你在看完这篇文章以后,再遇到LNK2005错误时候,不动脑筋的想对号入座的排除错误。编程的过程就是一个思考的过程,所以还是多多开动你的头脑,那样收获会更多!
7月17日 Error spawning 'vcspawn.exe'(转)关键词: vcspawn.exe VC++ 6.0“Error spawning 'vcspawn.exe'. The build could not be performed”问题终极解决(转) 如是大家就会打开熟悉的google,输入错误信息,但是google这次一改常态,并不是返回一大堆的相关的无用的信息,返回只有为数不多的16项(中文)或者所有的78项目(中文+英文等)的资料信息(笔者撰写本文是后测试结果)。然后你就查啊,找啊,但是好像结果并不是很乐观,因为基本都是提出问题的文章,解决方案却还是一筹莫展。 于是,想起了最后一招,重装:删除现有的,再忍受两次重启(一次卸载,一次安装)和相当时间后,以为天下太平了,但是结果可能还是令你失望(至少笔者是没有达到解决问题的效果),VC还是不听话。 在郁闷、分析后,笔者解决了这个郁闷的问题,这里给出来供大家参考:为机器添加(或者修改)环境变量ComSpec(环境变量的添加方法我就不罗嗦了:)),设定其置为:c:\WINDOWS\system32\cmd.exe。然后重启VC,万事大吉了! 附记:笔者也是在做一个编辑环境变量和注册表的App时候不小心把ComSpec环境变量给Delete了,痛苦啊! |
|
|