2009年2月11日星期三

探究CRC32算法实现原理-why table-driven implemention

探究CRC32算法实现原理-why table-driven implemention

Author : Kevin Lynx
email : zmhn320@163.com

Preface

基于不重造轮子的原则,本文尽量不涉及网络上遍地都是的资料。

What's CRC ?

简而言之,CRC是一个数值。该数值被用于校验数据的正确性。CRC数值简单地说就是通过让你需要做
处理的数据除以一个常数而得到的余数。当你得到这个数值后你可以将这个数值附加到你的数据后,
当数据被传送到其他地方后,取出原始数据(可能在传送过程中被破坏)与附加的CRC数值,然后将这里
的原始数据除以之前那个常数(约定好的)然后得到新的CRC值。比较两个CRC值是否相等即可确认你的
数据是否在传送过程中出现错误。

那么,如何让你的数据除以一个常数?方法是对你的数据进行必要的编码处理,逐字节处理成数字。
那么这个常数是什么?你不必关注它是什么,也不需要关注它是如何获得的。当你真的要动手写一个
CRC的实现算法时,我可以告诉你,CRC的理论学家会告诉你。不同长度的常数对应着不同的CRC实现算法。
当这个常数为32位时,也就是这里所说的CRC32。

以上内容你不必全部理解,因为你需要查阅其他资料来获取CRC完整的理论介绍。

The mathematics behind CRC ?

很多教科书会把CRC与多项式关联起来。这里的多项式指的是系数为0或1的式子,例如:
a0 + a1*x + a2*x^2 + ... + an*x^n。其中a0, a1, ..., an要么为0要么为1。我们并不关注x取什么值。
(如果你要关注,你可以简单地认为x为2) 这里把a0, a1, ..., an的值取出来排列起来,就可以表示比特
流。例如 1 + x + x^3所表示的比特流就为:1101。部分资料会将这个顺序颠倒,这个很正常。

什么是生成多项式?

所谓的生成多项式,就是上面我所说的常数。注意,在这里,一个多项式就表示了一个比特流,也就是一堆
1、0,组合起来最终就是一个数值。例如CRC32算法中,这个生成多项式为:
c(x) = 1 + x + x^2 + x^4 + x^5 + x^7 + x^8 + x^10 + x^11 + x^12 + x^16 + x^22 + x^23 + x^26 + x^32。
其对应的数字就为:11101101101110001000001100100000(x^32在实际计算时隐含给出,因此这里没有包含它
的系数),也就是0xEDB88320(多项式对应的数字可能颠倒,颠倒后得到的是0x04C11DB7,其实也是正确的)。

由此可以看出,CRC值也可以看成我们的数据除以一个生成多项式而得到的余数。

如何做这个除法?

套用大部分教科书给出的计算方法,因为任何数据都可以被处理成纯数字,因此,在某种程度上说,我们可以
直接开始这个除法。尽管事实上这并不是标准的除法。例如,我们的数据为1101011011(方便起见我直接给二进制
表示了,从这里也可以看出,CRC是按bit进行计算的),给定的生成多项式(对应的值)为10011。通常的教科书
会告诉我们在进行这个除法前,会把我们的数据左移几位(生成多项式位数-1位),从而可以容纳将来计算得到
的CRC值(我上面所说的将CRC值附加到原始数据后)。但是为什么要这样做?我也不知道。(不知道的东西不能含糊
而过)那么,除法就为:
1100001010
_______________
10011 ) 11010110110000 附加了几个零的新数据
10011......... 这里的减法(希望你不至于忘掉小学算术)是一个异或操作
-----.........
10011........
10011........
-----........
00001....... 逐bit计算
00000.......
-----.......
00010......
00000......
-----......
00101.....
00000.....
-----.....
01011....
00000....
-----....
10110...
10011...
-----...
01010..
00000..
-----..
10100.
10011.
-----.
01110
00000
-----
1110 = 这个余数也就是所谓的CRC值,通常又被称为校验值。

希望进行到这里,你可以获取更多关于CRC的感性认识。而我们所要做的,也就是实现一个CRC的计算算法。
说白了,就是提供一个程序,给定一段数据,以及一个生成多项式(对于CRC32算法而言该值固定),然后
计算得出上面的1110余数。

The simplest algorithm.

最简单的实现算法,是一种模拟算法。我们模拟上面的除法过程,遵从网上一份比较全面的资料,我们设定
一个变量register。我们逐bit地将我们的数据放到register中。然后判断register最高位是否为1,如果是
则与生成多项式异或操作,否则继续处理。这个过程简单地模拟了上述除法过程:



  1. /// The simplest CRC implement algorithm.
  2. ///
  3. /**//*
  4. Load the register with zero bits.
  5. Augment the message by appending W zero bits to the end of it.
  6. While (more message bits)
  7. Begin
  8. Shift the register left by one bit, reading the next bit of the
  9. augmented message into register bit position 0.
  10. If (a 1 bit popped out of the register during step 3)
  11. Register = Register XOR Poly.
  12. End
  13. The register now contains the remainder.
  14. */

  15. #include

  16. #define POLY 0x13

  17. int main()
  18. {
  19. /**//// the data
  20. unsigned short data = 0x035b;
  21. /**//// load the register with zero bits
  22. unsigned short regi = 0x0000;
  23. /**//// augment the data by appending W(4) zero bits to the end of it.
  24. data <<= 4;

  25. /**//// we do it bit after bit
  26. for( int cur_bit = 15; cur_bit >= 0; -- cur_bit )
  27. {
  28. /**//// test the highest bit which will be poped later.
  29. /// in fact, the 5th bit from right is the hightest bit here
  30. if( ( ( regi >> 4 ) & 0x0001 ) == 0x1 )
  31. {
  32. regi = regi ^ POLY;
  33. }
  34. /**//// shift the register
  35. regi <<= 1;
  36. /**//// reading the next bit of the augmented data
  37. unsigned short tmp = ( data >> cur_bit ) & 0x0001;
  38. regi |= tmp;

  39. }

  40. /**//// and now, register contains the remainder which is also called CRC value.

  41. return 0;
  42. }

better algorithm ?

很多时候这种让人容易理解的算法都不会被实际用到。这种逐bit操作的算法实在很慢。你可能知道
一般的CRC32算法都是一种基于表(table-driven)的算法。但是你可能不知道这个表是如何来的。

一种改善这种bit after bit的方法就是将这个bit扩大,例如典型的做法就是换成byte。这里我要详细地叙述下
上面那种算法的过程:

我们每次会先检查register的最高位是否为1,如果为1,则将生成多项式(所谓的Poly)与register进行异或操作。
然后,将register左移一位,也就舍弃了最高位。然后将我们的数据拿一bit出来放到register的最低位。

也就是说,register中的某一位的值会决定后面几位的值。如果将register最高字节每一bit编码为:
t7 t6 t5 t4 t3 t2 t1 t0。那么,t7会决定t6-t0的值(如果为1),t6会决定t5-t0的值,依次类推。但是,无论谁
决定谁的值,当上面那个算法迭代一个字节后(8bits),t7-t0都会被丢弃(whatever you do)。唯一留下来的东西,
就是对这个字节以后字节的影响。

那么,如果我们可以直接获取这个影响,我们就可以byte after byte地处理,而不是bit after bit。如何获取这个
影响呢?这个影响又是什么呢?这个影响就对应着我们的table-driven CRC算法中的表元素!

但是,为什么我们逐bit进行计算的过程为什么可以简化为一步操作?事实上,我们没有简化这个操作。一种用于教学
的算法,是实时地计算这个影响值:

  1. ///
  2. /// The table-driven CRC implement algorithm part 1.
  3. ///
  4. /**//*
  5. While (augmented message is not exhausted)
  6. Begin
  7. Examine the top byte of the register
  8. Calculate the control byte from the top byte of the register
  9. Sum all the Polys at various offsets that are to be XORed into
  10. the register in accordance with the control byte
  11. Shift the register left by one byte, reading a new message byte
  12. into the rightmost byte of the register
  13. XOR the summed polys to the register
  14. End
  15. */

  16. #include
  17. #include
  18. #include

  19. #define POLY 0x04C11DB7L

  20. int main()
  21. {
  22. /**//// the data
  23. unsigned long data = 0x1011035b;
  24. /**//// load the register with the data
  25. unsigned long regi = 0;
  26. /**//// allocate memory to contain the AUGMENTED data (added some zeros)
  27. unsigned char p[8];
  28. /**//// copy data
  29. memset( p, 0, 8 );
  30. memcpy( p, &data, 4 );

  31. /**//// because data contains 4 bytes
  32. for( int i = 0; i <>
  33. {
  34. /**//// get the top byte of the register
  35. unsigned char top_byte = (unsigned char)( ( regi >> 24 ) & 0xff );
  36. /**//// sum all the polys at various offsets
  37. unsigned long sum_poly = top_byte <<>
  38. for( int j = 0; j <>
  39. {
  40. /**//// check the top bit
  41. if( ( sum_poly >> 31 ) != 0 )
  42. {
  43. /**//// TODO : understand why '<<' first
  44. sum_poly = ( sum_poly <<>
  45. }
  46. else
  47. {
  48. sum_poly <<= 1;
  49. }
  50. }
  51. /**//// shift the register left by on byte, reading a new
  52. regi = ( ( regi <<>
  53. /**//// xor the summed polys to the register
  54. regi ^= sum_poly;
  55. }

  56. /**//// and now, register contains the remainder which is also called CRC value.

  57. return 0;
  58. }


其中:


  1. /// sum all the polys at various offsets
  2. unsigned long sum_poly = top_byte <<>
  3. for( int j = 0; j <>
  4. {
  5. /**//// check the top bit
  6. if( ( sum_poly >> 31 ) != 0 )
  7. {
  8. /**//// TODO : understand why '<<' first
  9. sum_poly = ( sum_poly <<>
  10. }
  11. else
  12. {
  13. sum_poly <<= 1;
  14. }
  15. }

就是用于计算这个影响值的。事实上,table-driven CRC算法中的那个表就是通过这段代码生成的(排除其他一些细节)。
你可能并不是很理解,这里我建议你忽略各种细节(更多的细节见参考资料)。你所需要知道的是,我们将8次逐bit的操
作合并到了一次byte操作中。而这个byte操作,就是8次bit操作的合操作(上面提到的影响值)。这个byte操作其实就是
一个数值,也就是table-driven CRC算法中那个表的一个元素。不同序列的bit操作其实对应着不同的unsigned char
值,因此那个table有256个元素。

show me where the table is :

如上所说,上面的算法很容易地就可以引进一个表:


进一步简化:

上述算法一个典型特征是会在我们的数据后面添加若干0。这样做其他做了很多没用的计算。一种简化做法就是将这些
没用的计算合并到其他计算中。其实这都是一些位操作的技巧:

  1. **////
  2. /// The table-driven CRC implement algorithm part 2.
  3. ///
  4. /**//*
  5. While (augmented message is not exhausted)
  6. Begin
  7. Examine the top byte of the register
  8. Calculate the control byte from the top byte of the register
  9. Sum all the Polys at various offsets that are to be XORed into
  10. the register in accordance with the control byte
  11. Shift the register left by one byte, reading a new message byte
  12. into the rightmost byte of the register
  13. XOR the summed polys to the register
  14. End
  15. */

  16. #include
  17. #include
  18. #include

  19. #define POLY 0x04C11DB7L

  20. unsigned long get_sum_poly( unsigned char top_byte )
  21. {
  22. /**//// sum all the polys at various offsets
  23. unsigned long sum_poly = top_byte <<>
  24. for( int j = 0; j <>
  25. {
  26. /**//// check the top bit
  27. if( ( sum_poly >> 31 ) != 0 )
  28. {
  29. /**//// TODO : understand why '<<' first
  30. sum_poly = ( sum_poly <<>
  31. }
  32. else
  33. {
  34. sum_poly <<= 1;
  35. }
  36. }

  37. return sum_poly;
  38. }

  39. void create_table( unsigned long *table )
  40. {
  41. for( int i = 0; i <>
  42. {
  43. table[i] = get_sum_poly( (unsigned char) i );
  44. }
  45. }

  46. int main()
  47. {
  48. /**//// the data
  49. unsigned long data = 0x1011035b;
  50. /**//// load the register with the data
  51. unsigned long regi = 0;
  52. /**//// allocate memory to contain the AUGMENTED data (added some zeros)
  53. unsigned char p[8];
  54. /**//// copy data
  55. memset( p, 0, 8 );
  56. memcpy( p, &data, 4 );

  57. /**//// the table
  58. unsigned long table[256];
  59. /**//// create the table
  60. create_table( table );

  61. /**//// because data contains 4 bytes
  62. for( int i = 0; i <>
  63. {
  64. /**//// get the top byte of the register
  65. unsigned char top_byte = (unsigned char)( ( regi >> 24 ) & 0xff );
  66. /**//// shift the register left by on byte, reading a new
  67. regi = ( ( regi <<>
  68. /**//// xor the summed polys to the register
  69. regi ^= table[top_byte];
  70. }

  71. /**//// and now, register contains the remainder which is also called CRC value.

  72. return 0;
  73. }

讨厌的附加0

以上算法有个很大的特征就是要为我们的数据附加很多0。附加0后其实也附加了很多无用的操作。我们要将这些
讨厌的0去掉:


  1. int main()
  2. {
  3. /**//// the data
  4. unsigned long data = 0x1011035b;
  5. /**//// load the register with the data
  6. unsigned long regi = 0;
  7. /**//// allocate memory to contain the data
  8. unsigned char p[4];
  9. /**//// copy data
  10. memcpy( p, &data, 4 );

  11. /**//// the table
  12. unsigned long table[256];
  13. /**//// create the table
  14. create_table( table );

  15. /**//// because data contains 4 bytes
  16. for( int i = 0; i <>
  17. {
  18. regi = ( regi <<>> 24 ) ^ p[i] ];
  19. }

  20. /**//// and now, register contains the remainder which is also called CRC value.

  21. return 0;
  22. }


关键的一句regi = ( regi <<>> 24 ) ^ p[i] ]; 简化了很多没用的操作。


In practice :

似乎一切被我说的很简单。我想只是因为我没说清楚。我尽量让你注意到事情的重点。我们进行到这里,似乎
我们立马就可以写出自己的CRC32算法并用于实践。但是你很快就会发现,事情并不如你想像的那么简单。

在实际处理时,很多数据的bit会进行一种颠倒操作,例如1010会被颠倒为0101。出现这样的情况是因为某些硬件
在实现CRC算法时,采用了这种(丑陋的)习惯。有些软件实现CRC算法时,也延用了这个习惯。

另外,关于register的初始值问题,有些CRC算法会初始化为0xffffffff。以下给出一个会进行bit颠倒的算法,
该算法可以直接输出table-driven中的表:


  1. **////
  2. /// The table-driven CRC implement algorithm part 4.
  3. ///
  4. /// Donot need augment W/8 zero bytes.
  5. ///
  6. #include
  7. #include
  8. #include

  9. #define POLY 0x04C11DB7L

  10. #define BITMASK(X) (1L << (X))

  11. unsigned long refelect( unsigned long v, int b )
  12. {
  13. int i;
  14. unsigned long t = v;
  15. for( i = 0; i <>
  16. {
  17. if( t & 1L )
  18. v |= BITMASK( (b-1)-i );
  19. else
  20. v &= ~BITMASK( (b-1)-i );
  21. t >>= 1;
  22. }

  23. return v;
  24. }

  25. /**//// i'll try to write a correct algorithm
  26. unsigned long get_sum_poly( unsigned char byte )
  27. {
  28. byte = (unsigned long) refelect( byte, 8 );
  29. unsigned long sum_poly = byte <<>

  30. for( int i = 0; i <>
  31. {
  32. /**//// check the top bit
  33. if( ( sum_poly >> 31 ) != 0 )
  34. {
  35. /**//// TODO : understand why '<<' first
  36. sum_poly = ( sum_poly <<>
  37. }
  38. else
  39. {
  40. sum_poly <<= 1;
  41. }
  42. }

  43. sum_poly = refelect( sum_poly, 32 );
  44. return sum_poly;
  45. }

  46. void create_table( unsigned long *table )
  47. {
  48. for( int i = 0; i <= 255; ++ i )
  49. {
  50. table[i] = get_sum_poly( (unsigned char) i );
  51. }
  52. }

  53. void output_table( const unsigned long *table )
  54. {
  55. FILE *fp = fopen( "table.txt", "w" );

  56. for( int y = 0; y <>
  57. {
  58. fprintf( fp, "0x%08lXL,\t0x%08lXL,\t0x%08lXL,\t0x%08lXL, \n",
  59. table[ y * 4 + 0],
  60. table[ y * 4 + 1],
  61. table[ y * 4 + 2],
  62. table[ y * 4 + 3] );
  63. }

  64. fclose( fp );
  65. }

  66. int main()
  67. {
  68. /**//// the table
  69. unsigned long table[256];
  70. /**//// the data
  71. unsigned long data = 0x1011035b;
  72. /**//// load the register with the data
  73. unsigned long regi = 0;
  74. /**//// allocate memory to contain the data
  75. unsigned char p[4];
  76. /**//// copy data
  77. memcpy( p, &data, 4 );
  78. /**//// create the table
  79. create_table( table );
  80. /**//// output the table
  81. output_table( table );

  82. /**//// because data contains 4 bytes
  83. for( int i = 0; i <>
  84. {
  85. regi = ( regi <<>> 24 ) ^ p[i] ];
  86. }

  87. /**//// and now, register contains the remainder which is also called CRC value.

  88. return 0;
  89. }


Please FORGIVE me

我想我并没有将整个过程彻底地讲清楚。但是我希望你能明白大致的原理。关于table-driven中那个神奇的表的来历,
关于CRC32算法的推导过程等等之类。


本文代码下载: http://www.cppblog.com/Files/kevinlynx/CRC%20Implement.rar

2009年2月10日星期二

wxWidgets & Code::Blocks Studio

wxWidgets & Code::Blocks Studio

近来无事想写一个小程序,又不想花太多时间在UI上;就想起了一直关注的wxWidgets(以前叫wxWindows,据说是因为微软施压才改的名)。选用他的理由有三:

  1. 只会C。。。
  2. 不想因为界面设计而花太多时间
    • wxWidgets已经有了像wxGlade这样的东东,画个简单的界面什么的足够了
  3. 我的服务器已经都转换到了Linux;而桌面电脑随着时间的推移已经逐步的将常用的程序切换成了跨Windows/Linux的版本。所以学习一个跨平台的UI比较能够做到知识的保值
  4. wxWidgets中的wxString类提供了统一的一个字符串操作界面。一些常用的文本处理函数不用再创建了;这个我喜欢
  5. 开源、免费!

下一步就是选择IDE

主要焦点在MinGW Developer StudioCode::Blocks Studio这两个跨平台(都使用的是wxWidgets UI库)的免费软件之间:

  1. MinGW Developer Studio使用感觉还不错。有预编译的wxWidgets库;入门方便(编译wxWidgets库遇到的问题多多,后面慢慢讲)
  2. Code::Blocks Studio提供插件接口而且最重要的是更新快。这方面MinGW Developer Studio就差多了,活跃的开发团队是开源项目发展的关键!

当然差别还有很多。我是菜菜鸟看得到的就这些。。。

安装Code::Blocks Studio

Code::Blocks Studio现在发布的是RC2(还没有正式版)。但RC2问题多多(我发现的就有对Unicode编译的支持问题);不过有Nightly Builds可以解决。我的解决步骤是:

  1. 先下载并安装RC2整合MingW的版本,这样也就不用再去下载MingW(MingW下载包太多了,安装头痛)
  2. 下载Nightly Builds(现在登陆C::B这个论坛要注册,前几天都还可以匿名的)中的
    1. Unicode wxWidget动态支持库(新版的C::B已经使用Unicodede发布)
    2. C::B的Nightly Builds,我下的是2006/1/20号的rev1823
  3. 将两个包中文文件解压覆盖到C:\Program Files\CodeBlocks

安装wxWdigets

  1. 我在wxWidgets官方下载页面上下载的wxMSW v2.6.2 的ZIP版
  2. 解到D:\(安装完成后的路径为:D:\wxWidgets-2.6.2,之后的设置都用的是这个路径)

编译wxWdigets(支持ODBC)

  1. 首先编辑D:\wxWidgets-2.6.2\include\wx\msw\setup.h以便编译后的动/静态库文件支持ODBC(为了让编译出来的库支持ODBC,我至少编译了4次才找到原来还要在这儿修改。虽然wxWidgets的手册上写要修改setup.h不过没有具体说是那个目录下的。。。吐血)。修改内容如下:
    • 将文件中的#define wxUSE_ODBC 0修改为#define wxUSE_ODBC 1
  2. 然后我在D:\wxWidgets-2.6.2\build\msw下创建了一个envset.bat文件来设置编译需要的环境参数。
    1. 内容如下

      set PATH=%PATH%;C:\Program Files\CodeBlocks\bin;C:\Program Files\CodeBlocks\mingw32\bin;
      set LIBRARY_PATH=C:\Program Files\CodeBlocks\lib
      set C_INCLUDE_PATH=C:\Program Files\CodeBlocks\include
      set CPLUS_INCLUDE_PATH=C:\Program Files\CodeBlocks\include;D:\wxWidgets-2.6.2\include;D:\wxWidgets-2.6.2\contrib\include;

    2. 其中C:\Program Files\CodeBlocks是我的C::B的安装路径
  3. 同时修改D:\wxWidgets-2.6.2\build\msw\config.gcc
    • USE_ODBC = 0修改为USE_ODBC = 1
  4. 进入DOS命令行
  5. 切换工作路径至D:\wxWidgets-2.6.2\build\msw
  6. 运行envset.net
  7. 执行清理命令

    mingw32-make -f makefile.gcc USE_XRC=1 SHARED=1 MONOLITHIC=1 BUILD=debug UNICODE=1 clean

    • 其中
      1. SHARED=1表示生成的动态链接库DLL,0就是静态链接库
      2. MONOLITHIC=1表示生成单一的库文件,0表示生成多个按模块分割的库文件
      3. BUILD=debug表示生成带Debug信息的版本方便调试,release是发布版
      4. UNICODE=1表示使用unicode编码
  8. 执行编译命令

    mingw32-make -f makefile.gcc USE_XRC=1 SHARED=1 MONOLITHIC=1 BUILD=debug UNICODE=1 VENDOR=cb

  9. 看看电视,泡壶茶。编译的时间可不短;-)

整合C::B和wxWidgets

  1. 运行C::B。程序会提示你填写wxWidgets的安装目录
    • codeblock-wx-global-var-set.png
  2. 然后使用新建向导创建一个Using UNICODE wxWidgets DLL的wxWidgets Appliction就可以开始了
  3. codeblock-wx-create-project.png

有点晚了,关于C::B中的wxWidgets项目配置方面的改天再写。。。

2009年2月9日星期一

Windows上配置Code::Blocks + wxWidgets(转)

Windows上配置Code::Blocks + wxWidgets



27号晚上我问一个做共享软件的朋友Lazaru(基于FreePascal的跨平台IDE,类似于Delphi)做桌面软件如何,他推荐用Code::Blocks,说Nightly Build已经很稳定,正式版很快就发布了,接着果然28号就发布了正式版。


本文内容来自Code::Blocks wiki上的WxWindowsQuickRef,本文内容并非按照原文完全逐字逐句的翻译。



Code::Blocks是一个跨平台的C++IDE,支持Windows、Linux、MacOSX。同时他还支持各种不同的编译器,如GNU/MinGW C/C++,VC++ 6.0/2003/2005/2008,Borland C++,Digital Mars等等各种不同的编译器。


经过14个组员长达2年对Code::Blocks的全部重写,终于发布了正式版8.02,这个版本更包括了对构建基于wxWidgets的跨平台GUI程序的支持,堪比Visual C++。


wxWidgets则是一个十分优秀的跨平台的GUI框架,用其编写的C++应用程序可以十分方便地迁移到不同的系统上去。


Code::Blocks +
wxWidgets两个同是支持跨平台的IDE和框架,使得跨平台的编程非常方便。然而Code::Blocks虽然包含了对wxWidgets的支持,
但是却没有包含wxWidgets的构建环境,我们必须手动进行配置。另外,Code::Blocks有一个安装包包含了MinGW的编译器,如果使用别
的编译器,同样也需要自己进行相应的配置。


前提准备


编译器


你至少应该正确安装了免费的MinGW/GCC编译器或者是某种微软的编译器Express editions是免费的,但是你还需要安装Platform SDK)。如果是用MinGW/GCC,至少要准备gcc-core、gcc-g++、binutils、w32api以及mingw32-make包;同时,确保包含编译器可执行文件的目录(一般是C:\MinGW\bin)在Windows的PATH环境变量中。


如果选择MinGW/GCC编译器,可以在直接选择包含MinGW的Code::Blocks安装包,见下一节。


最新版的Code::Blocks


请下载最新的8.02发布版。尚未选择编译器可以选择包含MinGW的安装包


wxWidgets


你可以选择下载wxWidgets的源代码然后自己进行构建,或者是直接安装预编译的wxPack。


wxWidgets源代码


安装包较小,可以根据自己的需求进行自定义构建,但是需要花费长时间进行编译。如果不清楚编译选项,可能导致无法成功配置Code::Blocks。


目前推荐的wxWidgets的版本是2.8.7。点击此处下载wxWidgets 2.8.7源代码Windows安装包 (wxMSW-2.8.7-Setup.exe; 12.0 MB)。你也可以检查一下wxWidgets的下载页面看看有没有更新的稳定版下载。强烈建议你将代码安装到不带空格的路径中。必须保证盘中至少有300MB的剩余空间。



wxPack

虽然安装包达200MB,全部安装需要3G,但是包含了预编译的所有可能用到的库文件,而且包含VC和GCC的两种版本,可以不用去考虑构建选项了。


当前wxPack的稳定发布版是 v2.8.7.03,基于 wxWidgets 2.8.7。点击此处下载 wxPack v2.8.7.03 (wxPack_v2.8.7.03.exe, 236.9 MB)。你也可以查看wxPack下载页面看看有没有更新的稳定版下载。强烈建议将wxPack安装到没有空格的路径中。如果你选择只MSVC版本,应保证至少有700MB的剩余空间;如果只选择MinGW/GCC版本,则应保证至少有2.2GB的剩余空间。


提示


如果磁盘使用了NTFS格式,可以开启文件压缩功能,上述的目录在压缩后可以减少50%的空间占用。



编译wxWidgets


使用wxPack则可以跳过这一步。


打开命令行(在开始菜单中点击“运行”,输入cmd并回车)。如果使用的MSVC,你可以使用特定的用于设置环境变量的命令行。如果你使用的
MSVC版本还要求你单独下载Platform SDK,确保全部包含了标准编译工具和Platform SDK中要用到的环境变量。


转到wxWidgets的构建目录,其中<wxWidgets>是源码所在路径,通常是C:\wxWidgets-2.8.7


cd <wxWidgets>\build\msw</pre><br /><p>执行构建命令,<strong>MinGW/GCC</strong>推荐的命令是:</p><br /><pre>mingw32-make -f makefile.gcc BUILD=release SHARED=1 MONOLITHIC=1 UNICODE=1</pre><br /><p><strong>MSVC</strong>推荐的构建命令是:</p><br /><pre>nmake -f makefile.vc BUILD=release SHARED=1 MONOLITHIC=1 UNICODE=1

这个过程需要花很久,快的机器大概30分钟可以完成,慢的可能就需要几个小时了。


如果使用的GCC的版本较新,构建过程中可能会出现大量的警告。这样会明显导致构建过程变慢;你可以将错误信息重定向到文件中,在上述命令后面添加2> err.log,也可以通过2>nul直接禁止警告信息。


其中关于BUILD、SHARED、MONOLITHIC以及UNICODE选项的解释,请仔细参考文章后面关于wxWidgets的构建参数的解
释,这些参数十分关键,他们直接定义了你所使用的基本的wxWidgets开发环境。你必须严格按照你的编译参数设置Code::Blocks的配置向
导。


在Code::Blocks中创建wxWidgets项目


在Code::Blocks的起始页面中,选择“Create a new project”,也可以在File菜单中,选择“New” -> “Project…”。


找到并选择“wxWidgets project”,并创建,接下来会出现一个向导帮助进行wxWidgets项目的配置:


  1. 第一个页面是简介,可以选择以后跳过。
  2. 选择你要使用的wxWidgets版本。如果你是按照本文的过程配置的,那么你应该选择“wxWidgets 2.8.x”。
  3. 设置你的项目的名字的位置。
  4. 输入作者的信息(非必要)
  5. 选择自动代码和文件生成的选项。
  6. 选择wxWidgets的位置。强烈建议在此使用全局变量:输入“$(#wx)”(不包含引号)。如果你还没定义这个全局变量,那么全局变量对话框会出现,在Base Path中,选择你的wxWidgets安装路径。其他路径可以不用填。
  7. 为你的项目选择debug/release配置。推荐至少选择debug配置。
  8. 选择你的wxWidgets构建选项。必须和你构建wxWidgets时所使用的选项一致!如果你按照本文之前的方式构建的,应该将
    “wxWidgets Library
    Settings”下的全部三个选项选中。如果用的是wxPack,由于wxPack包含了各种不同的版本,所以你只需要选择你需要的选项。这个页面的另
    一个设置和wxWidgets的构建选项没有关系,你可以按照喜好来选择。如果,出于某种原因,你想使用调试版本的wxWidgets构建,选择
    “Configure Advanced options”然后在下一页选择“Use __WXDEBUG__ and Debug
    wxWidgets lib”。
  9. 如果需要,选择额外的库。一般应用的话应该无须选择其中任何一个。

构建并运行程序


接下来,就可以选择“Build and run”(F9)对程序进行构建并运行了。如果顺利,你的wxWidgets应用程序就会出现。如果出现了什么问题,你可以参考后面的常见问题。


wxWidgets编译选项简介


BUILD


BUILD控制wxWidgets构建调试版本(BUILD=debug)或者是发布版本(BUILD=release)。绝大多数情况下你只需要
wxWidgets的发布版本就可以了,因为你应该不想要去调试wxWidgets自身,同时你依然可以通过链接wxWidgets的发布版本来构建你自
己的程序的调试版本。


  • 调试构建wxWidgets会创建带有”d”后缀的库,例如”libwxmsw28d.a”、”wxmsw28d_gcc_custom.dll”。
  • 调试构建wxWidgets会在wxWidgets库的输出目录中创建”mswd” 或者 “mswud” 目录。
  • 发布构建wxWidgets创建的库没有”d”后缀,例如”libwxmsw28.a”、”wxmsw28_gcc_custom.dll”。
  • 发布构建wxWidgets会在wxWidgets库的输出目录中创建”msw” 或者 “mswu” 目录。

SHARED


SHARED控制wxWidgets是构建DLL(SHARED=1)还是静态库(SHARED=0)。利用构建的DLL,主程序构建时间较快,可执行文件更小。但是可执行文件加上wxWidgets DLL的总大小更大,但是不同的可执行文件可以使用同一个DLL。


  • wxWidgets的DLL构建会创建导入库(如 libwxmsw28.a)以及DLL文件(如wxmsw28_gcc_custom.dll)。你必须在发布你的程序的时候包含这个DLL。
  • wxWidgets的静态构建只会创建静态库(如 libwxmsw28.a),发布的时候也无须包含wxWidgets的DLL。

MONOLITHIC


MONOLITHIC控制是构建一个单一的库(MONOLITHIC=1)还是多个组件库(MONOLITHIC=0)。使用单一构建,项目的设置
和开发会更加简单,如果你同时使用DLL构建的话,你只需要分发一个DLL文件。如果使用非单一构建(multilib),会构建出多个不同的库同时你可
以避免将整个wxWidgets的基本代码链接到主程序,就可以去掉不需要的库。同时你也必须确保你选择了正确的组件库。


  • wxWidgets的单一构建仅会创建一个wxWidgets导入库(如libwxmsw28.a)以及一个DLL(如wxmsw28_gcc_custom.dll)。
  • wxWidgets的多库(multilib)构建会创建多个导入库(libwx28_base.a等)以及多个DLL文件。
  • 无论何种wxWidgets构建,都会创建额外的静态库(如libwxexpat.a、libwxjpeg.a等)。这些库对于wxWidgets的DLL构建一般是不需要的,但是当使用静态构建的时候,则是必须的。

UNICODE


UNICODE控制wxWidgets以及你的程序是否使用支持Unicode的宽字符串。大多数Windows 2000或更高系统上的应用程序都应该支持Unicode。早期的Windows版本不一定有Unicode支持。你应该总是使用wxWidgets的_("string")_T("string")宏来确保硬编码的字符串编译时是正确的类型。


  • wxWidgets的Unicode(UNICODE=1)构建将会创建带有”u”后缀的库,例如”libwxmsw28u.a”、”wxmsw28u_gcc_custom.dll”。
  • wxWidgets的Unicode构建会在wxWidgets库的输出目录中创建”mswu”或”mswud”目录。
  • wxWidgets的ANSI(UNICODE=0)构建创建的库没有”u”后缀,例如”libwxmsw28.a”、”wxmsw28_gcc_custom.dll”。
  • wxWidgets的ANSI构建会在wxWidgets库的输出目录中创建”msw”或”mswd”目录。

常见问题


出现类似于”wx/setup.h: No such file or directory”的错误


你在构建选项中缺少了很重要的编译器搜索路径。首先确认你是否在运行wxWidgets项目向导的时候正确选择了wxWidgets的构建配置。如果重新运行向导并配置依然无效,那么打开你的项目的构建选项并给编译起的搜索路径中添加”$(#wx.lib)\gcc_dll\mswu“(这里假设是一个单一的Unicode DLL构建)。


出现类似于”cannot find -lwxmsw28u”的错误


构建选项中的链接库错了。首先确认你是否在运行wxWidgets项目向导的时候正确选择了wxWidgets的构建配置。如果重新运行向导并配置依然无效,确定你构建了什么库,并相应在构建选项中调整库的名字。