汇编调用C函数,C调用汇编子程序

时间:2021-10-16 01:00:49
     既然汇编中用C的变量的话,要加下划线,那为什么汇编调用C函数的时候不按同样的名字转换法(前面加下划线,后面如果是stdcall的话加@para_num*4)来调用呢?而是直接就用C里面声明的原名字?(非内联汇编)
     汇编中编写stdcall的子程序时候,不是要自己调整堆栈吗?那么最后应该写成add esp, para_num*4    ret或者ret para_num*4啊。但为什么还是直接写ret呢?那谁来调整堆栈?还有,我上面的写法对吗?望指正。
     当用invoke来调用stdcall 的API时,如果用call来代替,该怎么写?

13 个解决方案

#1


这是个简单例子:
/* C语言程序:a1.c */
    extern asub();
    main()
    {    asub();     /* 调用汇编语言子程序 */
    }
     csub(char * str)   /* C语言函数,str是地址参数 */    {    printf("%s\n",str);
    }
    ; 汇编语言程序:a1s.asm
     .model small,c
     extern csub:near
     .data
    astring   db ’OK, Assembly !’,0dh,0ah,’$’
    cstring   db ’Good, Turbo C 2.0 !’,0
     .code
     PUBLIC asub
    asub proc
     mov dx,offset astring  ;汇编语言子程序显示信息
     mov ah,09h
     int 21h
     mov ax,offset cstring  ;得到字符串的偏移地址
     push ax  ;压入调用参数
      call csub ;调用C函数
     add sp,2 ;平衡堆栈
     ret
    asub endp
     end

#2


对于汇编中的函数(PROC)同样有修饰符比如syscall,c,stdcall 楼主在与C语言对接时不要搞错了。

#3


谢谢楼上2位,可否进一步回答一下我的疑问?

#4


顶一下,等待解答!

#5


invoke ->call
先自己压栈然后call
主要是注意stdcall cdecl fastcall等的差异
这个google一下就知道了

#6


睡前顶一下,谢谢各位,现在只剩下第一个问题了。

#7


为什么没高手给解释下呢?难道标题不够吸引人?

#8


引用楼主 sytstarac 的回复:
    汇编中编写stdcall的子程序时候,不是要自己调整堆栈吗?那么最后应该写成add esp, para_num*4    ret或者ret para_num*4啊。但为什么还是直接写ret呢?那谁来调整堆栈?还有,我上面的写法对吗?望指正。

stdcall 一般情况下是不需要调整堆栈的(见下面说明),调整堆栈的概念来自于c。在使用masm宏汇编伪指令的情况下,调整堆栈一般都是通过伪指令来完成的,用户并不需要关心堆栈的平衡问题,这也就是使用宏汇编所带来的最大好处,许多人都认为使用invoke不如直接使用call来得直观,但实际上在proto、invoke、proc等伪指令自动完成了堆栈平衡。其中proc伪指令更是为使用者建立了局部变量的概念。
这是proc的语法
  Syntax:   label PROC [distance] [langtype] [visibility]
            [<prologuearg>]
            [USES reglist] [,parameter [:tag]]...
                    [LOCAL varlist]
                    statements
            label ENDP
[distanc]       距离可选项   near, near16, near32, far, far16, far32
[langtype]      调用类型选项 C , syscall , stdcall , basic , fortran , pascall
[visibility]    可见性      PRIVATE, PUBLIC, EXPORT
[<prologuearg>] 框架     可选择缺省框架 , 无框架 , 用户自建框架 ,一般很少使用
[USES reglist]  使用寄存器   完成保护寄存器的作用,即在程序头 push 程序位 pop 所指定的寄存器
[,parameter [:tag]]... 入口参数和类型,对于 C , syscall , stdcall 可以使用 VARARG (不定长参数),其中 stdcall 需要根据变长参数来自己平衡堆栈
[LOCAL varlist] 局部变量
下面我们将以一个简单的冒泡排序bubble来详细说明这些伪指令具体起了什么作用:
首先我们选择远程 stdcall 调用,代码中 uses 选项指令的需要保存的寄存器,并指定了两个入口参数,local 指定了两个局部变量。因为局部变量是建立在堆栈框架内,所以子程序返回时自动清除内存。
dseg  segment para 'data'
  Source  dw  50,90,20,80,100,10,0
  Len     equ  ($ - Source) / 2 -1
dseg  ends
public bubble
cseg  segment  para  'code'
      assume  cs:cseg
; 入口 pSource: 排序数据起始地址
;     sLen:    排序数据长度
;返回  pSource: 已经排序的数据
bubble proc far stdcall uses ds es cx bx di, pSource:word, sLen:word
local save_cnt:word
local start_addr:word
    mov  ax, dseg
    mov  es, ax
    mov  ds, ax
    assume ds:dseg, es:dseg
    mov  di, pSource
    mov  start_addr, di
    mov  cx, sLen
    mov  save_cnt, cx
init:
    mov  bx, 1
    dec  save_cnt
    jz   sorted
    mov  cx, save_cnt
    mov  di, start_addr
next:
    mov  ax, es:[di]
    cmp  es:[di + 2], ax
    jae  cont
    xchg es:[di + 2], ax
    mov  es:[di], ax
    sub  bx, bx
cont:
    add  di, 2
    loop next
    cmp  bx,0
    je   init
sorted:
    assume ds:nothing, es:nothing
    ret
bubble endp
cseg  ends
text  segment para 'code'
start:
  invoke bubble, addr Source, Len
  int  20h
text  ends
end start

将上面的代码编译链接后生成可执行文件bubble.exe。我们使用IDA来具体分析bubble.exe,看看这些伪指令具体坐了些什么事。
len  = 6
dseg segment byte stack 'data' use16
    assume cs:dseg
    assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
Source dw    32h,   5Ah,   14h,   50h,   64h,   0Ah,     0, 0; 0
dseg ends
; ==========================================================================
cseg segment byte public 'CODE' use16
    assume cs:cseg
    assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
; *************** S U B R O U T I N E ***************************************
bubble proc far
start_addr = word ptr -4
save_cnt   = word ptr -2
pSource    = word ptr  6
sLen       = word ptr  8
    push bp                 ; / proc 伪指令产生的以bp位堆栈指针的堆栈框架
    mov bp, sp              ; | 堆栈框架的顶部
    add sp, -4              ; \ 堆栈框架的深度,这里因为使用了两个局部变量,所以 -4
    push ds                 ; /
    push es                 ; |
    push cx                 ; | uses 选项产生的代码 push xx
    push bx                 ; |
    push di                 ; \
    mov ax, seg dseg
    mov es, ax
    assume es:dseg
    mov ds, ax
    assume ds:dseg
    mov di, [bp+pSource]    ; 入口参数 pSource,即 [bp + 6],由 mov  ax, len; push ax 在堆栈上产生
    mov [bp+start_addr], di ; 局部变量 start_addr,即 [bp - 4],由 sp - 4 在堆栈框架内产生
    mov cx, [bp+sLen]       ; 入口参数 sLen,即 [bp +8],由 lea  ax, Source; push ax 在堆栈上产生
    mov [bp+save_cnt], cx   ; 局部变量 save_cnt,即 [bp - 2],由 sp - 4 在堆栈框架内产生
init:
    mov bx, 1
    dec [bp+save_cnt]
    jz  short sorted
    mov cx, [bp+save_cnt]
    mov di, [bp+start_addr]
next:
    mov ax, es:[di]
    cmp es:[di+2], ax
    jnb short cont
    xchg ax, es:[di+2]
    mov es:[di], ax
    sub bx, bx
cont:
    add di, 2
    loop next
    cmp bx, 0
    jz  short init
sorted:
    pop di                   ; /
    pop bx                   ; |
    pop cx                   ; | uses 选项产生的代码 pop xx
    pop es                   ; |
    assume es:nothing        ; |
    pop ds                   ; \
    assume ds:nothing
    mov sp, bp               ; / 恢复堆栈指针,即清除局部变量,释放堆栈
    pop bp                   ; \ 由endp伪指令产生的代码
    retf 4                   ; 由 far 选项产生 retf,由入口产生的长度决定堆栈平衡的长度 4
bubble endp
    align 10h
cseg ends
; ==========================================================================
text segment byte public 'CODE' use16
    assume cs:text
    assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
start proc near
    mov ax, len              ; / 由 invoke bubble, addr Source, Len 伪指令最右面的参数
    push ax                  ; \ Len 产生的代码
    lea ax, Source           ; / addr 指示符产生 lea ax, Source,即指针
    push ax                  ; \ 和 mov ax, offset Source 效果相同,取决于编译器
                             ; C,syscall,stdcall调用,参数入栈顺序由右至左
    call bubble              ; 参数入栈后开始调用,这里是远程调用,由子程序的 far 决定
                             ; 先压段地址,再压返回地址,相当于 push cs; push ip+5, jmp bubble
    int 20h
start endp
text ends
    end start

待续......

#9


从上面IDA的分析可以看出,stdcall 的栈平衡是通过返回指令来完成的,即 retf 4,而堆栈平衡的深度 4,则是由这些伪指令在编译时根据入口参数的长度自动设定的,用户不需要自己计算栈平衡。
同样的代码,我们来看看 C 调用格式有什么不同,下面我们省略了和前面相同的代码:
bubble proc far c uses ds es cx bx di, pSource:word, sLen:word
local save_cnt:word
local start_addr:word
......
......
    ret
bubble endp
text  segment para 'code'
start:
  ; 注意这里的C调用,和前面的stdcall调用是完全一样的
  invoke bubble, addr Source, Len
  int  20h
text  ends
end start

其实整个程序只是将 stdcall -> C ,其他都保持原样。
下面通过IDA,我们来看看 C 调用和stdcall到底有什么不同:
cseg segment byte public 'CODE' use16
    assume cs:cseg
    assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
bubble proc far
start_addr = word ptr -4
save_cnt   = word ptr -2
pSource    = word ptr  6
sLen       = word ptr  8
    push bp
    mov bp, sp
    add sp, -4
......
......
    pop di
    pop bx
    pop cx
    pop es
    assume es:nothing
    pop ds
    assume ds:nothing
    mov sp, bp
    pop bp
    retf                     ; 由 far 指示直接生成 retf 指令,不过没有对入口参数进行堆栈平衡
bubble endp
cseg ends
test segment byte public 'CODE' use16
    assume cs:test
    assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
    public start
start proc near
    mov ax, Len
    push ax
    lea ax, Source
    push ax
    call bubble
    add sp, 4                ; 在这里进行入口参数平衡,这就是 C 调用模式的堆栈平衡方法
                             ; 这段代码由 invoke 根据C调用指引自动产生,无需用户干涉
    int 20h
start endp
test ends
    end start

从上面两个实例中,我们可以看出,stdcall 和 C 调用格式,参数入栈顺序完全相同,都是由右至左,顺序入栈。其中stdcall由子程序在返回指令中获得堆栈平衡,而 C 调用则由 call 返回后,直接通过 add sp, ?? 来平衡。而这些平衡过程都是由伪指令通过编译器自动完成。
接着我们来看看 pascal 调用的方法,同样只需要将 C -> pascall ,然后编译链接即可:
bubble proc far pascal uses ds es cx bx di, pSource:word, sLen:word

使用IDA分析bubble.exe结果如下:
cseg segment byte public 'CODE' use16
    assume cs:cseg
    assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
bubble proc far
start_addr = word ptr -4
save_cnt   = word ptr -2
sLen       = word ptr  6     ; / 注意这里的次序
pSource    = word ptr  8     ; \ 先 sLen 后 pSource,和前面的两个调用相反
    push bp
    mov bp, sp
    add sp, -4
    push ds
    push es
    push cx
    push bx
    push di
......
......
    pop di
    pop bx
    pop cx
    pop es
    assume es:nothing
    pop ds
    assume ds:nothing
    mov sp, bp
    pop bp
    retf 4                   ; 栈平衡方法和 stdcall 完全相同
bubble endp
cseg ends
test segment byte public 'CODE' use16
    assume cs:test
    assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
    public start
start proc near
    lea ax, Source           ; 入栈顺序由左至右,和 C,stdcall相反
    push ax
    mov ax, Len
    push ax
    call bubble
    int 20h
start endp
test ends
    end start

从上面的分析,可以看出,pascal 调用和 C, stdcall调用,参数入栈顺序相反,由左至右,而堆栈平衡方法和 stdcall 相同。
至于其他的调用方式由什么不同,大家可以自己去检验,我就不多罗嗦了。

引用楼主 sytstarac 的回复:
    当用invoke来调用stdcall 的API时,如果用call来代替,该怎么写?

这个问题从上面的几个实例中都有显示,这里就不多说了。
前几天那个相杀人的家伙也提有类似的问题,我回答敲字手都敲痛了,结果一分不给,太令人失望了。
http://topic.csdn.net/u/20090730/20/2dc25c2f-fb3b-4048-8c03-81e730f8170b.html

#10


to gzgzlxg:好长!不过非常感谢,现在对堆栈平衡还有伪指令的辅助作用总算了解了。但第一个问题还是没搞清楚:
csource.h //c头文件
...
int func_c(int);
csource.c //c源文件
extern int func_asm(int,int);
void myown()
{
if(func_asm(2,3)) //call
.....
}
asmsource.asm //汇编源文件
func_c proto c :dword
func_asm proc c arg1:dword,arg2:dword
...
call func_c   //call
func_asm endp

如上,这样互相调用。名字都是一样的。为什么?而变量名字的互相引用是要加下划线的。

#11


我可不可以这样理解:
从C编译成汇编,只对变量名进行转换(加下划线),而函数名不转换。从汇编汇编成机器语言obj文件时,或者link时,才会发生函数名的转换。

#12


引用 10 楼 sytstarac 的回复:
如上,这样互相调用。名字都是一样的。为什么?而变量名字的互相引用是要加下划线的。

引用 11 楼 sytstarac 的回复:
我可不可以这样理解: 
从C编译成汇编,只对变量名进行转换(加下划线),而函数名不转换。从汇编汇编成机器语言obj文件时,或者link时,才会发生函数名的转换。

我怎么都读不明白你到底要问什么问题,“ 如上,这样互相调用。名字都是一样的。”--什么名字是一样的,看不懂。
这里也一样,“ 从C编译成汇编,只对变量名进行转换(加下划线)......”--加下划线?那是编译器和链接器之间的协议,爱加什么加什么,你管这些干什么,不能理解。
很抱歉,无法回答你的问题。
不论是 c 调用汇编,还是汇编调用 c ,只要调用申明一致即可,即都使用一种 langtype,如最常使用的 cdecl (缩写 c) 或其他的调用申明,只要两边一致即可。1#楼给出的例子已经写的很清楚了,其中 c 使用的是缺省调用申明,即cdecl,所以没有显式申明,而汇编中在第一句
.model small, c  ; 那个 c ,就是 cdecl 调用规则的申明。

#13


不知道你结了没,如果没结也不要结给我,我只是路过学习了一下,把我所了解的说一下

1、变量名字的互相引用是要加下划线的:这是因为对于外部变量,你只用extern声明,并没有声明编译语言,而如果这个变量是在C源文件里定义的,那在编译的时候就会按C命名习惯来对其重新命名如
      Var---->_Var  但如果个变量是在汇编源文件里定义的,那么在编译的时候就肯定会按汇编编译器的命名习惯来对其命名
2、对于函数名却不一样,因为你在声明函数的时候已经指明了 语言类型,就是前面说的c,stdcall,pascl等,这样的话,编译器在看到这个声明的时候就会自动根据你声明的语言类型所用的命名习惯来对函数名命名

#1


这是个简单例子:
/* C语言程序:a1.c */
    extern asub();
    main()
    {    asub();     /* 调用汇编语言子程序 */
    }
     csub(char * str)   /* C语言函数,str是地址参数 */    {    printf("%s\n",str);
    }
    ; 汇编语言程序:a1s.asm
     .model small,c
     extern csub:near
     .data
    astring   db ’OK, Assembly !’,0dh,0ah,’$’
    cstring   db ’Good, Turbo C 2.0 !’,0
     .code
     PUBLIC asub
    asub proc
     mov dx,offset astring  ;汇编语言子程序显示信息
     mov ah,09h
     int 21h
     mov ax,offset cstring  ;得到字符串的偏移地址
     push ax  ;压入调用参数
      call csub ;调用C函数
     add sp,2 ;平衡堆栈
     ret
    asub endp
     end

#2


对于汇编中的函数(PROC)同样有修饰符比如syscall,c,stdcall 楼主在与C语言对接时不要搞错了。

#3


谢谢楼上2位,可否进一步回答一下我的疑问?

#4


顶一下,等待解答!

#5


invoke ->call
先自己压栈然后call
主要是注意stdcall cdecl fastcall等的差异
这个google一下就知道了

#6


睡前顶一下,谢谢各位,现在只剩下第一个问题了。

#7


为什么没高手给解释下呢?难道标题不够吸引人?

#8


引用楼主 sytstarac 的回复:
    汇编中编写stdcall的子程序时候,不是要自己调整堆栈吗?那么最后应该写成add esp, para_num*4    ret或者ret para_num*4啊。但为什么还是直接写ret呢?那谁来调整堆栈?还有,我上面的写法对吗?望指正。

stdcall 一般情况下是不需要调整堆栈的(见下面说明),调整堆栈的概念来自于c。在使用masm宏汇编伪指令的情况下,调整堆栈一般都是通过伪指令来完成的,用户并不需要关心堆栈的平衡问题,这也就是使用宏汇编所带来的最大好处,许多人都认为使用invoke不如直接使用call来得直观,但实际上在proto、invoke、proc等伪指令自动完成了堆栈平衡。其中proc伪指令更是为使用者建立了局部变量的概念。
这是proc的语法
  Syntax:   label PROC [distance] [langtype] [visibility]
            [<prologuearg>]
            [USES reglist] [,parameter [:tag]]...
                    [LOCAL varlist]
                    statements
            label ENDP
[distanc]       距离可选项   near, near16, near32, far, far16, far32
[langtype]      调用类型选项 C , syscall , stdcall , basic , fortran , pascall
[visibility]    可见性      PRIVATE, PUBLIC, EXPORT
[<prologuearg>] 框架     可选择缺省框架 , 无框架 , 用户自建框架 ,一般很少使用
[USES reglist]  使用寄存器   完成保护寄存器的作用,即在程序头 push 程序位 pop 所指定的寄存器
[,parameter [:tag]]... 入口参数和类型,对于 C , syscall , stdcall 可以使用 VARARG (不定长参数),其中 stdcall 需要根据变长参数来自己平衡堆栈
[LOCAL varlist] 局部变量
下面我们将以一个简单的冒泡排序bubble来详细说明这些伪指令具体起了什么作用:
首先我们选择远程 stdcall 调用,代码中 uses 选项指令的需要保存的寄存器,并指定了两个入口参数,local 指定了两个局部变量。因为局部变量是建立在堆栈框架内,所以子程序返回时自动清除内存。
dseg  segment para 'data'
  Source  dw  50,90,20,80,100,10,0
  Len     equ  ($ - Source) / 2 -1
dseg  ends
public bubble
cseg  segment  para  'code'
      assume  cs:cseg
; 入口 pSource: 排序数据起始地址
;     sLen:    排序数据长度
;返回  pSource: 已经排序的数据
bubble proc far stdcall uses ds es cx bx di, pSource:word, sLen:word
local save_cnt:word
local start_addr:word
    mov  ax, dseg
    mov  es, ax
    mov  ds, ax
    assume ds:dseg, es:dseg
    mov  di, pSource
    mov  start_addr, di
    mov  cx, sLen
    mov  save_cnt, cx
init:
    mov  bx, 1
    dec  save_cnt
    jz   sorted
    mov  cx, save_cnt
    mov  di, start_addr
next:
    mov  ax, es:[di]
    cmp  es:[di + 2], ax
    jae  cont
    xchg es:[di + 2], ax
    mov  es:[di], ax
    sub  bx, bx
cont:
    add  di, 2
    loop next
    cmp  bx,0
    je   init
sorted:
    assume ds:nothing, es:nothing
    ret
bubble endp
cseg  ends
text  segment para 'code'
start:
  invoke bubble, addr Source, Len
  int  20h
text  ends
end start

将上面的代码编译链接后生成可执行文件bubble.exe。我们使用IDA来具体分析bubble.exe,看看这些伪指令具体坐了些什么事。
len  = 6
dseg segment byte stack 'data' use16
    assume cs:dseg
    assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
Source dw    32h,   5Ah,   14h,   50h,   64h,   0Ah,     0, 0; 0
dseg ends
; ==========================================================================
cseg segment byte public 'CODE' use16
    assume cs:cseg
    assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
; *************** S U B R O U T I N E ***************************************
bubble proc far
start_addr = word ptr -4
save_cnt   = word ptr -2
pSource    = word ptr  6
sLen       = word ptr  8
    push bp                 ; / proc 伪指令产生的以bp位堆栈指针的堆栈框架
    mov bp, sp              ; | 堆栈框架的顶部
    add sp, -4              ; \ 堆栈框架的深度,这里因为使用了两个局部变量,所以 -4
    push ds                 ; /
    push es                 ; |
    push cx                 ; | uses 选项产生的代码 push xx
    push bx                 ; |
    push di                 ; \
    mov ax, seg dseg
    mov es, ax
    assume es:dseg
    mov ds, ax
    assume ds:dseg
    mov di, [bp+pSource]    ; 入口参数 pSource,即 [bp + 6],由 mov  ax, len; push ax 在堆栈上产生
    mov [bp+start_addr], di ; 局部变量 start_addr,即 [bp - 4],由 sp - 4 在堆栈框架内产生
    mov cx, [bp+sLen]       ; 入口参数 sLen,即 [bp +8],由 lea  ax, Source; push ax 在堆栈上产生
    mov [bp+save_cnt], cx   ; 局部变量 save_cnt,即 [bp - 2],由 sp - 4 在堆栈框架内产生
init:
    mov bx, 1
    dec [bp+save_cnt]
    jz  short sorted
    mov cx, [bp+save_cnt]
    mov di, [bp+start_addr]
next:
    mov ax, es:[di]
    cmp es:[di+2], ax
    jnb short cont
    xchg ax, es:[di+2]
    mov es:[di], ax
    sub bx, bx
cont:
    add di, 2
    loop next
    cmp bx, 0
    jz  short init
sorted:
    pop di                   ; /
    pop bx                   ; |
    pop cx                   ; | uses 选项产生的代码 pop xx
    pop es                   ; |
    assume es:nothing        ; |
    pop ds                   ; \
    assume ds:nothing
    mov sp, bp               ; / 恢复堆栈指针,即清除局部变量,释放堆栈
    pop bp                   ; \ 由endp伪指令产生的代码
    retf 4                   ; 由 far 选项产生 retf,由入口产生的长度决定堆栈平衡的长度 4
bubble endp
    align 10h
cseg ends
; ==========================================================================
text segment byte public 'CODE' use16
    assume cs:text
    assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
start proc near
    mov ax, len              ; / 由 invoke bubble, addr Source, Len 伪指令最右面的参数
    push ax                  ; \ Len 产生的代码
    lea ax, Source           ; / addr 指示符产生 lea ax, Source,即指针
    push ax                  ; \ 和 mov ax, offset Source 效果相同,取决于编译器
                             ; C,syscall,stdcall调用,参数入栈顺序由右至左
    call bubble              ; 参数入栈后开始调用,这里是远程调用,由子程序的 far 决定
                             ; 先压段地址,再压返回地址,相当于 push cs; push ip+5, jmp bubble
    int 20h
start endp
text ends
    end start

待续......

#9


从上面IDA的分析可以看出,stdcall 的栈平衡是通过返回指令来完成的,即 retf 4,而堆栈平衡的深度 4,则是由这些伪指令在编译时根据入口参数的长度自动设定的,用户不需要自己计算栈平衡。
同样的代码,我们来看看 C 调用格式有什么不同,下面我们省略了和前面相同的代码:
bubble proc far c uses ds es cx bx di, pSource:word, sLen:word
local save_cnt:word
local start_addr:word
......
......
    ret
bubble endp
text  segment para 'code'
start:
  ; 注意这里的C调用,和前面的stdcall调用是完全一样的
  invoke bubble, addr Source, Len
  int  20h
text  ends
end start

其实整个程序只是将 stdcall -> C ,其他都保持原样。
下面通过IDA,我们来看看 C 调用和stdcall到底有什么不同:
cseg segment byte public 'CODE' use16
    assume cs:cseg
    assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
bubble proc far
start_addr = word ptr -4
save_cnt   = word ptr -2
pSource    = word ptr  6
sLen       = word ptr  8
    push bp
    mov bp, sp
    add sp, -4
......
......
    pop di
    pop bx
    pop cx
    pop es
    assume es:nothing
    pop ds
    assume ds:nothing
    mov sp, bp
    pop bp
    retf                     ; 由 far 指示直接生成 retf 指令,不过没有对入口参数进行堆栈平衡
bubble endp
cseg ends
test segment byte public 'CODE' use16
    assume cs:test
    assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
    public start
start proc near
    mov ax, Len
    push ax
    lea ax, Source
    push ax
    call bubble
    add sp, 4                ; 在这里进行入口参数平衡,这就是 C 调用模式的堆栈平衡方法
                             ; 这段代码由 invoke 根据C调用指引自动产生,无需用户干涉
    int 20h
start endp
test ends
    end start

从上面两个实例中,我们可以看出,stdcall 和 C 调用格式,参数入栈顺序完全相同,都是由右至左,顺序入栈。其中stdcall由子程序在返回指令中获得堆栈平衡,而 C 调用则由 call 返回后,直接通过 add sp, ?? 来平衡。而这些平衡过程都是由伪指令通过编译器自动完成。
接着我们来看看 pascal 调用的方法,同样只需要将 C -> pascall ,然后编译链接即可:
bubble proc far pascal uses ds es cx bx di, pSource:word, sLen:word

使用IDA分析bubble.exe结果如下:
cseg segment byte public 'CODE' use16
    assume cs:cseg
    assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
bubble proc far
start_addr = word ptr -4
save_cnt   = word ptr -2
sLen       = word ptr  6     ; / 注意这里的次序
pSource    = word ptr  8     ; \ 先 sLen 后 pSource,和前面的两个调用相反
    push bp
    mov bp, sp
    add sp, -4
    push ds
    push es
    push cx
    push bx
    push di
......
......
    pop di
    pop bx
    pop cx
    pop es
    assume es:nothing
    pop ds
    assume ds:nothing
    mov sp, bp
    pop bp
    retf 4                   ; 栈平衡方法和 stdcall 完全相同
bubble endp
cseg ends
test segment byte public 'CODE' use16
    assume cs:test
    assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
    public start
start proc near
    lea ax, Source           ; 入栈顺序由左至右,和 C,stdcall相反
    push ax
    mov ax, Len
    push ax
    call bubble
    int 20h
start endp
test ends
    end start

从上面的分析,可以看出,pascal 调用和 C, stdcall调用,参数入栈顺序相反,由左至右,而堆栈平衡方法和 stdcall 相同。
至于其他的调用方式由什么不同,大家可以自己去检验,我就不多罗嗦了。

引用楼主 sytstarac 的回复:
    当用invoke来调用stdcall 的API时,如果用call来代替,该怎么写?

这个问题从上面的几个实例中都有显示,这里就不多说了。
前几天那个相杀人的家伙也提有类似的问题,我回答敲字手都敲痛了,结果一分不给,太令人失望了。
http://topic.csdn.net/u/20090730/20/2dc25c2f-fb3b-4048-8c03-81e730f8170b.html

#10


to gzgzlxg:好长!不过非常感谢,现在对堆栈平衡还有伪指令的辅助作用总算了解了。但第一个问题还是没搞清楚:
csource.h //c头文件
...
int func_c(int);
csource.c //c源文件
extern int func_asm(int,int);
void myown()
{
if(func_asm(2,3)) //call
.....
}
asmsource.asm //汇编源文件
func_c proto c :dword
func_asm proc c arg1:dword,arg2:dword
...
call func_c   //call
func_asm endp

如上,这样互相调用。名字都是一样的。为什么?而变量名字的互相引用是要加下划线的。

#11


我可不可以这样理解:
从C编译成汇编,只对变量名进行转换(加下划线),而函数名不转换。从汇编汇编成机器语言obj文件时,或者link时,才会发生函数名的转换。

#12


引用 10 楼 sytstarac 的回复:
如上,这样互相调用。名字都是一样的。为什么?而变量名字的互相引用是要加下划线的。

引用 11 楼 sytstarac 的回复:
我可不可以这样理解: 
从C编译成汇编,只对变量名进行转换(加下划线),而函数名不转换。从汇编汇编成机器语言obj文件时,或者link时,才会发生函数名的转换。

我怎么都读不明白你到底要问什么问题,“ 如上,这样互相调用。名字都是一样的。”--什么名字是一样的,看不懂。
这里也一样,“ 从C编译成汇编,只对变量名进行转换(加下划线)......”--加下划线?那是编译器和链接器之间的协议,爱加什么加什么,你管这些干什么,不能理解。
很抱歉,无法回答你的问题。
不论是 c 调用汇编,还是汇编调用 c ,只要调用申明一致即可,即都使用一种 langtype,如最常使用的 cdecl (缩写 c) 或其他的调用申明,只要两边一致即可。1#楼给出的例子已经写的很清楚了,其中 c 使用的是缺省调用申明,即cdecl,所以没有显式申明,而汇编中在第一句
.model small, c  ; 那个 c ,就是 cdecl 调用规则的申明。

#13


不知道你结了没,如果没结也不要结给我,我只是路过学习了一下,把我所了解的说一下

1、变量名字的互相引用是要加下划线的:这是因为对于外部变量,你只用extern声明,并没有声明编译语言,而如果这个变量是在C源文件里定义的,那在编译的时候就会按C命名习惯来对其重新命名如
      Var---->_Var  但如果个变量是在汇编源文件里定义的,那么在编译的时候就肯定会按汇编编译器的命名习惯来对其命名
2、对于函数名却不一样,因为你在声明函数的时候已经指明了 语言类型,就是前面说的c,stdcall,pascl等,这样的话,编译器在看到这个声明的时候就会自动根据你声明的语言类型所用的命名习惯来对函数名命名