Profil de Libin绿色家园PhotosBlogListes Outils Aide

Blog


16 août

objcopy工具使用指南

objcopy Utility
objcopy [ -F bfdname | --target=bfdname ]
[ -I bfdname | --input-target=bfdname ]
[ -O bfdname | --output-target= bfdname ]
[ -S | --strip-all ] [ -g | --strip-debug ]
[ -K symbolname | --keep-symbol= symbolname ]
[ -N symbolname | --strip-symbol= symbolname ]
[ -L symbolname | --localize-symbol= symbolname ]
[ -W symbolname | --weaken-symbol= symbolname ]
[ -x | --discard-all ] [ -X | --discard-locals ]
[ -b byte | --byte= byte ]
[ -i interleave | --interleave= interleave ]
[ -R sectionname | --remove-section= sectionname ]
[ -p | --preserve-dates ] [ --debugging ]
[ --gap-fill= val ] [ --pad-to= address ]
[ --set-start= val ] [ --adjust-start= incr ]
[ --change-address= incr ]
[ --change-section-address= section{=,+,-} val ]
[ --change-warnings ] [ --no-change-warnings ]
[ --set-section-flags= section= flags ]
[ --add-section= sectionname= filename ]
[ --change-leading char ] [--remove-leading-char ]
[ --weaken ]
[ -v | --verbose ] [ -V | --version ] [ --help ]
input-file [ outfile ]

GNU实用工具程序objcopy的作用是拷贝一个目标文件的内容到另一个目标文件中。Objcopy使用GNU BFD库去读或写目标文件。Objcopy可以使用不同于源目标文件的格式来写目的目标文件(也即是说可以将一种格式的目标文件转换成另一种格式的目标文件)。通过以上命令行选项可以控制Objcopy的具体操作。
Objcopy在进行目标文件的转换时,将生成一个临时文件,转换完成后就将这个临时文件删掉。Objcopy使用BFD做转换工作。如果没有明确地格式要求,则Objcopy将访问所有在BFD库中已经描述了的并且它可以识别的格式,请参见《GNUpro Decelopment Tools》中"using ld"一章中"BFD库"部分和"BFD库中规范的目标文件格式"部分。
通过使用srec作为输出目标(使用命令行选项-o srec),Objcopy可以产生S记录格式文件。
通过使用binary作为输出目标(使用命令行选项-o binary),Objcopy可以产生原始的二进制文件。使用Objcopy产生一个原始的二进制文件,实质上是进行了一回输入目标文件内容的内存转储。所有的符号和重定位信息都将被丢弃。内存转储起始于输入目标文件中那些将要拷贝到输出目标文件去的部分的最小虚地址处。
使用Objcopy生成S记录格式文件或者原始的二进制文件的过程中,-S选项和-R选项可能会比较有用。-S选项是用来删掉包含调试信息的部分,-R选项是用来删掉包含了二进制文件不需要的内容的那些部分。

input-file
outfile
参数input-file和outfile分别表示输入目标文件(源目标文件)和输出目标文件(目的目标文件)。如果在命令行中没有明确地指定outfile,那么Objcopy将创建一个临时文件来存放目标结果,然后使用input-file的名字来重命名这个临时文件(这时候,原来的input-file将被覆盖)。

-I bfdname
--input-target=bfdname
明确告诉Objcopy,源文件的格式是什么,bfdname是BFD库中描述的标准格式名。这样做要比"让Objcopy自己去分析源文件的格式,然后去和BFD中描述的各种格式比较,通过而得知源文件的目标格式名"的方法要高效得多。

-O bfdname
--output-target= bfdname
使用指定的格式来写输出文件(即目标文件),bfdname是BFD库中描述的标准格式名。

-F bfdname
--target= bfdname
明确告诉Objcopy,源文件的格式是什么,同时也使用这个格式来写输出文件(即目标文件),也就是说将源目标文件中的内容拷贝到目的目标文件的过程中,只进行拷贝不做格式转换,源目标文件是什么格式,目的目标文件就是什么格式。

-R sectionname
--remove-section= sectionname
从输出文件中删掉所有名为sectionname的段。这个选项可以多次使用。
注意:不恰当地使用这个选项可能会导致输出文件不可用。

-S
--strip-all (strip 剥去、剥)
不从源文件中拷贝重定位信息和符号信息到输出文件(目的文件)中去。

-g
--strip-debug
不从源文件中拷贝调试符号到输出文件(目的文件)中去。

--strip-undeeded
剥去所有在重定位处理时所不需要的符号。

-K symbolname
--keep-symbol= symbolname
仅从源文件中拷贝名为symbolname的符号。这个选项可以多次使用。

-N symbolname
--strip-symbol= symbolname
不从源文件中拷贝名为symbolname的符号。这个选项可以多次使用。它可以和其他的strip选项联合起来使用(除了-K symbolname | --keep-symbol= symbolname外)。

-L symbolname
--localize-symbol= symbolname
使名为symbolname的符号在文件内局部化,以便该符号在该文件外部是不可见的。这个选项可以多次使用。

-W symbolname
-weaken-symbol= symbolname
弱化名为symbolname的符号。这个选项可以多次使用。

-x
--discard-all (discard 丢弃、抛弃)
不从源文件中拷贝非全局符号。

-X
--discard-locals
不从源文件中拷贝又编译器生成的局部符号(这些符号通常是L或 . 开头的)。

-b byte
--byte= byte
Keep only every byte of the input file (header data is not affected). byte can be
in the range from 0 to interleave-1, where interleave is given by the -i or
--interleave option, or the default of 4. This option is useful for creating files to
program ROM . It is typically used with an srec output target.

-i interleave
--interleave= interleave (interleave 隔行、交叉)
Only copy one out of every interleave bytes. Select which byte to copy with the
-b or --byte option. The default is 4. objcopy ignores this option if you do not
specify either -b or --byte.

-p
--preserve-dates (preserve 保存、保持)
设置输出文件的访问和修改日期和输入文件相同。

[ --debugging ]
如果可能的话,转换调试信息。因为只有特定的调试格式被支持,以及这个转换过程要耗费一定的时间,所以这个选项不是默认的。
--gap-fill= val
使用内容val填充段与段之间的空隙。通过增加段的大小,在地址较低的一段附加空间中填充内容val来完成这一选项的功能。

--pad-to= address
填充输出文件到虚拟地址address。通过增加输出文件中最后一个段的大小,在输出文件中最后一段的末尾和address之间的这段附加空间中,用--gap-fill= val选项中指定的内容val来填充(默认内容是0,即没有使用--gap-fill= val选项的情况下)。

--set-start= val
设置新文件(应该是输出文件吧?)的起始地址为val。不是所有的目标文件格式都支持设置起始地址。

--change-start = incr
--adjust-start= incr
通过增加值incr来改变起始地址。不是所有的目标文件格式都支持设置起始地址。

--change-addresses incr
--adjust-vma incr
Change the VMA and LMA addresses of all sections, section., as well as the
start address, by adding incr. Some object file formats do not permit section
addresses to be changed arbitrarily.

通过加上一个值incr,改变所有段的VMA(Virtual Memory Address运行时地址)和LMA(Load Memory Address装载地址),以及起始地址。某些目标文件格式不允许随便更改段的地址。

--change-section-address section{=,+,-} val
--adjust-section-vma section{=,+,-} val
设置或者改变名为section的段的VMA(Virtual Memory Address运行时地址)和LMA(Load Memory Address装载地址)。如果这个选项中使用的是"=",那么名为section的段的VMA(Virtual Memory Address运行时地址)和LMA(Load Memory Address装载地址)将被设置成val;如果这个选项中使用的是"-"或者"+",那么上述两个地址将被设置或者改变成这两个地址的当前值减去或加上val后的值。如果在输入文件中名为section的段不存在,那么Objcopy将发出一个警告,除非--no-change-warnings选项被使用。
这里的段地址设置和改变都是输出文件中的段相对于输入文件中的段而言的。例如:
(1)--change-section-address .text = 10000
这里是指将输入文件(即源文件)中名为.text的段拷贝到输出文件中后,输出文件中的.text段的VMA(Virtual Memory Address运行时地址)和LMA(Load Memory Address装载地址)将都被设置成10000。
(2)--change-section-address .text + 100
这里是指将输入文件(即源文件)中名为.text的段拷贝到输出文件中后,输出文件中的.text段的VMA(Virtual Memory Address运行时地址)和LMA(Load Memory Address装载地址)将都被设置成以前输入文件中.text段的地址(当前地址)加上100后的值。

--change-section-lma section{=,+,-} val
仅设置或者改变名为section的段的LMA(Load Memory Address装载地址)。一个段的LMA是程序被加载时,该段将被加载到的一段内存空间的首地址。通常LMA和VMA(Virtual Memory Address运行时地址)是相同的,但是在某些系统中,特别是在那些程序放在ROM的系统中,LMA和VMA是不相同的。如果这个选项中使用的是"=",那么名为section的段的LMA(Load Memory Address装载地址)将被设置成val;如果这个选项中使用的是"-"或者"+",那么LMA将被设置或者改变成这两个地址的当前值减去或加上val后的值。如果在输入文件中名为section的段不存在,那么Objcopy将发出一个警告,除非--no-change-warnings选项被使用。

--change-section-vma section{=,+,-} val
仅设置或者改变名为section的段的VMA(Load Memory Address装载地址)。一个段的VMA是程序运行时,该段的定位地址。通常VMA和LMA(Virtual Memory Address运行时地址)是相同的,但是在某些系统中,特别是在那些程序放在ROM的系统中,LMA和VMA是不相同的。如果这个选项中使用的是"=",那么名为section的段的LMA(Load Memory Address装载地址)将被设置成val;如果这个选项中使用的是"-"或者"+",那么LMA将被设置或者改变成这两个地址的当前值减去或加上val后的值。如果在输入文件中名为section的段不存在,那么Objcopy将发出一个警告,除非--no-change-warnings选项被使用。

--change-warnings
--adjust-warnings
如果命令行中使用了--change-section-address section{=,+,-} val或者--adjust-section-vma section{=,+,-} val,又或者--change-section-lma section{=,+,-} val,又或者--change-section-vma section{=,+,-} val,并且输入文件中名为section的段不存在,则Objcopy发出警告。这是默认的选项。

--no-chagne-warnings
--no-adjust-warnings
如果命令行中使用了--change-section-address section{=,+,-} val或者--adjust-section-vma section{=,+,-} val,又或者--change-section-lma section{=,+,-} val,又或者--change-section-vma section{=,+,-} val,即使输入文件中名为section的段不存在, Objcopy也不会发出警告。

--set-section-flags section=flags
为为section的段设置一个标识。这个flags变量的可以取逗号分隔的多个标识名字符串(这些标识名字符串是能够被Objcopy程序所识别的),合法的标识名有alloc,load,readonly,code,data和rom。
You can set the contents flag for a section which does not havecontents, but it is not meaningful to clear the contents flag of a section which does have contents; just remove the section instead. Not all flags are meaningful for all object file formats.

--add-section sectionname=filename
进行目标文件拷贝的过程中,在输出文件中增加一个名为sectionname的新段。这个新增加的段的内容从文件filename得到。这个新增加的段的大小就是这个文件filename的大小。只要输出文件的格式允许该文件的段可以有任意的段名(段名不是标准的,固定的),这个选项才能使用。

--change-leading-char
Some object file formats use special characters at the start of symbols. The most
common such character is underscore, which compilers often add before every
symbol. This option tells objcopy to change the leading character of every
symbol when it converts between object file formats. If the object file formats use
the same leading character, this option has no effect. Otherwise, it will add a
character, or remove a character, or change a character, as appropriate.

--remove-leading-char
If the first character of a global symbol is a special symbol leading character used
by the object file format, remove the character. The most common symbol leading
character is underscore. This option will remove a leading underscore from all
global symbols. This can be useful if you want to link together objects of different
file formats with different conventions for symbol names.

--weaken
Change all global symbols in the file to be weak. This can be useful when building
an object that will be linked against other objects using the -R option to the linker.
This option is only effective when using an object file format that supports weak
symbols.

-V
--version
Show the version number of objcopy.

-v
--verbose
Verbose output: list all object files modified. In the case of archives, objcopy -V
lists all members of the archive.

--help
Show a summary of the options to objcopy.

关于GNU ld和ld脚本


ld,即GNU的连接工具,用于将各目标文件合并在一起,并重新安排他们的数据以及符号的引用,常常是程序编译的最后一步。

ld scripts 即ld脚本。ld 脚本的主要目的是要描述怎样将输入文件的各段印象到输出文件中去。它控制输出文件在内存的布局情况。

--------------------------------------------------------------------------------

关于VMA ,LMA

每一个可装载的输出段都有两个地址:VMA(Virtual memory address) 和 LMA(Load mem
ory address)VMA 是输出段运行时的地址,LMA 则是输出段被装载的地址。而这2个地址常常是相同的。在某些情况下二者是有区别的。比如,一个data段被装载到ROM中,然后在程序启动的时候被拷贝到了RAM中去。(这种技术常常用在以ROM 为基础的系统中,用来初始化全局变量,而我们的系统的处理方法可能与此类似?)

--------------------------------------------------------------------------------

关于SECTIONS命令的使用
SECTIONS告诉LD怎样将输入的段印象到输出的段,以及怎样将输出的段装载到内存中去。这是我们在内存布局中常常要用到的命令。段的基本结构如下:
SECTIONS
{
sections- command
sections- command
...
}
其中的sections-command 可作如下选择:
* 程序入口点设置命令ENTRY。
* 符号赋值
* 输出段描述(下面会提到)
* 覆盖描述。(overlay description)

1.输出段描述(Output Section Description):
输出段描述的完整格式如下:

SECTION [ address] [( type)] : [AT( LMA)]
{
output-sections-command
output-sections-command
...
} [> region] [: phdr : phdr ...] [=fillexp]

一般对于以上的描述不会全部用到,对于这些描述的用法,后面将会讲述到。
(1).关于段名
对于不同的输出格式,段名应该满足相应的约定。比如a.out格式的文件就应该只能
使用这些段名:.text .data .bss
(3).输出段地址

输出段地址 address 是输出段的VMA地址。例:

.text [address] :{ *(.text)}
如果没有指定 address 则将按照 region 或者是当前地址记数值"."来分配地址。注意,这里分配的是VMA地址(见VMA的说明)。
另外,在分配地址的时候还有地址对齐操作,这里就不赘述了。


2.输入段描述:
输出段描述告诉连接器怎样在内存中安排你的程序布局,而输入段描述则告诉连接器怎样将各输入文件映射到你的内存布局中去。
(1).输入段的基本语法如下:

输入文件名(段名)
例: file1(.text)
表示将文件 file1的.text 段放于此处。
也支持通配符,如:
*(.text)
将所有的文件的.text段放置到此处。
又例:
a.out
直接将a.out的所有段放置此处。
(2).输入段的通配符使用,就是一般的通配符语法,这里就不赘述了。
3.COMMON 段的设置
在很多的目标文件格式中,对于common symbols(什么是common symbols?)都没有专门的段来存放。
所以在连接时,连接器专门指定了一个COMMON段来包含这些common symbols.
而COMMON段一般放于输出文件的.bss段中。
例:
.bss { *(.bss) *(COMMON) }
在.bss段中放置所有输入文件的.bss段 和 所有输入文件的 common symbols
4.输入段描述的示例:

SECTIONS {
outputa 0x10000 :
{
all.o all.o的所有段
foo.o (.input1) foo.o的所有.input1 段
}
outputb :
{
foo.o (.input2) foo.o的所有.input2 段
foo1.o (.input1) foo1.o的所有.input1段
}
outputc :
{
*(.input1) 所有文件的余下的.input1段
*(.input2) 所有文件的余下的.input2段
}
}


5.在输出段中装填数据。
(1). BYTE, SHORT, LONG, and QUAD
如:BYTE(1)
在当前位置装填1字节的1
(2). FILL 的使用,可以从当前位置开始装填本SECTION。
6.两个输出段关键字。
CREATE_OBJECT_SYMBOLS 和 CONSTRUCTORS

CREATE_OBJECT_SYMBOLS:
每一个输入的文件将对应一个同名的symbol.而这些symbol将被放置在CREATE_OBJECT_SYMBOLS
命令出现的段中。
CONSTRUCTORS:
在此命令出现的地方放置C++全局构造和析构函数信息。对于a.out 格式,它使用其专有的方法来支持C++构造与析构函数。对于不能使用任意段名的文件格式如:XCOFF 和ECOFF 则需要一个CONSTRUCTORS 命令来在输出文件中记录C++全局构造函数和析构函数的信息。对于能使用任意段名的文件格式如COFF 和 ELF 。则连接器将自动生成名为.ctors 和 .dtors 的段来记录相应信息。我们可能暂时用不到这个东西。

7.丢弃段。
命名为 /DISCARD/ 的段的内容将不被包含到输出文件中。

8.输出段描述的其它特性,输出段描述的基本结构正如前边所提到的如下所示:

SECTION [ address] [( type)] : [AT( LMA)]
{
output-sections-command
output-sections-command
...
} [> region] [: phdr : phdr ...] [=fillexp]

下面对其各个特性做一个比较详细的说明:
(1). Output Section Type (即输出段的type属性)

此属性可以赋值为:
NOLOAD 此段的内容将不被装载到内存中去,比如直接在ROM中运行程序。

DSECT
COPY
INFO
OVERLAY
以上4个代表运行时不另外为改段分配内存空间,这样可以一定的节省程序
空间。

(2). 输出段LMA
使用AT 命令来设置段的LMA
这样的话可以使用这个特性很方便的建立ROM image.
例如:
SECTIONS
{
.text 0x1000 : { *(.text) _etext = . ; } //.text 的VMA为0x1000
.mdata 0x2000 : //.mdata 的VMA为0x2000
AT ( ADDR (.text) + SIZEOF (.text) ) //.mdata 的LMA为紧随.text
//段之后。
{ _data = . ; *(.data); _edata = . ; }
.bss 0x3000 : //.bss的VMA为0x3000
{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
与之配合的初始化代码示例如下(可能是由ld自动生成):
extern char _etext, _data, _edata, _bstart, _bend;
char *src = &_etext; //从这里可以看出,"_etext=."的意思实际上是令_etext的地址
//等于当前的VMA
char *dst = &_data;
/* ROM has data at end of text; copy it. */
while (dst < &_edata) {
*dst++ = *src++;
}
//将.text段结尾的.data段拷贝到其运行时地址去。

/* Zero bss */
for (dst = &_bstart; dst< &_bend; dst++)
*dst = 0;
//将未初始化数据全部清零。
(3). Output Section Region
使用 >region 属性可以将某个段定位到预定义好的内存位置去。
例:
MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }
(4). Output Section Segmentation
程序段的定义。
例:
PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text }
后面将提到PHDRS
(5).输出段填充
例:采用如下的方法来填充段
SECTIONS { .text : { *(.text) } =0x9090 }
9.覆盖描述:(Overlay Description)
定义如下:
OVERLAY [ start] : [NOCROSSREFS] [AT ( ldaddr )]
{
secname1
{
output-section-command
output-section-command
...
} [:PHDR...] [=FILL]
secname2
{
output-section-command
output-section-command
...
} [: phdr...] [= fill]
...
} [> region] [: phdr...] [= fill]
OVERLAY 命令应该定义于SECTIONS 命令之内。
--------------------------------------------------------------------------------
------------
10.MEMORY 命令

分配内存区域
MEMORY
{
name [( attr)] : ORIGIN = origin, LENGTH = len
...
}

atrr 为段的匹配特性,如果一个段没有明确指定将其放置到某一个region中去,那么如果
其属性匹配该region的atrr属性,将被加入到该region中去。

有以下属性:
R 只读段
W 只写段
X 执行段
A 可分配段
I 已初始化段
L 与I相同。
! 非特性

例子:
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0x40000000, l = 4M
}
一旦完成了region的定义,就可以使用 >region 的方法将某段指定添加到某个region中去

9 août

VcExtend工具


前    言

    VcExtend 扩展了VC6.0的功能,包括函数体批生成、数据接口自动生成,注释可隐可现,注释自动生成文档等功能,可以大大提高VC的工作效率。

    VcExtend使用VC60的扩展宏接口,采用VBScript编写,使用起来跟VC本身的命令几乎没有区别。VcExtend完全公开源代码,可以无偿使用和修改。

    可以到http://www.mmmnn.com/";>http://www.mmmnn.com下载VcExtend.

    下边介绍几个主要的命令:

    建立新类:生成干净清爽的类框架,代码模板可自由修改。

    代码整理:为函数声明生成函数体,为成员变量生成读写接口(包括声明和实现),将函数体按声明的顺序重排序。这些操作一次性完成,无论你是新设计一个类还是修改一个类,这个命令都会工作得很好。

    注释:生成注释模板,注释可隐藏,函数体可调出头文件写的函数注释进行阅读和修改。注释自动生成类似于MSDN的文档。文档数据采用XML文件存储,你可以按照自己的喜好定义显示格式。

    注释批处理:显示/隐藏全部详细注释,显示/隐藏注释摘要,自动为数据接口函数生成注释,文档整理理。

 

 
一、预备----开始!

    当然,你要先下载http://www.mmmnn.com/Download/VcExtend.rar";>VcExtend.rar 。另外,也需要安装microsoft.com/download/xml/SP/3.1/W98NT42KMe/EN-US/msxml3_cn.exe">http://download .microsoft.com/download/xml/SP/3.1/W98NT42KMe/EN-US/msxml3_cn.exe";>微软的XML插件。

    解包以后,可以看到Txt和Doc文件夹,Txt是一些生成新类时使用的代码模板,Doc文件夹存放由你的注释生成的文档和显示文档的工具。在你的工程中使用VcExtend要把这两个目录拷到你的工程的根目录下,后面的讲解使用Example目录下的Demo工程,Txt和Doc文件夹已经在里边了。

    VcExtend.dsm是VcExtend的源代码,把它拷贝到C:\\Program FilesMicrosoft\\ Visual Studio \\ Common\\ MSDev98\\ Macros目录下,并把里边提供的命令挂接到工具栏上,就可以使用了。

    如果你从没用过VC60的扩展宏,可以照下边的说明来操作。

    启动VC60,不要打开任何工程。

    在Toos菜单选择Macro,点击Options >>, 点击Load Files,选中VcExtend前面的复选框,取消其他选中的框。

    点击Toolbars标签,新建一个工具条,命名为VcExtend。

    点击Commands标签,在Category下拉列表中选中Macros,Commands框里就会显示VcExtend的全部命令。

    选中ClassNew,拖到VcExtend工具条上,在弹出的对话框中选中Text Only,你就会看到ClassNew命令已经挂到工具条上了。把其他命令也挂到工具条上。

    为了便于说明,我们先全部使用Text Only,以后,等你熟悉VcExtend后,可以把文字按钮改为你喜欢的图标。

    在Show Menus For下拉列表中选择All Editors,点击Close关闭对话框。

    关闭VC60, 再重新打开。好啦,下面可以尝尝滋味如何了!

                                                       

 
二、看一看,试一试,味道怎么样?     启动VC60, 打开Example目录下的Demo工程。

    这是AppWidzard生成的SDI工程,全部使用缺省选项,只不过加了两个类罢了。

    让我们先来看一看。

    打开CShow类的头文件,是不是觉得代码特别的清爽干净?

    再用浏览器打开Doc目录下的Doc.htm文件。

    如果你没有安装微软XML插件,赶紧装一个。我用的是msxml3_cn。没有这个可能无法看到文档的效果。

    你可以看到CShow的文档了,虽然这是随便贴的一些文字,没有实际意义,但如果是真正的工程,这个文档就可以在编码、调试、维护的过程中发挥很大的作用,这些东西完全是由注释自动生成的。CTaste类的文档还是空的,等一下由你来完成。

    现在,我们再来试一试。请打开CTaste类的头文件,这是一个什么功能也没有的类。

    先试一下CodeCleanUp功能。    

    随便写两个函数的声明,不要使用VC的Add Member Function功能,而是直接在头文件上写。如:

    CString CreateNewObj(CString Name, int Age);

    在private:下面加个几成员变量,也是直接在头文件上写。每个变量要独占一行,并且,在要需要提供读写接口的变量后面加上可读写标记,可读的为//R,可写的为//W,可读写的为//RW,只能加在注释的最前面,如:

private:    
    CString m_strName; //RW 对象的名字
    int     m_nAge;    //R 对象年龄
    CString m_strAddr; //W

    写完了吧?现在,点一下CodeCleanUp按钮。

    把光标放到你的函数的声明上,点击CodeJump按钮,怎么样?到了函数体了吧?再点一下CodeJump,又回到声明这边来了。

    再看看头文件的下方,注明R的函数生成了Get函数,注明W的生成了Set函数,把光标移到这些函数的上面,点击CodeJump,怎么样?函数体和实现都有了!

    再用CodeJump跳回头文件。我们只是走马观花一下,后面会有更详细的说明,现在先看看另一个主要功能:注释!

    把光标在一个函数的声明上,点击Comment按钮,函数上方会出来一个注释模板。先不管这些注释项是否符合你的要求,如果需要,你可以修改的,以后再说。

    在各注释项上填一些内容,最好不要填垃圾文字,不想写的话,从别的地方拷一些也行,比如说就从这篇说明书上拷一些文字。

    再点击Comment按钮,出问题啦,注释不见了!不会丢了吗?再点Comment,还好,又回来了。

    用CodeJump跳到函数体。当你在写函数实现时想看一下注释怎么办?点一下Comment,真棒,头文件写的注释,源文件也可以查看,当然,修改也是可以的。

    这是函数注释,类注释有没有?当然有了,还是这个Comment,只不过要先把光标放到类声明或类声明上方。

    在类注释各项上填些文字,特别是简述和说明两项,多填一些,当然,内容可以是随便拷来的。

    无论是函数注释还是类注释,写完或修改后都要再点一下Comment,并且,光标要放在注释或函数上,当注释隐藏了,才是保存了。

    现在我们来看看你写的文档。没写过文档?刚才填的注释就是了。

    用浏览器打开工程目录下的Doc目录下的doc.htm,接下来,就随便逛吧,反正,文档很象MSDN,格式是我们大家都很熟悉的。

    毛主席教导我们,要知道梨子的滋味,就得亲口尝一尝。味道怎么样?如果你喜欢,就继续往下看,如果不喜欢,如果不喜欢,那么......不会是真的吧?

 


三、丑话说在前头     如果你浏览到了这里,说明对VcExtend至少不反感。趁你高兴,我可要把一些问题先交代清楚。

    这是使用VC提供的宏功能和几个组件,用VBScript写的东东,语言的功能比较有限,并且,我对VBScript也不太熟,有些地方可能写得比较烂。

    最糟的是出错处理,比如我想打开一个文件,使用Documents.Open函数,这个函数没有返回值,如果文件不存在,它不会告诉我不存在,而是造成一个致命的内存错误,可能导致VC要整个退出。哪位知道有没有解决办法啊?

    当然,你不要害怕,这也没什么大不了的。如果你新建了一个工程,要使用VcExtend功能,千万记住要把Doc和Txt两个文件夹拷到工程的目录下,和Debug和res目录并列,如果你忘了的话,我以我的人格担保:一定会出问题的。

    如果修改了VcExtend的源代码,最好把VC退出后再重新打开,要不然也可能产生与上边所说相似的内存错误。

    出了上述的问题后,如果在win2000环境下,最好注销再登录。

    还有一个问题是,由于语言所限,考虑到效率和性能,无法对代码进行复杂的词法分析,所以代码要比较规范,当然也不太特别,都是非常普通的编码规范,只要做到了下面几点就行:

    一是每个类一定要有构造函数,并且放在最上面,构造函数上方的代码将不作处理,你可以把内置的数据类型,如enum,struct或嵌套类什么的,放在构造函数的上边。

    二是每个成员变量的声明要独占一行,是指头文件的成员变量,函数体内的东西,VcExtend是完全不管的。

    三是如果要写额外的注释,不能使用/**/,要使用//。

    存在的主要问题,大概就这些了吧。如果你发现了什么问题,请在论坛发表,我将尽力解决。

    另外要说明的是,Inline函数的实现放在.inl文件中,所以一个类有三个文件。这是自动处理的,可不敢给你添麻烦。CodeJump的良好跳转功能,会让你感觉不到多了个文件。

    虽然还有一些问题,但我觉得VcExtend还是挺好用的,能提高工作效率十分之N。我向你推荐它,就表示这个东东不错,因为,我觉得不好的东西,是从来不会给人的,比如说,我从来不会随便送钱给人家,因为,我知道,金钱是肮脏的东西。

 


四、与清爽干净的代码为伴----类框架生成
    如果你象我一样,喜欢清爽干净的代码,那么,用ClassNew来生成新类是一个不错的选择,尤其是生成CObject的子类。

    先用一下试试,点击ClassNew,在输入框中输一个类名:CMyClass,确定,感觉怎么样?

    ClassNew很简单易用,但还是有一些需要说明的。

    首先是一些可选项,请根据你的需要来填写,如继承自某一个类的,就加:BaseName,不指定则会认为以CObject为基类;要指定文件名,就加#FileName,不能有后缀,因为.cpp和.h和.inl都会共用这个文件名,不指定则会以类名去掉前缀C作为文件名,我主张不指定为好;至于目录名,现在不理它吧,很少用,以后会提到的;最后就是宏选项了,如果是可序列化的类,就加/S,消息类就加/M,加了这些选项就会生成相应的宏。除了类名在最前面外,所有的选项都不需要考虑输入的顺序。

    你还可以修改代码模板,Txt目录下的cpp.txt,hpp.txt,inl.txt就是生成新类框架的模板,可以按自已的喜好来修改。

    ClassNew有一个显著的缺陷,就是生成的类不会自动加入工程,也不会加入到ClassWizard中,要使用Project->Add To Project->Files功能手工把它加入工程,如果要加入到ClassWizard中,可以删掉工程的.clw文件,再重新编译。一个更简单的办法是,先用VC的New CLass功能生成一个类,再用ClassNew重新生成一遍,这样就不会有这些缺陷了。

    不是使用ClassNew生成的类,也完全可以使用VcExtend的功能,只不过首次使用的时候会要求你输入类名和基类名而已,唯一的问题是代码跳转,偶尔可能会找不到,不过很少见。

    如果你用VC的AppWizard新建了一个工程,想把VC生成的代码改为用ClassNew来生成,也很简单的。先把工程另存一份,并启动另一个VC打开另存的工程。用ClassNew重新生成各个类,并从另存的工程中把所缺少的代码拷过来。最好是先拷头文件的代码,拷完后点一下CodeCleanUp生成函数体,再拷函数体的内容。每做完一个类后编译并运行工程看有没有问题,没问题再做下一个类。APP类千万不要忘了拷CMyApp theApp这一行。

 


五、生成函数体与数据接口----好用的代码完成功能
    虽然Rose的代码生成功能不错,但附加的无数的注释却让我深恶痛绝,最后还是决定不用它生成的代码。我希望做一个简单的工具,可以先进行类设计,然后自动生成代码框架,修改了类设计后还可以很方便地在不影响有效代码的前提下重新生成代码框架,当然,不能有很多的附加注释!CodeCleanUp就是这样子的一个工具!

    使用CodeCleanUp,你可以先写头文件,把所有的函数声明和成员变量都写出来,然后一下子就把函数体和数据接口生成了。

    CodeCleanUp主要有三个功能,一是函数体生成,二是数据接口函数生成,三是函数体按声明的顺序重排序。不管你的头文件写了多少或修改了多少,CodeCleanUp都会很好的工作。

    要提供公共接口的成员变量,只要在注释上加上读写标记就行,只读为//R,只写为//W,可读写为//RW,这样,你自己可以对哪些成员变量是可读写的一目了然,而CodeCleanUp会根据这些标记生成公共数据接口。任何时候,你都可以修改读写标记,然后点击CodeCleanUp,都会自动生成或删除接口函数。

    接口函数默认的命名规则是Get或Set加上去掉前缀的变量名,如果不喜欢,可以修改,参见后面的关于可选项的章节。

    接口函数的实现代码也是自动生成的,一般情况下可以直接使用,当然你也可以修改它。

    接口函数都是内联的,实现在.inl文件里,你可以使用CodeJump方便的在声明与实现之间跳转。

    除了接口函数外,其他函数也可以声明为内联,只要在声明的最前面加inline,CodeCleanUp就会把它生成到.inl文件中去,但函数体里的实现代码当然不会象接口函数一样自动生成了,可以使用CodeJump跳过去写自己的代码。

    要删除一个或一些函数,只要删掉声明后运行CodeCleanUp就行,接口函数则只需修改成员变量的读写标记。内联函数一般都是很简单的,所以删除时会完全删掉,但普通函数则不会删除函数体,因为里边的代码说不定你还要用呢,所以只会把它移到最上边,要手工删掉才行。

    CodeCleanUp会把接口函数的声明放在头文件的最下边,这样你所要关注的代码就少了很多,可以把注意力放在最重要的代码上。

 


六、让你爱上写注释----注释可隐可现,文档自动生成
    Comment会让你觉得写注释是一件让人愉快的事情。

    Comment的基本用法已经在前面说过了。它是一个开关式的多功能的命令,当你光标下的函数还没有注释时,使用它会生成注释模板,当已经有注释时,使用它会保存并隐藏注释,当你的函数已有注释但隐藏了时,会调出注释供你阅读和修改,当你在编辑cpp或inl文件时,也可以用它调出注释来查看或修改,当你的光标在头文件的类声明行或其上方的任一行时,操作的就是类注释!

    注释项分为三部份,一是全自动处理的,完全不用理会它。包括:类注释的类名,基类名,文件名;函数注释的函数名,函数原形,这部分不会在源文件中显示出来,但可以在文档中显示。二是必选项,包括类注释的简述,函数注释的简述,简述就是最简单的功能描述,如果连这个都不写的话,你这个注释也就没什么意义了。函数注释的返回值和参数项也属于这一类,但不写这两项注释还比较无所谓。三是可选项,你可以增删一些项目,如说明就是可选的。可选项的增删方法参见后面的关于选项的章节。

    生成注释模板时,Comment并不是直接贴个模板了事,而是先进行了一些分析的。重载函数会当作一个函数,所有的参数都会收集起来。如果你为多个重载函数写了多份注释,后保存的就会覆盖先保存的。同名的函数,即使不是连续声明,也会当作重载函数。

    类模板中的[]内的返回类型或参数,千万不要删掉它。你的注释加在它后面就是了。用空格隔开比较好看一些。

    如果你修改了函数,如加了参数,或加了重载的函数,可以把当前的注释保存,再调出来,就会把新加的参数加入模板。

    当注释隐藏时,实际上是保存到Doc目录下的Doc.xml文件上去了。隐藏和保存是同时进行的,所以修改后最好隐藏一下,虽然你不隐藏,注释还在你的源文件上,不会丢失,但当你在cpp文件上打开相应的注释时,只会调出已经保存过的内容,并且,没保存的注释不会出现在文档上。这些操作,可以使用注释批处理功能,参见后续章节。

    每一个注释项都是以///$加标签名开头的,如果要换行,从第二行开始,要用///开头,千万不要用///$,否则会当作是一个新的项,也不要用//开头,否则会认为格式不规范而拒绝存入。如果注释较长,应该分多行来写,就象代码一样。多行注释可以先写成普通文字,写完后用MultiLineComment命令来处理。因为注释是要用来生成文档的,而文档的显示环境与在源文件中不一样,写注释时的分行,文档中会忽略。如果分档中要强制分行,必须加r,要强制分行并空一行,就加rr。下面是一个多行注释项的例子: 

    ///$说明: 
    ///这是第一行, 
    ///这是第二行,在文档中不会强制换行。 
    ///\r这是第二段,在文档中会强制换行,但\r不会显示出来。 
    ///\r\r这是第三段,在文档中会在前面插一个空行。 

    写完以后选中注释,注意不要选中///$那一行,点击MultiLineComment,就会按标准格式排版。保存前是什么样子的注释,以后调出来还是什么样子。

    注释与函数声明(或类注释与类声明)之间不能有空行。

    注释将存成XML文件,这样内容和显示可以完全分开。如果你对XML比较熟悉,可以定义自己的XSL文件来显示文档。不过,使用我写的文档显示工具也不错啊,虽然我对XML不熟悉,但是费了很多心血的。考虑到VC程序员比较习惯MSDN的风格,所以基本上是按照MSDN的格式来显示文档的,如果你喜欢漂亮一点,可以加一些配色或图片。

    文档的显示其实只使用一个文件Doc.htm,具体内容完全是用脚本和xsl模板来实现的,所以IE的前进后退刷新之类的功能无法使用。

    还有一个不错的功能:你也可以象MSDN一样,对函数进行分类。同一类的函数的声明要放在一起,并且在每类的第一个函数的注释上加分类这一项。用手工加上这一项 

    ///$分类:类名

    注意是中文的分号。象访问控制符一样,后面的函数都会当作是这一类的,直到定义了另一个分类。同一类的函数只能在第一个加分类这个注释项,如果你定了多个同名的类,我可不帮你合并。如果不明白,打开CShow类的头文件,点击CommentDetail调出全部注释,可以很清楚的看到是如何将函数分类的,再看一下CShow的文档,可以看到分类的效果。

    后面还提供一些自动化功能,帮你高效地处理注释和文档。

 


七、注释批处理的魔法----省时高效、随心所欲
    注释批处理包括四个命令,它们帮你随心所欲地摆弄注释,并尽可能节省时间。

    CommentDetail 显示/隐藏全部注释。这个很简单,试用一下就知道了。

    CommentDesc 显示/隐藏注释摘要。这个功能会把所有函数注释的简述部份作为普通注释加在函数声明后面。这样有两方面的作用,一是帮助读者快速的理解类的功能,避免了注释隐藏后完全没有注释的极端,也不会让大量的注释掩没了有效的代码,第二呢,猜到了吗?这是供VC调用的,就是在对象成员列表中弹出函数注释。怎么样?想得很细吧。我的建议是,当一个类的代码完成后,就调用CommentDesc,然后作为最终代码保存。

    CommentDetail和CommentDesc是"不兼容"的,互相都会把对方显示的注释先隐藏掉,以避免太多的注释。两者都是开关式的,使用它们,你可以很方便地让你的源文件完全没有注释(成员变量除外),显示全部注释,或是只显示注释摘要。

    值得注意的一点是,CommentDesc仅仅是显示注释摘要,在这里作修改,是无法保存到文档中去的。

    AttributeComment 为成员变量的读写接口生成注释。如果你给的成员变量写了注释,如: CString m_strName; //RW 主角的名字。 并调用AttributeComment,那么GetName和SetName函数都会自动生成注释,即使你还没有调用CodeCleanUp生成GetSet函数。注释包括简述和返回值注释和参数注释。生成的注释也许并不太合你的心意,但是修改它总比完全重新写它要省时得多。生成的注释会直接保存到文档,而不会在源文件中显示出来,可以调用CommentDetail全部调出来并修改它。不管有多少成员变量,都会一次性生成注释。已经修改并保存的注释,不会重新生成,以免删掉了你的工作成果。

    DocCleanUp 文档整理,这个功能会把多余的文档删掉,比如说函数已经删除了,调用这个命令就会把相关的文档删除。另外,这个命令会把文档按函数声明的顺序重排序。如果你看到文档有些混乱,就调用这个命令试一下。

 


八、还有一些小玩意,喜欢吗?    CodeJump:代码跳转;
    VirtualFunction:为CObject的子类覆盖基类的虚函数;
    ThisList:显示this指针的成员列表。 

     VC60的代码跳转功能,常常找不到,并且也没有方便的头文件源文件之间的跳转命令,加了一个.inl文件之后,就更不方便了,所以我写了一个自己的跳转命令CodeJump。

    函数声明和函数实现的相互跳转,跟VC的跳转命令差不多,但CodeJump一般不会找不到。

    CodeJump还会根据函数是否内联,来自动跳到.cpp或.inl文件,.inl到.h的跳转也工作良好。

    如果找不到,CodeJump会弹出一个窗口,问你是否跳转到相应的文件,它会根据函数是否内联来判断哪个文件是你的可能目标。

    如果你只是想作文件的跳转,可以把光标放在空行或注释上行上,点击CodeJump,就会进行文件的跳转。如果你工作在头文件上,想跳到.cpp文件,光标就放到//!begin get/set这一行的上方,如果想跳到.inl文件,光标就放到//!begin get/set的下方。

    VC60中为MFC的子类显示基类有哪些可覆盖函数,自定义的CObject子类就没有这个功能了,VirtualFunction可以弥补这个小缺陷。它的界面有点恶心,要填序号来选函数,并且只显示函数名。别骂我做得不好,VBScript就提供InputBox这么一个输入工具,我容易吗我?

    我一直很奇怪VC60的ListMembers命令为什么做成默认是显示全局成员,而不是显示this指针的成员,有一个::来显示全局成员了,默认显示this指针的成员不是很好吗?害我每次都要写一个this->,然后又要删掉它。ThisList就是用来显示this指针成员的。这个命令也做得不好,要调用ThisList,再调用VC60的ListMembers命令才能调出成员列表,我在ThisList宏的最后加了ExecuteCommand "ListMembers"语句,但却不起作用。ThisList实际上只是删掉代码中的上一个this->,再在光标位置插入一个this->。我把ThisList的快捷键设为Shift + Left Arrow,把ListMembers的快捷键设为Shift + Right Arrow,这样按住shift再按左右箭头就出来了。开头感觉不是很爽,习惯了觉得也不错,总比写一个this->,然后又删掉它好些。请注意,如果你的代码中有Isthis->这样的字符,ThisList会把其中的this->删掉,不过这种代码出现的可能性很小,并且编绎时也很容易发现的。

 


九、可选项----定制你的VcExtend

     你可以通过对源代码的一些全局常量进行修改来使VcExtend更适合你的习惯或开发组的规范。源代码中已对这些全局常量加了注释,很容易理解的,这里只对其中的几个可选项加一些说明。

HppDir = "" ' "Hpp" 'Hpp文件的存储目录,如果不为"",一定要加
CppDir = "" ' "Cpp" 'Cpp文件的存储目录,如果不为"",一定要加
InlDir = "" ' "Hpp" 'Inl文件的存储目录,如果不为"",一定要加

    一个工程里文件较多时可能要分目录存储,修改上面的存储目录即可实现分目录存储。如果定义了存储目录,一定要在工程目录下建立这些目录,如果忘了,就会产生第三节所说的内存错误。如果把InlDir设为等于HppDir,inl文件就会和.h文件放在一起。

    关于目录,还有一点要说明的。生成新类的时候,有一个目录选项,所填目录不包括上述的存储目录的部份。比如HppDir="Hpp",CppDir="Cpp", InlDir=HppDir,生成新类时加了目录名@Views,那么cpp文件会存到CppViews目录下,而hpp和inl文件会存到HppViews目录下。并且,一定要先建好Views目录!!!


Mark_Desc = "简述"  '注释标签,修改了这些项则必须对Doc目录下的.xsl文件作相同修改。
Mark_Return = "返回" '
Mark_Param = "参数" '
Mark_Cort = "分类" '

    这些是注释的必选项,你可以改变标签名字,比如说改为英文。我喜欢用中文,因为英文长短不一。

Marks_Class = "版权/作者/初建/修改/说明" '可选类注释项,修改则须修改.xsl文件
Marks_Function = "说明" '可选函数注释项

    这些是注释的可选项,你可以随便增删或改为英文,也可以按自己的喜好或开发规范重新排序。

NameLen = Len(Mark_Desc) '等于上面的最长的标签的Len
NameLang = 2             '标签的语言,英文为1,中文2

    如果你改变了标签,这两个变量也要做相应改变,否则注释会很难看,存入时还可能读错。

    下面的可选项目前不推荐改动,有兴趣的话可以看看。

    AssertGet = False '是否在所有的读函数中加断言语句
    AssertSet = False '是否在所有的写函数中加断言语句
    GetSetAll = False '是否为所有的成员变量生成读写函数
    AssertSentence = "ASSERT_VALID(this);" '断言语句的内容

    前面已经说过,用//RW来标记成员变紧是否可读写,标记为//RW的成员变量会生成public:的读写接口。

    如果你把GetSetAll设为True,VcExtend就会为所有的成员变量生成数据读写函数,其中,有可读写标记的成员变量会生成public:的读写函数,而其他读写函数的访问控制符与成员变量一致。有点迷糊吧?看看例子:

protected:
    CString m_strName; //R
private:
    int     m_nAge;    //W
    CStrign m_strAddr;

    如果GetSetAll设为True,则会生成:

public:
    CString GetName() const;
    void SetAge(int Age);
protected:
    void SetName(const CString& Name);
private:
    int GetAge() const;
    CStrign* GetAddr();
    void SetAddr(const CStrign& Addr);

    可以看出,公共的读写接口还是一样的,那么protected和private的读写函数有什么意义吗?

    我正在尝试一种C++编程观念:类的封装可以进一步扩大,将类设计和类实现完全分开,函数声明、成员变量、构造与析构及对象有效性检查、数据接口均属于类设计,其他功能函数的实现属于类实现,类实现完全不接触成员变量,而是通过数据读写函数来操作数据。这样做有一些很吸引人的好处,比如说AssertGet、AssertSet、GetSetAll全部设为True,类实现中全部通过GetSet函数来读写成员数据,那么将自动形成严密的断言网络,可以拦截很多很难查找的错误,照我的说法,是用密集阵火炮拦截巡航导弹。当然,这是指AssertValid函数已经实现得很好。

    还有其他好处,但也有一些缺点,我正在尝试使用,有兴趣的朋友可以一起探讨,目前不推荐。

 


十、如果VcExtend对你有用,请跟我一起完善它

     跟开发工具有关的东西,是否有用好用,要靠实际应用中来检验。如果你发现了什么问题,或修改了其中的某些缺陷, 请在http://www.mmmnn.com/bbs2" ;>论坛中发表。如果你有建议,更不要藏私啊。我专门搞了个小论坛,就是为了收集各种问题和建议,过一段时间,我会推出修改后的版本。

    还有一些功能正在酝酿中,比如小组中多人各自开发,如何合并文档等等。你如果有什么相关需求,请当作建议提出来。

Intelligent Screen Saver using OpenCV

Screenshot - facede.png

Introduction

It is a utility to control screen saver using human face detection. Human face detection is performed using OpenCV Haar-Cascade method. The software is primarily a daemon that resides in your system tray and keeps observing the input from a webcam to analyze if the user is no longer in view. Currently two angles are supported namely frontal pose and profile pose.

Background

The idea of an intelligent screen saver is not new. In past it was not achievable because of the accuracy problems with computer vision algorithms and computation power of computers. OpenCV (open source computer vision library) has an implementation of the object detection algorithm suggested originally by Paul Viola. I have adopted from the face detection sample that comes with this library and have used the cascades files that come with it.

Using the code

Facedetection is performed by a mix-mode dll, i.e. it uses unmanaged code to perform face detection and provides a managed wrapper. This dll, hence can now be called from any CLR enabled language (C#, VB.NET etc.). This dll expects a managed System.Drawing.Image object and performs face detection.

This managed System.Drawing.Image object is taken from the ctlDxCam library (a custom library built from code posted on an article on codeproject.com, it can capture frames from a webcam as System.Drawing.Image objects) which captures video frames from a webcam.

Screensaver is controlled by a class that uses borrowed code from another article on codeproject.com. Please consult references section for details.

Collapse
// On the first thread we perform polling for face detection
CommonVariables.FaceDetected = (faceLocator.WrapDetectFaces(LatestFrame)>0);

// On the second thread we evaluate conditions to trigger screen saver
private void Run()
  {
   bool IsScreenSaverRunning;
   while(KeepRunning)
   {
    //check if screen saver is currently active
    IsScreenSaverRunning = ScreenSaverHandler.IsScreenSaverRunning();
    
    //if last captured frame has not been evaluated
    if(!CommonVariables.IsConsumed)
    {
    //was faces detected in the last polled frame
     if(CommonVariables.FaceDetected)
     {
      CommonVariables.VoteCount = 0;
    //yes face was detected, kill screen saver if running
      if(IsScreenSaverRunning)
        ScreenSaverHandler.StopScreenSaver();
     }
     else if(!IsScreenSaverRunning)
     {     
    //face was not detected, screen saver isnt running either
    //collect votes of no_face_detected
      CommonVariables.VoteCount++;
    //if votes exceed tolerated amount of no_face detected condition
      if(CommonVariables.VoteCount >= CommonVariables.MinVoteRequired)
      {
    //reset votescount and launch screen saver
       CommonVariables.VoteCount = 0;
       ScreenSaverHandler.LaunchScreenSaver();
      }
     }
    }
 
    if(IsScreenSaverRunning)
      Thread.Sleep(1000);     //shorter polling timer
    else             //longer polling timer
      Thread.Sleep(CommonVariables.PollingTimer); 
   }
  }  

The source code performs polling on the camera input and evaluates the captured frame at desired interval of time. For each frame that does not have a face in it, a counter (VoteCount) is incremented. After a specified number of such votes, screen saver is launched.

If the polling timer is set to 5 seconds and required votes is set to 2, then if a person leaves his seat, screen saver will start running in approximately (5*2) 10 seconds.

Points of Interest

This source code demonstrates an interesting concept to use OpenCV from managed code (C#, VB.NET, Managed VC++.NET). The algorithm of face detection is very robust but still has its limitations. False detection of the algorithm can be adjusted by setting the light condition of the environment and camera position etc. User can select whether he wants frontal face detection or profile face detection and hence place camera in respective angles.

References

Interface Detection

Contents

Introduction

In any C++ program, calling a function that hasn't been declared obviously leads to a compilation error. Detecting the interface of a class lets the programmer check the presence of a given public member function or data without generating such errors, allowing him to specify a different behavior when the member doesn't exist. Interface detection doesn't use any inheritance properties and, as such, offers new cleaner and safer solutions. This article describes a way to implement such detection facilities, by exploring and explaining some advanced C++ topics.

Motivating example

Suppose we want to write a container class that provides all standard container operations such as push, pop, insert, etc. and adds new functionalities unseen in any other container. We will call this new container class MetaContainer. Its implementation relies on a given classic container -- typically STL ones such as std::vector and std::list -- but user defined containers are also accepted. Consequently, the MetaContainer class defines a template parameter that accepts a basic container class.

Usage

MetaContainer< int, std::vector > myContainer1;
MetaContainer< double, std::list > myContainer2;
MetaContainer< int, UserContainer > myContainer3;

The programmer can then choose the underlying container to tune performance. For example, if a lot of insertions in the middle of the collection are performed, std::list is a better choice than std::vector . However, the Container template argument isn't forced to follow a strict interface. For instance, std::list provides a remove function whereas std::vector does not. The MetaContainer class does provide a remove function though, whose implementation calls the remove function of the underlying container, along with other internal operations.

void MetaContainer::Remove(...) 
{
    ... 
    m_UnderlyingContainer.remove(...); 
    ... 
}

If the underlying container class, such as std::vector or some other user container, doesn't declare a remove function, then a generic, low performance remove algorithm is used. To apply this strategy, we need to answer the following question: How can we know whether the container class has a remove function, so that we can silently switch to the generic remove implementation when the container doesn't provide one?

Required technical background

Before explaining the solution, some key C++ concepts used in the interface detection implementation must be reviewed. In this chapter, the following topics are tackled in a very concise way:

  • Pointers to member syntax and behavior
  • Dependent names
  • Pointers to members as non-type template arguments
  • SFINAE

If you already know all of those concepts, you may jump directly to the next chapter.

Pointers to member

Introduction

Pointers to members can be divided into 4 categories:

  • Pointers to non-static member functions
  • Pointers to non-static data members
  • Pointers to static member functions
  • Pointers to static data members

Example:

struct MyClass 
{
    void MF (int); // non static Member Function
    int  MD;       // non static Data Member

    static void Static_MF (int); // static Member Function
    static int  Static_MD;       // static Data Member
}

We'll refer to those 4 member declarations in the rest of the chapter.

Syntax of pointers to member

Pointers to each of the aforementioned members adopt a homogeneous syntax:

Member Pointer
MF &MyClass::MF
MD &MyClass::MD
Static_MF &MyClass::Static_MF
Static_MD &MyClass::Static_MD

Because the syntax of pointers to members includes the name of a class, such pointers can be dependent names. In contrast, pointers to ordinary functions or data can't. A dependent name is a name that depends on a template parameter. For example:

template< class T >
void f () 
{
    ...
    pf = &T::MF; // pointer dependent on the T parameter
}

The name dependence property plays an important role in our solution.

Type of pointers to members

Type of pointers to member functions

The pointers to member functions have the following types:

Pointer Type
&MyClass::MF void (MyClass::*)(int)
&MyClass::Static_MF void (*)(int)

Notice how the type of a pointer to a static member function can be misleading. A non-static member function follows a specific convention: it adds an implicit parameter that accepts a pointer to an object. A static member function doesn't apply to an object and doesn't have an implicit object parameter. A pointer to such a function has the same type as an ordinary function pointer. 1 The important point here is that pointers to non-static and static member functions have different types that are incompatible with each other.

Type of pointers to data members

Types of pointers to static and non-static data members follow the same syntax differences as their function counterparts:

Pointer Type
&MyClass::MD int MyClass::*
&MyClass::Static_MD int*

The types of pointers to static and non-static members are again different and incompatible. This is because the latter is associated with an object and thus contains an offset rather than an address.

Pointers to member as non-type template arguments

A pointer to a member can be a non-type template argument. The syntax is straightforward. As an example, we'll use each of the types of pointers we saw above as template parameters:

template < void (MyClass::*)(int) >  
void f1 () {}

template < void (*)(int) >  
void f2 () {}

template < int MyClass::* >  
void f3 () {}

template < int* >  
void f4 () {}

f1 < &MyClass::MF > (); // ok
f1 < &MyClass::Static_MF > (); // Error: type mismatch
f2 < &MyClass::Static_MF > (); // ok

A pointer to a member as a template argument must be of the exact type specified in the template declaration. There is no possible conversion.

SFINAE

SFINAE, an acronym for "Substitution Failure Is Not An Error," is a principle that works during function overload resolution as follows: if the instantiation of a template function produces an invalid parameter or return type, the compiler silently removes the ill-formed function instantiation from the overload resolution set. For example:

struct Test 
{
    typedef int Type;
};

template < typename T > 
void f( typename T::Type ) {} // definition #1


template < typename T > 
void f( T ) {}                // definition #2


f< Test > ( 10 ); //call #1 
f< int > ( 10 );  //call #2 without error thanks to SFINAE

Without SFINAE, the second call would have generated an error during the substitution of the template parameter in #1. The result of the instantiation of #1 from the second call is: void f( typename int::Type ) {} . Thanks to SFINAE, the resulting function instantiation doesn't generate an error because there is another function that matches the call.

Interface detection implementation

Member function detection

As pointed out in the introduction, using the identifier of an undeclared function generates a compilation error. Checking if a function exists logically necessitates the use of its identifier, as we don't use any inheritance property or any extra information. We must find a mechanism that doesn't generate an error when the identifier refers to an undeclared function. SFINAE fits well in this task.

Since member function pointers can be dependent names, we could use the SFINAE principle on them. However, SFINAE is applied to the parameter type or return type of a function. A member function pointer isn't a type and can't be directly used in a function signature. To overcome this, the function pointer is used as a template argument:

template < void (MyClass::*)() >
struct TestNonStatic { };

With the TestNonStatic structure, we can use a pointer to a non-static member function in a parameter or return type of a function:

template < class T >
TestNonStatic<&T::foo> Test( );

SFINAE doesn't generate an error as long as there is another function in the overload resolution set. We need to declare a function that will be used as a "fallback" when the member function -- foo , in the example -- doesn't exist. A function with the ellipsis parameter is the right candidate for this job since such a function always has the lowest priority in the overload resolution process and matches any argument.

template < class T >
void Test( ... );

A function with the ellipsis parameter is always the lowest priority function in an overload resolution set, isn't it? The C++ standard says yes, Visual C++ 8 says yes, GCC says no for our case. 2 The alternative solution is to simply use the TestNonStatic structure as a parameter to the Test function instead of a return type.

The next step is to find a way of knowing at compile-time which Test function will be selected. The trick commonly used in template metaprogramming is to specify a different return type for each function declaration and employ sizeof on a call of the function. The return types must then have different sizes to obtain different results from sizeof. The sizeof operand isn't evaluated. That's why the function called inside sizeof doesn't need to be defined. The following example determines whether the class MyClass contains a member function void foo():

// Return types for sizeof
typedef char NotFound;  
struct NonStaticFound { char x[2]; }; 
struct StaticFound { char x[3]; }; 

// Test Structures for SFINAE
template < void (MyClass::*)() >
struct TestNonStatic ;

template < void (*)() >
struct TestStatic ;

// Overload functions
template < class T >
StaticFound 
Test( TestStatic< &T::foo >* );

template < class T >
NonStaticFound 
Test( TestNonStatic< &T::foo >* );

template < class T >
NotFound 
Test( ... );

check_presence = sizeof( Test< MyClass >( 0 ) ); 

The final step is to wrap the whole mechanism into a reusable class from which you can specify the class and function signature to test. However, the identifier of a function -- in the above example, foo -- can't be specified as a parameter of the reusable class. Preprocessor macros are provided in the current interface detection implementation in order to deliver this need. Usage of those macros is detailed in the last chapter.

Last consideration: what about constant member functions? The above code doesn't detect them. If the user specifies a constant member signature to the reusable detector class, it won't compile because as we use the given function signature to construct the template parameter of the TestNonStatic structure, we use it also for the TestStatic structure. Static member functions can't be constant; there is no implicit object to apply the constant qualifier. Using two interfaces, one for detecting member functions and one for constant member functions, would be too cumbersome for the user.

Adding a test structure with a constant member signature to the detector class seems to deal with the problem:

template < void (MyClass::*)() const >
struct TestNonStaticConst ;

Simply putting together the functions Test( TestNonStatic< &T::foo >* ) and Test( TestNonStaticConst< &T::foo >* ) in the same overload set would lead to an ambiguous resolution error when the given class has both a constant member function and non-constant member function with exactly the same signature. We need to lower the priority of one of the two functions by using the ellipsis as a second parameter:

// Test Structures for SFINAE
template < void (MyClass::*)() >
struct TestNonStatic ;

template < void (*)() >
struct TestStatic ;

template < void (T::*)() const >
struct TestNonStaticConst ;

// Overloaded functions
template < class U >
NonStaticFound 
Test( TestNonStatic< &U::aff >*, ... );

template < class U >
NonStaticFound 
Test( TestNonStaticConst< &U::aff >*, int );

template < class U>
StaticFound 
Test( TestStatic< &U::aff >*, int );

template < class U >
NotFound 
Test( ... );

check_presence = sizeof( Test( 0,0 ) );

Data member detection

After seeing how to implement member function detection, doing the same for data members is straightforward. It is, in fact, easier since we don't have to care about the constant members problem. The only changes concern the template parameter of the TestStatic and TestNonStatic structures:

// Return types for sizeof
typedef char NotFound;
struct NonStaticFound { char x[2]; }; 
struct StaticFound { char x[3]; }; 

// Test Structures for SFINAE 
template < int MyClass::* > // change 1 of 2
struct TestNonStatic ;

template < int * > // change 2 of 2
struct TestStatic ;

// Overload functions
template < class T >
StaticFound 
Test( TestStatic< &T::foo >* );

template < class T >
NonStaticFound 
Test( TestNonStatic< &T::foo >* );

template < class T >
NotFound 
Test( ... );

Known limitations and problems

Limitations of the current interface detector fall into 2 categories:

  • Compilation problems: everything that leads to a compilation error
  • Design limitations: what is not possible to do with the current interface detection implementation and design warnings

Compilation problems

There are two kinds of compilations errors:

  • Errors directly coming from the C++ standard
  • Compiler-specific errors

Standard compilation errors

Access checking error

Only public members are concerned by the interface detector. However, if a function given to the detector happens to exist in a private or protected section of the class, the compiler will issue an "access denied" error. Because class member access checking comes after name look-up and overload resolution, SFINAE won't silence the error.

Compiler-specific errors

This section lists non-standard errors generated by the latest C++ compilers. Of course, earlier compilers that don't fully support templates -- such as Visual C++ 6 -- are likely to give some errors, but they aren't listed here. The following table represents the detection capabilities and bugs from the currently tested compilers:

Simple member function Overloaded member function Member function template specialization Data member
Visual C++ 8
Visual C++ 7.1
GCC 4.1
Comeau
Visual C++ data member detection bug

In Visual C++, a dependent name consisting of a pointer to a static or non-static data member gives an error during the substitution if the data member isn't of a built-in type.
Example:

struct Y {};

struct X 
{
    Y a;
};

template < Y X::* >
struct Test ;

template < class T >
void f (Test< &T::a >*) {}

f< X >(0); // error on Visual c++ 8

The above code is well-formed according to the C++ standard. GCC and Comeau compile it, but not Visual C++. It leads to a pernicious effect in our interface detector since the data member detection is included in a SFINAE mechanism and therefore won't generate errors. This leads to the fallacious behavior of returning the NOT_FOUND value, although the data member is indeed present. For this reason, the data member detection macros are disabled for Visual C++.

Design limitations

Exact signature

The interface detector checks the exact signature with no conversion. For example, if you want to check the presence of void foo(int), you won't be able to detect a compatible function such as void foo(double) . An important side-effect of this limitation is the impossibility of detecting an inherited function. This is because the type of the implicit parameter of such functions is a pointer to the parent class from which the function is declared.

Semantic discrepancy

The second limitation is the possible semantic difference between the detected function and the actual use of the function. For instance, in biology, some cells can be cloned. I can check this "clonable" capability by detecting whether the object contains a clone function. However, some classes that aren't even cells may declare a clone function that has other purposes, i.e. virtual constructor.

Usage

The utilization of the provided interface detector relies on 4 macros:

  • CREATE_FUNCTION_DETECTOR
  • CREATE_DATA_DETECTOR
  • DETECT_FUNCTION
  • DETECT_DATA

The first two macros are needed to construct the detector from the identifier of the function or the data to be detected. This is the first step before proceeding to the actual detection. For example, if I want to detect the function int foo (double), I need first to construct the detector:

CREATE_FUNCTION_DETECTOR(foo);

Note that once the detector for foo is constructed, any signature associated with the identifier foo can be detected: int foo(double), void foo(), etc. The DETECT_FUNCTION and DETECT_DATA macros perform the detection. The first argument of those two macros is the name of the class subject to the detection. The rest of the arguments follow the declaration syntax of the function or data to be detected, except that commas are needed around the identifiers to separate them from the rest of the type:

// detection: int foo (double, int)
DETECT_FUNCTION ( MyClass, int, foo, (double, int) ) 

// detection: const int bar
DETECT_DATA ( MyClass, const int, bar ) 

// detection: void foo()
DETECT FUNCTION ( MyClass, void, foo, () ) 

The DETECT macros return the following self-explanatory constants:

  • NOT_FOUND (== 0)
  • STATIC_FUNCTION
  • NON_STATIC_FUNCTION
  • STATIC_DATA
  • NON_STATIC_DATA

All of those constants belong to the Detector namespace. As a simple example, suppose that we want to check whether a class contains the member function void Print(). We use the function if it's available. Otherwise, we print a "No Print function available" message. We'll test the 2 following structures:

struct X 
{
    void Print() 
    { 
        std::cout << "X Print"  << std::endl ;
    }
};

struct Y 
{};

First, we need to construct the detector:

#include "Detector.h"

CREATE_FUNCTION_DETECTOR(Print);

Second, we have to define a structure that will be used to select a different behavior according to the presence of the function. The first template parameter of the structure will be used to hold the result of the DETECT_FUNCTION macro. The second parameter is the class we want to test:

template < int, class T >
struct Select 
{
    static void Print ( T obj )
    {
        obj.Print();
    }
};

template < class T >
struct Select < Detector::NOT_FOUND , T > 
{
    static void Print ( ... )
    {
        std::cout << "No Print function" << std::endl;
    }
};

// Helper function

template < class T >
void PrintHelper( T a )  
{
    Select< DETECT_FUNCTION ( T, void, Print, () ) , T >::Print( a );
}

Now we can safely call PrintHelper on any object of type X or Y:

X a;
Y b;

PrintHelper(a); // "X Print"
PrintHelper(b); // "No Print function"

The process of selecting the correct behavior is done at compile-time. A whole class interface can be checked at once. For example, let's say that any object that can fly and quack is a duck. To know whether a class represents a duck according to this definition, we can check if both the Fly and Quack functions are present within a single expression:

DETECT_FUNCTION( Class, void, Fly, () ) &
DETECT_FUNCTION( Class, void, Quack, () )

A better way to do a multiple function or data check is to define a macro like this:

#define DUCK_INTERFACE( Class ) \
    DETECT_FUNCTION( Class, void, Fly, () ) & \
    DETECT_FUNCTION( Class, void, Quack, () )

This way, a simple and understandable expression can be used and reused to detect whether a class is a duck. DUCK_INTERFACE( MyDuckClass ) returns Detector::NOT_FOUND if MyDuckClass doesn't strictly follow a duck interface.

Now here's the solution of the "motivating example," i.e. "How can we know whether the container class has a remove function?"

CREATE_FUNCTION_DETECTOR(remove);

template < class T, template < class , class > class Container >
int HasRemove ()
{
    return DETECT_FUNCTION( Container< T >, void, remove , (const T& ) );
}

HasRemove< int, std::vector >(); // NOT_FOUND 
HasRemove< int, std::list >();   // NON_STATIC_FUNCTION

Conclusion

Interface detection brings unique solutions to specific problems. It can also be used to support any duck typing 3 design -- such as policy-based design -- resulting in a safer, cleaner and extended design. One might also check the BCCL 4 that makes this kind of design more robust. The interface detection implementation heavily relies on many advanced C++ techniques, especially template ones. As such, it has some inevitable downsides: code complexity, support only by the latest compilers, difficulty in tracking bugs exhaustively and homogeneously amongst compilers, etc. Fortunately, the next C++ standard 5 should ease the programming of such template solutions.

Notes

[1] The compiler, for the purpose of overload resolution only, assumes that a static member function accepts an implicit object parameter.

[2] GCC considers that 2 functions share the same priority if one of them has the same declaration as the other with the addition of an ellipsis at the end of its parameter list

[3] In a duck typing system, the value of an object determines the object's behavior. C++, through the use of templates, implements a static form of duck typing. Duck typing on Wikipedia

[4] BCCL: Boost Concept Check Library

[5] Bjarne Stroustrup, A Brief Look at C++0x

Wake On LAN (WOL)


Already read it all? use: Update History

AllSamples

Before you start

You'll need a background of computers, Networks, Socket Programming, VC++(MFC) before reading this article.

Introduction

The problem is to find a way to turn on other machines in a local area network, from our machine which might or might not be the Server. The Solution is known as Wake On LAN. WakeOnLan (or for short just WOL) is a mechanism with which a network Interface Card (NIC) could turn a machine on by receiving a special packet through the LAN.

Acknowledgements

  • I searched the whole web (well, CodeProject) and was unable to find anything about WOL in C++. That was why after discovering the mechanism I decided to Dis-cover it to others. This is my first article and I hope it will be helpful.
  • English is not my native language, so sometimes it might be easier to understand the code itself rather than my descriptions
  • Thanks a lot to Khalid Shaikh and his nice article on: Three ways to get your MAC address Thanks a lot to José Pedro Oliveira and his nice article on: Wake On lan - Mini HOWTO.
  • Sentences written in italic copied directly from other articles.

How does it work

While your computer is Turned Off, The Network Interface Card remains on and looks forward to hearing a message! More accurately, a packet does, which is called a magic packet. Whenever the card receives information, a magic packet tries to switch on the computer. This packet must contain a certain byte-sequence, but can be encapsulated in any kind of packet (IPX, IP, anything). So the mechanism relies on the Hardware's ability, and that's why we need some ingredients!

Hardware requirements

Both motherboard and NIC must support WOL. If you have a built-in NIC with WOL support, it's almost done! But many PCI NICs come with a connector and a wire, which has to be connected to the motherboard's WOL connector. After these, your power supply and OS must support WOL. And finally, you should enable Wake-On-LAN in your systems BIOS (or whatever called). That's it.

Magic Packet

I'm not sure, but it seems that the name comes from AMD. I did not do a search but José Pedro Oliveira (second link above) says that there is more documentation about the protocol in their web site. As I mentioned above, you need to send a special packet in your LAN network, so that the remote computer will receive it and wake up. This so-called Magic packet consists of the following parts:

  1. 6 Bytes Header which is nothing but 6 bytes of 0xff.
  2. 16*6 Bytes Data. To produce data you'll need to repeat remote computers MAC (Media Access Control) Address 16 times. Look at the below figure:

Magic Packet Architecture

Magic Packet(With Secure On Password)

(This part added on an update on: 2005/09/03. Note that 'PowerOn' sample has been updated in all downloads either. [as well as executables!])

Some clients require a password in the packet to be turned on, otherwise they simply won't! This password is also known as SecureOn, and will be attached at the end of the packet. In this case, a packet will look something like this:

Magic Packet SecureOn Architecture

So we have six cells, each capable of saving an integer number between 0 and 255(Just like MAC bytes).

What's MAC address and how to obtain it

MAC stands for Media Access Control, and is a unique 48-bit (6 Bytes) hexadecimal number assigned to a NIC when it is manufactured. And it is unique; you'll not find two NICs with the same MAC Address in the world. The first three octets (24 bits) are known as the Organizationally Unique Identifier (OUI) and identifies its manufacturer. It means the MAC address of all cards of a company that say X start with a particular fixed number at the beginning. I'm not sure if this address is just assigned to Ethernet Cards or any other types of network Interface or equipment. This address is typically written as six colon/dash-separated hexadecimal numbers.

In Microsoft Windows (98 SE and above, perhaps winipcfg.exe ) there's a tool called ipconfig.exe which could be used to obtain a MAC address. Ipconfig refers to this address as Physical Address . In order to see the MAC address of your NIC, just type this in the command line: ipconfig /all.

There are several alternative ways of finding a MAC address, like looking for an ARP table (if any exists! or it has any entries), ifconfig (just in Linux), netstat, GetMAC (XP), all of which are enough to be a topic for a new article!

ommand line - ipconfig/all

So let's come back to our subject! For example, and according to the above figure, you should send a packet like : ff-ff-ff-ff-ff-ff 02-00-4c-4f-4f-50 02-00-4c-4f-4f-50 ... 02-00-4c-4f-4f-50 to the computer with the above MAC address to turn it on. But the question is: "How to obtain this address in your app".

To be honest, before Googling the web I tried to do a disassembly on ipconfig.exe and I found GetAdaptersAddresses and some other functions between the functions it uses which comes from iphlpapi.dll . I guess this is the key to our problem! Then I searched the web and found an article on CodeGuru. You could find a link to this article at the top of this page. I just selected his third way which is: GetAdaptersInfo but since I was unable to compile it (it needed some header files and a lib (iphlpapi.lib) which could only be found in MS VC++ .NET), I changed it in order to be able to compile it with my compiler (VC++ 6.0).

// Allocate information for NIC
IP_ADAPTER_INFO AdapterInfo;
// Save memory size of buffer
DWORD dwBufLen = sizeof(AdapterInfo);
// Call GetAdapterInfo
DWORD dwStatus = GetAdaptersInfo(&AdapterInfo,&dwBufLen);
// Verify return value is valid
ASSERT(dwStatus == ERROR_SUCCESS);
// Contains pointer to current adapter info
PIP_ADAPTER_INFO pAdapterInfo = (&AdapterInfo);
pAdapterInfo->Address;//Is the MAC Address of our NIC

//for a more acurate implementation and my modifications, 
//Please see the demo project.

We have the address, but still have the problem of sending data to a switched off computer! The solution is to broadcast a UDP packet. Did this already? Add a new feature to your local network project: Turn On All Network Machines . Did not do this already? Take a look at the demo project -PowerOn.

Remote MAC finder & ARP

After publishing this article on 2005/08/29, I found out that most readers wanted two major features:

  1. To be able to turn on a PC based on its IP instead of MAC.
  2. To be able to turn on a PC over the Internet!

Thanks to Mr. Tupack Mansur, and other readers who created this temptation in my heart! (See discussion and comments below)

Based on the above demands, I started another project: Remote MAC finder (third project to download), which is designed to find the MAC of a computer which we have it's IP or Host Name. Why I didn't upgrade old PowerOn project? Just to keep it simple.

I could do something with the second request if I was IEEE! The problem is that to send a packet over the internet, routers need an IP address, but a turned off machine has not need one, so there is not any way to address a remote machine.

As far as I know, routers will remove broadcast packets from the Internet, so don't think about it. One way would be to send a WOL packet to your LAN router which might or might not support this. An internally broadcast address like 192.168.1.255 must forwarded.

Two other methods might be using telnet (wol up) directly or indirectly by 'Remote Desktop' to an always on machine and turning on others from there(Thanks to an anonymous reader who described his solution.).

Remote MAC Finder works based on ARP. If you already know ARP protocol, you can ignore this next part, but since I'm still a beginner in Networks, Programming ,and even worse, self-educated (my university did not teach me these things), there might be mistakes and it will be greatly appreciated if you help to correct me. Thank you.

ARP

ARP(RFC 826) stands for Address Resolution Protocol. Although all host machines and network tools use unique IP addresses, but any IP packet will cross the first layer of TCP/IP model before going to channel. This first layer is called the Network Interface Layer. The Network Interface layer will work just with a physical address known as MAC. Any IP packet containing an IP address will be placed in a Data Field(Payload) of the first layer frames and a header containing a MAC address of the destination will be added later. In other words, each machine is a network which has a packet to send to another, and should know both destination's MAC and IP addresses.

Network interfaces will also recognize packets on the net, containing their MAC address and will take them for further processing. But what if a computer in a network doesn't have its peer MAC address? This is what ARP was created for. The duty of ARP is to broadcast a packet over a network. This packet actually asks: "Any one whose IP address is (say) ' 192.168.1.5', What is it's MAC address?". Broadcasted packets will be received by all Network machines, and as soon as a machine sees its own IP in the packet will send a reply to the requester and places its MAC address in the reply packet. In order to increase ARP speed, each time a machine finds a MAC address corresponding to an IP, the protocol will save these numbers in a table in main memory. The table is called ARP cache. So one way of finding the MAC of a remote computer is by enumerating IP's from this table and when a match is found, taking it's MAC.

But the problem is that the life of this table (ARP Cache) is too small (we should exclude routers, I guess). (My test on Windows XP SP1 was about 2 Minutes!) Some commands will update entries of this table. I found that the function gethostbyaddr does that. So I first call this function both to retrieve HostName, and IP of remote machine, and update the ARP table. Then I use GetIpNetTable to enumerate IPs and find an appropriate MAC.

But this process is not always reliable! The reason is that with this mechanism, an attack could be shaped :ARP Spoofing! I don't know much a lot about how it works, but it might be a good idea for an administrator to think about it! That's why I say it's not always reliable.

Using the code

(added upon update: I did not have the chance to test the new uploaded 'PowerOn' to see how it works with a password. It would be appreciated if anyone does can tell if his/her test was successful. Thank you.

I did not provide any C++ classes, because I didn't want to hide such easy stuff from readers and write a class for one function!

There are two demos included in this article.

  1. Power On
  2. MAC Finder

In the PowerOn demo I tried to turn on a computer in our LAN network, using the mechanism I described above. The job is completed in two functions OnPowerOn() and HexStrToInt(CString hexStr). The second function's job is to convert a hex string say "E9" to an integer value, 233 in our example. It uses the strtol function to do the duty.

The OnPowerOn does the following:

  1. Creates a UDP socket.
  2. Creates a magic packet.
  3. Broadcasts the packet.
Collapse
    //Socket to send magic packet
    CAsyncSocket s;
    //Buffer for packet
    BYTE magicP[102];

...

    //Fill in magic packet with 102 Bytes of data
    //Header
    //fill 6 Bytes with 0xFF
    for (int i=0;i<6;i++)
        magicP[i] = 0xff;
    //First 6 bytes (these must be repeated!!)
    //fill bytes 6-12
    for (i=0;i<6;i++) {
        //Get 2 charachters from mac address and convert it to int to fill
        //magic packet
        magicP[i+6] = HexStrToInt(macAddr.Mid(i*2,2));
    }

    //fill remaining 90 bytes (15 time repeat)
    for (i=0;i<15;i++) memcpy(&magicP[(i+2)*6],&magicP[6],6);

...    
    //Broadcast Magic Packet, Hope appropriate NIC will take it ;)
    s.SendTo(magicP,102,atol(m_port));

MAC Finder

At first I thought I couldn't add the following project, but thought more and decided to do it because you might want to find a MAC automatically, and probably give the found address to your peer (TCP socket) application in a LAN, and use it later! Whatever you want to do, this might be helpful.

I used Mr. Khalid Shaikh's code, but his code was not 'compileable' in my VC++ 6.0, so I found the appropriate DLL where the function had been located and used MSDN to reproduce the needed structures (hope this is not a copyright problem) and then used a pointer to the function and LoadLibrary to get the function. It seems that the function is added as a resource to the DLL (it's not documented) so we can't use GetModuleHandle. I tested the result on MS Windows 98, Me, 2K, XP(SP1). It worked fine! All the processes is done in a function named GetMacAddress(). This function is called in OnInitDialog of MAC Finder.

Most Interesting part of the story

There was a very interesting point I encountered, while working with MACs! Look at the above MAC address (in the figure). It's the MAC of a Microsoft loopback adapter. Since I do not have 2 (or more) computers, I tested my network applications (bot PowerOn) using this NIC. Look carefully at its MAC address:02-00-4c-4f-4f-50, and try converting numbers to characters using ASCII standard. What do you see? : 02= :) 00= blank 4C= L 4F= O 50= P = > Microsoft Loop back Adapter's MAC address is :) LOOP

Who decided on these numbers, that's the mystery!

GLUT Window Template

Screenshot - GLUT_Window_Template.jpg

Note: This article has been reposted at the author's request after the original was deleted at the author's request.

Contents

Introduction

This program sets up a platform-independent OpenGL window using the GLUT library.

What is OpenGL?

OpenGL is the most widely used application programming interface (API) for developing portable 2D and 3D graphics applications. It was originally developed in 1992 by Silicon Graphics and is currently controlled and maintained by the OpenGL ARB (Architecture Review Board), which has become a part of the Khronos Group in 2006. ARB representatives include SGI, Microsoft, Apple, nVidia, ATI, Intel, id Software (famous for creating DOOM and QUAKE), and 3D Labs.

OpenGL has become an industry standard and a very popular API for its ease of use, portability, stability and rich documentation. According to OpenGL.org, OpenGL is the only truly open, vendor-neutral, multiplatform graphics standard. OpenGL 2.1 is the latest version of OpenGL and was released on August 2, 2006. OpenGL 3.0 is currently under development and will run specifically on hardware born after November 8th, 2006.

Why use GLUT?

OpenGL is a window system independent graphics library. This means that it doesn't handle window system operations that are specific to the Operating System. The reason for this is to make OpenGL portable on even new platforms. Creating a rendering window and handling events is left to the native window system to define. The OpenGL Utility Toolkit (GLUT) is intended to fill this gap, and thus provides developers with a window system independent API for OpenGL programs. GLUT supports the following functionality:

  • Window management
  • Event handling
  • Right click menu
  • Rendering fonts
  • Rendering various solid and wireframe objects
  • Reading from sophisticated input devices

It is important to note that GLUT is not a fully-featured windowing toolkit. It is mostly used for learning OpenGL and developing simple OpenGL programs. GLUT is simple, easy and small. The GLUT library has C, C++, FORTRAN and Ada programming bindings. It is portable to nearly all OpenGL implementations for the X Window System and Windows. The latest stable GLUT version is 3.6; the 3.7 version is currently in Beta phase.

Why have a GLUT window template?

Knowing that OpenGL is an API intended for graphical applications, it is very clear that a window is always required to render the graphical objects. To avoid having to write the same code every time you want to create a graphical application using OpenGL, this program code can be used as a template to get you directly started with what matters to you in the program. The OpenGL GLUT window template has the following properties:

  • Title: "GLUT Window Template"
  • Background Color: black (R = 0, G = 0, B = 0)
  • Dimensions: width = 240, height = 240
  • Position: x = (Screen Width - Window Width) / 2, y = (Screen Height - Window Height) / 2. This would mean that the window is centered on the screen
  • Handling of keyboard, mouse and display events
  • Showing when events occur, including their meaning, through the command prompt

Usage

Running the program

In order to run the program, 3 dynamic link libraries (DLLs) are required: opengl32.dll, glu32.dll and glut32.dll. The opengl32.dll and glu32.dll files already come with your Windows OS and are found in the system folder. To run the executable, you have to download the glut32.dll and copy it into your system folder. You can find all of the DLL files in the attached ZIP file, GLUT_Window_Template_dll.zip under Visual C++\dll.

Compiling the code with Microsoft Visual C++

In order to write a C OpenGL application with GLUT using Microsoft Visual Studio on a Windows platform, you need the following files:

C header files: GL.h, GLU.h and GLUT.h

C LIB files: glui32.lib, opengl32.lib and glut32.lib

You can find all of the header and LIB files in the attached ZIP file GLUT_Window_Template_dll.zip under Visual C++\include\GL and Visual C++\lib, respectively.

Microsoft Visual C++ 6

To use the code under a Visual C++ 6.0 environment, perform the following steps:

  • Open Microsoft Visual C++ 6.0
  • Create a new Win32 Console Application Project

    Screenshot - GLUT_Window_Template_1.jpg
  • Copy the source file GLUT_Window_Template.c to your project folder. This is GLUT_Window_Template_src in our case. You may want to rename the file to fit your program's needs
  • Add the source file to the project using the menu option Project\Add To Project\Files

    Screenshot - GLUT_Window_Template_2.jpg
  • Make sure that the header files are in the include folder, LIB files are in the lib folder and DLLs are in the system folder

    Files Description Source folder (attached) Target folder
    GL.H, GLU.H, GLUT.H Header Files Visual C++/include/GL C:\Program Files\Microsoft Visual Studio\VC98\Include\GL
    OPENGL32.LIB, GLU32.LIB, GLUT32.LIB Lib Files Visual C++/lib C:\Program Files\Microsoft Visual Studio\VC98\Lib
    OPENGL32.DLL, GLU32.DLL, GLUT32.DLL DLL Files Visual C++/dll C:\WINDOWS\system32
  • Link the code to the libraries

    Screenshot - GLUT_Window_Template_3.jpg

    To avoid having to set the link settings in every Visual C++ 6.0 project you create, you may want to include the following code segment in your code, which would basically do the same thing as above.
    // Link the lib files to the program. This is not necessary 
    
    // if you have added the lib names using Project/Settings/Link
    #pragma comment (lib, "opengl32.lib")
    #pragma comment (lib, "glut32.lib")
    #pragma comment (lib, "glu32.lib")
  • To avoid getting the console window whenever you want to run your OpenGL window, you may want to include this directive in your code
    #pragma comment(
        linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

Using the code

The source code is intended to be used as a template for your OpenGL applications. To use it in your new application, you can simply rename the C file and add it to your Visual Studio project.

Explaining the code

The header files for GLUT should be included in GLUT programs with the following include directive:
  #include <GL/glut.h>  

There is no need to include <GL/gl.h> or <GL/glu.h> since <GL/glut.h> already includes them.

Initialization

Functions starting with glutInit are used to initialize the GLUT state. The glutInit function is the primary initializing routine and must only be called once in a GLUT program. OpenGL or GLUT functions that are not starting with glutInit are not allowed to be called before the primary glutInit function. However, other functions starting with glutInit can be called before the primary glutInit function. The reason for this is to allow for specifying the window initialization state in command-line arguments and before the session is negotiated with the window system.

Below I show each initialization function prototype, its description and how I used it in my program.

Connecting to the window system

void glutInit(int *argcp, char **argv);

glutInit initializes the GLUT library and negotiates a session with the window system.

glutInit(&argc, argv);

Setting window size

void glutInitWindowSize(int width, int height);  

Use glutInitWindowSize to set the initial size of the window that will be created. The value for the width and height parameters must be greater than zero. In case no value is specified, i.e. function not called, the default window size is 300 by 300. Note that the window system is not obligated with this information. Thus, GLUT programs must depend on the glutReshapeFunc to determine the true size of the window.

//  variables representing the window size
int window_width = 240;
int window_height = 240;

//  Set the window size
glutInitWindowSize (window_width, window_height); 

Setting window position

void glutInitWindowPosition(int x, int y);  

Use the glutInitWindowPosition to set the initial position of a window. The default value is -1 by -1, which tells the window system to determine the appropriate position.


//  define the window position on screen
int window_x;
int window_y;

//-------------------------------------------------------------------------
//  This function sets the window x and y coordinates
//  such that the window becomes centered
//-------------------------------------------------------------------------
void centerOnScreen (void)
{
    window_x = (glutGet (GLUT_SCREEN_WIDTH) - window_width)/2;
    window_y = (glutGet (GLUT_SCREEN_HEIGHT) - window_height)/2;
}

//  Set the window position
glutInitWindowPosition (window_x, window_y);

In the code above, I am simply making the window centered on the screen. glutGet (GLUT_SCREEN_WIDTH) gets the width of the screen in pixels and glutGet (GLUT_SCREEN_HEIGHT) gets its height.

Setting display mode

void glutInitDisplayMode(unsigned int mode);  

The display mode specifies what buffers are to be used in order to render the OpenGL graphics. One may choose more than one mode by using the logical OR operator. The following are the possible modes that can be selected; they have been copied from the GLUT API Version 3 Reference Manual.:

Mode Description
GLUT_INDEX Bit mask to select a color index mode window. This overrides GLUT_RGBA if it is also specified.
GLUT_SINGLE Bit mask to select a single buffered window. This is the default if neither GLUT_DOUBLE nor GLUT_SINGLE are specified.
GLUT_DOUBLE Bit mask to select a double buffered window. This overrides GLUT_SINGLE if it is also specified.
GLUT_ACCUM Bit mask to select a window with an accumulation buffer.
GLUT_ALPHA Bit mask to select a window with an alpha component to the color buffer(s).
GLUT_DEPTH Bit mask to select a window with a depth buffer.
GLUT_STENCIL Bit mask to select a window with a stencil buffer.
GLUT_MULTISAMPLE Bit mask to select a window with multisampling support. If multisampling is not available, a non-multisampling window will automatically be chosen. Note: both the OpenGL client-side and server-side implementations must support the GLX_SAMPLE_SGIS extension for multisampling to be available.
GLUT_STEREO Bit mask to select a stereo window.
GLUT_LUMINANCE Bit mask to select a window with a "luminance'' color model. This model provides the functionality of OpenGL's RGBA color model, but the green and blue components are not maintained in the frame buffer. Instead, each pixel's red component is converted to an index between zero and glutGet(GLUT_WINDOW_COLORMAP_SIZE)-1 and looked up in a per-window color map to determine the color of pixels within the window. The initial colormap of GLUT_LUMINANCE windows is initialized to be a linear gray ramp, but can be modified with GLUT's colormap routines.
glutInitDisplayMode (GLUT_RGBA | GLUT_DOUBLE);

To display graphical content on a window, we need to tell GLUT whether to use single buffering or double buffering. In case single buffering is to be used, we need to set the mode to GLUT_SINGLE (by default). To display OpenGL contents on the window, we must call the glFlush OpenGL function which copies the contents of the frame buffer to the window.

However, GLUT_DOUBLE is usually the better option, as it allows for faster graphics rendering without flickering due to the use of two frame buffers that are continuously swapped. To swap those buffers, we call the GLUT function glutSwapBuffers. Note that GLUT_DOUBLE uses more memory than GLUT_SINGLE due to the use of 2 frame buffers rather than 1.

Create window

int glutCreateWindow(char *name);  

Creates the GLUT window with name as its title. The value returned is a unique small integer identifier for the window. Rendering to a created window is ineffective before the glutMainLoop function is called because the window can not yet be displayed.

//  variable representing the window title
char *window_title = "GLUT Window Template";

//  Create Window
glutCreateWindow (window_title);

After creating the window, you may want to display it in full screen.

//  Tells whether to display the window full screen or not
//  Press Alt + Esc to exit a full screen.
int full_screen = 0;

//  View in full screen if the fullscreen flag is on
if (full_screen)
    glutFullScreen ();

Set program initial state

The purpose here is to set some OpenGL properties and initialize any data structures you have in your program. In our case, we need to set the frame buffer clear color to black. This would mean that whenever we want to refresh our graphics, we need to first clear the old drawing with a black canvas and then redraw our new contents. Otherwise, our graphics won't display in the correct manner.

//-------------------------------------------------------------------------
//  Set OpenGL program initial state.
//-------------------------------------------------------------------------
void init ()
{    
    //  Set the frame buffer clear color to be black. 
    glClearColor (0.0, 0.0, 0.0, 0.0);
} 

The arguments to glClearColor are R (red), G (green), B (blue) and A (alpha). Alpha is used for transperancy. The values range between 0.0 and 1.0. For example, in case you want to set the clear color to gray (128, 128, 128), you need to divide each component by 255 in order to get the right parameters for glClearColor.

Set callback functions

// Set the callback functions
glutDisplayFunc (display);
glutReshapeFunc  (reshape);
glutMouseFunc (mouse);
glutMotionFunc (motion);
glutPassiveMotionFunc (pmotion);
glutKeyboardFunc (keyboard);
glutSpecialFunc (special);

These are the callback functions that are mostly used. To see the remaining callback functions that GLUT supports, please refer to the GLUT API Version 3 Reference Manual .

glutDisplayFunc

//-------------------------------------------------------------------------
//  This function is passed to glutDisplayFunc in order to display 
//    OpenGL contents on the window.
//-------------------------------------------------------------------------
void display (void)
{
    //  Clear the window or more specifically the frame buffer...
    //  This happens by replacing all the contents of the frame
    //  buffer by the clear color (black in our case)
    glClear (GL_COLOR_BUFFER_BIT);
    
    //  Draw scene
    drawObject ();
    
    //  Swap contents of backward and forward frame buffers
    glutSwapBuffers ();
}

glClear (GL_COLOR_BUFFER_BIT) will clear the frame buffer so that things can be re-drawn clearly. glutSwapBuffer places the contents of the back buffer into the from buffer visible on the window.

The drawObject function simply displays an icosahedron and tells us that the display function is currently being called. When GLUT determines that the window needs to be redisplayed, the display callback for the window is called. The display callback function in GLUT can be either set explicitly by calling glutPostRedisplay or implicitly as the result of window damage reported by the window system. Multiple posted redisplays for a window are coalesced by GLUT to minimize the number of display callbacks called.

//-------------------------------------------------------------------------
//  Draws our object.
//-------------------------------------------------------------------------
void drawObject ()
{
    //  Show when are displaying an object
    printf ("Displaying object...\n");
    
    //  Draw Icosahedron
    glutWireIcosahedron ();
}

You will notice the "Displaying object..." message on the command prompt in the following cases:

  • glutPostRedisplay is explicitly called in the code
  • After the window is resized
  • When the window loses focus and then gets it back

If you notice this happening in other cases, please let me know.

glutReshapeFunc

//-------------------------------------------------------------------------
//  This function is passed to the glutReshapeFunc and is called 
//  whenever the window is resized.
//-------------------------------------------------------------------------
void reshape (int w, int h)
{
    //  Stay updated with the window width and height
    window_width = w;
    window_height = h;
    
    //  Reset viewport
    glViewport(0, 0, window_width, window_height);
    
    //  Print current width and height on the screen
    printf ("Window Width: %d, Window Height: %d.\n", 
        window_width, window_height);
}

The reshape function will be called every time a window is resized and when the window is first displayed on the screen. The w and h parameters of the callback specify the new window size in pixels.

I am using the glViewport function here to reset the drawing area to be equal to the window size whenever the window is resized. If you try commenting out the glViewport function, you will notice that the drawing remains the same no matter how you resize the window. However, calling the glViewport function will enlarge and shrink the drawing area based on the changes in the window size.

glutMouseFunc

When a user presses and releases a mouse button, two events are generated: one for the press and the other for the release. The state parameter is either GLUT_UP or GLUT_DOWN, indicating whether the callback was due to a release or press, respectively. The button parameter is one of GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON or GLUT_RIGHT_BUTTON and indicates which button was clicked. The x and y parameters indicate the mouse coordinates relative to the GLUT window when the mouse button's state changed. If a menu is attached to a button for a window, mouse callbacks will not be generated for that button. Passing NULL to glutMouseFunc disables the generation of mouse callbacks.

Collapse
//-------------------------------------------------------------------------
//  This function is passed to the glutMouseFunc and is called 
//  whenever the mouse is clicked.
//-------------------------------------------------------------------------
void mouse (int button, int state, int x, int y)
{
    switch (button)
    {
        //  Left Button Clicked
        case GLUT_LEFT_BUTTON:

            switch (state)
            {
                //  Pressed 
                case GLUT_DOWN:
                    printf ("Mouse Left Button Pressed (Down)...\n");
                    break;
                //  Released
                case GLUT_UP:
                    printf ("Mouse Left Button Released (Up)...\n");
                    break;
            }

            break;

        //  Middle Button clicked
        case GLUT_MIDDLE_BUTTON:
            
            switch (state)
            {
                //  Pressed
                case GLUT_DOWN:
                    printf ("Mouse Middle Button Pressed (Down)...\n");
                    break;
                //  Released
                case GLUT_UP:
                    printf ("Mouse Middle Button Released (Up)...\n");
                    break;
            }

            break;

        //  Right Button Clicked
        case GLUT_RIGHT_BUTTON:
            
            switch (state)
            {
                //  Pressed
                case GLUT_DOWN:
                    printf ("Mouse Right Button Pressed (Down)...\n");
                    break;
                //  Released
                case GLUT_UP:
                    printf ("Mouse Right Button Released (Up)...\n");
                    break;
            }

            break;
    }
}

In order to check if the Shift, Ctrl or Alt key was pressed while the mouse event occured, we can use the glutGetModifiers function. This returns the modifier key state at the time the input event for a keyboard, special or mouse callback is generated. The modifiers are GLUT_ACTIVE_SHIFT (Shift or Caps Lock), GLUT_ACTIVE_CTRL (Ctrl) and GLUT_ACTIVE_ALT (Alt).

glutMotionFunc

As long as the user is moving the mouse while a button is pressed (i.e. dragging), this event will be called continuously.

//-------------------------------------------------------------------------

//  This function is passed to the glutMotionFunc and is called 
//  whenever the mouse is dragged.
//-------------------------------------------------------------------------
void motion (int x, int y)
{
    //  Print the mouse drag position
    printf ("Mouse Drag Position: %d, %d.\n", x, y);
}

glutPassiveMotionFunc

As long as the user is moving the mouse while no button is pressed, this event will be called continuously.

//-------------------------------------------------------------------------

//  This function is passed to the glutPassiveMotionFunc and is called 
//  whenever the mouse is moved.
//-------------------------------------------------------------------------
void pmotion (int x, int y)
{
    //  Print mouse move positopn
    printf ("Mouse Move Position: %d, %d.\n", x, y);
}

glutKeyboardFunc

Each key press generating an ASCII character will generate a keyboard callback. The key callback parameter is the generated ASCII character. The x and y callback parameters indicate the mouse location in window-relative coordinates when the key was pressed. Passing NULL to glutKeyboardFunc disables the generation of keyboard callbacks.

Collapse
//-------------------------------------------------------------------------
//  This function is passed to the glutKeyboardFunc and is called 
//  whenever the user hits a key.
//-------------------------------------------------------------------------
void keyboard (unsigned char key, int x, int y)
{
    //  Print what key the user is hitting
    printf ("User is hitting the '%c' key.\n", key);
    printf ("ASCII code is %d.\n", key);
    
    switch (key)
    {
        //  User hits A key
        case 'a':
            
            break;

        //  User hits Shift + A key
        case 'A':
            
            break;

        //  User hits Enter
        case 13:
            printf ("User is hitting the Return key.\n"); 
            break;

        //  User hits Space
        case 32:
            printf ("User is hitting the Space key.\n"); 
            break;

        //  User hits back space
        case 8:
            printf ("User is hitting the Back Space key.\n"); 
            break;

        //  User hits ESC key
        case 27:
            exit (1);
            break;
    }

    glutPostRedisplay ();
}

In order to check if the Shift, Ctrl or Alt key was pressed while the ASCII key was pressed, we can use the glutGetModifiers function. Also, modifiers might have an effect on the ASCII character itself. For example, if Caps Lock is off and we press Shift + A Key, the value of the key parameter will be set to A. If only the A key is pressed, then the key parameter is set to a.

glutSpecialFunc

Collapse
//-------------------------------------------------------------------------
//  This function is passed to the glutSpecialFunc and is called 
//  whenever the user hits a special key.
//-------------------------------------------------------------------------
void special (int key, int x, int y)
{
    switch (key)
    {
        case GLUT_KEY_F1 :
            printf ("F1 function key.\n"); 
            break;
        case GLUT_KEY_F2 :
            printf ("F2 function key. \n");  
            break;
        case GLUT_KEY_F3 :
            printf ("F3 function key. \n");  
            break;
        case GLUT_KEY_F4 :
            printf ("F4 function key. \n");  
            break;
        case GLUT_KEY_F5 :
            printf ("F5 function key. \n");  
            break;
        case GLUT_KEY_F6 :
            printf ("F6 function key. \n");  
            break;
        case GLUT_KEY_F7 :
            printf ("F7 function key. \n");  
            break;
        case GLUT_KEY_F8 :
            printf ("F8 function key. \n");  
            break;
        case GLUT_KEY_F9 :
            printf ("F9 function key. \n");  
            break;
        case GLUT_KEY_F10 :
            printf ("F10 function key. \n");  
            break;
        case GLUT_KEY_F11 :
            printf ("F11 function key. \n");  
            break;
        case GLUT_KEY_F12 :
            printf ("F12 function key. \n");  
            break;
        case GLUT_KEY_LEFT :
            printf ("Left directional key. \n");  
            break;
        case GLUT_KEY_UP :
            printf ("Up directional key. \n");  
            break;
        case GLUT_KEY_RIGHT :
            printf ("Right directional key. \n");  
            break;
        case GLUT_KEY_DOWN :
            printf ("Down directional key. \n");  
            break;
        case GLUT_KEY_PAGE_UP :
            printf ("Page up directional key. \n");  
            break;
        case GLUT_KEY_PAGE_DOWN :
            printf ("Page down directional key. \n");  
            break;
        case GLUT_KEY_HOME :
            printf ("Home directional key. \n");  
            break;
        case GLUT_KEY_END :
            printf ("End directional key. \n");  
            break;
        case GLUT_KEY_INSERT :
            printf ("Inset directional key. \n");  
            break;
    }
    
    glutPostRedisplay ();
}

Each key press generating a non-ASCII character will generate a special callback. The x and y callback parameters indicate the mouse location in window relative coordinates when the key was pressed. Passing NULL to glutSpecialFunc disables the generation of special callbacks. Here is the list of special keys as specified in the GLUT API Version 3 Reference Manual:

Key Description
GLUT_KEY_F1 F1 function key.
GLUT_KEY_F2 F2 function key.
GLUT_KEY_F3 F3 function key.
GLUT_KEY_F4 F4 function key.
GLUT_KEY_F5 F5 function key.
GLUT_KEY_F6 F6 function key.
GLUT_KEY_F7 F7 function key.
GLUT_KEY_F8 F8 function key.
GLUT_KEY_F9 F9 function key.
GLUT_KEY_F10 F10 function key.
GLUT_KEY_F11 F11 function key.
GLUT_KEY_F12 F12 function key.
GLUT_KEY_LEFT Left directional key.
GLUT_KEY_UP Up directional key.
GLUT_KEY_RIGHT Right directional key.
GLUT_KEY_DOWN Down directional key.
GLUT_KEY_PAGE_UP Page up directional key.
GLUT_KEY_PAGE_DOWN Page down directional key.
GLUT_KEY_HOME Home directional key.
GLUT_KEY_END End directional key.
GLUT_KEY_INSERT Insert directional key.

Begin event processing

void glutMainLoop(void);

After a GLUT program has completed initial setup, such as creating windows and menus, GLUT programs enter the GLUT event processing loop by calling glutMainLoop. This function should be called at most once in an OpenGL program.

Conclusion

I think this article can help significantly in getting you started with OpenGL. At the same time, the template can be used to save lots of copy and paste from old projects or the Internet. In case you find this template useful or have suggestions, please let me know.

References