Jump to content

Assembly Language Programming for OS/2 Presentation Manager: Difference between revisions

From EDM2
No edit summary
 
Ak120 (talk | contribs)
mNo edit summary
 
(13 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Original Work by [[Micho Durdevich]]
''Original Work by [[Micho Durdevich]]''


<h2>Introduction</h2>
== Introduction ==
The aim of this article is to sketch a methodology for developing nice OS/2 Presentation Manager applications in assembly language. Our recommended tools are Watcom assembler (WASM), linker (WLINK) and IBM resource compiler RC. Of course, this means that Watcom environment and IBM Developer Toolkit/2 should be installed.


<p>The aim of this article is to sketch a methodology for developing nice OS/2 Presentation Manager
It is truly a pleasure to work with a well-documented, simple and elegant API set of the OS/2 operating system. No matter in which programming language we work, it is anyway a good idea to check how a given programming scheme looks in assembly. Hence, we propose the following ''criterion of higher elegance'':
applications in assembly language. Our recommended tools are Watcom assembler (WASM), linker (WLINK) and
IBM resource compiler RC. Of
course, this means that Watcom environment and IBM Developer Toolkit/2 should be
installed.</p>


<p>It is truly a pleasure to work with a well-documented, simple and elegant API set of the OS/2
'''Assembly Test.''' A given execution environment and programming model are considered ''q-acceptable'', if it is possible to perform clean and nice assembly language programming of all tasks, without introducing some extra complications (beyond those that are in the nature of assembly paradigm).
operating system. No matter in which programming language we work, it is anyway a good idea to  
check how a given programming scheme looks in assembly. Hence, we propose the following <i>criterion
of higher elegance</i>: </p>


<p><b>Assembly Test.</b> A given execution environment and programming model are considered
== Calling OS/2 Functions in Assembly ==
<i>q-acceptable</i>, iff it is possible to perform clean and nice assembly
Let us consider an arbitrary 32-bit OS/2 API, say OS2SampleFunction, and suppose that it takes n arguments, say {arg_1, ... , arg_n}. Then the assembly language call of this routine looks like this:
language programming of all tasks, without introducing some extra complications
<pre>
(beyond those that are in the nature of assembly paradigm).</p>
  push dword arg_n                    ; Arguments are push-ed in the reverse
 
      ...                              ; order.
<h2>Calling OS/2 Functions in Assembly</h2>
      ...
 
  push dword arg_1
<p>Let us consider an arbitrary 32-bit OS/2 API, say OS2SampleFunction, and suppose that it  
  call      OS2SampleFunction
takes n arguments, say {arg_1, ... , arg_n}. Then the assembly language call of this routine  
  add        esp, 0xN                ; Clean up the stack space, here N=4*n.  
looks like this:  
</pre>
<pre>     push dword arg_n                    ; Arguments are push-ed in the reverse
Normally, the return code of such a function is stored in the eax-register. In the ideal situation, only registers that have to do something with return values of functions should be allowed to change across the API calls. Unfortunately, at the moment not all OS/2 APIs satisfy this property (implying, strictly speaking, that the API system fails to satisfy completely our Test!) so one should be very careful when storing something in registers and then making an external call.
        ...                              ; order.  
        ...
    push dword arg_1
    call      OS2SampleFunction
    add        esp, 0xN                ; Clean up the stack space, here N=4*n.  
</pre>
<div>Normally, the return code of such a function is stored in the eax-register. In the ideal situation,  
only registers that have to do something with return values of functions should be allowed to  
change across the API calls. Unfortunately, at the moment not all OS/2 APIs satisfy this property
(implying, strictly speaking, that the API system fails to
satisfy completely our Test!) so one should be very careful  
when storing something in registers and then making an external call.</div></p>
 
 
<h2>Creating a Standard Window</h2>
 
<p>Here is a piece of the assembly code, responsible for the creation of a standard window.
The main functions are WinRegisterClass and WinCreateStdWindow. The interface
to this window is carried out with the help of a <i>window routine</i>, which is specified as an
argument in the call to WinRegisterClass.</p>
<pre>    push      0x00000000                ; @ first initialize the interface!
    call      WinInitialize
    add      esp, 0x00000004
    mov      hab, eax


    push      0x00000000
== Creating a Standard Window ==
    push      eax                        ; From the previous procedure...
Here is a piece of the assembly code, responsible for the creation of a standard window. The main functions are WinRegisterClass and WinCreateStdWindow. The interface to this window is carried out with the help of a ''window routine'', which is specified as an argument in the call to WinRegisterClass.
    call     WinCreateMsgQueue          ; Create the message queue, and return
<pre>
    add      esp, 0x00000008            ; its handle.
  push     0x00000000                ; @ first initialize the interface!
    mov      hmq, eax
  call      WinInitialize
  add      esp, 0x00000004
  mov      hab, eax


  push      0x00000000
  push      eax                        ; From the previous procedure...
  call      WinCreateMsgQueue          ; Create the message queue, and return
  add      esp, 0x00000008            ; its handle.
  mov      hmq, eax


    push      0x00000000
  push      0x00000000
    push      CS_SIZEREDRAW              ; Diverse styles of the class...
  push      CS_SIZEREDRAW              ; Diverse styles of the class...
    push      offset main_window_proc    ; The main window procedure, handling
  push      offset main_window_proc    ; The main window procedure, handling
    push      offset cls_client          ; all messages.  
  push      offset cls_client          ; all messages.
    push      hab
  push      hab
    call      WinRegisterClass
  call      WinRegisterClass
    add      esp, 0x00000014
  add      esp, 0x00000014
   
   
wincreateflags = (FCF_SYSMENU+FCF_TITLEBAR+FCF_MINBUTTON+FCF_ICON)
wincreateflags = (FCF_SYSMENU+FCF_TITLEBAR+FCF_MINBUTTON+FCF_ICON)
Line 69: Line 46:
wincreateflags = wincreateflags+FCF_SHELLPOSITION+FCF_TASKLIST
wincreateflags = wincreateflags+FCF_SHELLPOSITION+FCF_TASKLIST


    mov      flCreate, wincreateflags
  mov      flCreate, wincreateflags


    push      offset hwndClient
  push      offset hwndClient
    push      RES_CLIENT  
  push      RES_CLIENT
    push      0x00000000                    
  push      0x00000000
    push      (CS_SIZEREDRAW+CS_SYNCPAINT)
  push      (CS_SIZEREDRAW+CS_SYNCPAINT)
    push      offset windows_message  
  push      offset windows_message
    push      offset cls_client
  push      offset cls_client
    push      offset flCreate
  push      offset flCreate
    push      (WS_VISIBLE+WS_ANIMATE)
  push      (WS_VISIBLE+WS_ANIMATE)
    push      HWND_DESKTOP
  push      HWND_DESKTOP
    call      WinCreateStdWindow
  call      WinCreateStdWindow
    add      esp, 0x00000024
  add      esp, 0x00000024
    mov      hwndFrame, eax                      
  mov      hwndFrame, eax
</pre>
</pre>
The above WinCreateStdWindow procedure actually does the job of two separate WinCreateWindow calls: It creates the frame window (eax holds the handle to it) and it also creates the client window as a child of the frame (hwndClient is filled with the client window handle, after a successful completion of the function). In the case of more subtle windows (for example if we want to mix WPS stuff with PM functions) it is necessary to use two separate WinCreateWindow calls in order to have a full control over both frame and client windows.
This is discussed in our WPS programming article.


<p>The above WinCreateStdWindow procedure actually does the job of two separate WinCreateWindow
The window dispatch loop looks as follows:
calls: It creates the frame window (eax holds the handle to it) and it also creates
the client window as a child of the frame (hwndClient is filled with the client window
handle, after a succesful completion of the function). In the case of more subtle windows
(for example if we want to mix WPS stuff with PM functions) it is necessary to use two
separate WinCreateWindow calls in order to have a full control over both frame and client windows.
This is discussed in our WPS programming article. </p>
 
<p>The window dispatch loop looks as follows:</p>
<pre>@loopdispatch:
<pre>@loopdispatch:


    push      0x00000000
  push      0x00000000
    push      0x00000000
  push      0x00000000
    push      0x00000000
  push      0x00000000
    push      offset qmsg
  push      offset qmsg
    push      hab
  push      hab
    call      WinGetMsg
  call      WinGetMsg
    add      esp, 0x00000014
  add      esp, 0x00000014
   
    cmp      eax, 0
  cmp      eax, 0
    jz @exitdispatch
  jz @exitdispatch


    push      offset qmsg
  push      offset qmsg
    push      hab
  push      hab
    call      WinDispatchMsg
  call      WinDispatchMsg
    add      esp, 0x00000008
  add      esp, 0x00000008
   
    jmp short @loopdispatch              ; We are still online, go ahead and get a
  jmp short @loopdispatch              ; We are still online, go ahead and get a
                                        ; new message...
                                      ; new message...


@exitdispatch:
@exitdispatch:
    push      hwndFrame
  push      hwndFrame
    call      WinDestroyWindow          ; Destroy the frame window...
  call      WinDestroyWindow          ; Destroy the frame window...
    add      esp, 0x00000004
  add      esp, 0x00000004


    push      hmq
  push      hmq
    call      WinDestroyMsgQueue        ; Destroy the message queue...  
  call      WinDestroyMsgQueue        ; Destroy the message queue...
    add      esp, 0x00000004  
  add      esp, 0x00000004  
   
   
    push      hab  
  push      hab  
    call      WinTerminate              ; And finally destroy the associated PM
  call      WinTerminate              ; And finally destroy the associated PM
    add      esp, 0x00000004            ; threads.
  add      esp, 0x00000004            ; threads.
 
</pre>
</pre>
<p>The dispatched messages are then handled by the main (or another, associated) window procedure.</p>
The dispatched messages are then handled by the main (or another, associated) window procedure.


<h2>Example: Quantum Rectangles</h2>
== Example: Quantum Rectangles ==
=== Program Description ===
This sample program creates a simple PM window, and paints rectangular areas of the window with the help of WinInvalidateRect function. The colors of the rectangles and their sizes are randomly chosen, using a simple but very powerful random number generator. As an illustration for coding a pull-down menus, we introduce a control dialog, allowing us to specify the go/stop state, and also the speed of these fluctuating rectangles.


<h3>Program Description</h3>
In developing this sample we were principally motivated by elegant random-rectangles samples for Windows, by Adam Stanislav [2].


<p>This sample program creates a simple PM window, and paints rectangular areas of the window with
===Painter Thread===
the help of WinInvalidateRect function. The colors of the rectangles and their
In order to perform paintings of rectangles, we create a cyclic thread.
sizes are randomly chosen, using a simple but very powerful random number
<pre>
generator. As an illustration for coding a pull-down menus, we introduce a
  push      0x00001000                ; The thread stack size, one page here!
control dialog, allowing us to specify the go/stop state, and also the speed of
  push      0                          ; Thread flags
these fluctuating rectangles.</p>
  push      0                          ; Parameter to be passed to the thread
 
  push      offset cyclic_painter      ; Thread routine
<p>In developing this sample we were principally motivated by elegant random-rectangles samples
  push      offset thread_id          ; Thread id variable
for Windows, by Adam Stanislav&nbsp;[2].</p>
  call      DosCreateThread
 
  add      esp, 0x00000014            ; Clean up the stack
<h3>Painter Thread</h3>
 
<p>In order to perform paintings of rectangles, we create a cyclic thread.</p>
<pre>   push      0x00001000                ; The thread stack size, one page here!  
    push      0                          ; Thread flags                    
    push      0                          ; Parameter to be passed to the thread
    push      offset cyclic_painter      ; Thread routine                      
    push      offset thread_id          ; Thread id variable                  
    call      DosCreateThread                                                    
    add      esp, 0x00000014            ; Clean up the stack                                  
</pre>
</pre>
<p>The thread procedure cyclic_painter is simply invalidating randomly chosen rectangles of the window  
The thread procedure cyclic_painter is simply invalidating randomly chosen rectangles of the window paint area. This in its turn, causes the corresponding WM_PAINT message to be sent to the main window procedure, where the actual paintings occur. A simpler way to perform rectangle drawings in given intervals of time is to create a timer, and then put slightly modified cyclic_painter code within WM_TIMER section of the main window procedure. This is how we proceed in the WPS version of quantum rectangles.
paint area. This in its turn, causes the corresponding WM_PAINT message to be sent to the main  
window procedure, where the actual paintings occur. A simpler way to perform rectangle drawings  
in given intervals of time is to create a timer, and then put slightly modified cyclic_painter code  
within WM_TIMER section of the main window procedure. This is how we proceed in the WPS version of  
quantum rectangles.</p>


<h3>Random Number Generator</h3>
===Random Number Generator===
 
As we already mentioned, our program has a built-in random number generator.  
<p>As we already mentioned, our program has a built-in random number generator.  
It is interesting, and also of a separate value, to describe here a beautiful mathematics behind this simple subroutine:
It is interesting, and also of a separate value, to describe here a beautiful
<pre>
mathematics behind this simple subroutine:</p>
  mov      eax, 0x4019CF16
 
  mul      random_var
<pre>   mov      eax, 0x4019CF16
  add      eax, random_var[4]
    mul      random_var
  mov      random_var, eax
    add      eax, random_var[4]
  adc      edx, 0
    mov      random_var, eax
  mov      random_var[4], edx
    adc      edx, 0
    mov      random_var[4], edx
</pre>
</pre>
The code above is an example of a so-called multiply-with-carry (MWC) random number generator. We refer to [5] for more detailed explanations, and reviews of various types of random number generators.


<p>The code above is an example of a so-called multiply-with-carry (MWC) random
What happens here is that we apply iteratively the following transformation T:
number generator. We refer to [5] for more detailed explanations, and reviews of various types
<center>T{2<sup>32</sup>x+y} = Ay+x</center>
of random number generators. </p>
where x, y and A are 32-bit integers (A=0x4019CF16 in the code above).


<p>What happens here is that we apply iteratively the following transformation T:  
More generally, we can consider transformations of the form:
<center>T{2<sup>32</sup>x+y} = Ay+x</center><div>where x, y and A are 32-bit
<center>T{Bx+y} = Ay+x</center>
integers (A=0x4019CF16 in the code above).</div></p>
where A,B are natural numbers, A < B, and (x,y) take values from the set '''S''' defined as the "cornerless" product
<center><b>S</b>={0,...,A-1}x{0,...,B-1}\{(A-1, B-1)}</center>
Let us observe that all these pairs can naturally be viewed as 2-digit numbers in the system with base B.


<p>More generally, we can consider transformations of the form:
Let us now define M=AB-1, and let '''R'''[M] be the ''residue ring'' modulo M.  
<center>T{Bx+y} = Ay+x</center><div>
Clearly, we can make a natural identification '''S'''='''R'''[M] and in what follows we will not really distinguish between these two objects.  
where A,B are natural numbers, A &lt; B, and (x,y) take values from the set <b>S</b> defined as the
 
&quot;cornerless&quot; product</div>
<center><b>S</b>={0,...,A-1}x{0,...,B-1}\{(A-1, B-1)}</center><div>
Let us observe that all these pairs can naturally be viewed as 2-digit numbers in the system with base B.</div></p>
 
<p>Let us now define M=AB-1, and let <b>R</b>[M] be the <i>residue ring</i> modulo M.  
Clearly, we can make a natural identification  
<b>S</b>=<b>R</b>[M] and in what follows we will not really distinguish between these two objects.  
Let us denote by <b>G</b>[M] the group of invertible elements of the ring <b>R</b>[M].  
Let us denote by <b>G</b>[M] the group of invertible elements of the ring <b>R</b>[M].  
These elements are represented by residues relatively prime with M. As a simple  
These elements are represented by residues relatively prime with M. As a simple example, if M=8 then <b>R</b>[M] has 4 invertible elements: 1, 3, 5, 7 and the multiplication table for <b>G</b>[M] looks as follows:
example, if M=8 then <b>R</b>[M] has 4 invertible elements: 1, 3, 5, 7 and the  
{| class="wikitable"
multiplication table for <b>G</b>[M] looks as follows:</p>
|-
| 1 || 3 || 5 || 7
|-
| 3 || 1 || 7 || 5
|-
| 5 || 7 || 1 || 3
|-
| 7 || 5 || 3 || 1
|}
It is nice - we obtained a famous Klein 4-element group :)


<center>
Returning to the general discussion, let us view '''S''' as a left '''R'''[M]-module, by means of the left regular representation. A simple but very important observation is that the action of the random-operator T is ''nothing but the left module multiplication'' by A. Within the ring '''R'''[M] the numbers A and B are ''mutually inverse'', in other words A=B<sup>-1</sup> in '''R'''[M].
<table border="1" cellspacing="3" id="tabla-4">
Let '''C''' be the cyclic subgroup of '''G'''[M] generated by A. It is of principal interest to answer how '''S''' decomposes into irreducible components (also coinciding with orbits of the action of '''C''') with respect to the action of '''C'''. These components correspond to our pseudorandom sequences.
 
  <tr>
    <td>1</td><td>3</td><td>5</td><td>7</td>
  </tr>
  <tr>
    <td>3</td><td>1</td><td>7</td><td>5</td>


  </tr>
In order to figure out the size of a given orbit, let us consider an arbitrary element F in '''S'''. Let D be the maximal common denominator between F and M. Let '''S'''<sub>F</sub> be the (cyclic) submodule of '''S''' generated by F. It is easy to see that a natural identification <center>'''S'''<sub>F</sub> = '''R'''[M/D]</center>
  <td>5</td><td>7</td><td>1</td><td>3</td>
  </tr>
  <tr>
  <td>7</td><td>5</td><td>3</td><td>1</td>


  </tr>
holds. This is because
</table>
<center>'''R'''[M]'''I'''[D] = '''R'''[M/D]</center>
</center>
where <b>I</b>[D] is the ideal of <b>R</b>[M] generated by M/D. The ideal <b>I</b>[D] consists exactly of elements that annihilate F.
<p>It is nice--we obtained a famous Klein 4-element group :) </p>


<p>Returning to the general discussion, let us view <b>S</b> as a
It follows that the size of the orbit {<b>C</b>F} is the degree of A in the factor-ring
left <b>R</b>[M]-module, by means of the left regular
representation. A simple but very important observation is that
the action of the random-operator T is <i>nothing but the left module
multiplication</i> by A. Within the ring <b>R</b>[M] the numbers A and B are
 
<i>mutually inverse</i>, in other words A=B<sup>-1</sup> in <b>R</b>[M].
Let <b>C</b> be the cyclic
subgroup of <b>G</b>[M] generated by A. It is of principal interest to answer
how <b>S</b> decomposes into irreducible components (also coinciding with
orbits of the action of <b>C</b>) with respect to the action
of <b>C</b>. These components correspond to our pseudorandom sequences.</p>
 
<p>In order to figure out the size of a given orbit, let us consider an arbitrary
element F in <b>S</b>. Let D be the maximal common denominator between F and M. Let
<b>S</b><sub>F</sub> be the (cyclic) submodule of <b>S</b> generated by F. It is
easy to see that a natural identification
<center><b>S</b><sub>F</sub> = <b>R</b>[M/D]</center>
 
<div>holds. This is because</div>
<center><b>R</b>[M]/<b>I</b>[D] = <b>R</b>[M/D]</center>
<div>where <b>I</b>[D] is the ideal of <b>R</b>[M] generated by M/D. The ideal
<b>I</b>[D] consists exactly of elements that annihilate F.</div></p>
 
<p>It follows that the size of the orbit {<b>C</b>F} is the degree of A in the factor-ring
<b>R</b>[M/D]. So, in general, the structure of the orbits is considerably complex. For  
<b>R</b>[M/D]. So, in general, the structure of the orbits is considerably complex. For  
example, if M=32, A=3, B=11 we have  
example, if M=32, A=3, B=11 we have  
Line 251: Line 174:
<b>C</b>=<b>{</b>1, 3, 9, 11, 17, 19, 25, 27<b>}</b>  
<b>C</b>=<b>{</b>1, 3, 9, 11, 17, 19, 25, 27<b>}</b>  
</center>
</center>
<div>and <b>S</b> splits into the following orbits:</div> </p>
and <b>S</b> splits into the following orbits:


<p align="center">
<b>{</b>1, 3, 9, 11, 17, 19, 25, 27<b>}</b> <b>{</b>5, 7, 13, 15, 21, 23, 29, 31<b>}</b>
<b>{</b>1, 3, 9, 11, 17, 19, 25, 27<b>}</b> <b>{</b>5, 7, 13, 15, 21, 23, 29, 31<b>}</b>
<center>
<center>
<b>{</b>4, 12<b>}</b> <b>{</b>8, 24<b>}</b> <b>{</b>20, 28<b>}</b>  
<b>{</b>4, 12<b>}</b> <b>{</b>8, 24<b>}</b> <b>{</b>20, 28<b>}</b>  
 
</center>
</center><center>
<center>
<b>{</b>0<b>}</b> <b>{</b>16<b>}</b>
<b>{</b>0<b>}</b> <b>{</b>16<b>}</b>
</center>
</center>
<center>
<center>
<b>{</b>2, 6, 18, 22<b>}</b> <b>{</b>10, 14, 26, 30<b>}</b>
<b>{</b>2, 6, 18, 22<b>}</b> <b>{</b>10, 14, 26, 30<b>}</b>
</center>
From the point of view of random number sequences, we are interested only in the ''low digits'' of elements in each orbit. Here digits are understood in terms of the base-B number system. These digits form the actual pseudorandom sequence (during a cycle, a given digit will in general repeat many times, while the full numbers in the orbit are, by nature of things, always different). A very important question is whether the resulting pseudorandom sequence is sufficient to reconstruct the whole orbit. Let us denote by (r<sub>i</sub>) the sequence of low digits associated to the sequence of orbit elements
<center>z<sub>i</sub>=B<sup>i</sup>z, z=z<sub>0</sub>.</center>


</center></p><p>
The above equality, of course, is written in <b>R</b>[M]. In terms of standard integers (in <b>Z</b>) it gives
From the point of view of random number sequences, we are interested only in
the <i>low digits</i> of elements in each orbit. Here digits are understood in terms
of the base-B number system. These digits form the actual pseudorandom sequence (during a cycle, a
given digit will in general repeat many times, while the full numbers in the orbit are, by
nature of things, always different). A very important question is whether the resulting
pseudorandom sequence is sufficient to reconstruct the whole orbit. Let us denote by (r<sub>i</sub>)
the sequence of low digits associated to the sequence of orbit elements
<center>z<sub>i</sub>=B<sup>i</sup>z,&nbsp;&nbsp;&nbsp&nbsp;z=z<sub>0</sub>.</center>
<div>The above equality, of course, is written in <b>R</b>[M]. In terms of standard integers (in <b>Z</b>)  
it gives</div>


<center>z<sub>i+1</sub>=Bz<sub>i</sub>+r<sub>i+1</sub>(1-AB).</center>
<center>z<sub>i+1</sub>=Bz<sub>i</sub>+r<sub>i+1</sub>(1-AB).</center>


<div>Therefore, knowing the sequence (r<sub>i</sub>) and the initial element z<sub>0</sub>  
Therefore, knowing the sequence (r<sub>i</sub>) and the initial element z<sub>0</sub> of our orbit, we can reconstruct the whole orbit. The above recurrent formula also implies that the period of the pseudorandom sequence is always equal to the length of the corresponding orbit. Indeed, if l<sub>i</sub> are high digits of z<sub>i</sub> then we have
of our orbit, we can reconstruct the whole orbit. The above recurrent formula also  
implies that the period of the pseudorandom  
sequence is always equal to the length of the corresponding orbit. Indeed, if l<sub>i</sub> are high
digits of z<sub>i</sub> then we have</div>
 
<center>
l<sub>i+1</sub>=[r<sub>i </sub>- Ar<sub>i+1</sub>]<sub>B</sub>
</center>
<div>
where [ ]<sub>B</sub> denotes the projection in <b>R</b>[M] =| {0, ... , B-1}.</div>


</p>
<center>l<sub>i+1</sub>=[r<sub>i </sub>- Ar<sub>i+1</sub>]<sub>B</sub></center>


<p>Now, if M is a <i>prime</i>, then the theory simplifies. Every non-zero F will be invertible
where [ ]<sub>B</sub> denotes the projection in <b>R</b>[M] =| {0, ... , B-1}.
(as <b>R</b>[M] is a field iff M is prime) and all non-trivial orbits will have
the same number of elements--the degree L of A in <b>R</b>[M] (number of elements in
<b>C</b>). According to the Lagrange theorem, the number L is a divisor of the
order of the group <nobr><b>G</b>[M]=<b>R</b>[M]\{0}</nobr>. In other words, <nobr>


L|(AB-2).</nobr> </p>
Now, if M is a ''prime'', then the theory simplifies. Every non-zero F will be invertible (as '''R'''[M] is a field iff M is prime) and all non-trivial orbits will have the same number of elements - the degree L of A in '''R'''[M] (number of elements in '''C'''). According to the Lagrange theorem, the number L is a divisor of the order of the group '''G'''[M]='''R'''[M]\{0}. In other words, L|(AB-2).


<p>If, furthermore, P=(M-1)/2 is a prime (primes M satisfying this additional property  
If, furthermore, P=(M-1)/2 is a prime (primes M satisfying this additional property are called safeprimes) then <nobr>L={1, 2, P, 2P=M-1}</nobr>. In particular, if L=P then we will have only two non-trivial orbits.
are called safeprimes) then <nobr>L={1, 2, P, 2P=M-1}</nobr>. In particular, if L=P then we will  
have only two non-trivial orbits.</p>


<p>To conclude, let us go back to our random number generator. It corresponds to the  
To conclude, let us go back to our random number generator. It corresponds to the following specifications:
following specifications:
<center>A=0x4019CF16, B=2<sup>32</sup>, P=0x200CE78AFFFFFFFF, M=0x4019CF15FFFFFFFF.</center>
<center>A=0x4019CF16, B=2<sup>32</sup>, P=0x200CE78AFFFFFFFF, M=0x4019CF15FFFFFFFF.</center><div>
So we have two possible pseudorandom sequences digits, each of period P=L. Note that the case
of the single orbit is exculded because A has the squre root in <b>R</b>[M] and hence
the whole <b>G</b>[M] is square-rootable. Which
one will run depends on the initial value for global random_var and
random_var[4] variables.</div></p>


<h3>How To Compile</h3>
So we have two possible pseudorandom sequences of digits, each of period P=L. Note that the case
of the single orbit is excluded because A has the square root in '''R'''[M] and hence the whole '''G'''[M] is square-rootable. Which one will run depends on the initial value for global random_var and random_var[4] variables.


<p>The creation of the executable file is quite simple. Just 3 steps. Assembling, linking  
=== How To Compile ===
and resource-compiling. Explicitly,  
The creation of the executable file is quite simple. Just 3 steps. Assembling, linking and resource-compiling. Explicitly,  
 
<pre>
<pre>wasm qhello.asm
wasm qhello.asm
wlink @qhello
wlink @qhello
rc qhello qhello.exe
rc qhello qhello.exe
</pre>
</pre>
Here, the linking info is stored in the file qhello.lnk and the resource info is within hello.rc file. Don't forget to check the size of the program (take into account the pre-compiled icon size, too) :)


<div>Here, the linking info is stored in the file qhello.lnk and the resource info is within qhello.rc
The binary with the full source code can be found at our download area.
file. Don't forget to check the size of the program (take into account the pre-compiled icon
size, too) :)</div></p>
 
<p> The binary with the full source code can be found at our download area. </p>
<h2>Concluding Remarks</h2>
 
<p>As we have seen, it is conceptually very simple to code OS/2 PM in assembly language.
However it does not mean that assembly programming is easy or trivial. True simplicity
is always connected with something non-trivial at a deeper level (here it is a good time to
recall of the true simplicity of quantum mechanics, as revealed for example in Feynman's book [6]).
It is logical to expect that a language with a higher expressive power requires a higher-level
knowledge in order to properly use it. And here, in our context, such a knowledge means knowing the
internals of the machine, and understanding what is really happening... Invaluable
scientific aspect of programming directly in the language of Machine.</p>
 
<h2>References</h2>
<p> <b>[1]</b> [http://webster.cs.ucr.edu The Art of Assembly
Language Programming and HLA]. By Randall Hyde. An extensive and beautiful
assembly language tutorial + related topics. </p>
 
<p> <b>[2]</b> [http://www.geocities.com/SiliconValley/Heights/7394 Whiz
Kid Technomagic]. By Adam Stanislav. A wealth of assembly language sample programs, for
Windows. Includes the mentioned random rectangles program for Windows.</p>
 
<p> <b>[3]</b> [http://www.int80h.org FreeBSD Programming in
Assembly  Language], by Adam Stanislav.</p>


<p> <b>[4]</b> [http://grc.com/smgassembly.htm Assembly Language Windows
==Concluding Remarks==
Applications]. By Steve Gibson. Highly recommended source of valuable informations and links regarding
As we have seen, it is conceptually very simple to code OS/2 PM in assembly language. However it does not mean that assembly programming is easy or trivial. True simplicity is always connected with something non-trivial at a deeper level (here it is a good time to recall of the true simplicity of quantum mechanics, as revealed for example in Feynman's book [6]).  
assembly language programming for Windows. Includes a Small Is Beautiful Starter Kit.</p>
It is logical to expect that a language with a higher expressive power requires a higher-level knowledge in order to properly use it. And here, in our context, such knowledge means knowing the internals of the machine, and understanding what is really happening... Invaluable
scientific aspect of programming directly in the language of Machine.


<p> <b>[5]</b> [http://stat.fsu.edu/~geo/diehard.html Random Number CDROM],  
==References==
by George Marsaglia. A package consisting of truly random bits, selected random number generator topics, and a software system to test true randomness.</p>
# [http://webster.cs.ucr.edu The Art of Assembly Language Programming and HLA]. By Randall Hyde. An extensive and beautiful assembly language tutorial + related topics.
# [http://www.geocities.com/SiliconValley/Heights/7394 Whiz Kid Technomagic]. By Adam Stanislav. A wealth of assembly language sample programs, for Windows. Includes the mentioned random rectangles program for Windows.
# [http://www.int80h.org FreeBSD Programming in Assembly Language], by Adam Stanislav.
# [http://grc.com/smgassembly.htm Assembly Language Windows Applications]. By Steve Gibson. Highly recommended source of valuable information and links regarding assembly language programming for Windows. Includes a Small Is Beautiful Starter Kit.
# [https://web.archive.org/web/20160125103112/http://stat.fsu.edu/pub/diehard/ Random Number CDROM], by George Marsaglia. A package consisting of truly random bits, selected random number generator topics, and a software system to test true randomness.
# Feynman's Lectures in Physics: By Richard Feynman. Books 8/9-Quantum Mechanics.


<p> <b>[6]</b> Feynman's Lectures in Physics: By Richard Feynman. Books 8/9-Quantum Mechanics.</p>
[[Category: PM Articles]]

Latest revision as of 03:32, 27 December 2022

Original Work by Micho Durdevich

Introduction

The aim of this article is to sketch a methodology for developing nice OS/2 Presentation Manager applications in assembly language. Our recommended tools are Watcom assembler (WASM), linker (WLINK) and IBM resource compiler RC. Of course, this means that Watcom environment and IBM Developer Toolkit/2 should be installed.

It is truly a pleasure to work with a well-documented, simple and elegant API set of the OS/2 operating system. No matter in which programming language we work, it is anyway a good idea to check how a given programming scheme looks in assembly. Hence, we propose the following criterion of higher elegance:

Assembly Test. A given execution environment and programming model are considered q-acceptable, if it is possible to perform clean and nice assembly language programming of all tasks, without introducing some extra complications (beyond those that are in the nature of assembly paradigm).

Calling OS/2 Functions in Assembly

Let us consider an arbitrary 32-bit OS/2 API, say OS2SampleFunction, and suppose that it takes n arguments, say {arg_1, ... , arg_n}. Then the assembly language call of this routine looks like this:

   push dword arg_n                    ; Arguments are push-ed in the reverse
      ...                              ; order.
      ...
   push dword arg_1
   call       OS2SampleFunction
   add        esp, 0xN                 ; Clean up the stack space, here N=4*n. 

Normally, the return code of such a function is stored in the eax-register. In the ideal situation, only registers that have to do something with return values of functions should be allowed to change across the API calls. Unfortunately, at the moment not all OS/2 APIs satisfy this property (implying, strictly speaking, that the API system fails to satisfy completely our Test!) so one should be very careful when storing something in registers and then making an external call.

Creating a Standard Window

Here is a piece of the assembly code, responsible for the creation of a standard window. The main functions are WinRegisterClass and WinCreateStdWindow. The interface to this window is carried out with the help of a window routine, which is specified as an argument in the call to WinRegisterClass.

  push      0x00000000                 ; @ first initialize the interface!
  call      WinInitialize
  add       esp, 0x00000004
  mov       hab, eax

  push      0x00000000
  push      eax                        ; From the previous procedure...
  call      WinCreateMsgQueue          ; Create the message queue, and return
  add       esp, 0x00000008            ; its handle.
  mov       hmq, eax

  push      0x00000000
  push      CS_SIZEREDRAW              ; Diverse styles of the class...
  push      offset main_window_proc    ; The main window procedure, handling
  push      offset cls_client          ; all messages.
  push      hab
  call      WinRegisterClass
  add       esp, 0x00000014
 
wincreateflags = (FCF_SYSMENU+FCF_TITLEBAR+FCF_MINBUTTON+FCF_ICON)
wincreateflags = wincreateflags+FCF_SIZEBORDER+FCF_MENU
wincreateflags = wincreateflags+FCF_SHELLPOSITION+FCF_TASKLIST

  mov       flCreate, wincreateflags

  push      offset hwndClient
  push      RES_CLIENT
  push      0x00000000
  push      (CS_SIZEREDRAW+CS_SYNCPAINT)
  push      offset windows_message
  push      offset cls_client
  push      offset flCreate
  push      (WS_VISIBLE+WS_ANIMATE)
  push      HWND_DESKTOP
  call      WinCreateStdWindow
  add       esp, 0x00000024
  mov       hwndFrame, eax

The above WinCreateStdWindow procedure actually does the job of two separate WinCreateWindow calls: It creates the frame window (eax holds the handle to it) and it also creates the client window as a child of the frame (hwndClient is filled with the client window handle, after a successful completion of the function). In the case of more subtle windows (for example if we want to mix WPS stuff with PM functions) it is necessary to use two separate WinCreateWindow calls in order to have a full control over both frame and client windows. This is discussed in our WPS programming article.

The window dispatch loop looks as follows:

@loopdispatch:

  push      0x00000000
  push      0x00000000
  push      0x00000000
  push      offset qmsg
  push      hab
  call      WinGetMsg
  add       esp, 0x00000014
 
  cmp       eax, 0
  jz @exitdispatch

  push      offset qmsg
  push      hab
  call      WinDispatchMsg
  add       esp, 0x00000008
 
  jmp short @loopdispatch              ; We are still online, go ahead and get a
                                       ; new message...

@exitdispatch:
  push      hwndFrame
  call      WinDestroyWindow           ; Destroy the frame window...
  add       esp, 0x00000004

  push      hmq
  call      WinDestroyMsgQueue         ; Destroy the message queue...
  add       esp, 0x00000004 
 
  push      hab 
  call      WinTerminate               ; And finally destroy the associated PM
  add       esp, 0x00000004            ; threads.

The dispatched messages are then handled by the main (or another, associated) window procedure.

Example: Quantum Rectangles

Program Description

This sample program creates a simple PM window, and paints rectangular areas of the window with the help of WinInvalidateRect function. The colors of the rectangles and their sizes are randomly chosen, using a simple but very powerful random number generator. As an illustration for coding a pull-down menus, we introduce a control dialog, allowing us to specify the go/stop state, and also the speed of these fluctuating rectangles.

In developing this sample we were principally motivated by elegant random-rectangles samples for Windows, by Adam Stanislav [2].

Painter Thread

In order to perform paintings of rectangles, we create a cyclic thread.

  push      0x00001000                 ; The thread stack size, one page here!
  push      0                          ; Thread flags
  push      0                          ; Parameter to be passed to the thread
  push      offset cyclic_painter      ; Thread routine
  push      offset thread_id           ; Thread id variable
  call      DosCreateThread
  add       esp, 0x00000014            ; Clean up the stack

The thread procedure cyclic_painter is simply invalidating randomly chosen rectangles of the window paint area. This in its turn, causes the corresponding WM_PAINT message to be sent to the main window procedure, where the actual paintings occur. A simpler way to perform rectangle drawings in given intervals of time is to create a timer, and then put slightly modified cyclic_painter code within WM_TIMER section of the main window procedure. This is how we proceed in the WPS version of quantum rectangles.

Random Number Generator

As we already mentioned, our program has a built-in random number generator. It is interesting, and also of a separate value, to describe here a beautiful mathematics behind this simple subroutine:

  mov       eax, 0x4019CF16
  mul       random_var
  add       eax, random_var[4]
  mov       random_var, eax
  adc       edx, 0
  mov       random_var[4], edx

The code above is an example of a so-called multiply-with-carry (MWC) random number generator. We refer to [5] for more detailed explanations, and reviews of various types of random number generators.

What happens here is that we apply iteratively the following transformation T:

T{232x+y} = Ay+x

where x, y and A are 32-bit integers (A=0x4019CF16 in the code above).

More generally, we can consider transformations of the form:

T{Bx+y} = Ay+x

where A,B are natural numbers, A < B, and (x,y) take values from the set S defined as the "cornerless" product

S={0,...,A-1}x{0,...,B-1}\{(A-1, B-1)}

Let us observe that all these pairs can naturally be viewed as 2-digit numbers in the system with base B.

Let us now define M=AB-1, and let R[M] be the residue ring modulo M. Clearly, we can make a natural identification S=R[M] and in what follows we will not really distinguish between these two objects. Let us denote by G[M] the group of invertible elements of the ring R[M]. These elements are represented by residues relatively prime with M. As a simple example, if M=8 then R[M] has 4 invertible elements: 1, 3, 5, 7 and the multiplication table for G[M] looks as follows:

1 3 5 7
3 1 7 5
5 7 1 3
7 5 3 1

It is nice - we obtained a famous Klein 4-element group :)

Returning to the general discussion, let us view S as a left R[M]-module, by means of the left regular representation. A simple but very important observation is that the action of the random-operator T is nothing but the left module multiplication by A. Within the ring R[M] the numbers A and B are mutually inverse, in other words A=B-1 in R[M]. Let C be the cyclic subgroup of G[M] generated by A. It is of principal interest to answer how S decomposes into irreducible components (also coinciding with orbits of the action of C) with respect to the action of C. These components correspond to our pseudorandom sequences.

In order to figure out the size of a given orbit, let us consider an arbitrary element F in S. Let D be the maximal common denominator between F and M. Let SF be the (cyclic) submodule of S generated by F. It is easy to see that a natural identification

SF = R[M/D]

holds. This is because

R[M]I[D] = R[M/D]

where I[D] is the ideal of R[M] generated by M/D. The ideal I[D] consists exactly of elements that annihilate F.

It follows that the size of the orbit {CF} is the degree of A in the factor-ring R[M/D]. So, in general, the structure of the orbits is considerably complex. For example, if M=32, A=3, B=11 we have

C={1, 3, 9, 11, 17, 19, 25, 27}

and S splits into the following orbits:

{1, 3, 9, 11, 17, 19, 25, 27} {5, 7, 13, 15, 21, 23, 29, 31}

{4, 12} {8, 24} {20, 28}

{0} {16}

{2, 6, 18, 22} {10, 14, 26, 30}

From the point of view of random number sequences, we are interested only in the low digits of elements in each orbit. Here digits are understood in terms of the base-B number system. These digits form the actual pseudorandom sequence (during a cycle, a given digit will in general repeat many times, while the full numbers in the orbit are, by nature of things, always different). A very important question is whether the resulting pseudorandom sequence is sufficient to reconstruct the whole orbit. Let us denote by (ri) the sequence of low digits associated to the sequence of orbit elements

zi=Biz, z=z0.

The above equality, of course, is written in R[M]. In terms of standard integers (in Z) it gives

zi+1=Bzi+ri+1(1-AB).

Therefore, knowing the sequence (ri) and the initial element z0 of our orbit, we can reconstruct the whole orbit. The above recurrent formula also implies that the period of the pseudorandom sequence is always equal to the length of the corresponding orbit. Indeed, if li are high digits of zi then we have

li+1=[ri - Ari+1]B

where [ ]B denotes the projection in R[M] =| {0, ... , B-1}.

Now, if M is a prime, then the theory simplifies. Every non-zero F will be invertible (as R[M] is a field iff M is prime) and all non-trivial orbits will have the same number of elements - the degree L of A in R[M] (number of elements in C). According to the Lagrange theorem, the number L is a divisor of the order of the group G[M]=R[M]\{0}. In other words, L|(AB-2).

If, furthermore, P=(M-1)/2 is a prime (primes M satisfying this additional property are called safeprimes) then <nobr>L={1, 2, P, 2P=M-1}</nobr>. In particular, if L=P then we will have only two non-trivial orbits.

To conclude, let us go back to our random number generator. It corresponds to the following specifications:

A=0x4019CF16, B=232, P=0x200CE78AFFFFFFFF, M=0x4019CF15FFFFFFFF.

So we have two possible pseudorandom sequences of digits, each of period P=L. Note that the case of the single orbit is excluded because A has the square root in R[M] and hence the whole G[M] is square-rootable. Which one will run depends on the initial value for global random_var and random_var[4] variables.

How To Compile

The creation of the executable file is quite simple. Just 3 steps. Assembling, linking and resource-compiling. Explicitly,

wasm qhello.asm
wlink @qhello
rc qhello qhello.exe

Here, the linking info is stored in the file qhello.lnk and the resource info is within hello.rc file. Don't forget to check the size of the program (take into account the pre-compiled icon size, too) :)

The binary with the full source code can be found at our download area.

Concluding Remarks

As we have seen, it is conceptually very simple to code OS/2 PM in assembly language. However it does not mean that assembly programming is easy or trivial. True simplicity is always connected with something non-trivial at a deeper level (here it is a good time to recall of the true simplicity of quantum mechanics, as revealed for example in Feynman's book [6]). It is logical to expect that a language with a higher expressive power requires a higher-level knowledge in order to properly use it. And here, in our context, such knowledge means knowing the internals of the machine, and understanding what is really happening... Invaluable scientific aspect of programming directly in the language of Machine.

References

  1. The Art of Assembly Language Programming and HLA. By Randall Hyde. An extensive and beautiful assembly language tutorial + related topics.
  2. Whiz Kid Technomagic. By Adam Stanislav. A wealth of assembly language sample programs, for Windows. Includes the mentioned random rectangles program for Windows.
  3. FreeBSD Programming in Assembly Language, by Adam Stanislav.
  4. Assembly Language Windows Applications. By Steve Gibson. Highly recommended source of valuable information and links regarding assembly language programming for Windows. Includes a Small Is Beautiful Starter Kit.
  5. Random Number CDROM, by George Marsaglia. A package consisting of truly random bits, selected random number generator topics, and a software system to test true randomness.
  6. Feynman's Lectures in Physics: By Richard Feynman. Books 8/9-Quantum Mechanics.