30.1 例子#1

  1. #include <windows.h>
  2. int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  3. {
  4. MessageBeep(MB_ICONEXCLAMATION);
  5. return 0;
  6. };
  1. WinMain proc near
  2. push bp
  3. mov bp, sp
  4. mov ax, 30h ; 0 ; MB_ICONEXCLAMATION constant
  5. push ax
  6. call MESSAGEBEEP
  7. xor ax, ax ; return 0
  8. pop bp
  9. retn 0Ah
  10. WinMain endp

到现在为止,看起来都很简单。

30.2 例子#2

  1. #include <windows.h>
  2. int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  3. {
  4. MessageBox (NULL, "hello, world", "caption", MB_YESNOCANCEL);
  5. return 0;
  6. };
  1. WinMain proc near
  2. push bp
  3. mov bp, sp
  4. xor ax, ax ; NULL
  5. push ax
  6. push ds
  7. mov ax, offset aHelloWorld ; 0x18. "hello, world"
  8. push ax
  9. push ds
  10. mov ax, offset aCaption ; 0x10. "caption"
  11. push ax
  12. mov ax, 3 ; MB_YESNOCANCEL
  13. push ax
  14. call MESSAGEBOX
  15. xor ax, ax ; return 0
  16. pop bp
  17. retn 0Ah
  18. WinMain endp
  19. dseg02:0010 aCaption db caption’,0
  20. dseg02:0018 aHelloWorld db hello, world’,0

有两个重要的信息:PASCAL调用转换表明先传递最后的参数(MB_YESNOCANCEL),然后才是第一个参数NULL。这个调用也表明了调用者恢复栈指针:因为RETN有一个0Ah的参数,这个意味着栈指针将在函数退出时上移10个字节。 指针按对传递:一组数据先传递,指针就在这组数据里面。例子这里只有一组数据,所以DS永远指向可执行文件的data段。

30.3 例子#3

  1. #include <windows.h>
  2. int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  3. {
  4. int result=MessageBox (NULL, "hello, world", "caption", MB_YESNOCANCEL);
  5. if (result==IDCANCEL)
  6. MessageBox (NULL, "you pressed cancel", "caption", MB_OK);
  7. else if (result==IDYES)
  8. MessageBox (NULL, "you pressed yes", "caption", MB_OK);
  9. else if (result==IDNO)
  10. MessageBox (NULL, "you pressed no", "caption", MB_OK);
  11. return 0;
  12. };
  1. WinMain proc near
  2. push bp
  3. mov bp, sp
  4. xor ax, ax ; NULL
  5. push ax
  6. push ds
  7. mov ax, offset aHelloWorld ; "hello, world"
  8. push ax
  9. push ds
  10. mov ax, offset aCaption ; "caption"
  11. push ax
  12. mov ax, 3 ; MB_YESNOCANCEL
  13. push ax
  14. call MESSAGEBOX
  15. cmp ax, 2 ; IDCANCEL
  16. jnz short loc_2F
  17. xor ax, ax
  18. push ax
  19. push ds
  20. mov ax, offset aYouPressedCanc ; "you pressed cancel"
  21. jmp short loc_49
  22. ; ---------------------------------------------------------------------------
  23. loc_2F:
  24. cmp ax, 6 ; IDYES
  25. jnz short loc_3D
  26. xor ax, ax
  27. push ax
  28. push ds
  29. mov ax, offset aYouPressedYes ; "you pressed yes"
  30. jmp short loc_49
  31. ; ---------------------------------------------------------------------------
  32. loc_3D:
  33. cmp ax, 7 ; IDNO
  34. jnz short loc_57
  35. xor ax, ax
  36. push ax
  37. push ds
  38. mov ax, offset aYouPressedNo ; "you pressed no"
  39. loc_49:
  40. push ax
  41. push ds
  42. mov ax, offset aCaption ; "caption"
  43. push ax
  44. xor ax, ax
  45. push ax
  46. call MESSAGEBOX
  47. loc_57:
  48. xor ax, ax
  49. pop bp
  50. retn 0Ah
  51. WinMain endp

就是前一节的扩展而已。

30.4 例子#4

  1. #include <windows.h>
  2. int PASCAL func1 (int a, int b, int c)
  3. {
  4. return a*b+c;
  5. };
  6. long PASCAL func2 (long a, long b, long c)
  7. {
  8. return a*b+c;
  9. };
  10. long PASCAL func3 (long a, long b, long c, int d)
  11. {
  12. return a*b+c-d;
  13. };
  14. int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  15. {
  16. func1 (123, 456, 789);
  17. func2 (600000, 700000, 800000);
  18. func3 (600000, 700000, 800000, 123);
  19. return 0;
  20. };
  1. func1 proc near
  2. c = word ptr 4
  3. b = word ptr 6
  4. a = word ptr 8
  5. push bp
  6. mov bp, sp
  7. mov ax, [bp+a]
  8. imul [bp+b]
  9. add ax, [bp+c]
  10. pop bp
  11. retn 6
  12. func1 endp
  13. func2 proc near
  14. arg_0 = word ptr 4
  15. arg_2 = word ptr 6
  16. arg_4 = word ptr 8
  17. arg_6 = word ptr 0Ah
  18. arg_8 = word ptr 0Ch
  19. arg_A = word ptr 0Eh
  20. push bp
  21. mov bp, sp
  22. mov ax, [bp+arg_8]
  23. mov dx, [bp+arg_A]
  24. mov bx, [bp+arg_4]
  25. mov cx, [bp+arg_6]
  26. call sub_B2 ; long 32-bit multiplication
  27. add ax, [bp+arg_0]
  28. adc dx, [bp+arg_2]
  29. pop bp
  30. retn 12
  31. func2 endp
  32. func3 proc near
  33. arg_0 = word ptr 4
  34. arg_2 = word ptr 6
  35. arg_4 = word ptr 8
  36. arg_6 = word ptr 0Ah
  37. arg_8 = word ptr 0Ch
  38. arg_A = word ptr 0Eh
  39. arg_C = word ptr 10h
  40. push bp
  41. mov bp, sp
  42. mov ax, [bp+arg_A]
  43. mov dx, [bp+arg_C]
  44. mov bx, [bp+arg_6]
  45. mov cx, [bp+arg_8]
  46. call sub_B2 ; long 32-bit multiplication
  47. mov cx, [bp+arg_2]
  48. add cx, ax
  49. mov bx, [bp+arg_4]
  50. adc bx, dx ; BX=high part, CX=low part
  51. mov ax, [bp+arg_0]
  52. cwd ; AX=low part d, DX=high part d
  53. sub cx, ax
  54. mov ax, cx
  55. sbb bx, dx
  56. mov dx, bx
  57. pop bp
  58. retn 14
  59. func3 endp
  60. WinMain proc near
  61. push bp
  62. mov bp, sp
  63. mov ax, 123
  64. push ax
  65. mov ax, 456
  66. push ax
  67. mov ax, 789
  68. push ax
  69. call func1
  70. mov ax, 9 ; high part of 600000
  71. push ax
  72. mov ax, 27C0h ; low part of 600000
  73. push ax
  74. mov ax, 0Ah ; high part of 700000
  75. push ax
  76. mov ax, 0AE60h ; low part of 700000
  77. push ax
  78. mov ax, 0Ch ; high part of 800000
  79. push ax
  80. mov ax, 3500h ; low part of 800000
  81. push ax
  82. call func2
  83. mov ax, 9 ; high part of 600000
  84. push ax
  85. mov ax, 27C0h ; low part of 600000
  86. push ax
  87. mov ax, 0Ah ; high part of 700000
  88. push ax
  89. mov ax, 0AE60h ; low part of 700000
  90. push ax
  91. mov ax, 0Ch ; high part of 800000
  92. push ax
  93. mov ax, 3500h ; low part of 800000
  94. push ax
  95. mov ax, 7Bh ; 123
  96. push ax
  97. call func3
  98. xor ax, ax ; return 0
  99. pop bp
  100. retn 0Ah
  101. WinMain endp

32位的值(long数据类型代表32位,int代表16位数据)在16位模式下(MSDOS和win16)都会按对传递,就像64位数据在32位环境下使用的方式一样(21章)。

Sub_B2在这里是一个编译器生成的库函数,他的作用是“long乘法”,例如两个32位类型想成,其他的编译器函数列在了附录E, D.中。 ADD/ADC指令对用来相加两个值:ADD将设置/清空CF进位标识,ADC将会使用它。 SUB/SBB将会做减法,SUB会设置/清空CF标识位,SBB将会使用它。 32位值按照DX:AX寄存器对返回。 常数同样在WinMain()中按照值对的方式传递。 Int类型的123常量首先被转为32位的值,使用的是CWD指令。

30.5 例子#5

  1. #include <windows.h>
  2. int PASCAL string_compare (char *s1, char *s2)
  3. {
  4. while (1)
  5. {
  6. if (*s1!=*s2)
  7. return 0;
  8. if (*s1==0 || *s2==0)
  9. return 1; // end of string
  10. s1++;
  11. s2++;
  12. };
  13. };
  14. int PASCAL string_compare_far (char far *s1, char far *s2)
  15. {
  16. while (1)
  17. {
  18. if (*s1!=*s2)
  19. return 0;
  20. if (*s1==0 || *s2==0)
  21. return 1; // end of string
  22. s1++;
  23. s2++;
  24. };
  25. };
  26. void PASCAL remove_digits (char *s)
  27. {
  28. while (*s)
  29. {
  30. if (*s>=’0 && *s<=’9’)
  31. *s=’-’;
  32. s++;
  33. };
  34. };
  35. char str[]="hello 1234 world";
  36. int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  37. {
  38. string_compare ("asd", "def");
  39. string_compare_far ("asd", "def");
  40. remove_digits (str);
  41. MessageBox (NULL, str, "caption", MB_YESNOCANCEL);
  42. return 0;
  43. };
  1. string_compare proc near
  2. arg_0 = word ptr 4
  3. arg_2 = word ptr 6
  4. push bp
  5. mov bp, sp
  6. push si
  7. mov si, [bp+arg_0]
  8. mov bx, [bp+arg_2]
  9. loc_12: ; CODE XREF: string_compare+21j
  10. mov al, [bx]
  11. cmp al, [si]
  12. jz short loc_1C
  13. xor ax, ax
  14. jmp short loc_2B
  15. ; ---------------------------------------------------------------------------
  16. loc_1C: ; CODE XREF: string_compare+Ej
  17. test al, al
  18. jz short loc_22
  19. jnz short loc_27
  20. loc_22: ; CODE XREF: string_compare+16j
  21. mov ax, 1
  22. jmp short loc_2B
  23. ; ---------------------------------------------------------------------------
  24. loc_27: ; CODE XREF: string_compare+18j
  25. inc bx
  26. inc si
  27. jmp short loc_12
  28. ; ---------------------------------------------------------------------------
  29. loc_2B: ; CODE XREF: string_compare+12j
  30. ; string_compare+1Dj
  31. pop si
  32. pop bp
  33. retn 4
  34. string_compare endp
  35. string_compare_far proc near ; CODE XREF: WinMain+18p
  36. arg_0 = word ptr 4
  37. arg_2 = word ptr 6
  38. arg_4 = word ptr 8
  39. arg_6 = word ptr 0Ah
  40. push bp
  41. mov bp, sp
  42. push si
  43. mov si, [bp+arg_0]
  44. mov bx, [bp+arg_4]
  45. loc_3A: ; CODE XREF: string_compare_far+35j
  46. mov es, [bp+arg_6]
  47. mov al, es:[bx]
  48. mov es, [bp+arg_2]
  49. cmp al, es:[si]
  50. jz short loc_4C
  51. xor ax, ax
  52. jmp short loc_67
  53. ; ---------------------------------------------------------------------------
  54. loc_4C: ; CODE XREF: string_compare_far+16j
  55. mov es, [bp+arg_6]
  56. cmp byte ptr es:[bx], 0
  57. jz short loc_5E
  58. mov es, [bp+arg_2]
  59. cmp byte ptr es:[si], 0
  60. jnz short loc_63
  61. loc_5E: ; CODE XREF: string_compare_far+23j
  62. mov ax, 1
  63. jmp short loc_67
  64. ; ---------------------------------------------------------------------------
  65. loc_63: ; CODE XREF: string_compare_far+2Cj
  66. inc bx
  67. inc si
  68. jmp short loc_3A
  69. ; ---------------------------------------------------------------------------
  70. loc_67: ; CODE XREF: string_compare_far+1Aj
  71. ; string_compare_far+31j
  72. pop si
  73. pop bp
  74. retn 8
  75. string_compare_far endp
  76. remove_digits proc near ; CODE XREF: WinMain+1Fp
  77. arg_0 = word ptr 4
  78. push bp
  79. mov bp, sp
  80. mov bx, [bp+arg_0]
  81. loc_72: ; CODE XREF: remove_digits+18j
  82. mov al, [bx]
  83. test al, al
  84. jz short loc_86
  85. cmp al, 30h ; 0
  86. jb short loc_83
  87. cmp al, 39h ; 9
  88. ja short loc_83
  89. mov byte ptr [bx], 2Dh ; ’-’
  90. loc_83: ; CODE XREF: remove_digits+Ej
  91. ; remove_digits+12j
  92. inc bx
  93. jmp short loc_72
  94. ; ---------------------------------------------------------------------------
  95. loc_86: ; CODE XREF: remove_digits+Aj
  96. pop bp
  97. retn 2
  98. remove_digits endp
  99. WinMain proc near ; CODE XREF: start+EDp
  100. push bp
  101. mov bp, sp
  102. mov ax, offset aAsd ; "asd"
  103. push ax
  104. mov ax, offset aDef ; "def"
  105. push ax
  106. call string_compare
  107. push ds
  108. mov ax, offset aAsd ; "asd"
  109. push ax
  110. push ds
  111. mov ax, offset aDef ; "def"
  112. push ax
  113. call string_compare_far
  114. mov ax, offset aHello1234World ; "hello 1234 world"
  115. push ax
  116. call remove_digits
  117. xor ax, ax
  118. push ax
  119. push ds
  120. mov ax, offset aHello1234World ; "hello 1234 world"
  121. push ax
  122. push ds
  123. mov ax, offset aCaption ; "caption"
  124. push ax
  125. mov ax, 3 ; MB_YESNOCANCEL
  126. push ax
  127. call MESSAGEBOX
  128. xor ax, ax
  129. pop bp
  130. retn 0Ah
  131. WinMain endp

我们可以看到所谓的“near”指针和“far”指针:另一个奇怪的16位8086现象。 可以在70章继续读到相关内容。 近指针就是那些指向当前数据段内的指针。因为,string_compare()函数仅仅用到2个16位指针,而且访问数据通过DS指向了它(mov al, es:[bx])。远指针也同样在我的16位MessageBox()例子里面:见30.2节。 因此,在访问文本时,Windows内核并不关心使用那个数据段,所以它需要更完整的信息。 使用这种区别的原因可能是因为紧凑的程序可能使用仅仅一个64kb的数据段。所以他并不需要传递地址的高位数据,因为它们永远是不变的。大一点的程序可能会使用多个64kb数据段,所以它们每次操作都需要需要区分它们是在哪个数据段里面。 对代码段来说也是相同的故事,比较短小的程序可能在64k的数据段里面包含有所有的可执行代码,然后所有的函数都会由CALL NEAR来调用,代码使用RETN返回。但是,如果有多个代码段的话,函数地址就会按对区分,然后使用CALL FAR来调用,代码会使用RETF返回。 这就是在编译器中指定“内存模型”会发生的事情。 MS-DOS和Win16编译器针对每个内存模型都有有特别的库:它们会因为数据和代码的不同的指针模型而不同。

30.6 例子#6

  1. #include <windows.h>
  2. #include <time.h>
  3. #include <stdio.h>
  4. char strbuf[256];
  5. int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  6. {
  7. struct tm *t;
  8. time_t unix_time;
  9. unix_time=time(NULL);
  10. t=localtime (&unix_time);
  11. sprintf (strbuf, "%04d-%02d-%02d %02d:%02d:%02d", t->tm_year+1900, t->tm_mon, t->tm_mday,
  12. t->tm_hour, t->tm_min, t->tm_sec);
  13. MessageBox (NULL, strbuf, "caption", MB_OK);
  14. return 0;
  15. };
  1. WinMain proc near
  2. var_4 = word ptr -4
  3. var_2 = word ptr -2
  4. push bp
  5. mov bp, sp
  6. push ax
  7. push ax
  8. xor ax, ax
  9. call time_
  10. mov [bp+var_4], ax ; low part of UNIX time
  11. mov [bp+var_2], dx ; high part of UNIX time
  12. lea ax, [bp+var_4] ; take a pointer of high part
  13. call localtime_
  14. mov bx, ax ; t
  15. push word ptr [bx] ; second
  16. push word ptr [bx+2] ; minute
  17. push word ptr [bx+4] ; hour
  18. push word ptr [bx+6] ; day
  19. push word ptr [bx+8] ; month
  20. mov ax, [bx+0Ah] ; year
  21. add ax, 1900
  22. push ax
  23. mov ax, offset a04d02d02d02d02 ; "%04d-%02d-%02d %02d:%02d:%02d"
  24. push ax
  25. mov ax, offset strbuf
  26. push ax
  27. call sprintf_
  28. add sp, 10h
  29. xor ax, ax ; NULL
  30. push ax
  31. push ds
  32. mov ax, offset strbuf
  33. push ax
  34. push ds
  35. mov ax, offset aCaption ; "caption"
  36. push ax
  37. xor ax, ax ; MB_OK
  38. push ax
  39. call MESSAGEBOX
  40. xor ax, ax
  41. mov sp, bp
  42. pop bp
  43. retn 0Ah
  44. WinMain endp

UNIX时间是32位的,所以它返回在DX:AX寄存器对中,而且将他们存储到两个本地16位变量中。然后一个指向值对的指针会被当作参数传给localtime()函数。Localtime()函数有一个struct tm,它将通过C库分配内存,所以只有指向它的指针返回了。顺便一提,这也意味着在它的结果被使用之前,函数不能被再次调用。 对time()和localtime()两个函数来说,Watcom调用转换将会在这里:前四个参数使用AX、DX、BX、CX传递,剩余的通过栈来传递。使用这个转换的函数也会在名字最后使用下划线来标记。 Sprintf()并不使用PASCAL调用转换,也不会使用watcom转换,所以参数将使用寻常的cdecl方式传递(47.1节)。

30.6.1 全局变量

这里用同样的例子,但是变量是全局变量:

  1. #include <windows.h>
  2. #include <time.h>
  3. #include <stdio.h>
  4. char strbuf[256];
  5. struct tm *t;
  6. time_t unix_time;
  7. int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  8. {
  9. unix_time=time(NULL);
  10. t=localtime (&unix_time);
  11. sprintf (strbuf, "%04d-%02d-%02d %02d:%02d:%02d", t->tm_year+1900, t->tm_mon, t->tm_mday,
  12. t->tm_hour, t->tm_min, t->tm_sec);
  13. MessageBox (NULL, strbuf, "caption", MB_OK);
  14. return 0;
  15. };
  1. unix_time_low dw 0
  2. unix_time_high dw 0
  3. t dw 0
  4. WinMain proc near
  5. push bp
  6. mov bp, sp
  7. xor ax, ax
  8. call time_
  9. mov unix_time_low, ax
  10. mov unix_time_high, dx
  11. mov ax, offset unix_time_low
  12. call localtime_
  13. mov bx, ax
  14. mov t, ax ; will not be used in future...
  15. push word ptr [bx] ; seconds
  16. push word ptr [bx+2] ; minutes
  17. push word ptr [bx+4] ; hour
  18. push word ptr [bx+6] ; day
  19. push word ptr [bx+8] ; month
  20. mov ax, [bx+0Ah] ; year
  21. add ax, 1900
  22. push ax
  23. mov ax, offset a04d02d02d02d02 ; "%04d-%02d-%02d %02d:%02d:%02d"
  24. push ax
  25. mov ax, offset strbuf
  26. push ax
  27. call sprintf_
  28. add sp, 10h
  29. xor ax, ax ; NULL
  30. push ax
  31. push ds
  32. mov ax, offset strbuf
  33. push ax
  34. push ds
  35. mov ax, offset aCaption ; "caption"
  36. push ax
  37. xor ax, ax ; MB_OK
  38. push ax
  39. call MESSAGEBOX
  40. xor ax, ax ; return 0
  41. pop bp
  42. retn 0Ah
  43. WinMain endp

T不会被使用,但是编译器还是用代码存储了这个值。因为他并不确定,也许这个值会在某个地方被用到。 # 类