0
Emacs开发VC程序
vimers都说emacs慢,Eclipse, Visual Studio, Netbeans笑而不语。 -- 佚名
背景
前些日子,微软宣布Visual studio 2010的EMACS扩展。此前,Visual studio20056.01已然引入了Emacs的键盘模拟,这次又将Visual studio的Emacs化更进一步,这种事发生在Microsoft这样无利不起早的企业,原因呢,你懂得。
话说,武功再高,也怕菜刀。Emacs玩的再溜,也保不齐哪天你要去开发Visual studio程序。习惯了Emacs的高效,使用Visual studio就是一个杯具1。这种情况,一般一颗红心,两手准备。一颗红心:cosplay,两手准备:1.Visual studio扮Emacs2Emacs扮Visual studio。
VC扮EMACS
EMACS扮VC
配置EMACS为IDE
一个基本的IDE,必备功能:编辑,编译,调试。
编辑
应该很多人都很眼红Visual studio的销魂的插件visual assist x吧。实时语法检查,快速打开文件,h,cpp文件,回到刚才编辑的地方,函数跳转,自动补全,插入模板。
可以号称操作系统的Emacs怎么可能没有这些功能呢?尽管这些不在本文范围,但是稍稍提及功能在Emacs中的对应,有兴趣想深入的请Google,Duckduckgo,Wikipedia.
VAX功能 | EMACS对应功能 |
实时语法检查 | Flymake3 |
快速打开文件 | ECB,快速打开文件 |
h,cpp跳转 | CEDET4 |
回到刚才编辑的地方 | Recent-jump5 |
函数跳转 | CEDET4 |
自动补全 | Auto-complete6 |
插入模板 | Yasnippet7 |
- 快速打开文件
至于快速打开文件一项,我感觉ECB做的并不是特别好,如果C++程序的头文件和实现文件没有在一个文件夹,很难找到。所以我用了一个比较笨的方法,生成文件的列表到一个文件,使用Emacs的查找功能和打开当前光标下文件的功能 find-file-at-point,我把它编定到了C-c C-f。这个过程还有两个副产物供cscope和etags用。确认你的机器上有cscope, find, etags8,将以下代码成为uptags.bat,放到系统的pathz中的某个文件夹下。%1 cd %2 rm TAGS rm cscope.files rm filelist.txt rm cscope.in.out rm cscope.out rm cscope.po.out if "%3" EQU "java" set PARAM=-name "*.java" -print if "%3" EQU "c++" set PARAM=-name "*.cpp" -print -o -name "*.r" -print -o -name "*.[hcrHs]" -print -o -name "*.hpp" -print -o -name "*.lua" -print echo %PARAM% find . %PARAM% > cscope.files less cscope.files | xargs etags -aR cscope -bkq -i cscope.files cp cscope.files filelist.txt find . -name "*.txt" -print >> filelist.txt
假设我们的C++工程在d:\mydocuments\workspace\cpp\,那么调用方法9为:
uptags d: d:\mydocuments\workspace\cpp\ c++
java工程在d:\mydocuments\workspace\java\, 那么调用方法为:
uptags d: d:\mydocuments\workspace\java\ java
那么打开文件helloworld.cpp的操作就是:C-x b filelist.txt , C-s helloworld.cpp, C-c C-f, RET。确实,稍显复杂,期待有更好的方法。
编译
编译的话基本上还是要靠Visual studio自带的工具:nmake10,msdev。本文主要介绍msdev。
- 使用msdev.exe
msdev.exe位于安装路径下的bin目录,如我本机使用Visual studio6.0,目录为”C:\Program Files\Microsoft Visual Studio\VC98\bin”。 直接调用msdev.exe,即启动Visual studio的UI界面,同时msdev.exe也接受命令行调用。我们看其帮助。msdev /? msdev /? Usage: MSDEV [myprj.dsp|mywksp.dsw] - load project/workspace [] - load source file /? - display usage information /EX - execute a VBScript macro /OUT - redirect command line output to a file /USEENV - ignore tools.options.directories settings /MAKE [] [...] - build specified target(s) [ - ] [[ |ALL] - [DEBUG|RELEASE|ALL]] /CLEAN - delete intermediate files but don't build /REBUILD - clean and build /NORECURSE - don't build dependent projects
假设我们有这么一个工程,路径为d:\Mydocuments\workbench\,工程目录结构,Configuration如下图
- 可以通过命令msdev.exe来编译工程test211。
C:\Program Files\Microsoft Visual Studio\VC98\bin\msdev.exe test2.dsw /Make /NORECURSE
同样,使用clean,rebuild可以清除、重编译该工程。将test2 改为test21,test23,即改变编译对象。 总这样写也很麻烦,而且为了在Emacs中调用 ,我们将其写成一个批处理。
d: cd d:\Mydocuments\workbench\ set project=%1 set target=%2 if == set project=test2 if == set target=/NORECURSE msdev test2.dsw /Make %target%
保存为makTest2.bat。调用方式为:
makeTest2 [工程 [目标]]
默认为编译test2 的 /NORECURSE。如果要编译test23的rebuild,调用方式为:
makeTest2 test23 /REBUILD
调试
实际上,如果有了编辑,编译,那么调试就不需要了。因为调试器是一切罪恶他妈12。
哈哈,当然是开玩笑的了。作为一个有思想的程序员,要认清楚,编译器,调试器都只是恶魔,程序员本身,也就是我们,才是恶魔他妈(当然大部分是他爹^_^ )。
哈哈,那么作为恶魔的产生者,我们当然要丰富自己的技能,而调试就是一个必杀技。我杀,bug闪……..
- cdb-gud
要用Emacs调试Visual studio的程序,首先需要一个el –cdb-gud.el。cdb-gud使用Microsoft的命令行调试器cdb来调试程序。 cdb-gud.el只需下载,然后在.emacs中使用下面语句,cdb-gud就随时待命了。+cdb [[http://msdn.microsoft.com/en-us/vstudio/default.aspx][Visual studio ]] (when (eq system-type 'windows-nt) (load-file ) )
调用cdb-gud: M-x cdb RET。
- Microsoft命令行调试器cdb.exe
如果你是一个Windows程序员,Windbg听说过吗?如果没有听说过,我建议你去看看《软件调试》,这本书讲的非常的透彻。 注意,这是一本厚度为1000页的砖头书。如果你没有耐心去读这么厚的书,那么还有一个选择,《Windows用户态程序高效排错》, 内容236页,内容写的不多,但是书内提供的资源。 两本书排名不分先后。那么cdb就在Windbg的安装目录下。Windbg是Windows下的能用户态调试核心态调试的强大的图形界面的调试器。那么,简而言之,尽管不太准确13,cdb就是非图形界面的调试用户态程序的windbg。简要介绍一下cdb:cdb就是一个命令行的调试器,over。
深入探讨一学cdb: 我想很深入的探讨一下cdb,但是实在是没有如此深厚的内功,但是我可以推荐一些内容供有兴趣的去深入。
- WINDBG安装目录内的debugger.chm
- windbg info
- MSDN的帮助
强烈推荐将windbg info打印出来在手边供不时查阅之需。
现在,来启动cdb-gud吧:M-x cdb RET
Showtime14!
- 例子
建立一个Visual studio的console典型的Hello world工程(比如刚才的test2),源代码如下15:#include "stdafx.h" #include void assign (int a[], int n) { for (int i = 0; i < n; ++i) { if (a[i] == 0) { a[i] = i; } } } int main(int argc, char* argv[]) { int a[100]; assign(a, 100); for (int i = 0; i < 100; ++i) { printf("%d\t", a[i]); } return 0; } }
通过上边程序,我们期待打出所有0-99的数,每个数是间隔一个制表符,每十个数打出一个换行。
我们编译运行代码,得到结果:
-858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460
有点奇怪,为什么没有得到我们需要的东西呢??
从代码中貌似看不到直接的证据证明我们的代码逻辑的错误。我们就祭出cdb。
M-x cdb RET
minibuffer: cdb d:\Mydocuments\workbench\test2\debug\test2.exe 会有以下界面提示。
Microsoft (R) Windows Debugger Version 6.11.0001.404 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: d:/Mydocuments/workbench/tmp/VCTest/test2/test/Debug/test.exe Symbol search path is: C:\symbolsXpSp3;d:/Mydocuments/workbench/via/helios/coyote/Bin/MULTIMEDIA_PRO_240x320_Debug Executable search path is: *** WARNING: Unable to verify checksum for test.exe ModLoad: 00400000 0042c000 test.exe ModLoad: 7c900000 7c9b2000 ntdll.dll ModLoad: 7c800000 7c8f6000 C:\WINDOWS\system32\kernel32.dll (a90.1eb8): Break instruction exception - code 80000003 (first chance) eax=00241eb4 ebx=7ffd9000 ecx=00000000 edx=00000001 esi=00241f48 edi=00241eb4 eip=7c90120e esp=0012fb20 ebp=0012fc94 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 ntdll!DbgBreakPoint: 7c90120e cc int 3 0:000> cdb: Reading initial command 'l+*;l-s' Source options are ffffffff: 1/t - Step/trace by source line 2/l - List source line at prompt 4/s - List source code at prompt 8/o - Only show source code at prompt Source options are fffffffb: 1/t - Step/trace by source line 2/l - List source line at prompt 8/o - Only show source code at prompt 0:000>
0:000: bp main
bp 表示打断点,断点的方式有几种:函数名,文件行数,内存地址。
但是我最喜欢的还是:当调试过程已经在运行,*在Emacs中打开文件,走到需要打断点的行,C-x space*,断点就打到该行,很给力啊。
Breakpoint 0 hit test!main: 0040d880 55 push ebp 0:000> p test!main+0x1e: 0040d89e 6a64 push 64h 0:000> t test!ILT+15(?assignYAXQAHHZ): 00401014 e907c80000 jmp test!assign (0040d820) 0:000> p test!assign: 0040d820 55 push ebp 0:000> p test!assign+0x18: 0040d838 c745fc00000000 mov dword ptr [ebp-4],0 ss:0023:0012fd8c=cccccccc
g==go,所以表示开始运行程序。
p 单步跟踪,不进入子函数。
t 单步跟踪,但是进入子函数。
不出意外,当你使用p或者t跟踪程序的时候,源代码窗口已经打开,同时随着跟踪,相应的代码行也会高亮。
0:000> dv a = 0x0012fdf0 n = 100 i = -858993460 0:000> p test!assign+0x32: 0040d852 8b55fc mov edx,dword ptr [ebp-4] ss:0023:0012fd8c=00000000 0:000> dv a = 0x0012fdf0 n = 100 i = 0 0:000> dt a[0] Local var @ 0x12fd98 Type a[ 100] [0] 0x0012fdf0 -> -858993460 0:000> dt a[1] Local var @ 0x12fd98 Type a[0] [1] 0x0012fdf0 -> -858993460 0:000> dt a[i] Local var @ 0x12fd98 Type a[1] [0] 0x0012fdf0 -> -858993460
dv:显示当前的局部变量。
dt: 显示指定的变量。
当前发现为什么a数组的值怎么是负数,不是期待的0呢?
0:000> k ChildEBP RetAddr 0012fd90 0040d8ac test!assign+0x32 [D:\Mydocuments\workbench\tmp\VCTest\test2\test\test.cpp @ 10] 0012ff80 00401209 test!main+0x2c [D:\Mydocuments\workbench\tmp\VCTest\test2\test\test.cpp @ 21] 0012ffc0 7c817077 test!mainCRTStartup+0xe9 [crt0.c @ 206] WARNING: Stack unwind information not available. Following frames may be wrong. 0012fff0 00000000 kernel32!RegisterWaitForInputIdle+0x49 0:000> .frame 1 01 0012ff80 00401209 test!main+0x2c [D:\Mydocuments\workbench\tmp\VCTest\test2\test\test.cpp @ 21] 0:000> dv argc = 1 i = -858993460 Type information missing error for a 0:000>
k:打印出当前的栈内容
.frame:跳转到栈中的第几帧,当前为第0帧。
在.frame 1后,我们注意到以下代码:
int a[100];
貌似a[ 100]没有初始化吧。
0:000> q
然后修改代码后
#include "stdafx.h" #include void assign (int a[], int n) { for (int i = 0; i < n; ++i) { if (a[i] == 0) { a[i] = i; } } } int main(int argc, char* argv[]) { int a[100]={0}; assign(a, 100); for (int i = 0; i < 100; ++i) { if (0 == (i % 10)) { printf("\n"); } printf("%d\t", a[i]); } return 0; }
编译运行,结果:
test\debug\test.exe 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
那么cdb简单使用就介绍完了。
然而,其实很多cdb的强大功能都没有介绍到:
- 可以调试程序崩溃时留下的内存转储文件dump,调出其堆栈休息。
- 可以附挂到一个正在运行的程序。
- 远程调试
- 显示,执行代码
- 处理断点(支持复杂的条件断点)
- 读写内存地址
- 反汇编
- 多线程调试
- Windows符号文件
对于一般的console程序,可能这样就够了。但是在调试广大的Windows程序时,哪能不接触Windows的API啊,调试这些个玩意儿,才是让人头痛的东西呢,没有源代码!!cdb的一个机制可以让你没有源代码的情况下看到很多Windows的公开数据结构。首先,先查看自己的系统的版本,在我的电脑->右键->属性,我可以看到我的系统是Microsoft Windos XP Professional Service Pace 3, 那么我就来这里下载Windows的相应的符号文件。
将其安装到本机,比如:C:\symbolsXpSp3。添加环境变量:_NT_ALT_SYMBOL_PATH,设其值为安装目录。
经此设置,cdb在调试时就可以看到Windows API的符号文件。更多内容,还请参考帮助文档或者我推荐的两本书。
Footnotes:
1@木鱼指出6.0中的Epsilon即为emacs模式。可查到资料中最早的是2005,来源猛击我。但是有可能更早,如果有资料,麻烦请留言告之.
2此处没有贬低Visual studio的意思,只是我中Emacs的毒已太深,用其它什么都不爽:)
3下载链接Visual studio Emacs emulation addon
4flymake配置见http://marcelotoledo.com/2007/07/11/emacs-flymake/
5CEDET配置见http://emacser.com/c-cedet.htm
6recent-jump 配置见http://liuminzhao.com/emacs/recent-jump-el-for-emacs/
7auto-complete 配置见http://emacser.com/auto-complete.htm
8yasnippet主页http://code.google.com/p/yasnippet/
9cygwin, mingw, msys, unixutils
10好吧,我承认这个调用方法很蛋疼,如果你的工程比较少的话,其实可以将路径,程序类型什么的写到文件里,多整几个文件即可。upCppProject1Tags.bat, upCppProject2Tags.bat,upJavaPro1Tags.bat,upJavaPro2Tags.bat。
11此文介绍了如何使用nmake,但是恕我直言,我在Visual studio的安装目录里没有找到nmake。
12如果不成功,请注意检查大小写和空格。
13调试器,恶魔之母http://stackoverflow.com/questions/602138/is-a-debugger-the-mother-of-all-evil
14其实,用户态的windbg是图形界面的cdb。
15谨以此名纪念我经历的一个软件项目:Showtime 0.7。
16请注意,这是实验代码,所以请忽略魔幻数,程序是否有意义等话题。
15请注意,这是实验代码,所以请忽略魔幻数,程序是否有意义等话题。

相关日志
- 用CEDET浏览和编辑C++代码(157)
- Emacs初学者必知必会(7)
- 致Emacs初学者(81)
- 我的Emacs配置文件 - DEA(210)
- 在Emacs下用C/C++编程(27)