Objectives

The aim of this chapter is to help you understand how to use CPU registers to handle communication with the Windows operating system. To do this, you will need to be able to:

- Manipulate values in binary, decimal and hexadecimal
- Understand the way in which data can be "chunked"
- Understand the use of the CPU Registers
- Use different types of data in your programs
- Create your own event handler
- Use the Win32 help manual
- Call a Windows function
- Call an EasyCode function
- Manipulate a string

Now that we are reasonably comfortable with compiling programs in EasyCode, let's move on to the heart of assembly programs - number systems. Yes, I ** know** this chapter is called "

Let's take another look at our first number:

1 x 10000 | ||

5 x 1000 | ||

1 x 100 | ||

3 x 10 | ||

4 |

Binary. Computers, sad to say, do not work in decimal. Why should they? They have no fingers. Instead they operate on what is termed a "Boolean" basis (named after the famous mathematician George Boole). This means "True" or "False", "one" or "zero", "present" or "absent". Since data is represented inside your PC as little blips of electricity on the various tracks of the motherboard, a specific track either **has** a blip present or is doesn't. There's is nothing in between. This may sound a bit limited but, in fact, it imparts great robsutness to the system. If the voltage should drop a little, it doesn't really matter. What **is** important is that there is or isn't a charge on this line or that one. A number system which has only two digits - 0 or 1 - is known as **binary**. The digits 2-9 simply do not exist in the binary world. So, how do we represent larger numbers? Well, think about it a little. If we want to represent a number greater than "9" (the largest digit) in the decimal system, we (in effect) divide the number by 10 and take the remainder. This becomes the least siginificant digit. We then take the dividend (the result of the division) and divide it again, taking the remainder and putting that in the next box, and so on until the result of the division is zero. In the case of a binary number, we would divide by 2 instead of 10. That's all.

Binary to decimal. Let's try an example. Suppose we wanted to translate the binary number 100112 into decimal - how would we do it? The first thing is to use our number boxes as before but, this time, instead of the number base being 10, it would be 2. Like so:

Decimal to binary. As you can see, converting from binary into decimal is relatively straightforward - just multiply each digit by the power of two for the box that it occupies. In fact, this process works for **any** base into decimal. For example, there is another base used in computing - octal. This means that each box is equivalent to a power of 8 and the possible range of digits in each box is 0-7. All we would do is multiply each digit by the power of 8 for that box and total up all the results. But what about converting **from** decimal to a number base like binary (or any other)? I gave you a clue to this earlier in the chapter - instead of multiplying, we perform repeated division. Let's take our long-suffering "156" and turn it into decimal:

Two's complement. The thing about decimal numbers in the outside world is that you can express any number, however large, simply by appending more and more digits to it. Inside computers, however, things are considerably different. You will be familiar with the term **byte**. This is a binary number made up of 8 bits and so the maximum number which can be stored in it is 255. If we try to store 256 in it, all the bits will be set to zero. Do the conversion yourself - you'll end up with a ninth bit. Since the byte only **has** eight bits, the ninth is lost in the ether. Well, not quite. You see, the CPU has these registers, as we know, but it also has tiny, single-bit pieces of information called **flags**. We'll be looking at these falgs in much more detail when we deal with branching commands. Some instructions affect these flags and one of them is called the "carry" flag. If the result of an operation causes an overflow, the carry flag will be set to "1" to let us know that the result was too big for the byte.
Bytes are also a bit "schizophrenic" in that they can be regarded as negative or positive at any time. Think about it. If we store 255 in a byte, it will be set to "111111112". If we add another 1 to this byte, it will carry over and we will be left with "000000002" and the carry flag set. Thus, you could say that "111111112" is 1 less than "000000002". In other words, "111111112" could be regarded as -1 in decimal. Wierd. Check it out for yourself. If you add 1 to -1 in decimal, what do you get? Zero, of course. Add 1 to "111111112" and see what you end up with! And so, we can either regard "111111112" as 255 or as -1. If we choose to do the former, we are said to regard the number as "unsigned". If the latter, we say it is "signed".

- Change all the ones to zeros, and vice versa.
- Add 1.

We should, however, try to prove that this is the case. Think in decimal for a moment. If we add -114 to +114, what would we get? Zero. So let's add "011100102" to "100011102", starting at bit 0 (the rightmost one) ...

-114 | + | +114 | Result |

0 | + | 0 | = 0 |

1 | + | 1 | = 0, carry 1 |

0 | + | 1 + carry | = 0, carry 1 |

0 | + | 1 + carry | = 0, carry 1 |

1 | + | 0 + carry | = 0, carry 1 |

1 | + | 0 + carry | = 0, carry 1 |

1 | + | 0 + carry | = 0, carry 1 |

0 | + | 1 + carry | = 0, carry 1 |

So, there you are. The proof. Negating numbers like this is often called "**two's complement**" and it can cause problems for newcomers to programming. There is absolutely no difference in the assembly commands used when dealing with signed numbers. It's all down to how the programmer wants to regard them. For a little more depth on this subject, you should read Jeremey's article on two's complement in the goAsm help file.

Hexadecimal. Lastly in this trot through number systems, I want to talk about **hexadecimal**. Since computers deal naturally in powers of two (with the binary system), it is often convenient to express them in larger "chunks" of this approach. Octal (powers of eight) are sometimes used but, since we will not really deal with octal values, I will not discuss it further. However, chunks of 16 are very often used. The reason why I particularly want to discuss hexadecimal is that it presents us with a problem right away - we know about the values 0-9 because we use them in decimal but how to we express a **single** digit which can hold the value 10-15? The convention in hexadecimal (or "hex", for short) is to use the letters "A" - "F". That would mean that the value "FF16" would be equivalent to "255" (the rightmost "F" is 15 times no 16's and the next one is 15 x one 16's - work it out for yourself). Converting from binary to hex is quite simple. Take your byte and split it into two sets of 4 bits (these are called "nibbles", believe it or not). Convert these to their decimal equivalents, which will lie in the range 0-15, and change these into the value 0-F. Easy.

Packets of data. We should be fairly familiar by now with the concept of bytes and I have already mentioned that we often treat data in chunks of double-bytes, called a "word". See if you can work out the maximum unsigned value that could be stored in a word - yes, 65,535 (or 64K, as it is often termed). There is also a **dword** ("double word") consisting of 4 bytes. That would allow us to code a staggering 4,294,967,295 (4 Gb). There is even a **qword** ("quad-word") which is made up of 8 bytes and also a **tword** ("ten-word") which has 10 bytes. Huge numbers. Finally, there are even occasions where 16 bytes are used.