News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Re: Making Sure Code is Self Contained

Started by jj2007, November 29, 2013, 08:53:12 AM

Previous topic - Next topic

jj2007

Quote from: hamper on November 29, 2013, 03:37:50 AM
.code

This is a segment directive to the assembler. It tells the assembler where the start of the executable code segment of the program begins. The very next line in your source code should consist only of a label, and the following one is traditional...

start:

You will often find code that doesn't follow this advice, like this snippet:
include \masm32\include\masm32rt.inc

; NOT NEEDED: mbox PROTO: DWORD, :DWORD, :DWORD

.code
mbox proc txt, xtitle, mode
  MsgBox 0, txt, xtitle, mode
  ret
mbox endp

; ### we move the entry point to the end of the program ###

start: invoke mbox, chr$("Hello World"), chr$("Masm32 is great:"), MB_OK
exit

end start


As Hutch already mentioned, assemblers are dumb animals: They pass the code from top to bottom, and if you want to call a proc that the assembler has not yet seen, it will complain bitterly. With the standard order (first start: then below the procs), you will see such complaints frequently, unless you tell it beforehand "hey, below you will find a proc called 'mbox' with three dword args". You tell it through a mbox PROTO: DWORD, :DWORD, :DWORD

Now, the example above works without the PROTO. The reason is simple: When the assembler hits the invoke mbox, ..., it has already seen the mbox proc and its args, so no 'announcement' needed.

hamper

Yep jj2007, but to quote Microsoft:

"Declaring procedure prototypes is good programming practice, but is optional.
Prototypes in MASM perform the same function as prototypes in C and other
high-level languages. A procedure prototype includes the procedure name, the
types, and (optionally) the names of all parameters the procedure expects.
Prototypes usually are placed at the beginning of an assembly program or in a
separate include file so the assembler encounters the prototype before the actual
procedure"

So what you are doing (for the sake of proving a point, I guess) is pointing out to someone just starting out in assembly language programming that they can ignore good programming practice and just define procedures immediately above the point in the code where they are called - no prototype needed.

Personally, I try to always follow good programming practice. I prototype every function at the top of my program (after the final includelib), followed by structure type declarations, then the data segments, then .code start: then the code itself, and finally define all my procedures just before the end start.

Others will do things in a different way, but I don't think it helps a newcomer to point out that bad programming practice is perfectly acceptable, or maybe even the norm. Surely we should try and guide newcomers towards a structured program layout?

dedndave

heavens - i rarely PROTO all the PROC's   :redface:
i prototype the ones that have parameters, so i can use INVOKE
i even prototype WndProc's - not generally required
i prototype thread procedures, so i can reference them before they appear in the source
a few other cases that are similar to the thread issue
sometimes, externals that are not otherwise prototyped

once in a while, all of the procedures in a program fall into one of the catagories above
then, i guess they're all prototyped   :P

hutch--

I think the example JJ posted was to demonstrate how MASM reads a source file from top to bottom so that if you have a procedure before it is called, it is already known by the assembler when it is called and often old assembler code is written that way. I don't personally like order dependent code and have always used prototypes for normal procedures but then with an assembler you can do other things as well, make a set of similar procedures then jump to a global label in another procedure that acts as a collector, it may not be politically correct but then most assembler is not either.

Then of course you can make a proc within a proc which is a no no in most higher level languages, the virtue of an assembler is design freedom and while it is worth developing good habits, its also very useful to know how to do things in other ways.

dedndave

with masm v5.1, i could nest procedures
newer versions don't seem to like it   :(

as you say, there is the public label - that does the job

jj2007

Quote from: hamper on November 29, 2013, 09:31:06 AMI don't think it helps a newcomer to point out that bad programming practice is perfectly acceptable, or maybe even the norm. Surely we should try and guide newcomers towards a structured program layout?

OMG I feel so ashamed :redface: :redface: :redface:

As Hutch wrote, I simply wanted to demonstrate why the assembler, reading top-down, needs a PROTO if the invoked procedure sits below.

But OK, since we are now in flaming mode, let's put the record straight: I don't care a s**t what Micros**t declares as good programming practice. Fact is that

- 99% of all PROTOs are hidden in the include files, and NOBODY ever looks at them, except Hutch because he has to write them; all others check the documentation at MSDN or in Win32.hlp

- 99% of all user-defined PROTOs are of the form myprog PROTO :DWORD,:DWORD,:DWORD, and therefore don't tell the user anything about the paras other than that they are DWORDs (and if it's a REAL4, they come here asking)

- 99% of all users write that bloody PROTO once on top and than never ever look at it again, except when they added a para to the proc below and the assembler complaints bitterly "why didn't you tell me explicitly that you have changed your mind?"

- 99% of all users, when asking themselves "why does my invoke not work?", go straight to the proc, not to the PROTO, and look at the paras there.

So, in short: PROTOs are a crappy bad relic, and their only reason to be there is that MASM is too dumb to look forward.

As to myself, I do as Dave, and call my procs. Occasionally I put PROTOs, but only because the assembler otherwise doesn't understand me.

Have a nice day :biggrin:

sinsi

The great thing about assembly is the control you have.
I don't prototype anything (except externals) and try to avoid INVOKE, for me it's bare-bones.
No such thing as "best programming practice" with ASM. Find one way and be consistent.



include \masm32\include\masm32rt.inc

.code
start:  call fwd      ;OK
        call fwd2     ;OK
        invoke fwd2   ;error A2006:undefined symbol : fwd2
        ret

fwd:    ret

fwd2    proc
        ret
fwd2    endp

end start

INVOKE requires PROTO

Just for fun, ML64 will use PROTO but not INVOKE and only uses END, not END <label>. The entry is set by the linker but must be PUBLIC for ML64.

Antariy

Hardcore (i.e. entangled :greensml:) invoke, pointer protorypes, name decoration and polymorphism example :biggrin:
Take notice that the label uses name decoration specifics that allows declare a "proc" label without defining the proc itself. Could be used inside procs to provide "inner" local procs... so this is incapsulation example as well :lol:



include \masm32\include\masm32rt.inc

.686
.mmx
.xmm

.data

dd1 dd      1
dd2 dd      2
dd3 dd      3
dd4 dd      4
fl1 REAL8   123.456
fl2 REAL8   789.012

.code

stdcall@dword@dword@dword@dword@dword typedef PROTO stdcall :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
stdcall@dword@qword@qword typedef PROTO stdcall :DWORD, :QWORD, :QWORD

ProcOverloaded PROTO :DWORD, :DWORD, :QWORD, :DWORD

start proc

mov ebx,offset ProcOverloaded

invoke stdcall@dword@dword@dword@dword@dword ptr ebx,-1,dd1,dd2,dd3,dd4
invoke stdcall@dword@qword@qword ptr ebx,-2,fl1,fl2

invoke ProcOverloaded, -3,dd1,fl1,dd2

invoke crt__getch
invoke crt_exit,0

start endp

PUBLIC ProcOverloaded@20
ProcOverloaded@20:

    switch dword ptr [esp+4]

        case -1
            invoke crt_printf,CTXT("Invoked with 5 dwords: %d, %d, %d, %d, %d",13,10),dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4]

        case -2
            invoke crt_printf,CTXT("Invoked with dword and two doubles: %d, %lf, %lf",13,10),dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4]

        case -3
            invoke crt_printf,CTXT("Invoked with two dwords, double and dword: %d, %d, %lf, %d",13,10),dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4]

    endsw

    ret 20

end start


Antariy

The output:

Invoked with 5 dwords: -1, 1, 2, 3, 4
Invoked with dword and two doubles: -2, 123.456000, 789.012000
Invoked with two dwords, double and dword: -3, 1, 123.456000, 2

jj2007

Hi Alex,
            invoke crt_printf,CTXT("Invoked with 5 dwords: %d, %d, %d, %d, %d",13,10),dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4],\
            dword ptr [esp+4+4*4]

The red part is not n00b-safe, you should add some comments ;-)

Here is an example of bad programming practiceTM - a nested proc that can be invoked:

include \masm32\MasmBasic\MasmBasic.inc        ; download
.code
MainBox proc arg1, arg2, arg3
  invoke MessageBox, 0, arg1, arg2, arg3

  mov CreateInvoke(MyBox, 3*dword), @F
  invoke MyBox, chr$("Invoked from inside a proc"), chr$("Nested proc:"), MB_OK
  ret

@@:                ; internal anonymous label
  push ebp         ; stack frame
  mov ebp, esp
  invoke MessageBox, 0, [ebp+8], arg2, [ebp+16]   ; <<<< how to obfuscate a source ;-)
  leave
  retn 3*DWORD
MainBox endp

        Init
        fn MainBox, "This is MainBox", "Just for fun:", MB_OK
        Exit
end start