News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Varargs and stdcall

Started by markallyn, September 16, 2017, 01:34:16 AM

Previous topic - Next topic

markallyn

Hello everyone,

I have run into problems using the VARARG keyword.  According to ml VARARG cannot work with STDCALL.  However, STDCALL is the default lcalling convention for masm32 programs as defined in the include files.  I have written a PROC that wants VARARG in its parameter list.  It's being called by a main PROC with STDCALL as its calling convention.  If I define the called PROC as C then I get into mangling issues when the called PROC is invoked.

Perhaps someone could post some snippets that show how to work around this.

Thanks very much,
Mark Allyn

hutch--

Mark,

MASM can handle C calling convention and use VARARG.

Function C PROTO required:DWORD,args:VARARG  ; prototype

mov argcount, 3

invoke Function,argcount,arg1,arg2,arg3        ; function call

When you call a C format function the stack balance is done by MASM from the caller, not within the procedure.

markallyn

Hi Hutch,

Thanks for the quick response.  Looking it over, I decided to send the code that's failing.  I'm having trouble even when I forego include /masm32/include/masm32rt.inc.  I declare the whole thing as .model flat, c.  The code is more complicated than needs be to illustrate the problem.  This stuff assembles fine, but the linker complains about an unresolved external: _addup3.

Quote.486
.model flat, c

includelib \masm32\lib\msvcrt.lib

printf   PROTO  :ptr, :vararg

.DATA
frmt1   BYTE   "The result of adding the three integers is %d",13,10,0

addup3  PROTO  :DWORD, :VARARG

.CODE
main   PROC
   invoke   addup3, 3,5,2,4

   INVOKE   printf, ADDR frmt1, eax
   ret
main   endp
end   main

addup3   PROC   argcount:DWORD, arg1:VARARG
   sub  eax, eax
   sub  esi, esi

   .WHILE argcount > 0
   add  eax, arg1[esi]
   dec  argcount
   inc  esi
   inc  esi
   .ENDW
   ret
addup3  ENDP

Any suggestions would be most welcome.  I love this forum.

Regards,
Mark

hutch--

Mark,

The problem is that by making the default C you add more problems to using STDCALL. Do as I suggested, write the prototype for your function as a C function.

YourProc PROTO C arg1:DWORD,arg2:DWORD,extra_args:VARARG

Then you call it with "invoke" as normal.

invoke YourProc,item1,item2,var1,var2,var3


Note that with a C procedure that you write yourself, you only use RET on exit, you do not balance the stack in the procedure, MASM balances the stack from the location where you called the C function.

aw27

Mark,

The function addup3 is not in the game, you need to place it before end main.

markallyn

HI Hutch, aw27,

Thanks folks.  I amended the code as the following shows.  There is still a bug in my code, but all the names are resolved correctly.  Hutch- you're quite right as always.  STDCALL and VARARG (C) can coexist!  Nicel!!

Quote.486
.model flat, stdcall

includelib \masm32\lib\msvcrt.lib

printf   PROTO C :ptr, :vararg
addup3  PROTO C :DWORD, :VARARG

.DATA
frmt1   BYTE   "The result of adding the three integers is %d",13,10,0


.CODE
main   PROC
   invoke   addup3, 3,5,2,4

   INVOKE   printf, ADDR frmt1, eax
   ret
main   endp

addup3   PROC C  argcount:DWORD, arg1:VARARG
   sub  eax, eax
   sub  esi, esi

   .WHILE argcount > 0
   add  eax, arg1[esi]
   dec  argcount
   inc  esi
   inc  esi
   .ENDW
   ret
addup3  ENDP
END main

The key fix was  the very last line (END main), which AW27 correctly observed.

Thanks again.  Now I can get back to my washing machine ...

Regards,
Mark

markallyn

Hutch, aw27,

For the record, here is the final code.  It runs and links and produces the correct answer:  11.  The bug was the two inc esi instructions.  Changed them to add esi, 4 and all was well.

Quote.486
.model flat, stdcall

includelib \masm32\lib\msvcrt.lib

printf   PROTO C :ptr, :vararg
addup3  PROTO C :DWORD, :VARARG

.DATA
frmt1   BYTE   "The result of adding the three integers is %d",13,10,0


.CODE
main   PROC
   INVOKE   addup3, 3,5,2,4

   INVOKE   printf, ADDR frmt1, eax
   ret
main   endp

addup3   PROC C  argcount:DWORD, arg1:VARARG
   xor  eax, eax
   xor  esi, esi
   
   .WHILE argcount > 0
   add  eax, arg1[esi]
   dec  argcount
   add  esi, 4
   ;inc  esi
   .ENDW
   ret
addup3  ENDP
END main

Thanks again.  As always, I learned a lot of new stuff. 

Regards,
Mark

Vortex

Hi markallyn,

Here is a similar example counting the number of arguments with a macro :

include     \masm32\include\masm32rt.inc

CalcSum PROTO C args:VARARG

cinvoke MACRO func:REQ,args:VARARG

LOCAL paramcount
     
    paramcount=0
   
    FOR item,<args>

    paramcount = paramcount + 1
   
    ENDM

    invoke func,paramcount,args

ENDM

.data

string  db 'Sum = %u',13,10,0

.data?

.code

start:

    cinvoke CalcSum,1,3,5,7,9

    invoke  crt_printf,ADDR string,eax

    cinvoke CalcSum,2,4,6

    invoke  crt_printf,ADDR string,eax

    invoke  ExitProcess,0


CalcSum PROC C args:VARARG

    xor     eax,eax
    mov     ecx,DWORD PTR [esp+8]
    lea     edx,[esp+12]
@@:
    add     eax,DWORD PTR [edx]
    add     edx,4
    dec     ecx
    jnz     @b
   
    ret       
                   
CalcSum ENDP

END start

jimg

Here's a couple more examples-
generic proc SYSCALL uses esi edi Keyword:dword,Parms:vararg
local NumParms,Parm1,Parm2,Parm3
mov eax,[ebp+4]  ; get the return address (address after the call in the calling procedure)
movzx eax,byte ptr [eax+2]  ; which will contain the number of bytes to remove from the stack
;  **** note, this will have to be changed if more than 48 parameters are possible
shr eax,2 ; convert to count
sub eax,1 ; skip fixed argument(s) before the vararg
mov NumParms,eax
invoke SetDlgItemInt,hWin,1003,eax,FALSE

m2m Parm1,Parms[0*4] ; get first arg
m2m Parm2,Parms[1*4] ; etc.
m2m Parm3,Parms[2*4]

ret
generic EndP

; concatenate multiple strings by address
scat proc SYSCALL uses esi edi outloc:dword,inloc:vararg
    mov edi,outloc
    mov eax,[ebp+4]  ; get the return address (address after the call in the calling procedure)
    movzx ecx,byte ptr [eax+2]  ; which will contain the number of bytes to remove from the stack
    sub ecx,4*1 ; skip any fixed arguments before the vararg
    mov edx,0   ; index from inloc
catloop:
    cmp edx,ecx
    je done
    mov esi,inloc[edx]  ; address of the input locations
    add edx,4
@@: lodsb
    or al,al
    jz catloop  ; go get next one
    stosb
    jmp @b
done:
    stosb       ; zero from last one
    ret
scat EndP

AddNumbers Proc SYSCALL args:VARARG
mov eax,[esp+4] ; return address
movzx ecx,byte ptr [eax+2]
mov eax,0
addloop:
sub ecx,4 ; get rid of the extra for the return address
jb done ; process the zero offset also
mov edx,args[ecx]
add eax,args[ecx]
jmp addloop
done:
ret
AddNumbers EndP

AddNumbers2 Proc SYSCALL dummy:dword,args:VARARG
mov eax,[ebp+4] ; return address
movzx ecx,byte ptr [eax+2]  ; get number of arguments on stack
sub ecx,4*1 ; skip all entries before args (4*number of extras)
mov eax,0
addloop:
sub ecx,4 ; get rid of the extra for the return address
jb done ; process the zero offset also
add eax,args[ecx]
jmp addloop
done:
ret
AddNumbers2 EndP

jj2007

And yet another one, printing a variable number of strings:include \masm32\include\masm32rt.inc ; pure Masm32

.code
PrintStrings proc C uses ebx edi argCount, strings:VARARG
Local buffer[8192]:BYTE
  lea edi, buffer
  xor ebx, ebx ; arg counter
  mov [edi], bl ; clear the buffer
  .repeat
invoke lstrcat, edi, strings[4*ebx]
inc ebx
  .until ebx>=argCount
  print edi
  ret
PrintStrings endp

start:
  invoke PrintStrings, 1, cfm$("Hello\n")
  invoke PrintStrings, 2, chr$("Hello"), cfm$(" World\n")
  invoke PrintStrings, 4, chr$("Hello"), chr$(" World"), chr$(", how"), cfm$(" are you?\n")
  inkey "That was simple, right?"
  exit

end start


Similar but the end of the arg gets signalled by a zero:include \masm32\include\masm32rt.inc ; pure Masm32

.code
PrintStrings proc C uses ebx edi strings:VARARG
Local buffer[8192]:BYTE
  lea edi, buffer
  xor ebx, ebx ; arg counter
  mov [edi], bl ; clear the buffer
@@:
  mov ecx, strings[4*ebx]
  jecxz @F
  invoke lstrcat, edi, ecx
  inc ebx
  jmp @B
@@:
  print edi
  ret
PrintStrings endp

start:
  invoke PrintStrings, cfm$("Hello\n"), 0
  invoke PrintStrings, chr$("Hello"), cfm$(" World\n"), 0
  invoke PrintStrings, chr$("Hello"), chr$(" World"), chr$(", how"), cfm$(" are you?\n"), 0
  inkey "That was simple, right?"
  exit

end start


And finally, a macro variant:include \masm32\include\masm32rt.inc ; pure Masm32

PrintStrings MACRO args:VARARG
Local argCt
  argCt=0
  for arg, <args>
argCt=argCt+1
push reparg(arg)
  ENDM
  push argCt
  call psp
ENDM

.code
psp proc C uses ebx edi strings:VARARG
lea edi, [esp-8000] ; create a buffer
mov [edi], bl ; clear the buffer
mov ebx, strings ; arg counter

@@: invoke lstrcat, edi, strings[4*ebx]
dec ebx
jg @B

@@: print edi
ret
psp endp

start:
PrintStrings cfm$("Hello\n")
PrintStrings chr$("Hello"), cfm$(" World\n")
PrintStrings chr$("Hello"), chr$(" World"), chr$(", how"), cfm$(" are you?\n")
inkey "That was simple, right?"
exit

end start

aw27

Quote from: markallyn on September 16, 2017, 01:34:16 AM
According to ml VARARG cannot work with STDCALL.

In ASM it is possible to "simulate" a VARARG in STDCALL, but I can't really find it useful. Even Microsoft uses CDecl in its API when there are variadic arguments. We discussed this here not long ago.

jj2007

Quote from: aw27 on September 17, 2017, 03:28:17 AMIn ASM it is possible to "simulate" a VARARG in STDCALL, but I can't really find it useful

It's a matter of taste, maybe something for those who like being "close to the metal" ;)include \masm32\include\masm32rt.inc ; pure Masm32

PrintStrings MACRO args:VARARG
Local argCt
  argCt=0
  for arg, <args>
argCt=argCt+1
push reparg(arg)
  ENDM
  push argCt
  call psp
ENDM

.code
psp:
push edi
push ebx
lea edi, [esp-8000] ; create a buffer
mov [edi], bl         ; clear the buffer
strings equ [esp+3*4]
mov ebx, strings ; arg counter

@@: invoke lstrcat, edi, strings[4*ebx]
dec ebx
jg @B

@@: print edi
pop ebx
pop edi
pop edx
pop eax
lea esp, [esp+4*eax]
jmp edx      ; oops, no RET??

start:
PrintStrings cfm$("Hello\n")
PrintStrings chr$("Hello"), cfm$(" World\n")
PrintStrings "Hello", " World", ", how", cfm$(" are you?\n")
inkey "That was simple, right?"
exit

end start


Not recommended, but it works, of course :bgrin:

Vortex

#12
Quote from: aw27 on September 17, 2017, 03:28:17 AM
In ASM it is possible to "simulate" a VARARG in STDCALL, but I can't really find it useful. Even Microsoft uses CDecl in its API when there are variadic arguments. We discussed this here not long ago.

Exactly. The simulation is possible but not very useful as you said. Here is a quick example :

include     \masm32\include\masm32rt.inc

CalcSum PROTO


sinvoke MACRO func:REQ,args:VARARG

LOCAL paramcount,paramcount2
     
    paramcount=0
   
    FOR item,<args>

        paramcount = paramcount + 1
   
    ENDM

    paramcount2 = paramcount + 1

    invoke @CatStr(<pr>,%paramcount2) PTR func,paramcount,args

ENDM


.data

string  db 'Sum = %u',13,10,0

.code

start:

    sinvoke CalcSum,1,3,5,7,9

    invoke  crt_printf,ADDR string,eax

    sinvoke CalcSum,2,4,6

    invoke  crt_printf,ADDR string,eax

    invoke  ExitProcess,0


CalcSum PROC

    xor     eax,eax
    mov     ecx,DWORD PTR [esp+4]
    lea     edx,[esp+8]
@@:
    add     eax,DWORD PTR [edx]
    add     edx,4
    dec     ecx
    jnz     @b

    mov     ecx,DWORD PTR [esp+4]
    inc     ecx
    shl     ecx,2
    mov     edx,DWORD PTR [esp]     ; get the return address
    add     esp,ecx                 ; balance the stack
    mov     DWORD PTR [esp],edx
    ret       
                   
CalcSum ENDP

END start

aw27

A variation of Vortex's that does not send the count of parameters as one of the parameters:


.486       
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
includelib \masm32\lib\msvcrt.lib
printf proto C :ptr, :vararg

calcsum proto

varginvoke MACRO func:REQ,args:VARARG
push ebp
mov ebp, esp
paramcount=0
   
FOR item,<args>
        paramcount = paramcount + 1
ENDM

INVOKE @CatStr(<pr>,%paramcount) ptr func, args
pop ebp
ENDM

.data
format0 db "result %d",10,0

.code

main proc
varginvoke calcsum,1,2,3,4,5
invoke printf, offset format0, eax
ret
main endp



calcsum proc
mov ecx, esp
add ecx, 4
mov eax, [ecx]
sub ebp, 4
.while ecx<ebp
add ecx, 4
add eax,[ecx]
.endw
mov ecx, [esp]
add ebp, 4
mov esp, ebp
push ecx
ret
calcsum endp

end main