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
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.
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
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.
Mark,
The function addup3 is not in the game, you need to place it before end main.
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
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
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
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
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
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.
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:
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
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