Personal C Sharp                                                         By  famsoft.org
HomeHow To StartExamples-DesktopExamples-WebPC# MethodsReference-DesktopReference-Web


======================================= Boosting Personal C Sharp speed further by using C and Assembly Sub-Programs ======================================= For version 4.30 and later We know that the persons who are interested in this chapter are the ones who must learn everything in depth. Therefore, you'll see the listing of all codes. This is to give you an idea on what you'll be doing: ==================================================== When you have been writing C# code alone, all your code have been mainly in the PC# method run() Now, you have 3 methods to write and to combine together: (1) The PC# method run() (2) The C-Language function crun() (3) The Assembly-Language function arun: In order to make them work together, PC# has added two small files: (4) The C-Language pcsc.c (5) The Assembly language pcsa.a The PC# program in (1) will be compiled to a seperate executable file as normal. Files of (2), (3), (4) and (5) will be compiled and linked together into a second executable file. The code listing of files (4) and (5) will be stored into subfolder (Langs\Bin) with other compiler and assembler files to protect them. Their object files only will be available to the linker. Organization is very important. All file names will be selected according to a unified formula. Compiling and linking will be all done by one single tool. In order to reduce errors, We'll try to think in similar manner as we do with PC# programming and to use similar variable and method names. After Example 2, we'll show you a second stage of automation in which the C and assembly files can be added to the ".cs" file making tool "pcp" the only tool necessary to do the entire job. ========================================= Downloading a C-Compiler and an Assembler ========================================= For Versions 4.30 and later There are many C-Compilers and Assemblers on the market. Some cost you money and some are free. It seems that Windows compiler and assembler cannot be seperated from the Visual Studio which is a problem to us since we like to be able to use them programmatically. We have found a seperate Compiler and Assembler which can do the two jobs fine. Both are free. The C-Compiler is called "DJGPP Compiler" and the Assembler is called "NASM Assembler". The Compiler's website is : http://www.delorie.com/djgpp and the Assembler's website is: http://sourceforge.net/projects/nasm Documentation: ============== All documentations are available at authors websites. Subfolder "Langs\Docs" of your working directory contains some documentation also. If you're looking for a nice book on Assembly language, We recommend "PC Assembly Language" by Dr. Paul Carter. The book teaches you how to use several C-Compilers and Assemblers including the two we have selected. Dr. Carter's website is at: http://www.drpaulcarter.com/pcasm. Downloading the Compiler and the Assembler: =========================================== Although we always prefer that you contact the author's sites by youreslf to do the downloads, we have decided this time to do a sample download of the two languages by ourselves and will include the samples with our new version of Personal C Sharp since we have found that the download job is a very messy one! The DJGPP compiler includes C++ and other languages, but we have downloaded "C" alone. What we have downloaded is enough to run all examples in this chapter only. After you read this chapter and like to go further with your C and Assembly programming skills, you may redo the download process in the manner you prefer. For Windows 10 users: ===================== The version of the DJGPP C compiler which we have downloaded in 2010 may have a compatibility issue with the new 64 bit operating systems. You may check to see if they have a newer version which you can download. We will try to solve this problem also for you. This problem does not affect the last two examples (8 and 9) since they do not use C as intermediary. Additionally, you may also use Microsoft's Windows assembler and the Visual Studio. Read the section titled "USING WINDOWS ASSEMBLER" following example 9. Updating System Environment: ============================ Normally, when you download anything, they modify the system %PATH% of your computer permanently which adds to the mess. Personal C Sharp never modifies your PATH permanently. Each time you open a new "Command mode" window and attempt to compile a file, PC# applies temporary modification to the %PATH% which ends when you close the window. The Compiler and Assembler files have been mixed up into one small directory tree in order to simplify their access. The temporary %PATH% modification has been extended to include the 2 new pathes: "Your Working Directory"\Langs\Bin; "Your Working Directory"\Langs\Include; After downloading the new PC# version which includes C and Assembly Languages, you need to repeat the setup process which you have done when you downloaded PC# for the first time. This means that you must browse to your working directory from command mode, run class (pcs)'s menu and execute selsction (c) as explained in "How to start" (See our home page) IMPORTANT REMARK CONCERNING THE NEW INSTALLATION: ================================================= If you plan to have no more than one working directory, you may neglect this remark. The new addition to the %PATH% includes a variable which is "Your Working Directory name". So, file "SDKVars.bat" at your working directory contains that name, therefore it cannot be transported to another directory without modification. If you must have more than one working directory, You need to run (pcs)'s menu at each location and do as explained above. Additionally, if you have more than one working directory, you should access each of them using a seperate "Command mode" window, since each requires different environment setup. Some few dificulties with the new compiler and assembler: ========================================================= Both our C-Compiler and Assembler are great softwares despite the following few difficulties: (1) The Assembler NASM does not use the same symbols PC# uses to do some jobs. For example: (;) is the Commenting symbol. All C-based languages use (//) or (/* */) (%) is the Directive symbol. All C-based languages use (#) (2) DJGPP Compiler seems to have a problem in recognizing the system %PATH%. In the next example, notice the top line of the "pcsc.c" file: #include "langs\include\stdio.h" Although file "stdio.h" is in folder "include" which is on the %PATH% We could not call it simply by its name. Until this problem is corrected, we must use this long pathname. (3) In some of the GJGPP files, you need to change a phrase like #include < include\stdio.h > with #include "include\stdio.h" in order to make it recognizable. How we have overcome those difficulties: ======================================== The Assembler problem: ---------------------- Our 3 languages must be unified in the way we write them since this makes your job easy and prevents errors. Additionally, we don't like writing one statement per line, we write one sentence per line which occupies half of the line and its description occupies the other half. We can write C this way with no problem. For the assembler we have a converter which allows you to write it identically to writing C and PC# then convert it to the NASM Assembler format. The conversion, compiling and linking are all done together with one simple new tool which is (pco) REMARK: The directive symbol is still "%". This is because using the symbol "#" would cause interference with C# software. The C Compiler problem: ----------------------- Concerning the %PATH% problem, we'll be using the long pathname for the include files until this problem is corrected. Remember that this is a simple developing time problem only. After development, the two executable files generated can run anywhere inside or outside your computer. ============================================================================================== ======== LESSON 1 ======== Computer Languages are like people, some know more and some know less. Let us think about PC#, C and Assembly as three young girls. PC# is the oldest, she knows the most. Assembly is very young, she knows the least and C is in between the two. Each of the three girls has a bank of computer memory which she can access and tools which she knows how to use. Let us see how each one of them can handle a job for us. Executing the statement: (string os="John";) -------------------------------------------- We wanted the computer to store the string "John" in memory and use (os) as a label for this memory. When Miss PC# was asked to do this job, she did it with no question asked. When we asked Miss C to do the same, she complained saying that she does not know what type "string" means. We know that she knows the type "character array" which is enough to do the job with. She has some tools to handle this type but she expects us to select the tool for her. We ended by putting it in this form for her: char *os="John"; We told her to use a pointer of the type which handles characters and make it point to the first char of the string. Miss Assembly could not understand either form. She does not know strings or character arrays or has any tools to handle them with. All she knows is that her memory is measured in: Bytes WORDS = 2 bytes each DWORDS = 4 bytes each QWORDS = 8 bytes each So, we need to make her do the job despite her limited knowledge and tools. We know that a string is a group of characters terminated with a null (which is a byte whose bits are all zeros) So, this is what we told Miss Assembly to do: os db "John",0; We told her to count the characters which make "John" and the null, allocate one byte for each, put them in the memory and set the label (os) where they start. Displaying what is stored at (os): ---------------------------------- Miss PC# can be told to do so using method tm() Miss C can be told to do so using function printf() Miss Assembly has not been taught how to do this job yet, so she asks Miss C to do the display job for her. The cpu register EAX acts as a message carrier between the two. Miss Assembly gives him a copy of the label of whatever she likes to display with: mov eax,os; // Copy address of 1st byte of the string to register EAX Miss C picks it up and does the rest. THE CPU INSIDE: =============== The CPU contains several registers. In this lesson we'll discuss the "General Purpose Registers". There are 4 of them: EAX, EBX, ECX and EDX. They are used for data movement and arithmatic operations. You can use them as you do with the GUV's in PC#. A reasonable analogy would be: o:eax i:ebx j:ecx k:edx You'll normally use them as 32 bit registers, but you can also use pieces of them. For example: al=Lower 8 bits of EAX, ah=Next 8 bits of EAX and ax=Lower 16 bits of EAX You may use those small registers to handle small numbers, but you should know that changing the value of (al) automatically changes the values of (ax) and (eax) Here are some examples on using registers: mov eax,ebx; // Copy (ebx) to (eax) Notice that the destination register comes first. mov eax,[ebx]; // Copy the value stored at the address which is assigned to (ebx) to (eax) add eax,ebx; // Add the value of (ebx) to the value of (eax) and keep result in (eax) sub eax,ebx; // Subtract the value of (ebx) from the value of (eax) and keep result in (eax) mov eax,5; // Assign (5) to (eax) add eax,2; // Add (2) to (eax) inc ecx; // Increment (ecx) dec ebx; // Decrement (ebx) mov eax,os; // Copy the address of first byte of string (os) to (eax) mov eax,[os]; // Copy the value stored at the address of first byte of string (os) to (eax) mov [os],eax; // Copy (eax) to the value stored at the address of first byte of string (os) Storing data in memory: ======================= This is another way to store and use data in Assembly. It provides a large space for data but it never runs as fast as when you use CPU registers. We have done this process in the demonstration above when we stored the string "John" in memory. We'll repeat it here with additional examples: iy db 0; // Allocate 1 byte, store (0) into it and label it (iy) jy db FFh; // Allocate 1 byte, store the hex number (FF) into it and label it (jy) oc db "X"; // Allocate 1 byte, store the ASCII code of (X) into it and label it (oc) oh dw 0; // Allocate 1 word, store (0) into it and label it (oh) i dd 0; // Allocate 1 DWORD, store (0) into it and label it (i) of DD 1.25; // Allocate 1 DWORD, Store the floating pt no. 1.25 into it, label it (of) od DQ 24.123456; // Allocate 1 QWORD, store a double prcision number into it, label it (od) os db "John",0; // Allocate 5 bytes, store "John"+ null into them and Label them (os) O dd 1, 2, 3; // Allocate 3 DWORD's, store 1,2,3 into them and label them (O) OY times 1000 db 0; // Allocate 1000 bytes, initialize them with zeros and label them OY REMARKS: -------- (1) All these labels represent addresses where data are stored in memory, not the data themselves. This means that i is an address not a stored value, [i] is the stored value. (2) When you store new data into a register, you must state the size of initial data which you like to replace with the new data. So: DO NOT: mov [eax],5; DO : mov DWORD [eax],5; OR : mov byte [eax],5; (3) Register names and CPU instructions are not case sensitive but variable names and Assembler directives are. How to display text in "C": =========================== Function printf() displays on the console. You assign it two items seperated with a comma. The format to display at and the item to be displayed. Here are the most popular two syntaxes: printf("%s\n",os); // Print a string with a "\n" at its end. printf("%d\n",o); // Print a number with a "\n" at its end. Connecting the three languages together: ======================================== (1) It starts by calling your "C" or "Assembly" sub-program from your PC# program supplying it with upto 4 parameters, the first one must be either "c" meaning that you like it to be executed by the "C-language" function crun() or "a" meaning that you like it to be executed by the "Assembly-Language" function arun() The call is done this way: os="SubProg c Par1 par2 par3";sm("pn"); or os="SubProg a Par1 par2 par3";sm("pn"); (2) The "C" function main() provides the entry point where your parameters are received, analized and forwarded to either function crun() or arun() depending on your selection. Those functions always receive 4 parameters regardless to how many parameters you called with. The first of the 4 parameters is an integer which represents the total number of parameters you have less the first one. The other 3 param's are all parameters you sent except the first one which is only to select the function. If any of the 3 parameters is missing, the missing ones are replaced with ""'s. Example: If your call was: os="SubProg c Par1";sm("pn"); function crun() will be called from function main() as follows: crun(1,"Par1","",""); (3) Before crun() terminates it loads its return string pointer into string (rts) and before arun() terminates it loads its return string pointer into register EAX. (4) Function main() displays the return string on the console of the sub-program's process which leads to receiving it at your PC# program assigned to (os) File pcsc.c: ------------ The operation mentioned above is done in "pcsc.c" which contains function main. Look at this code to understand how it's done. ----------------------------------------------------------------------------------------------- // ** Entry point for C and Assembly Sub programs ** #include "langs\include\stdio.h" // Using printf() requires this i/o file. int rti; // (rti) stores address of 1st byte of arun() return string char *rts; // (rts) Stores pointers to 1st char of crun() return string // Function main receives the number of arguments in (argc) and a string array containing all args. // The string array is actually an array of char arrays. int main(char argc, char** argv) { if(argc<5) argv[4]=""; // Make sure to include any missing argument and assign if(argc<4) argv[3]=""; // "" to it. 1st arg (c/a) must be available if(argc<3) argv[2]=""; if(argc<2) { rts="Error: No Language selection made."; printf("%s\n",rts);return; } //--------------------------- Calling wanted Language function --------------------------- // arg[0] is the program name, arg[1] is language selection ("c"/"a"), so actual par no. is // argc-2. arg[1] is used for branching but not sent to the functions. if(*argv[1]==*"a") { // Check argv[1] and call crun() or arun() accordingly. rti=arun(argc-2,argv[2],argv[3],argv[4]);printf("%s\n",rti); } else { // and output the return string to the consol in either case. crun(argc-2,argv[2],argv[3],argv[4]);printf("%s\n",rts); } return 0; } //-------------------------------------- Function tm() -------------------------------------- tm(char* ls){ // Simulates the PC# function tm("dl") printf("%s\n",ls); } ----------------------------------------------------------------------------------------------- Selecting file names, converting, compiling and linking them all: ================================================================= We are using a seperate C-Compiler and Assembler. Each of them has its own compiling tools. After you read this chapter and like to expand your knowledge, you can read their manuals and learn what they have. For now, you are with Personal C Sharp whose major purpose is simplifying. We should use only the tools which are absolutely necessary and combine them all into one tool. Here is what you need to do: (1) Select a base name of one or more letters. Let us assume you have selected the base name "x". (2) Add "c.c" to make the 'C' file name and 'a.a' to make the Assembly file name. So they must be named "xc.c" and "xa.a" in this case. (3) Write function crun() into file "xc.c" and write 'arun:' into file "xa.a". Your code in "xa.a" is not expected to be readable to the NASM Assembler at this point since it should be written similarly to writing C and C#. (4) From command mode, call our single compiling and linking tool as follows: pco x [ENTER] Everything will be done. xa.a will be converted to xa.asm which the NASM Assembler can read, then all files will be compiled and linked together generating the executable file "xe.exe". Using the tools seperately: --------------------------- Converting "xa.a" to "xa.asm" .......................... tonasm xa.a Compiling "xa.asm" to generate "xa.o" .................. nasm -f coff xa.asm Compiling "xc.c" to generate "xc.o" .................... gcc -c xc.c Linking "xa.o,xc.o,pcsc.o,pcsa.o" to generate "xe.exe".. gcc -o xe.exe xc.o xa.o pcsc.o pcsa.o All commands should be done at command mode and should be followed with [ENTER] New Debugging Tool: =================== (1) Tool ln: This tool was made for PC# code debugging. When you enter from command mode: ln 24 [ENTER] this tool understands that you like to see line 24 of file "pca.cs" which is the last PC# file compiled. We have extended its ability by allowing you to enter a 2nd parameter which is the full file name (with extension) of the file to be displayed. So, now you can do: ln 15 MyProg.c or ln 27 Myprog.asm To display a line number, the 10 lines before it and the 10 lines after it of the file you like. File "pca.cs" is still the default. =============================================================================================== Example 1: Write two sub-programs. One in "C" and another in "Assembly". When either one of them is called from a Personal C Sharp program it returns a greeting message. Then, write a PC# program which calls each one of them and displays the returned messages. =============================================================================================== The C Program "xc.c": ===================== extern char *rts; char* crun(int parn,char *js,char *ks,char *os){ rts="Greetings from PC# C-Language Sub-Program."; } ============================================================================================= The Assembly Program "xa.a": ============================ segment .data; // Data Section // Allocate enough bytes for the string + its null terminator rts db "Greetings from PC# Assembly Language Sub-Program.",0; segment .text; // Code Section //--- Function arun() must be defined global so it can be accessed from all liked files --- global _arun; // Function definition _arun: { // Function label. arun() in C translates to _arun: in Assembly mov eax,rts; // Copy address of 1st byte of return string to EAX ret; } ============================================================================================== PC# Program "a.cs": =================== public class a:pcs { public override void init() { j=700;k=375;dm("s"); // Resize Form tia=toa="t"; base.init(); } public override void run() { cls="r0";os="";tm(); os=" Accessing sub-program functions written in C and Assembly languages"; tm();os="";tm(); cls="b0";os="Calling Sub-Program xe.exe and requesting a C-Language Function:"; tm();os="";tm(); os="xe c par1";sm("pn"); // Call the C language function cls="G3";tm(); // Display returned string in green. os="";tm();cls="b0"; os="==========================================================================="; tm();os="";tm(); cls="b0";os="Calling Sub-Program xe.exe and requesting an AssemblyLanguage Function:"; tm();os="";tm(); os="xe a par1";sm("pn"); // Call the Assembly language function cls="G3";tm(); // Display returned string in green. } } ============================================================================================== COMPILING: ========== Use notepad to make all files, then from command mode type: pco x [ENTER] Then you can compile and run the pc# file with: pcpr a [ENTER] as normal ==============================================================================================


======== LESSON 2 ======== Available Registers: ==================== (1) GENERAL PURPOSE REGISTERS (EAX,EBX,ECX,EDX): Already discussed in previous lesson. (2) INDEX REGISTERS (ESI, EDI): Could be used as pointers or for general purpose, however they can't be split into bytes or words like general purpose registers. (3) Stack and base pointers (ESP, EBP): Used to manage the stack (will be discussed next) (4) Segment registers (CS, DS, SS, ES, FS, GS): "CS, DS & SS" point to "code, data and stack" segments. "ES, FS and GS" are extra registers. Instruction pointer (IP) points to next instruction address in memory. It helps (CS) (5) Flags register: Indicates how last instruction has been executed. Each bit does a special indication job. The stack: ========== The stack is an area of the memory where you can store data. Like your mail box, the first item you can pick-up is the last item which has been put into the stack. The only thing unusual about the stack is that it uses its memory at a reverse order. The first item in it is stored at the highest memory address and the last item is stored at the lowest memory address. The address of the byte which is next down to the last one occupied is called the "top of the stack". The stack pointer ESP always points to the top of the stack. We can push the value of (eax) into the stack with (push eax) This will make the value of ESP go 4 bytes down. The stack is now 4 bytes larger and contains a copy of (eax) We can then use the instruction (pop ecx) to make (ecx=eax) and return (ESP) to its original value. This means that the stack has now returned to its original size and the copy of (eax) in the stack has been lost. Now, If we have replaced this last instruction with: (1) mov ecx, [esp]: we could have ended with (ecx=eax) but (esp) stayed unchanged meaning that the (eax) copy in the stack has not been lost. (2) mov ecx, [esp+4]: we could have ended with (ecx=Previous Data stored into stack) and also (esp) unchanged. (3) add esp, 4: The copy of (eax) in the stack could have been lost since any new data pushed into the stack will take its place. Storing Register group in one instruction: ------------------------------------------ EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI can be all pushed (at this order) into stack and popped out of stack with: (pusha) & (popa) respectively. =============================================================================================== ================= Calling functions ================= The standard convention states that the function parameters are passed by the stack and the return value is passed by register EAX. A Function should leave the stack exactly as it was when it received it. This means that it should pick its parameters with the (mov) instruction as described above not with the (pop) instruction. The calling and returning mechanism: ------------------------------------ (1) The caller must push the parameters at a reverse order so that the function can receive them at the expected order. (2) Next, the caller gives the instruction to call the function. The assembler wants the program to resume at the instruction which immediately follows this one when the function terminates. So it pushes the address of the next instruction into the stack. Now, this return address is stored at [ESP] and the first parameter has been pushed deeper to [ESP+4]. (3) If the fuction can make it without using the stack for itself, it can get its first parameter with: (mov eax,[esp+4]) and its second parameter with (mov ebx,[esp+8]) and so on. At the end it should load its return value into (eax) then give (ret) instruction. (4) The Assembler then pops the return address from the stack and jumps there so the program resumes. What will be left into the stack at this point are the parameters only. Popping them out of the stack is the responsiblity of the calling program. How about if the function must use the stack: --------------------------------------------- Obviously if the function could use the stack correctly, everything would go fine, but functions are not to be trusted. This is why Intel came up with the base register EBP idea. It's used to back-up the stack at the function start and to restore it back before the return. The function does not have to do this job by itself. You can make the Assembler do this job for your function by giving the instruction (enter 0,0) at the start and the instruction (leave) immediately before return. One difficulty which you get when choosing this option is that your parameters will be pushed 4 bytes deeper into the stack. So the first parameter will be at [esp+8] instead of [esp+4]. This is because they push EBP into the stack at the start of this process. The routine of starting and ending a function: ---------------------------------------------- //---- Starting Routine ---- enter 0,0; // Push EBP into stack then copy the ESP stack to the EBP stack. pusha; // Save EAX, EBX, ECX, EDX, ESI, EDI and EBP the fastest way pushf; // Save flag register // ** Remember that you can't easily use ESP to get your parameters now since they have been // pushed too deep after storing all those registers into stack. So use EBP instead. mov eax,[ebp+8]; // Get first par ............... // Do the function's job. //---- Ending Routine ---- popf; // Restore flag register popa; // Restore EAX, EBX, ECX, EDX, ESI, EDI and EBP Leave; // Restore the stack from EBP then pop original EBP out of it. mov eax,....; // Load function's return value into eax ret; // Return Assembly to Assembly function calls: ------------------------------------ It would be nice to do it using standard convention. However, you can do it any way you choose. You can just use registers or memory values for both parameters and return values if you like. Here is an example: //--- Calling Program (Assembly) --- mov ecx, 5; // Start with 5 push ecx; // Push this value into the stack to send to function call increment; // Call the function add esp,4; // All done. Readjust stack pointer so that parameter is lost. //--- Function (Assembly) --- increment:{ // Function Increment: IN:Value in stack OUT: EAX=value incremented mov eax, [esp+4]; // Get parameter from the stack into EAX inc eax; // Increment EAX value (This is what this function is made to do) ret; // and return } Assembly to C function calls: ----------------------------- We'll show you here part of the next example in which function 'arun:' used the 'C' function tm() to display a string. When you call a 'C' function from Assembly, you must be careful not to mix register and stack setups of the 2 languages. So you can't do the call using a short code. We solve this problem by having one intermediate Assembly function which does the lengthy job of interfacing with 'C' and call it with short code within the Assembly program as many times as we like. //--- Sample call of the intermediate Assembly function (atm:) from function (arun:) --- mov eax, aos; // Make EAX point to the string to be displayed. call atm; // Call the intermidiate assembly function //--- Intermediate Assembly function (atm) Calls C function tm() internally --- extern _tm;global atm; // Extern def of C function and global def of Assembly function atm: { // Assembly Function atm() enter 0,0;pusha;pushf; // Function start routine push eax;call _tm;pop ecx; // Call C function tm(eax), restore stack popf;popa;leave; // Function end routine ret; } //--- The 'C' Function tm() --- tm(char* ls){ printf("%s\n",ls); } C to Assembly function calls: ----------------------------- The call of function main() in which it sends all PC# parameters to the Assembly function 'arun:' is an example of that. The parameters are passed by the stack. Next example will show you how this is done. Rules concerning global function and variable names in C and Assembly: ====================================================================== (1) Our C language understands that the name x in 'C' translates to the name _x in Assembly and the name x() in 'C' translates to _x: in Assembly. So, whenever we like to call an Assembly function from C, like we do with function arun(), we name the function (_arun) in Assembly and call it from C with: arun() Also when Assembly calls the C function tm(), it calls it with (call _tm;) (2) Any function or variable name in Assembly which is expected to be accessed from outside the file in which it has been defined, must be declared global. In 'C', top level functions and variables are global by default. For example, the 'C' functions main() and tm() are global. Any top level variable (not defined inside a function) is also global. (3) When any of those global items is accessed from another Assembly file of the linked group, that other Assembly file must contain a definition of the item as "extern" in its data section. (4) When a top level variable is defined in one 'C' file and accessed from another 'C' file of the linked group, it must also be defined as extern in that other 'C' file. The type should be also stated. Here is an example: On top of file "pcsc.c", (rts) was defined with (char* rts;) This variable has been used in file "xc.c", so we have defined it at the top of that file with (extern char* rts;) (5) Remember that if you have defined the top level variable (int x=5;) in 'C' and like to access it in Assembly, the item whose value is (5) will not be _x, it will be [_x] (which means the stored value at address _x) If this was confusing to you, you need to know that all variable names indicate addresses in memory, not stored values. Miss C has allowed us to use the shorthand (x=5) to indicate that we want to store (5) at address x. Miss Assembly is too young to do the same, so we must give it to her like it actually is. pcsa.a: ======= As mentioned in lesson 1, the two files "pcsc.c" and "pcsa.a" are written by PC# for the purpose of making the 3 languages communicate together. File "pcsc.c" contains the software which receives the startup parameters from the PC# program and sends them to either the C function crun() or the Assembly function 'arun:' according to the PC# program selection. File "pcsa.a" contains the intermediate Assembly functions which handle the communication with 'C'. Here it is: ---------------------------------------------------------------------------------------------- // ** All labels must be at least 3-char long. segment .data; t_d db "%s",0;t_dn db "%d",0;t_dnl dd 10 // Parameters of function tm() extern _printf;global tm_d,tm_dn,tm_dnl; // printf() function definition in C,Assembly tm_d: { // Assembly function tm("d") Display string enter 0,0;pusha;pushf; // Function start routine push eax;push t_d; // Send Par's at reverse order call _printf; // Call printf() pop ecx;pop ecx; // fix stack popf;popa;leave; // Function end routine ret; } tm_dn: { // Assembly Function tm("dn") Display number enter 0,0;pusha;pushf; // Same as tm_d: push eax;push t_dn; call _printf; pop ecx;pop ecx; // fix stack popf;popa;leave; // Function end routine ret; } tm_dnl: { // Assembly Function tm("dnl") Display "\nl" enter 0,0;pusha;pushf; // Same as tm_d: push eax;push t_dnl; call _printf; pop ecx;pop ecx; // fix stack popf;popa;leave; // Function end routine ret; } extern _tm;global atm; // Function def in C,Assembly atm: { // Assembly function atm() enter 0,0;pusha;pushf; // Function start routine push eax;call _tm;pop ecx; // Call C function tm(eax), fix stack popf;popa;leave; // Function end routine ret; } ============================================================================================== Example 2: Show how both functions crun() and 'arun:' can receive the PC# program parameters and display them using their own version of function tm() Show also how can function 'arun:' send a string to the 'C' function version of tm() to be displayed. Additionally, the two return strings must be displayed by function main() as done in Example 1. Write a PC# program which sends the parameters to both functions, receives all their displayed text and displays it on the text screen. ============================================================================================== xc.c: ===== extern char* rts; char* os; crun(int parn,char* js,char* ks,char* os){ tm(js);tm(ks);tm(os); // Display all parameters locally. rts="Greetings from PC# C-Language Sub-Program."; } xa.a: ===== //--------------------------------------- Data Segment---------------------------------------- segment .data; extern atm,tm_d,tm_dn,tm_dnl; rts1 db "Greetings from PC# Assembly Language Sub-Program.",0; aos db "This String is sent from Assembly to be displayed by the C function tm()",0; //--------------------------------------- Code Segment --------------------------------------- segment .text; global _arun; _arun: { enter 0,0;pusha;pushf; // Function start routine. //------ Using Assembly tm() to display PC# Parameters ------ // Notice that [ebp+8] contains the number of parameters, so PC# parameters start at [ebp+12] mov eax,[ebp+12];call tm_d;call tm_dnl; // Get 1st par & display it. mov eax,[ebp+16];call tm_d;call tm_dnl; // Get 2nd par & display it. mov eax,[ebp+20];call tm_d;call tm_dnl; // Get 3rd par & display it. //------ Using C Function tm() to display (aos) string ------ mov eax, aos; // Make eax point to the string to be displayed. call atm; // Call the intermidiate function to the C language tm() popf;popa;leave; // Function end routine. //------ String to be returned to PC# Program ------ mov eax,rts1; // Load string to be returned to PC#. ret; // Return } a.cs: ===== public class a:pcs { public override void init() { j=720;k=438;dm("s"); // Resize Form tia=toa="t"; base.init(); } public override void run() { cls="r0"; os=" Accessing sub-program functions written in C and Assembly languages"; tm();os="";tm(); cls="b0";os="Calling Sub-Program xe.exe and requesting the C-Language Function crun()"; tm();cls="S9"; os="crun() will use its local function tm() to display all received parameters. "; os+="Then will return its greeting\nstring which will be displayed by main(). All displayed"; os+=" text comes to PC# program assigned to (os)";tm(); os="xe c PC#_Calling_C_par_1 PC#_Calling_C_par_2 PC#_Calling_C_par_3"; sm("pn"); // Call the C language function cls="G3";tm("d"); // Display received (os) in green color cls="b0"; os="==========================================================================="; tm();//os="";tm(); cls="b0";os="Calling Sub-Program xe.exe and requesting an Assembly Language Function arun:"; tm();cls="S9"; os="arun: will use its local function 'tm:' to display all received parameters. Then will"; os+=" send a string to the\n'C' function tm() to display it. Finally will return its "; os+="greeting string which will be displayed by main()"; tm(); os="xe a PC#_Calling_Assembly_par_1 PC#_Calling_Assembly_par_2 PC#_Calling_Assembly_par_3"; sm("pn"); // Call the C language function cls="G3";tm("d"); // Display received (os) in green color } } =============================================================================================== Compiling: Same as with Example 1. ===============================================================================================


============================================================================================== One more level of automation: ============================= You have executed each of the last two examples in the following 5 steps. (1) Making file "xc.c" (2) Making file "xa.a" (3) Running the tool "pco x" generating the additional files "xa.asm", "xa.o" and "xe.exe" (4) Making file "a.cs" (5) Running the tool "pcpr a" You can eliminate the first 3 steps if you add the contents of files "xc.c" and "xa.a" to the contents of file "a.cs". If you do so, you'll generate all files, compile, link and run them all with a single command which is "pcpr a". How to add the ".c" and ".a" files to the ".cs" file? ----------------------------------------------------- They must be at the top of the ".cs" file. Each one of them should be preceded and followed with an opening and closing "XML-like" tags. The tags should be on seperate lines. Here is a sample: < xc.c> Contents of the "xc.c" file < /xc.c> < xa.a> Contents of the "xa.a" file < /xa.a> REMARKS: ======== (1) The sub-programs must be at the top of the file. Not even comment lines are allowed above them. Only empty lines which may contain space characters are allowed. (2) In order to make your browser display the XML-like tags correctly, we have added a space between the opening bracket and the rest of each tag. You are expected to eliminate those spaces in your program. However, we have adjusted our software so it can identify them even if the spaces have not been removed. To test this new feature: ------------------------- (1) Delete files "xc.c", "xa.a", "xa.asm", "xa.o", "xe.exe" and "a.exe" from your directory. (2) Modify the "a.cs" file by adding the contents of "xc.c" and "xa.a" at its top with the 4 tags. Here is how it becomes: ============================================================================================== < xc.c> extern char* rts; char* os; crun(int parn,char* js,char* ks,char* os){ tm(js);tm(ks);tm(os); // Display all parameters locally. rts="Greetings from PC# C-Language Sub-Program."; } < /xc.c> < xa.a> //--------------------------------------- Data Segment---------------------------------------- segment .data; extern atm,tm_d,tm_dn,tm_dnl; rts1 db "Greetings from PC# Assembly Language Sub-Program.",0; aos db "This String is sent from Assembly to be displayed by the C function tm()",0; //--------------------------------------- Code Segment --------------------------------------- segment .text; global _arun; _arun: { enter 0,0;pusha;pushf; // Function start routine. //------ Using Assembly tm() to display PC# Parameters ------ mov eax,[ebp+12];call tm_d;call tm_dnl; // Get 1st par & display it. mov eax,[ebp+16];call tm_d;call tm_dnl; // Get 2nd par & display it. mov eax,[ebp+20];call tm_d;call tm_dnl; // Get 3rd par & display it. //------ Using C Function tm() to display (aos) string ------ mov eax, aos; // Load string to be displayed. call atm; // Call the intermidiate function to C lang tm() popf;popa;leave; // Function end routine. //------ String to be returned to PC# Program ------ mov eax,rts1; // Load string to be returned to PC#. ret; // Return } < /xa.a> public class a:pcs { public override void init() { j=720;k=438;dm("s"); // Resize Form tia=toa="t"; base.init(); } public override void run() { cls="r0"; os=" Accessing sub-program functions written in C and Assembly languages"; tm();os="";tm(); cls="b0";os="Calling Sub-Program xe.exe and requesting the C-Language Function crun()"; tm();cls="S9"; os="crun() will use its local function tm() to display all received parameters. "; os+="Then will return its greeting\nstring which will be displayed by main(). All displayed"; os+=" text comes to PC# program assigned to (os)";tm(); os="xe c PC#_Calling_C_par_1 PC#_Calling_C_par_2 PC#_Calling_C_par_3"; sm("pn"); // Call the C language function cls="G3";tm("d"); // Display received (os) in green color cls="b0"; os="==========================================================================="; tm();//os="";tm(); cls="b0";os="Calling Sub-Program xe.exe and requesting an Assembly Language Function arun:"; tm();cls="S9"; os="arun: will use its local function 'tm:' to display all received parameters. Then will"; os+=" send a string to the\n'C' function tm() to display it. Finally will return its "; os+="greeting string which will be displayed by main()"; tm(); os="xe a PC#_Calling_Assembly_par_1 PC#_Calling_Assembly_par_2 PC#_Calling_Assembly_par_3"; sm("pn"); // Call the C language function cls="G3";tm("d"); // Display received (os) in green color } } ============================================================================================== (3) Compile and run the "a.cs" file with: pcpr a [ENTER] All files will be regenerated and your program will run as it did before. ============================================================================================== ======== LESSON 3 ======== Decisions and Loops: ==================== What makes the difference between intelligent and non-intelligent Languages? The answer is their ability to handle decisions and loops. All our languages (C#,C and Assembly) are intelligent languages. HTML and XML are non-intelligent languages. Decisions and loops are handled the same in C and C# with even the same keywords. Let us see how they can be done in Assembly. The Flag Register: ------------------ Bits of this register can set or reset depending on the last instruction the CPU has performed. We are now intersted in two bits: Bit #6: The Zero flag bit. It sets if last result was zero. Bit #7: The Sign flag bit. It sets if last result was negative. The CPU Jump instructions: -------------------------- We're allowed to unconditionally jump to a label or to make the jump coditional to the state of specific flag bits. For example: jmp label; // Unconditional jump jz label; // Jump if last result =0 jnz label; // Jump if last result<>0 js label; // Jump if last result -ve jns label; // Jump if last result +ve or 0 je label; // Jump if cmpare found the two to be equal. jne label; // Jump if cmpare found the two to be nonequal. Those jump instructions can work fine if the label was upto 32,000 bytes from the source of the jump in either direction. If the label was no more than 128 bytes from the source, we can make it run faster by adding the word "short" before the label. The cpu compare instruction: ---------------------------- The instruction (cmp eax,5;) compares eax with (5) If (eax-5)=0, the zero bit of the flag register sets and you can use this as a condition to jump to a label using the (jz) instruction. If (eax-5) was negative you can use this condition to jump to a label using the (js) instruction. Decisions: ---------- The compare and jump instructions can be used to simulate the (if) statement in C and C#. cmp ebx,0; // Compare (ebx) with (0) jz zero; // If (ebx==0) jump to block "zero" js negative; // If (ebx<0) jump to block "negative" positive: // If neither zero or negative it must be positive ........ // So execute the code at the "positive" label and return. ret zero: ........ // Execute the code at the "zero" label and return. ret negative: ........ // Execute the code at the "negative" label and return. ret Loops: ------ We know that we can do the loops in C and C# with either increments or decrements. Let us see how we do the same in Assembly. The example used is a loop which adds (2) to (o) 5 times, so after the loop ends, we get (o=10) ------------------------------------------------------------------------------------------- Doing it with increments: C & C# Assembly ==================== =========================== o=0; mov eax,0;mov ecx,0; for (j=0;j<5;j++) { Label: o+=2; add eax,2; } inc ecx;cmp ecx,5;jnz Label; ----------------------------------------------------------------------------- Doing it with decrements: C & C# Assembly Assembly (Shortest but not Best) ==================== =========================== =============================== o=0; mov eax,0;mov ecx,5; mov eax,0;mov ecx,5; for (j=5;j>0;j--) { Label: Label: o+=2; add eax,2; add eax,2; } dec ecx;cmp ecx,0;jnz Label; loop Label; --------------------------------------------------------------------------------------------- You can see that the last loop replaced 3 instructios with one. We have been thinking that it's the fastest, but our test showed that it is slower than the one before it. Arithmatic and bitwise operations: ================================== add eax,5; // Add 5 to eax, keep result in eax sub eax,ebx; // Calculate eax-ebx, keep result in eax imul eax,10; // Multiply signed numbers idiv eax,ebx; // Divide signed numbers inc eax; // Increment eax dec ebx // Decrement ebx and edx,eax; // Apply and to the two, keep result in first or edx,eax; // Apply OR to the two, keep result in first shl eax,4; // Shift left one nipple shr eax,4; // Shift right one nipple New addition to "pcsa.a": ========================= So far, we have been working with strings only. The parameters which PC# sends to function main() must be all strings. Also the return value which PC# receives must be also in a string form. Now, we like to be able to receive and send numbers too. So, we need to be able to convert numbers to hex strings and back in Assembly language. PC# can easily do those two conversions at their side, so we can communicate with hex strings then convert them to numbers when received. The new functions are 'tohexs:' and 'tohexn:'. We have also two new global memory locations, one is labeled "hexn" with size=1 dword to take the int number, and the other is labled "hexs" with size=8 bytes to take the hex string. To convert an integer number into a hex string, assign the number to eax and call function 'tohexs:'. The output will be in eax and hexs. Both point to the hex string To convert a hex string into an integer number, make eax point to the string and call function 'tohexn:'. The resulting int number value will be in eax and also in [hexn]. Here is the new addition: ----------------------------------------------------------------------------------------------- segment .data; global hexn,hexs,space,star,nline; hexn dd 0;hexs db " ",0; // hex int,hex string for tohexn & tohexs functions space db " ",0;star db "*",0;nline dd 10; // General use space, star, new line char //-------------------- Assembly function tohexs (Converts int to hex string) ------------------ global tohexs; tohexs:{ // IN:eax=int number OUT: eax=hexs=Hex String enter 0,0;pusha;pushf; // Function start routine. mov edx,eax; // Get number to be converted in EDX mov edi,hexs;add edi,7; // Make EDI point to last byte in (hexs) mov ecx,8; // Nipple counter (decremented by (loop) opcode) start: // ** start of Loop mov eax,edx;and eax,15; // Get one copy of no. Zero all its bits but first 4 add al,48; // Read first byte, Adjust so (0) becomes Ascii(0) cmp al,58;js hexh; // If in range (ascii 0:9) proceed to label add al,7; // Else add 7 so next digit becomes "A" hexh: mov byte [edi],al; // Move no to byte in hexs which is pointed to by edi dec edi; // Move edi to next byte towards front shr edx,4; // Shift original number right one nipple loop start; // Repeat Loop popf;popa;leave; // Function end routine. mov eax,hexs; // Make eax another output of this function. ret; // Return } //---------------------- Assembly function tohexn (Converts hex string to int) ---------------- global tohexn; tohexn:{ // IN:eax=ptr to string OUT: eax=[hexn]=int value enter 0,0;pusha;pushf; // Function start routine. mov edi,eax; // Make edi points to string mov edx,0; // edx will get the output number mov ecx,8; // Nipple counter (decremented by (loop) opcode) start1: // ** start of Loop mov eax,0; // Clear eax mov al,byte [edi]; // Get the byte from (hexs) which is pointed to by edi cmp al,0; // If string length<8 chars, a null will be met je finished; // If it happened end operation sub al,48; // Adjust so Ascii(0) becomes Actual dec(0) cmp al,10;js hexh1; // If in range (0:9) proceed to label sub al,7; // Else subtract 7 so next number becomes (10) not 'A' hexh1: shl edx,4; // Shift original value in edx one nipple left or edx,eax; // OR eax with it to receive new nipple inc edi; // Move edi to next byte towards back loop start1; // Repeat Loop finished: // ** process end mov dword [hexn],edx; // Temporarely store result here (before popa) popf;popa;leave; // Function end routine. mov eax,dword [hexn]; // Return resulting number value in eax and [hexn] ret; // Return } ASCII TABLE: ============ ------------------------------------------------------------------------------------- Char Dec Oct Hex | Char Dec Oct Hex | Char Dec Oct Hex | Char Dec Oct Hex ------------------------------------------------------------------------------------- (nul) 0 0000 0x00 | (sp) 32 0040 0x20 | @ 64 0100 0x40 | ` 96 0140 0x60 (soh) 1 0001 0x01 | ! 33 0041 0x21 | A 65 0101 0x41 | a 97 0141 0x61 (stx) 2 0002 0x02 | " 34 0042 0x22 | B 66 0102 0x42 | b 98 0142 0x62 (etx) 3 0003 0x03 | # 35 0043 0x23 | C 67 0103 0x43 | c 99 0143 0x63 (eot) 4 0004 0x04 | $ 36 0044 0x24 | D 68 0104 0x44 | d 100 0144 0x64 (enq) 5 0005 0x05 | % 37 0045 0x25 | E 69 0105 0x45 | e 101 0145 0x65 (ack) 6 0006 0x06 | & 38 0046 0x26 | F 70 0106 0x46 | f 102 0146 0x66 (bel) 7 0007 0x07 | ' 39 0047 0x27 | G 71 0107 0x47 | g 103 0147 0x67 (bs) 8 0010 0x08 | ( 40 0050 0x28 | H 72 0110 0x48 | h 104 0150 0x68 (ht) 9 0011 0x09 | ) 41 0051 0x29 | I 73 0111 0x49 | i 105 0151 0x69 (nl) 10 0012 0x0a | * 42 0052 0x2a | J 74 0112 0x4a | j 106 0152 0x6a (vt) 11 0013 0x0b | + 43 0053 0x2b | K 75 0113 0x4b | k 107 0153 0x6b (np) 12 0014 0x0c | , 44 0054 0x2c | L 76 0114 0x4c | l 108 0154 0x6c (cr) 13 0015 0x0d | - 45 0055 0x2d | M 77 0115 0x4d | m 109 0155 0x6d (so) 14 0016 0x0e | . 46 0056 0x2e | N 78 0116 0x4e | n 110 0156 0x6e (si) 15 0017 0x0f | / 47 0057 0x2f | O 79 0117 0x4f | o 111 0157 0x6f (dle) 16 0020 0x10 | 0 48 0060 0x30 | P 80 0120 0x50 | p 112 0160 0x70 (dc1) 17 0021 0x11 | 1 49 0061 0x31 | Q 81 0121 0x51 | q 113 0161 0x71 (dc2) 18 0022 0x12 | 2 50 0062 0x32 | R 82 0122 0x52 | r 114 0162 0x72 (dc3) 19 0023 0x13 | 3 51 0063 0x33 | S 83 0123 0x53 | s 115 0163 0x73 (dc4) 20 0024 0x14 | 4 52 0064 0x34 | T 84 0124 0x54 | t 116 0164 0x74 (nak) 21 0025 0x15 | 5 53 0065 0x35 | U 85 0125 0x55 | u 117 0165 0x75 (syn) 22 0026 0x16 | 6 54 0066 0x36 | V 86 0126 0x56 | v 118 0166 0x76 (etb) 23 0027 0x17 | 7 55 0067 0x37 | W 87 0127 0x57 | w 119 0167 0x77 (can) 24 0030 0x18 | 8 56 0070 0x38 | X 88 0130 0x58 | x 120 0170 0x78 (em) 25 0031 0x19 | 9 57 0071 0x39 | Y 89 0131 0x59 | y 121 0171 0x79 (sub) 26 0032 0x1a | : 58 0072 0x3a | Z 90 0132 0x5a | z 122 0172 0x7a (esc) 27 0033 0x1b | ; 59 0073 0x3b | [ 91 0133 0x5b | { 123 0173 0x7b (fs) 28 0034 0x1c | < 60 0074 0x3c | \ 92 0134 0x5c | | 124 0174 0x7c (gs) 29 0035 0x1d | = 61 0075 0x3d | ] 93 0135 0x5d | } 125 0175 0x7d (rs) 30 0036 0x1e | > 62 0076 0x3e | ^ 94 0136 0x5e | ~ 126 0176 0x7e (us) 31 0037 0x1f | ? 63 0077 0x3f | _ 95 0137 0x5f | (del) 127 0177 0x7f About the new example: ---------------------- The factorial of (5) = 5*4*3*2*1. We're going to write an Assembly function which computes the factorial of any number. The result will be limited to a DWORD size. So we can't accept numbers over 12. We'll be getting a number from user at the PC# program, converting it into a hex string and sending the string as a parameter to the assembly function. The Assembly function will convert our hex string to int, calculate the factorial then convert the result to a hex string and return it to the PC# program. ============================================================================================== Example 3: Write an assembly program to calculate the factorial of an integer number. Use a PC# program as an interface to allow the user to use the Assembly function. ============================================================================================== < xc.c> char* crun(int parn,char* js,char* ks,char* os){ } < /xc.c> < xa.a> //--------------------------------------- Data Segment---------------------------------------- segment .data; extern atm,tm_d,tm_dn,tm_dnl,tohexs,tohexn,hexn,hexs; //--------------------------------------- Code Segment --------------------------------------- segment .text; global _arun; _arun: { enter 0,0;pusha;pushf; // Function start routine. //------ Convert incoming hex string to int ------- mov eax,[ebp+12];call tohexn; // Get hex string in eax, call tohexn to conv to int //------ Finding factorial of the number in (eax) ------ mov ecx,eax; // Assign the number to the loop counter; mov eax,1; // Result=eax=1 (as initial value) st: // ** Loop start imul eax,ecx; // Keep multiplying result by counter loop st; // Repeat after dec ecx until zero. //------- Convert number in (eax) to hex string ------ call tohexs; popf;popa;leave; // Function end routine. mov eax,hexs; // Return string=Factorial result in hex ret; // Return } < /xa.a> public class a:pcs { public override void init() { j=720;k=438;dm("s"); // Resize Form bli=0; // Start at block 0 tia=toa="t"; base.init(); } public override void run() { //--------------------------------- Displaying information ---------------------------- if (blp==0) { os="";tm();cls="r0";fns="trb12"; os=" Using PC# to interface between the user and an Assembly"; os+=" Program"; tm();os="";tm();cls="b0";fns="trbi12"; os="The user will be asked to enter a decimal number. The number will be converted into a"; os+=" hex string at PC# then sent to the Assembly sub-program where the factorial of"; os+=" the number will be computed. The result will be sent back to PC# program as a hex"; os+=" string which will be converted into decimal.";tm(); os="";tm();fns="trb12"; cls="r0";os="Number in Decimal Number in Hex Factorial in Hex "; os+="Factorial in Decimal";tm(); // Display titles in red tm("df"); // Flush display to prevent flicker. bli=1;um("b");return; // Jump to block 1. } //--------------------------------- Getting data from user ------------------------------ else if (blp==1) { // User interface block. fns="trb12";cls="b0";os="Enter a decimal number between (1:12): ";bli=2;tm("i");return; } // Get user data and jump to block 2. //---------------------------- Communicating with Assembly ------------------------------ else if (blp==2) { // Communication and display block string o1s=os; // Save (os) temporarely om("ti"); // Get user's number. If beyond range, return if(o<1 || o>12) {bli=1;um("b");return;} cls="S9"; // Prepare display string ds=" "; os=ds;js=o1s;j=5;om("m");ds=os; // Insert user's decimal no. into display string os=o1s;om("th");hs=os; // Convert number to hex string, assign it to (hs) os=ds;js=hs;j=20;om("m");ds=os; // and insert hex no into display string os="xe a "+hs;sm("pn"); // Call Assembly sub-prog with hex string string fhs=os.Substring(0,os.Length-2); // Remove "\nl" from returned string, assign to (fhs) os=ds;js=fhs;j=32;om("m");ds=os; // then insert it into display string. os=fhs;om("fh");fs=os; // Convert (fhs) into integer number, assign to (fs) os=ds;js=fs;j=48;om("m");ds=os; // Insert (fs) into display string fns="crb12";os=ds;tm(); // Display "Display string" bli=1;um("b");return; // Jump to block 1 to get a new no. from user } } } =============================================================================================== Compiling: ========== pcp a [ENTER] will compile and link all files generating the two executable files "a.exe" and "xe.exe". ===============================================================================================


============================================================================================= ======== LESSON 4 ======== How is memory allocated for a running program: ============================================== The "PC Assembly Language" book explains to you this subject in details. What we like you to know here is that not all your program resides in RAM while it's running. Only the part of the program which is currently in use is stored in RAM, the rest is stored into the disk drive. Each program has an entry into a table which is managed by the operating system called the "descriptor table". This table contains information about each running process, which part of it is in RAM and which is in DISK, where in the RAM its entry point is, etc. These numbers are constantly changing. Memory allocation is not in bytes. It's in pages. A page is 4kb in size. This means that some little amount of memory is wasted. These operations are fully managed by the operating system and requires no action from our side. The Flag Register: ================== The flag register is a 32 bit one, but only its first two bytes are important. At present time we are intersted only in 3 flags. All of them are in first byte. They are the sign,zero and carry flags. First Byte Second Byte +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |S|Z|0|A|0|P|1|C| |0|N|P|P|O|D|I|T| +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 C: Carry Flag T: Trap Flag P: Parity Flag I: Interrupt Flag A: Auxiliary Carry Flag D: Direction Flag Z: Zero Flag O: Overflow Flag S: Sign Flag P: I/O Privilege Flag N: Nested Task Flag ---------------------------------------------------------------------------------------------- ERROR HANDLING ============== Error handling tools: ===================== (1) The commenting symbols /*...*/: ----------------------------------- This is a very handy tool in detecting errors fast. It allows you to eliminate a large portion of your code at once by setting those comment symbols around it then attempting to recompile the program to see if the error persists. Most errors can be detected by multiple elimination procedures. Despite that those comment symbols are not available in NASM Assembly language, we have allowed their use in our conversion software. So, you can use them to troubleshoot your ".a" Assembly file. After the file is converted, NASM compiler will not see the part of your code which was set between those two comment symbols in the generated ".asm" file. (2) The line number displayer tool "ln": ---------------------------------------- To demonstrate the use of this tool, we have changed the name of (eax) to (kax) in one statement of the next example and attempted to compile it. Here is the error we received: --------------------------------------------------------- xa.asm:12: error: symbol `kax' undefined gcc.exe: error: xa.o: No such file or directory (ENOENT) --------------------------------------------------------- The first line tells us that the error is at line 12 of file "xa.asm". The error of the second line is not important since it is caused by the same error. Now, you should call the tool with: ln 12 xa.asm [ENTER] It will display for you line 12, the 10 lines before it and the 10 lines after it. This tool can display lines of any file. So it can help with 'C' code too. With PC# code, use it without stating a file name (like ln 12 [ENTER]) See "Desktop Reference" for details. (3) BreakPoint Display: ----------------------- BreakPoints offer great help in troubleshooting an Assembly program. A BreakPoint is a point in your program where you like to see all registers and stack contents in order to investigate the cause of a problem. So, let us make a BreakPoint function which can do this job. Run Time Errors: ================ The most common run time error which you may get is the stack error. It brings a message like this: Exiting due to signal SIGSEGV Stack Fault at eip=000079c8 eax=00000000 ebx=00000123 ecx=1b081cda edx=00025ad8 esi=00000000 edi=00025ad8 ebp=00000006 esp=00000002 program=C:\TEMP1\XE.EXE cs: sel=01a7 base=01080000 limit=00b6ffff ds: sel=01af base=01080000 limit=00b6ffff es: sel=01af base=01080000 limit=00b6ffff fs: sel=017f base=00011ff0 limit=0000ffff gs: sel=01bf base=00000000 limit=0010ffff ss: sel=01af base=01080000 limit=00b6ffff App stack: [00b70000..00af0000 Before the 'C' function main() called function _arun: it recorded the top of the stack address and it expected to find it unchanged after the function return. It gives this error and terminate program if it finds that the stack has changed or in other words you have pushed into the stack more or less DWORD's than you have popped out of it. In general, you can troubleshoot run time errors with the commenting method described in (1) and the BreakPoint method described in (3) The BreakPoint function (bpt): ============================== This function will be in file "pcsa.a". It should display the following: (1) All bits of the first and second bytes of the flag register. (2) The contents of registers EAX, EBX, ECX, EDX, ESI, EDI and ESP. (3) The values stored at those registers whenever they contain valid memory addresses. (4) The top 9 contents of the stack. All values will be displayed in hex except for the flag bits which must be displayed in binary. We already have function (tohexs) to convert numbers to a hex string. We need to make the new function (tobins) to convert numbers to a binary string. We are going to have 3 display strings into which we'll insert numbers to be displayed. Each of the 3 strings is 80 bytes long. They are as follows: (1) String (bps) will contain data of the two flag bytes followed with the 7 register contents. (2) String (bps2) will contain the values stored at addresses pointed to by the 7 registers. (3) String (bps3) will contain the top 9 contents of the stack. Additionally we,ll have the 3 strings (bp1,bp2,bp3) which contain titles for (bps,bps2,bps3) contents respectively. Finally, we need a new function which copies bytes from one string to another. Each time we convert a register to hex. The hex string will be in the string (hexs) We'll use this function to copy (hexs) to the correct location of that register in string (bps) This function's name will be (copybytes) Function tobins: ------------------ This function converts the first byte of a number to a binary string. You assign the number to (eax) before calling this function. The result will be in string (bins) Register (eax) is made a pointer to (bins) before the return. So you can refer to the resulting string with either (bins) or (eax) Function copybytes: ------------------- Before calling this function, you make register (esi) point to the byte where copy should start in the source string and register (edi) point to the byte in the destination string which should receive the first byte of the comming data. Additionally, you assign the number of bytes to be copied to (eax) The new addition to file "pcsa.a" (available in versions 3.32 and later): ========================================================================= segment .data; global bins; // Defined below. For bin conversion bins db " ",0; //------ BreakPoint strings, See function bpt: ------ ln2 db "================================================================================",0; bp1 db "SZ0A0P1C 0NPPODIT EAX EBX ECX EDX ESI EDI ESP ",0; bp2 db "................. [EAX] [EBX] [ECX] [EDX] [ESI] [EDI] [ESP] ",0; bps db ". ",0; bps2 db "................. ",0; bps3 db ". ",0; bp3 db "[ESP+00] [ESP+04] [ESP+08] [ESP+12] [ESP+16] [ESP+20] [ESP+24] [ESP+28] [ESP+30]",0; //---------------- Assembly function tobins (Converts 1st byte to binary string) -------------- global tobins; tobins:{ // IN:eax=dword number OUT: eax=bins=Binary String push ecx;push edx;push edi;pushf; // Save used registers. mov edx,eax; // Get number to be converted in EDX mov edi,bins;add edi,7; // Make EDI point to last bit in (bins) mov ecx,8; // bit counter (decremented by (loop) opcode) start2: // ** start of Loop mov eax,edx;and eax,1; // Get one copy of no. Zero all its bits but first. add al,48; // Read first byte, Adjust so (0) becomes Ascii(0) mov byte [edi],al; // Move no to byte in bins which is pointed to by edi dec edi; // Move edi to next byte towards front shr edx,1; // Shift original number right one bit loop start2; // Repeat Loop popf;pop edi;pop edx;pop ecx; // Restore saved registers. mov eax,bins; // Make eax point to resulting binary string ret; // Return } //------------ Assembly function copybytes (Copy bytes from one string to another) ------------ global copybytes; copybytes: { // IN: eax=No of bytes,esi=Source strt,edi=Dest strt push ecx;push esi;push edi; // Save used registers mov ecx,eax;mov eax,0; // Loop counter=no. of bytes, Clean eax start3: // ** Start of loop mov al,[esi];mov [edi],al; // Keep copying byte contents from source to dest inc esi;inc edi; // move to next byte at both source & dest loop start3 // Repeat loop for all number of bytes requested pop edi;pop esi;pop ecx; // Restore saved registers ret; // and return. } //------------------------------------------ BreakPoint --------------------------------------- global bpt; bpt:{ // OUT: All register values and stack displayed enter 0,0; // Strt routine modified to push reg's at wanted order push ebp;push esp;push edi;push esi;push edx;push ecx;push ebx;push eax;pushf; //------ Flag Register ------ mov eax,[esp];call tobins; // Get Flags (1st in stack) convert 1st byte to binary mov esi,bins;mov edi,bps;mov eax,8; // Copy 8 bytes from resulting binary string to call copybytes; // start of string (bps) mov eax,[esp];shr eax,8;call tobins; // Get flags again, move 2nd byte to front, conv to bin mov esi,bins;mov edi,bps;add edi,9; // Move dest pointer 9 bytes from start of (bps) mov eax,8;call copybytes; // and copy the 2nd flag byte there //------ Other Registers ------ mov edx,esp; // Work with edx instead of esp since it's safer mov ecx,7;mov edi,bps;add edi,9; // Repeat loop 7 times. edi points to (bps) aftr flags start5: // ** Loop Start add edx,4;mov eax,[edx];call tohexs; // eax=next DWORD in stack. Convert to hix string add edi,9;mov esi,hexs;mov eax,8; // Move dest pointer 9 bytes from last item in (bps) call copybytes; // and copy resulting hex string there. loop start5; // Repeat loop //------ Values stored into other Registers ------ mov edx,esp; // Make edx a copy of ESP mov ecx,7;mov edi,bps2;add edi,18; // Repeat loop 7 times. edi points to same pos in bps2 start6: // ** Loop Start add edx,4;mov eax,[edx];cmp eax,0;js bp5;// eax=next DWORD in stack. If negative move to next cmp eax,0ffffh;jns bp5; // Result not guaranteed beyond the range (0:ffff) mov eax,[eax]; // else get value stored into it call tohexs; // and do the same as before. mov esi,hexs;mov eax,8; call copybytes; bp5: add edi,9; loop start6; // Repeat loop //------ DWORD Amounts stored into stack ------ mov ecx,9;add ebp,4;mov edi,bps3; // Repeat 9 times.edi points to start of string bps3 start4: // ** Loop Start add ebp,4;mov eax,[ebp];call tohexs; // Get next DWORD (1st at ebp+8) Convert to Hex string mov esi,hexs;mov eax,8;call copybytes; // Copy the 8 bytes in hexs to bps3 starting at edi add edi,9; // Move edi 9 bytes from last position in bps3. loop start4; // Repeat Loop //------ Display ------ mov eax,ln2;call tm_d;call tm_dnl; // Top line mov eax,bp1;call tm_d;call tm_dnl; // Register titles mov eax,bps;call tm_d;call tm_dnl; // Register data mov eax,ln2;call tm_d;call tm_dnl; // Next Line mov eax,bp2;call tm_d;call tm_dnl; // Register stored value titles mov eax,bps2;call tm_d;call tm_dnl; // Register stored value data mov eax,ln2;call tm_d;call tm_dnl; // Next Line mov eax,bp3;call tm_d;call tm_dnl; // Stack content titles mov eax,bps3;call tm_d;call tm_dnl; // Stack content data mov eax,ln2;call tm_d;call tm_dnl; // Bottom line popf;pop eax;pop ebx;pop ecx;pop edx;pop esi;pop edi;pop esp;pop ebp; leave; // Modified function end routine. ret; // Return } ---------------------------------------------------------------------------------------------- About the next Example: ----------------------- The next example will be a demonstration for the BreakPoint function we have just developed. The PC# program will be calling function arun() with one parameter which can be "1", "2" or "3". (1) When the function receives "1": It will assign values to all registers, push 4 registers into stack then call the BreakPoint function to get the display and compare it with what it is expected to be. (2) When the function receives "2": It will do an operation which returns zero then call the BreakPoint function to see if it shows that the "zero" flag is set. (3) When the function receives "3": It will do an operation which returns a negative number then call the BreakPoint function to see if it shows that the "Sign" and "Carry" flags are set. ============================================================================================== Example 4: Write an assembly program which demonstrate how the BreakPoint can help us in learning the current state of the stack and all registers including the Flag register after performing variety of operations. ============================================================================================== < xc.c> char* crun(int parn,char* js,char* ks,char* os){} < /xc.c> < xa.a> //--------------------------------------- Data Segment---------------------------------------- segment .data; extern atm,tm_d,tm_dn,tm_dnl,tohexs,tohexn,hexn,hexs,bins,bps,bpt,tobins,copybytes,space,star; extern source,dest; //--------------------------------------- Code Segment --------------------------------------- segment .text; global _arun; _arun: { enter 0,0;pusha;pushf; // Function start routine. //------ Convert incoming hex string to int ------- mov eax,[ebp+12];call tohexn; // Get hex string in eax, call tohexn to conv to int //------ Branching ------ cmp eax,1;jz demo1; // Jump to requested demo block cmp eax,2;jz demo2; cmp eax,3;jz demo3; jmp finished; //--- demo 1 --- demo1: // Do wanted assignments. mov eax,1;mov ebx,2;mov ecx,3;mov edx,space;mov esi,star; push eax;push ebx;push ecx;push edx; // Do wanted stack stores. add eax,0; // Do an operation which clears flag bits (z,s,c) call bpt; // Call BreakPoint function pop edx;pop ecx;pop ebx;pop eax; // Must fix stack before returning to 'C' jmp finished; // Return to 'C' //--- demo 2 --- demo2: xor eax,eax; // Zero eax call bpt; // then call BreakPoint jmp finished; // Return to 'C' //--- demo 3 --- demo3: mov eax,1;sub eax,2; // Make eax negative call bpt; // then call BreakPoint //--- Returning to 'C' --- finished: popf;popa;leave; // Function end routine. mov eax,space; // Give function main() nothing visible to display. ret; // Return } < /xa.a> public class a:pcs { public override void init() { tia=toa="t"; base.init(); } public override void run() { //--------------------------------- Displaying information ---------------------------- cls="r0";fns="trb12"; os=" Demonstrating how to use the BreakPoint function (bpt)"; tm();fns="crb11"; //------ Demo 1 ------ cls="b0"; os="Calling (bpt) after doing the following: ";tm(); os="(1) Assignment of eax=1; ebx=2; ecx=3; edx=space; esi=star;";tm(); os="(2) Pushing eax,ebx,ecx,edx into the stack";tm(); os="xe a 1";sm("pn"); // Call Assembly sub-prog with par1="1" cls="S9";tm("d"); // Display result //------ Demo 2 ------ cls="b0";os="Calling (bpt) after performing an operation which returns zero result: ";tm(); os="xe a 2";sm("pn"); // Call Assembly sub-prog with hex string cls="S9";tm("d"); // Display result //------ Demo 3 ------ cls="b0";os="Calling (bpt) after performing an operation which returns -ve result: ";tm(); os="xe a 3";sm("pn"); // Call Assembly sub-prog with hex string cls="S9";tm("d"); // Display result } } ============================================================================================== COMMENTS: ========= (1) In selection 1, we have assigned 1,2 and 3 to registers eax,ebx and ecx. You can see these assignments on the BreakPoint's top table. We have also assigned the strings (space) and (star) to EDX and ESI. Those strings are global ones which we have defined in "pcsa.a" file. The BreakPoint shows that their start addresses are "00010D20" and "00010D22". Those addresses are not important to us. What concerns us is that the value stored into the first must be (20) and the value stored into the second must be (2A) which are the ASCII values of space and star in hex. However, we know that each of those strings is 1 byte long. The BreakPoint shows that the DWORD values stored at those addresses are (002A0020) and (000A002A) All that matters to us are the first byte of each and they are correct. Actually, the second bytes are also ours. They are the null terminators of the two strings. We have also pushed eax,ebx,ecx and edx into the stack. So they show at reverse order in the BreakPoint display. (2) In selections 2 and 3, we have performed operations which can change flag bits only. You can see that the BreakPoint at the second operation shows the "Zero" flag bit set and the third operation shows both the "Sign" and the "Carry" flags set. (3) Function (bpt) displays the BreakPoint by itself and that was all we want to send to PC#. So, we made function (_arun) return a "space" to make sure nothing visible will be displayed by function main() before returning to PC#. ==============================================================================================


============================================================================================= Should we let our team have some fun? ===================================== Our three young girls team has been doing great lately. ** Miss Assembly, the girl who is as fast as the CPU itself, has been helping us in performing lengthy arithmetic operations like finding the factorial of a number and making us able to see what's inside the CPU registers and stack. ** Miss C, the girl who is young, but full of knowledge and power. She provides smart tools for herself and her younger friend Miss Assembly and plans to make more of them. Without her, We and Miss Assembly could not have been able to reach each other. ** Miss PC#, the oldest and smartest. She knows how to do many things far beyond what her other two friends can. She can do graphics, imaging, networking and more. She is the head of the team and the one who makes us able to communicate with Miss C and Miss Assembly. The three girls like to take a break and have some fun. Their beloved sport is racing. So let us prepare a race for them and join the fun. ============================================================================================= Example 5: Prepare a race for our team Miss PC#, Miss C and Miss Assembly. ============================================================================================= < xc.c> //-------------------------------------- Miss C's Race --------------------------------------- extern char* rts;int printf(); char* crun(int parn,char* js,char* ks,char* os) { int i,j,o=0; // Start with (o=0;) for(j=0;j<4;j++) { // Repeat 4 times for(i=0;i<1000000000;i++) o+=2; // Add (2) 1 billion times for(i=0;i<990000000;i++) o-=2; // Sub (2) 990 million times } printf("%d",o); // Display final result rts="";return rts; // Return nothing to be displayed by main() } < /xc.c> < xa.a> //------------------------------------ Miss Assembly's Race ---------------------------------- //---------------- Data Segment---------------- segment .data; extern atm,tm_d,tm_dn,tm_dnl,tohexs,tohexn,hexn,hexs,bins,bps,bpt,tobins,copybytes,space,star; extern source,dest; //---------------- Code Segment --------------- segment .text; global _arun; _arun: { enter 0,0;pusha;pushf; // Function start routine. xor eax,eax; // Start with (eax=0) //------ Repeat 4 times ------ mov ebx,4; // Outer loop counter, start at 4 start0: // *** 4-times loop start //--- Add (2) 1 billion times --- mov ecx,1000000000; // 1 billion Counter start: // ** Start of 1 billion loop add eax,2; // Keep adding 2's dec ecx;jnz short start; // Dec ecx, Loop until ecx=0 //--- Sub (2) 990 million times --- mov ecx,990000000; // 990 million Counter start1: // ** Start of 990 million loop sub eax,2; // Keep subtracting 2's dec ecx;jnz short start1; // Dec ecx, Loop until ecx=0 dec ebx;jnz short start0; // Dec ebx(4-times loop counter) Loop until ebx=0 call tm_dn; // Display final result popf;popa;leave; // Function end routine. mov eax,space; // Return nothing visible to display by main() ret; // Return } < /xa.a> //------------------------------------- Managing the Race ----------------------------------- public class a:pcs { public override void init() { j=720;k=438;dm("s"); // Resize Form tia=toa="t"; base.init(); } public override void run() { //--------------------------------- Displaying information ---------------------------- cls="r0";fns="trb12";os="";tm(); os=" PERSONAL C SHARP TEAM RACE"; tm();os="";tm();cls="b0";fns="trbi12"; os="RULES: Each contestant will start by assigning zero to an integer or a register, add (2) to "; os+="it 1 billion times, then subtract (2) from it 990 million times and repeat the "; os+="two operations 4 times. Final result must be returned to PC# program for evaluation. "; os+="The start and finish time of each contestant will be recorded and the race time will "; os+="be computed for all."; tm();os="";tm(); cls="r0";fns="crb12"; os="CONTESTANT START TIME FINAL RESULT FINISH TIME RACE TIME"; tm();os="";tm();tm("df"); int j1;string j1s=""; // General use variables cls="G3"; //----------- Miss Assembly ----------- ds="MISS ASSEMBLY "; os="";sm("dt");js=os;j1s=os; // Get start time, save it (for race time calc) os=ds;j=15;om("m");ds=os; // and add it to display os="xe a";sm("pn"); // Start Miss Assembly's race js=os.Substring(0,os.Length-2); // Get final result os=ds;j=30;om("m");ds=os; // and add it to display os="";sm("dt");js=os; // Get finish time os=ds;j=47;om("m");ds=os; // and add it to display ib=true;kb=true;ks=j1s;sm("dt");js=os; // Subtract start time from finish time os=ds;j=61;om("m");ds=os; // Add result (which is Race Time) to display os=ds;tm();os="";tm();tm("df"); // Display the display string and flush. //-------- Miss C --------- ds="MISS C "; os="";sm("dt");js=os;j1s=os; // Identical to Miss Assembly's operation os=ds;j=15;om("m");ds=os; os="xe c";sm("pn"); js=os.Substring(0,os.Length-2); os=ds;j=30;om("m");ds=os; os="";sm("dt");js=os; os=ds;j=47;om("m");ds=os; ib=true;kb=true;ks=j1s;sm("dt");js=os; // Subtract start time from finish time os=ds;j=61;om("m");ds=os; os=ds;tm();os="";tm();tm("df"); //----------- Miss PC# ------------ ds="MISS PC# "; os="";sm("dt");js=os;j1s=os; // Identical to Miss Assembly's operation os=ds;j=15;om("m");ds=os; o=0; // Start with (o=0) for(j1=0;j1< 4;j1++) { // Repeat 4 times for(int i=0;i<1000000000;i++) o+=2; // Add (2) 1 billion times for(int i=0;i<990000000;i++) o-=2; // Subtract (2) 990 million times } js=""+o; os=ds;j=30;om("m");ds=os; // Display final result os="";sm("dt");js=os; // Same as with Miss Assembly os=ds;j=47;om("m");ds=os; ib=true;kb=true;ks=j1s;sm("dt");js=os; // Subtract start time from finish time os=ds;j=61;om("m");ds=os; os=ds;tm();os="";tm();tm("df"); } } ============================================================================================= REMARK: This example was tested long ago on a slow computer with Windows XP operating system. It may run too fast on a new computer to make the results meaningful. You may slow down the operation by increasing the number of repetitions of the two arithmetic operations. =============================================================================================


============================================================================================= ======== LESSON 5 ======== Using the C Language ==================== For version 4.33 and later Miss Assembly was thrilled when she looked at the scoreboard. We all know that she should be proud of herself, but her score has surprised nobody. Her speed is the CPU speed which is the highest speed you can get from a computer. However, Some results have been unexpectable to some of us. For example, C# should have been slower than 'C' because it does plenty of time consuming jobs like code management and garbage collection which 'C' does not do. We know that C# is made by Microsoft which is a company with huge resources while our 'C' is made by a smaller business. However, most of the reason could have been due to the fact that we have used a simple test which did not require plenty of code management and garbage collection for C# to do. Miss C is not complaining about the race results. She understands that Miss Assembly is the fastest of her team, so she should send all the work which needs to be done fast to her. However, Miss C also knows that Miss Assembly is too young to understand complicated stuff like mathematics. For example, She knows nothing about trigonometric and hyperbolic functions or even knows how to pronounce their names. So, Miss C has decided to make a group of simple functions which Miss Assembly can call whenever she finds something that she can't do. Comparing C with C#: ==================== What is the difference between writing C and writing C#? In 'C' you must be very careful since you are on your own. Nobody is going to warn you when you make mistakes. Some smart people like that since it means that there is no time wasted, so their programs run fast. Let us see now the C features which are not identical to C# ones. static, auto and global variables: ================================== Static var's are declared once only and cannot be disposed. Global var's are var's which are declared outside functions. They can be accessed from all linked files. They are static by default. Auto means the opposite of static. It means a local variable which has been declared into one specific function and can be used only inside that function. Definitions of True and false: ============================== A number is considered to be false in C when its value is zero and true when its value is anything but zero. You may write the statement [if(x) y=1;] and the condition will be considered met if (x) value was anything but zero. Calling functions: ================== The default return type of a function is (int) Before calling a function with return type other than int, you should define it as you do with any other var. char *xs,*crun(); // Variable definitions xs=crun(); // Function call Function & Array Pointers: -------------------------- The pointer to function tm() is tm without () and the pointer to array OY[] is OY without [] Arrays: ======= To define an array in C#: char[] OC=new char[5]; int[] O={0,1,2,3}; To define an array in C : char OC[5]; static int O={0,1,2,3}; Notice that the second way of array assignment in C is allowed with static arrays only. If by mistake you assigned a value to an invalid row like in: OC[6]='X'; C# will give you an error message and terminate program. C will take it and assign your wrong value to an area of neighbouring data causing a mess. You are on your own in C. *C='X'; will work same as C[0]='X'; and *(C+3)='Y'; works same as C[3]='Y'; Strings: ======== A string is a character array in C. Top file and static strings can be assigned values at declaration with statements like: char *os="John"; static char os[4]="John"; But Auto strings must be declared and assigned values seperately as shown here: char js[4];js[0]='J'; Comparing C functions with C# methods: -------------------------------------- C PC# ---------------------- -------------------------- o=strlen(os); o=os.Length; // String length ys=xs; ys=xs; // Pointer to ys now points to xs if(strcmp(xs,ys)==0) {..} if(xs.Equals(ys)) {..} // String comparison if(strstr(os,js)) {..} if(os.IndexOf(js)>-1) {..} // If (os) contains (js) Assigning a string literals to a string: ---------------------------------------- You can define a new string and initialize it with a literal, but you can't easily assign a literal to a string after it has been created. Function am_sl() allows you to assign a literal to one of the strings (is, js, ks, os, fls) only. Memory allocation: ================== When you define an array or a string with specifying number of elements or initial value, memory is allocated automatically. You can free it with: free(os); or free(OY); If you are receiving strings from user with unknown lengths, you may define a large buffer to receive each of them then copy each to a string after allocating correct mem. amount to it with: char *os, *malloc(); // Must define any function to be used char buffer[255]; // Define a large buffer to take any string scanf ("%255s",buffer); // Get string from user. os=malloc(strlen(BUFFER)+1); // Allocate enough mem for string + null terminator if (os == NULL) QuitSafely (os); // If out of memory exit() after freeing (os) memory 'C' Preprocessor Commands: ========================= Before your program is compiled, While it's still in text form, it passes by the C preprocessor. The preprocessor starts by looking for commands which are preceded by '#' like these: #define pcs "Personal C Sharp"; #include "stdio.h"; The first one is called a "macro". It causes the preprocessor to search through your program and replace the name (pcs) with "Personal C Sharp" wherever it's found except if it was found between the two double quotes of a string assignment. The second one means "add the text of the external file "stdio.h" to the text of this file". We normally add header files' text to our programs this way. The header file may include other files in the same manner. Macro functions: ================ If the entire body of the function you like to write is made of one statement only like [x=x+5;], you may find it easier to replace it with the macro: #define AddFive(x) x+5 and use it within your program in a statement like [y=AddFive(10)*AddFive(15)+AddFive(z);]. The preprocessor will change them to [y=(10+5)*(15+5)+(z+5);] The two statements: if (a>b) x=a;else x=b; may be written in this form: (a>b) ? x=a : x=b; and that was the magic way to turn the two statements into one! So, we can now define the macro: #define MAX(a,b) (a>b) ? (a):(b) Similarly #define ABS(x) ((x)<0) ? -(x):(x) Assembly Preprocessor Commands: =============================== Our Assembler also accepts Macros. Actually, it accepts multi-statement macros which are the more valuable. Their definitions start with "%" instead of "#". We have no use for 'C' macros at present time, but we'll be using Assembly macros to simplify Assembly access of some of the global 'C' functions which will be discussed next. Additionally, there will be macros for all DOS interrupt functions which will be discussed in the next lesson. All the Assembly macros and the "extern" difinitions which you used to include at the top of your Assembly code are now in a seperate file named "pcsa.h". A copy of this file is available into subfolder Langs/Include with all other header files. So, from now on, you must set this file's pathname at the top of your Assembly section of your program with: %include "langs/include/pcsa.h" ============================================================================================== Personal C Sharp helpful C functions ==================================== Our main objective in using C and Assembly sub-programs is to increase execution speed whenever necessary. The fastest program you can write is the one which you design by yourself to do exactly what you need to do and nothing more. Therefore, we don't encourage extensive use of our new 'C' functions from Assembly. Use them only when you must. The 'C' functions are made to be very similar to PC# methods. We have used same techniques, same limited number of variables and same variable names. All functions are accessable from both C and Assembly and all variables are common to the two languages. There are some differences. In PC# we have not been too concerned of speed. Quality has been given top priority. For example, in PC#, after each method call, all the (i,j,k) GUV's (General Use Variables) have been reset in order to make you start clean at the next call. We can't have this luxury here since speed is a major concern. Only the (i,j,k) GUV's which have been used by a function are reset at its end. This should be enough to do the job, however you can at any time call um_c() to reset them all. The process of analyzing the mode string and branching to correct code block within each PC# method is not used here. Each function-mode combination have been made a seperate function. For example functions fm_r() and fm_w() are two independant 'C' functions. In PC# they used to be two modes of one method. The boolean type variables (ib,jb,kb,ob,erb) are available as integers which we assign (0) to mean "false" and (1) to mean "true". The byte type GUV's have been eliminated. The char types are the only ones available. Remember that a char in C is an unsigned byte which you can assign either a character or a number. So, (oc='A';) and (oc=65;) are both allowed and mean the same. Available global variables: =========================== int i,j,k,o,oi;int ib=0,jb=0,kb=0,ob=0,erb; // int and bool var's (now both of type int) char ic=' ',jc=' ',kc=' ',oc=' ';char *is,*js,*ks,*os,*fls,*lts; long ol;double id,jd,kd,od; // char, string, long and double var's float lf,jf,kf,of; // float var's ('if' replaced with 'lf') int *O;double *OD;char *OY;char **OS; // Arrays int dhp; // Device or file handle (now of type int) Those var's are defined at the top of file "pcsc.c". So they are all global. They should be defined into any C or Assembly file which uses them as extern. However, you no longer need to make these definitions in Assembly since they are included in the header file "pcsa.h" which you include into all your Assembly programs. In general, A 'C' file should keep their names as they are and include their types. An Assembly file should precede each with an underscore. Example: Variable (o) is defined in "pcsc.c" with (int o;) If needed in file "xc.c", should be defined with (extern int o;) Also, function fm_r() should be defined in "xc.c" with (extern int fm_r();) In the Assembly header file, they are defined with (extern _o;) and (extern _fm_r;) How can we assign values to the 'C' global variables? ----------------------------------------------------- From 'C', it's easy, you may make the assignments (j=x;k=y;os=xs;) assuming that (x,y,xs) are variables defined into your 'C' file and (j,k,os) are the global variables defined into file "pcsc.c". The assignment (os=xs) is not a costy one since it only made the pointer of (os) point to (xs) No copy of bytes was required. Assignments of arrays are done in the same manner. From Assembly, we can also do both the numeric and the less costy string and array assignments except that the code will not be as short. So, we have defined the following macros to do those jobs: movi [_o],100; // Make the int assignment (_o=100) movi [_o],[x]; // Make the int assignment (_o=x) movi [x],[_o]; // Make the int assignment (x=_o) movf [_jf],[fno] // Make the float assignment (_jf=fno) movf [fno],[_jf] // Make the float assignment (fno=_jf) movfn [_of],5.25; // Make the float assignment (_of=5.25) If it was 5 make it 5.0 movs 'o',xs; // Make the string assignment (_os=xs) movia 'O',IntArray; // Make the int array assignment (_O=IntArray) movda 'O',DoubleArray; // Make the double array assignment (_OD=DoubleArray) REMARKS: -------- (1) (movi,movf,movfn) change actual values of numeric GUV's. All others change pointer assignments only. (2) Both arguments in (movi,movf,movfn) are actual variable names or numbers The first arguments in (movs, movia, movda) is a character symbol which tells "pcsc.c" which PC variable we like to assign to. The codes are ('i','j','k','o','f') and in case of (movs), they mean assign to: (is,js,ks,os,fls) respectively. (3) In (movfn,movs, movia, movda) we always assign an external variable to a PC variable not the other way around, while in (movi,movf) Assignments can be in either direction. (4) Since (movi,movf,movfn) change the values of its arguments, they should be between brackets like ([_o],[_jf] and [x]) unless they were numeric constants like the (100 and 5.25) How can we assign literal string values to string variables? ------------------------------------------------------------ In both 'C' and Assembly, this is allowed one time only when the variable is created. After that you may change a string one character at a time only. Remember that these languages recognize a string as a character array. So, if you have created the string (xs="Jack") and later wanted to assign "William" to it, you could have done it in PC# with (xs="William") despite that the original string size cannot take the new assignment. This is because you know that the .NET can fix any mess which you do. Here, you are on your own. The only way you can make the assignment is by disposing the original (xs) and creating a new one with the new assignment. Function am_sl() can do part of this job for you if you want to assign a string literal to one of the PC string var's (is,js,ks,os,fls) Here is how to make this new assignment to (os) in 'C': am_sl('o',"William"); The function will create the new string for you but will not dispose the old one since the old (os) pointer could have been pointing to a string which is important to you. So, you should dispose any created string which you no longer need by yourself. You cannot use this function from Assembly, so you have no choice but to change the string character by character if possible or to have another string defined at the data section with the new assignment. Calling the new global 'C' functions from 'C' and Assembly: =========================================================== Let us start with an example. The PC# Methods om("ti") and om("fi") convert the string (os) to the int (o) and vice versa. The C functions om_ti() and om_fi() do the same. Let us see how we can use them to convert the string (xs) to the int (x) and back. PC# : os=xs;om("ti");x=o; // Assign os=xs,convert os to o,Assign x=o o=x;om("fi");xs=os; // Assign o=x,convert o to os,Assign xs=os C : os=xs;om_ti();x=o; // Do the same as in PC# o=x;os=xs;om_fi(); // Assign o=x,and os=xs, Convert o to os Assembly: movs 'o',xs;call _om_ti;movi [x],[_o]; // Do the same as in PC# movi [_o],[x];movs 'o',xs;call _om_fi; // Same as in 'C'; Not as in PC# Notice that in the second line of both 'C' and assembly, that we made the assignment (os=xs) before we called the function which is not the same as we did in PC#. Whenever we use any of the GUV strings or arrays we must do that for the following reasons: (1) we can't guarantee that the original (os) was large enough to take the conversion result. (2) The original (os) has been pointing to some other string from previous call. If we change it that string will change. So, we cannot use it even if it could take the result. (xs) is a string which you should have created before the call and made it large enough to take the conversion result. This is what we do with strings and arrays but not with numerics. With numerics, we do the same as we do in PC#. We change the actual value of the numeric GUV's before the call and expect the o-based ones to carry the result after the call. Any function automatically assigns zeros to the (i,j and k) based var's after each call if it has used them. Assembly macros used to call the new 'C' display functions: =========================================================== The new global 'C' display functions are good to be used from Assembly. We have made macros to make you able to use them with short code. All macros are in file "pcsa.h" which is in subfolder "Langs/Include" of your working directory. Here are the display macros: tm_s hw; // Display the locally pre-defined string (hw) and stay on same line. tm_sl [_os]; // Display the global string (_os) and move to next line. tm_sl esi; // Display the string whose 1st char is pointed to by esi and move to next line. tm_i [ino]; // Display the locally pre-defined int (ino) and stay on same line. tm_il [_o]; // Display the global int (_o) and move to next line. tm_il 25; // Display the number (25) and move to next line. tm_il ecx; // Display ecx and move to next line. tm_il [esi] // Display [esi] and move to next line. tm_f [fno],3;// Display locally pre-defined float (fno) with 3 dec digits; stay on same line. tm_fl [_of],4;// Display the global float (_of) with 4 dec digits and move to next line. tm_fl edx,2; // Display the float number in edx with 2 dec digits and move to next line. tm_fl [edi],2;// Display the float number in [edi] with 2 dec digits and move to next line. tm_d [dno],3;// Display locally pre-defined double (dno) with 3 dec digits; stay on same line. tm_dl [_od],4;// Display the global double (_od) with 4 dec digits and move to next line. REMARKS: (1) Notice that the macros are named the same as the functions except that they don't include the preceding underscore. You don't precede them with the the instruction (call) also. (2) Don't use the display macros of a (double) since they are not yet ready. ============================================================================================= Peraonal C Sharp global 'C' functions: ====================================== REMARKS: ======== (1) The Assembly examples assume that the following data are avaiable at the (.data segment): ino dd 725; fno DD 25.77; dno QD 12.4657; sno db "100",0; sfno db "1.23",0; hw db "Hello World",0; fl1s db "test.txt",0; (2) Although most of the DJGPP 'C' functions work great, some few ones seem to have problems. Function malloc() sometimes gives an error message which is not caused by no enough memory and function atof() seems to work fine with float numbers when called from Assembly but not when called from 'C'! However, it works fine with double numbers when called from 'C'. You'll see a "** PROBLEM" message at the functions which are affected by these problems. (3) In order to simplify the communication between 'C' and Assembly, we're not using type "double" numbers in Assembly at this time, we're using type "float" instead. Therefore, all mathematical functions which have been using "double" numbers have been modified to use float numbers for input and output. They seem to work fine with float numbers when called from either 'C' or Assembly. ----------------------------------- Assignment functions ----------------------------------- All these functions are necessary for Assembly language calls only except function am_sl() which is necessary for 'C' calls only (See How can we assign literal string values to string variables) //------ String type assignments ------ am_s(char cvar,char* avar) : Assign the variable (avar) to one of (is, js, ks, os or fls) Example, A: movs 'o',hw; // Assign "Hello World" string to (os) //------------------------------------- Display functions ------------------------------------ // As explained before, we normally don't access these functions directly from Assembly. We use // macros to do this job with shorter code. tm_s(char *ss) : Display the string (ss) and stay on same line. tm_sl(char *ss) : Display the string (ss) then move to next line. Example, C: am_sl('o',"John");tm_sl(os); Example, A: tm_sl hw; tm_i (int ii) : Display the integer value [ii] and stay on same line. tm_il(int ii) : Display the integer value [ii] then move to next line. Example, C: o=1234;tm_il(o); Example, A: tm_il 21567; also: tm_il [ino]; and: movi [_i],777;tm_il [_i]; tm_f (float ff) : Display the float value [ff] and stay on same line. IN: i=Number of decimal digits wanted (Digits to the right of the dec point) ** See PROBLEM at tm_fl tm_fl(float ff) : Display the float value [ff] then move to next line. IN: i=Number of decimal digits wanted (Digits to the right of the dec point) ** PROBLEM: This function works fine when called from Assembly but not from 'C' However, tm_dl() can display a float fine when called from 'C'. Example, C: of=10.4321;i=3;tm_dl(of); // Notice that we're using tm_dl instead. Example, A: mov eax,[fno];push eax;call _tm_fl;pop eax; // Displays 25.77 tm_d (double dd): Display the double value [dd] and stay on same line. IN: i=Number of decimal digits wanted (Digits to the right of the dec point) tm_dl(double dd): Display the double value [dd] then move to next line. IN: i=Number of decimal digits wanted (Digits to the right of the dec point) Example, C: od=12.5674;i=3;tm_dl(od); // Displays 12.567 //------------------------------------- Filing functions ------------------------------------- fm_w() : Write all data into file in one step. IN: fls=File name, os=Data, i=No. of byte to write (Should be = data length) OUT: o=No of bytes written, erb=0:Success Example, C: fls="test.txt";os="Shawn\nJack\nNathan";i=17;fm_w(); // Wr data into file. Example, A: movs 'o',sno;movs 'f',fl1s;move [_i],3;call _fm_w; // Wr "100" into file. fm_r() : Read the entire file data into (os). Must supply buffer of enough size, i=bytes expected IN: fls=File name,os=buffer to receive data,i=expected no of bytes to read. OUT: os=Data, o=No. of bytes read, erb (=0:Success) Example, C: fls="test.txt";os=xs;i=17;fm_r();tm_sl(os);// Displays same data in 3 lines Example, A: movs 'o',hw;movs 'f',fl1s;movi [_i],3;call _fm_r;tm_sl hw; // Displays "100". //------------------------------------- General functions ------------------------------------ om_cn() : Concatenate two strings. Adds (js) to end of (os) IN:os,js Example, C: am_sl('o',"Hello ");am_sl('j',"there.");om_cn();tm_sl(os);//D:"Hello there" Example, A: movs 'o',fl1s;movs 'j',sno;call _om_cn;tm_sl [_os]; //D:"test.txt100" om_u () : Convert (os) characters to upper case ones. IN/OUT:os Example, C: am_sl('o',"John");om_u();tm_sl(os); // Displays "JOHN" Example, A: movs 'o',hw;call _om_u;tm_sl hw; // Displays "HELLO WORLD" om_l () : Convert (os) characters to Lower case ones. IN/OUT:os Example, C: am_sl('o',"Hello");om_l();tm_sl(os); // Displays "hello" Example, A: movs 'o',hw;call _om_l;tm_sl hw; // Displays "hello world" om_ln() : Find the length of string (os) IN:os OUT:o=Length Example, C: am_sl('o',"John");om_ln();tm_il(o); // Displays 4 Example, A: movs 'o',sno;call _om_ln;tm_il[_o]; // Displays 3 om_cp() : Copy string (js) to string (os) IN:os,js OUT:os Example, C: am_sl('j',"Hello World");am_sl('o'," ");om_cp(); tm_sl(os); // Displays "Hello World" Example, A: movs 'j',sno;movs 'o',hw;call _om_cp;tm_sl [_os]; // Displays "100" om_ix() : Find index of string (js) into string (os) IN:os,js OUT:o=Index Example, C: am_sl('o',"Hello World");am_sl('j',"ll");om_ix();tm_il(o);// Displays 2 Example, A: movs 'o',hw;movs 'j',space;call _om_ix;tm_il[_o]; // Displays 5 om_cm() : Compare (os) with (js) IN:os,js OUT:o=0/+n/-n meaning os=js / os >js /os< js Example, C: am_sl('j',"ace");am_sl('o',"ace");om_cm();tm_il(o); // Displays 0 Example, A: movs 'j',fl1s;movs 'o',hw;call _om_cm;tm_il[_o]; // Displays -65 om_ti() : Convert (os) to int. IN:os OUT:o Example, C: am_sl('o',"1234");om_ti();tm_il(o); // Displays 1234 Example, A: movs 'o',sno;call _om_ti;tm_il[_o]; // Displays 100 om_tf() : Convert (os) to the float (of) IN:os OUT:of ** PROBLEM: This function works fine when called from Assembly but not from 'C' We advise working with (double) numbers at 'C' and float at Assembly. Example, A: movs 'o',sfno;call _om_tf;tm_fl [_of],2; // Displays 1.23 om_td() : Convert (os) to the double (od) IN:os OUT:od Example, C: am_sl('o',"12.6547");om_td();tm_dl(od); om_fi() : Convert to (os) from the int (o) IN:o,os OUT:os Example, C: o=1234;am_sl('o'," ");om_fi();tm_sl(os); // Displays "1234" Example, A: movi [_o],[ino];movs 'o',sno;call _om_fi;tm_sl sno; // Displays "725" om_ff() : Convert to (os) from the float (of) IN:of,i,os OUT:os i=Number of decimal digits wanted (Digits to the right of the dec point) Default:2 Example, C: of=12.6547;am_sl('o'," ");i=3;om_ff();tm_sl(os); // Displays 12.655 Example, A: movfn [_of],12.45;movi [_i],3;movs 'o',hw;call _om_ff; tm_sl [_os]; // Displays 12.450 om_fd() : Convert to (os) from the double (od) IN:od,i,os OUT:os i=Number of decimal digits wanted (Digits to the right of the dec point) Default:2 Example, C: od=12.6547;i=3;am_sl('o'," ");om_fd();tm_sl(os); // Displays 12.655 om_m() : Replace the part of (os) which starts at char (j) with (js) IN:j,js,os OUT:os Example, C: am_sl('o',"Hello World");am_sl('j',"endy");j=7;om_m();tm_sl(os); Example, A: movs 'o',hw;movs 'j',star;movi [_j],5;call _om_m;tm_sl [_os]; // The 'C' example displays "Hello Wendy". The Assembly example displays "Hello*World" om_s() : Seperate the strings into (os) which are seperated with a comma or any other seperator into different rows of array OS[]. IN:os,ks=Seperator,OS[] OUT:OS[] REMARK: This function is made for 'C' use only. Not for Assembly. Example, C: os="John,Jack,Nathan";ks=",";om_s();for(i=0;i< oi;i++) tm_sl(OS[i]); //--------------------------------------- Utilities ------------------------------------------ um_c : Clear (reset) all (i,j,k) GUV numerics and strings //-------------------------------------- Mathematics ----------------------------------------- Mathematics is handled by one function which is um_m(int FunctionCode) In addition to the function codes, it requires assignments to either (of) and/or (jf) and outputs (o) or (of) depending on the function required. Here are the function codes and their required par's: //-------------------- Arithmatic functions --------------------- 'a' : Find the abs(o) and return it also in (o) 'c' : Find the ceil of (of) and return it also in (of) 'f' : Find the floor of (of) and return it also in (of) 'p' : Find (of) to the power (jf) and return result in (of) 's' : Find the square root of (of) and return it also in (of) //----------------------- Random Numbers ------------------------- 'r' : Generate a random number of type (int) and return it in (o) //------------- Constants and other math functions --------------- 0 : Get the value of PI and return it in (of) 1 : Calculate log(of) and return it also in (of) 2 : Calculate log2(of) and return it also in (of) 10 : Calculate log10(of) and return it also in (of) //------------------ Trigonometric functions --------------------- REMARK: Angles are all in degrees (for both input and output) 11 : Find the sine of (of) and return it also in (of) 12 : Find the cosine of (of) and return it also in (of) 13 : Find the tan of (of) and return it also in (of) 21 : Find the asin of (of) and return it also in (of) 22 : Find the acos of (of) and return it also in (of) 23 : Find the atan of (of) and return it also in (of) //-------------------- Hyperbolic functions ----------------------- 31 : Find the sinh of (of) and return it also in (of) 32 : Find the cosh of (of) and return it also in (of) 33 : Find the tanh of (of) and return it also in (of) Examples, C: ------------ um_m('r');tm_il(o); // Random number of=4;um_m('s');tm_dl(of); // Square Root(4)=2 of=10;jf=2;um_m('p');tm_dl(of); // (10) to the power(2)=100 of=30;um_m(11);i=4;tm_dl(of); // sin (30)=0.5 of=45;um_m(13);i=4;tm_dl(of); // tan (45)=1.0 of=0;um_m(0);i=4;tm_dl(of); // PI Examples, A: ------------ mov eax,"r";push eax;call _um_m;pop eax;tm_il [_o]; // Random Number movfn [_of],25.0;mov eax,"s";push eax;call _um_m;pop eax;tm_fl [_of],2;// Sqrt(25)=5 movfn [_of],30.0;mov eax,11;push eax;call _um_m;pop eax;tm_fl [_of],4; // sin (30)=0.5 movfn [_of],45.0;mov eax,13;push eax;call _um_m;pop eax;tm_fl [_of],4; // tan (45)=1.0 mov eax,0;push eax;call _um_m;pop eax;tm_fl [_of],4; // PI movfn [_of],2.0;movfn [_jf],3.0;mov eax,"p";push eax;call _um_m;pop eax;tm_fl [_of],2; // (2) to the power(3)=8 ================================================================================================= About the next example: ======================= Modifying an image file pixel by pixel has always been a time consuming job. This is no longer a problem to us thanks to "Miss C" and "miss Assembly". they are now one team who can read a file of any kind into a buffer, operate on the buffer at an amazing speed then write it back into the file. Contents of the bitmap file: ---------------------------- In general, a bitmap file can handle compression, can use different number of bits per pixel and can contain opacity data, except that the one we use is the default one. It does not compress data, it uses 32 bits per pixel and stores (FFh) into the opacity bytes of all pixels. The file header contains some important data. The data of concern to us are the following: DATA WHICH BYTES STORE THEM ========================== ====================================== File size in bytes 2,3,4,5 Starting row of pixel data 10,11,12,13 Bitmap Width 18,19,20,21 Bitmap Height 22,23,24,25 Number of bits per pixel 28,29(Ours is 32 bits, last byte is FF) All numbers are simple to read and nothing is confusing. In the next example, we're going to create a 150X150 bitmap, draw on it and save it into file "drawing0.bmp" If you check the width and and height in the file's header, you'll find them 150 each. The "Number of bits per pixel" are always (32) in the bitmap files which we use. The "Starting row of pixel data" is stored at the file header into the 4 bytes starting at byte number 10. In our file, you'll always find (54) there. If you like to know how many bytes are stored into the file, you can calculate it with this formula. Number of bytes = 54 + 150*150*4=90054 This means that we need a buffer of this size to use for reading and writing. ================================================================================================= Example 6: Create a bitmap and draw 9 squares on its surface. Three of them should be red, three should be green and three should be blue. Save the drawing into a bitmap file. Then: a) Use Assembly Language to modify the file so that the color of each square is reversed which means that red color becomes cyan, green color becomes magenta and blue color becomes yellow. b) Do the same modification by PC# using the "pixel by pixel" modification method described in the chapter of "Imaging". Display all three drawings. ================================================================================================= < xc.c> char* crun(int parn,char* js,char* ks,char* os){} < /xc.c> < xa.a> %include "langs/include/pcsa.h" // Our header file. Must be included segment .data; // Data Section fl0s db "drawing0.bmp",0; // Input file name fl1s db "drawing1.bmp",0; // Output file name // bitmap pixel data start at row 54. Its size is 150X150 pixels Each pixel requires 4 bytes, So: // Necessary buffer size=54 + 150*150*4 = 90054 aos times 90055 db 0; // File buffer segment .text; // Code Section global _arun; // Function definition _arun: { enter 0,0;pusha;pushf; // Function start routine. movs 'o',aos;movi [_i],90054;movs 'f',fl0s;call _fm_r;// Read file into buffer mov esi,aos;add esi,54; // ESI points to start of pixel data mov edx,esi;add edx,90000; // EDX points to end of pixel data // Pixels order is: Blue,green,red,Opacity (always=0FFh) They start by the bottom row of the // image and move up to its top. start: // ** Loop start mov eax,[esi+2];cmp al,0FFh;jnz NotRed; // If Red=FFh: mov [esi],dword 0FF00FFFFh;jmp Done; // Convert to cyan and jump to done. NotRed: // Else if green or blue: mov eax,[esi+1];cmp al,0FFh;jnz Blue; // If Green=FFh: mov [esi],dword 0FFFF00FFh;jmp Done; // Convert to magenta and jump to done. Blue: // Else if blue: mov [esi],dword 0FFFFFF00h; // Convert to yello Done: add esi,4; // Move to next pixel mov eax,esi;cmp eax,edx;js start; // Repeat unless end of buffer reached movs 'o',aos;movi [_i],90054;movs 'f',fl1s;call _fm_w;// Write file. popf;popa;leave; // Function end routine. ret; } < /xa.a> public class a : pcs { public override void init() { j=700;k=375;dm("s"); base.init(); } public override void run() { os=" USING AN ASSEMBLY LANGUAGE SUB-PROGRAM TO MODIFY A BITMAP FILE"; cls="r0";gm("sps");fns="tr14";kf=120;gm("ctf"); //------------------- Making original drawing and saving it into bmp file ----------------- lf=of=150;gm("bn"); // Creat a new 150X150 (bip) gm("sdb"); // make it the Graph. output device cls="r0";gm("sps"); // Change color to red jf=0;kf=0;lf=of=50;gm("crf"); // and draw all red squares jf=-50;kf=50;lf=of=50;gm("crf"); jf=-50;kf=-50;lf=of=50;gm("crf"); cls="g0";gm("sps"); // Change color to green jf=0;kf=50;lf=of=50;gm("crf"); // and draw all green squares jf=0;kf=-50;lf=of=50;gm("crf"); jf=50;kf=0;lf=of=50;gm("crf"); cls="b0";gm("sps"); // Change color to blue jf=50;kf=-50;lf=of=50;gm("crf"); // and draw all blue squares jf=50;kf=50;lf=of=50;gm("crf"); jf=-50;kf=0;lf=of=50;gm("crf"); gm("sdd"); // Return to default gr Out device jf=-200;gm("br"); // Draw original file at left fls="drawing0.bmp";gm("bsb"); // and save it as a BMP file //------------------------- Modifying file by Assembly subprogram ------------------------- os="xe a";sm("pn"); // Call Assembly sub-program fls="drawing1.bmp";gm("blf"); // Load resulting file into (bip) jf=0;gm("br"); // and draw it at center. //------------------------- Modifying file by PC# Imaging software ------------------------ fls="drawing0.bmp";gm("blf"); // Loading file into a new (bip) and load the file into it. float wf=bip.Width/2,hf=bip.Height/2; for (float yf=-hf+0.5f;yf< (hf+0.5f);yf++) { // Scan image vertically for (float xf=-wf+0.5f;xf< (wf+0.5f);xf++) { // and horizontally jf=xf;kf=yf;gm("bcg"); // Get each pixel's color data if (CLI[i+1]==0 && CLI[i+2]==0) { // Converting red to cyan jf=xf;kf=yf;CLI=new int[]{0,255,255,255};gm("bcs"); } if (CLI[i]==0 && CLI[i+2]==0) { // Converting green to magenta jf=xf;kf=yf;CLI=new int[]{255,0,255,255};gm("bcs"); } if (CLI[i]==0 && CLI[i+1]==0) { // Converting blue to yello jf=xf;kf=yf;CLI=new int[]{255,255,0,255};gm("bcs"); } } } jf=200;gm("br"); // Draw modified image at the right. os=" ORIGINAL ASSEMBLY MODIFIED PC# MODIFIED"; cls="S9";gm("sps");fns="trb14";kf=-112;gm("ctf"); } } ===============================================================================================


============================================================================================= How to write the best performing PC# program: ============================================= You like your program to be easy to write, easy to edit and to run the fastest. Here are some factors which should be considered when deciding how to divide your program among our three languages: (1) While PC# is not as fast as Assembly language, it's a fast language compared to many others. Therefore, considering its ease of use and reliability, it should be your first choice. (2) It takes about a quarter of a second to go to Assembly and back. So, don't call Assembly unless you have a job which is lengthy enough to justify spending this time. (3) Hardware devices like the keyboard, display and hard drive are extremely slow compared to the CPU. This means that using a faster language to access them with will not change the overall time considerably. This also means that reading an entire file as one piece is considerably better than reading it one record at a time. (4) Humen are even slower. It takes us a long time to decide which key to push or which button to click on. This means that the speed of the software which interfaces with humen or handles the events they generate is also non-critical. (5) Normally, there is a very little gain in using a faster language to execute a statement once or twice. Loops with thousands or millions of iterations which do not include accessing hardware devices or interfacing with humen are the ones which you should consider using a faster language for. In the last example, modifying the bitmap file in Assembly language has made a noticeable speed advantage since the operation required a loop of 90,000 iterations and file access has been done outside the loop. REMARK: ------- Here are approximate guidelines which you should keep in mind when you think about speeds: ** CPU's execute their instructions in nanoseconds. ** Computer languages like C and C# execute their statements in microseconds. ** Hardware devices like the disk drive do their mechanical operations in milliseconds. ** Humen do their computer related operations (like pushing a key or clicking a button) in seconds. ============================================================================================= ======== LESSON 6 ======== Using Interrupt functions in Assembly ===================================== INTERRUPTS: =========== CPU Interrupts serve more than one purpose. A hardware device like a mouse needs to interrupt the currently running programs each time you move it in order to send its new data. Your program can initiate either a "BIOS" interrupt to operate on a hardware device or a "DOS" interrupt to access some basic DOS functions. Interrupt (21h) takes you to the most common DOS functions available. Each function is assigned a unique number. To access a specific function you assign its number to (ah) before initiating the interrupt. These DOS functions are old yet they are still operative. It seems that Microsoft has not made any new functions public since the 16-bit CPU's have been in use. All the registers mentioned in their documentation are either 8 bits or 16 bits. The 16 bit registers should be converted to 32 bit ones with upper 16 bits of zeros. We have added new macros to simplify the use of interrupt 21h functions. We used similar names to PC# method names in order to make it easy to memorize them. They cannot be mixed with the 'C' global functions which we'll also use since they are not preceded with underscores. MACRO WITH PARAMETERS FUNCTION DESCRIPTION AND OUTPUT =========================== ==================================================================== Text Functions: --------------- tm_ic; Input char from keybrd(C-Brk active) OUT: al=character tm_icn; Same as tm_ic but without echo OUT: al=Character tm_dc Character; Display character on screen OUT: al=Last char tm_ds String$; Display string (terminate with $) OUT: al=24h(=$) tm_df; Flush Display Buffer ------------------------------------------------------------------------------------------------- System Functions: ----------------- sm_d; Get current date OUT: al=wkday(sun=0),cx=yr,dh=mn,dl=dy sm_sd yyyy,mm,dd; Set system date OUT: al=0:Success sm_t; Get current time OUT: ch=hr,cl=mn,dh=sec,dl=1/100 sec sm_st hh,mm,ss; Set system time OUT: al=0:Success sm_fdm; Get free disk memory. Free Space=ax*bx*cx Total memory=ax*cx*dx OUT: dl=Drive no.(0=def,1=A,..),ax=sec's/clstr,bx=free,clstrs, cx=bytes/sectr,dx=total clusters sm_e ReturnCode; Exit Program. ------------------------------------------------------------------------------------------------- Filing Functions: ----------------- FILE ATTRIBUTE BITS IN (cx): 0=Normal (Can r & w) 1=Rd-Only 2=Hidden 4=System 20h=Archive fm_md Pathname; Make (Create) directory OUT: cf=0:Success fm_dd Pathname; Delete directory OUT: cf=0:Success fm_mf Attributes,FileName; Make (Create) file OUT: cf=0:Success fm_df FileName; Delete file OUT: cf=0:Success fm_. Buffer,Drive#; Get current directory.drv#(0=def,1=A...);OUT: si=Buffer,cf=0:Success fm_a FileName; Get file Attributes OUT: cx=attrib,cf=0:Success fm_as FileName,Attributes; Set file Attributes OUT: cf=0:Success fm_rf CurrentName,NewName; Rename file OUT: cf=0:Success fm_o AccessMode,FileName; Open file. Access mode can be 0:Rd only, 1:Wr Only, 2:R/W OUT: cf=0:Success,ax=Handle. Must Do:mov ebx,eax (for rd/wr/cl) fm_r BytesToRead,Buffer; Read File. OUT: dx=Buffr,ax=bytes rd,cf=0:Success fm_w BytesToWrite,Buffer; Write file OUT: ax=bytes written,cf=0:Success fm_s Position,Offset; Seek into file. OUT: Position=Strt=0/1/2h:File strt/ dx:ax=Position from file strt, Currnt pos/Fl end Offset=2 par,s cf=0:Success fm_c Close File. OUT: cf=0:Success ================================================================================================ Our Assembly language tools inventory: ====================================== Miss Assembly is now happy since she has more tools than she could have wished for. Her tools come from three sources: (1) 'C' global functions which have been discussed in previous lesson. (2) Interrupt 21h functions which have been discussed in this lesson. (3) Assembly language tools which we have developed before and are available in file "pcsa.a". Here is a list of those tools: a) copybytes: Copies a number of bytes to a string starting at a specific location from a different location in same string or another one. IN: esi points to start of source data, edi points to start of destination data eax=Number of bytes to be copied. b) tohexs : Converts an integer to a hex string. IN: eax=Integer number. OUT: eax pointing to the resulting hex string. c) tohexn : Converts a hex string to an integer number. IN: eax points to the hex string. OUT: eax=Resulting integer number. d) tobins : Converts least signeficant byte of an integer to a binary string. IN: eax=Integer number. OUT: eax pointing to the resulting binary string. e) tm_d (Display string), tm_dn (Display an integer) and tm_dnl (Move to next line) Although the new display macros are better, those functions are still active. f) bpt : Our BreakPoint display function. It shows the flag registers first two bytes in binary, the registers eax,ebx,ecx,edx,esi,edi, the dword values stored into them, the stack pointer and the 9 dword values stored into the stack. The global Assembly data defined into file "pcsa.a": ---------------------------------------------------- Miss Assembly can use any of these global variables without definition since their "extern" difinitions are included into the header file "pcsa.h". hexs : An 8-bytes string which stores a DWORD as a hex string. bins : An 8-bytes string which stores a byte as a binary string. space : A space character (20h) star : A star character (2Ah) nline : A new line char. (0Ah) ================================================================================================ Example 7: Obtain current date and time using Interrupt 21h functions. Put them into convenient String forms and return them to PC#. ================================================================================================ < xc.c> char* crun(int parn,char* js,char* ks,char* os){} < /xc.c> < xa.a> %include "langs/include/pcsa.h" segment .data; // Data Section wkdays db "Sun Mon Tue Wed Thu Fri Sat ",0; // Week days months db "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ",0;// Months yr db "0000",0; // Year's 4-digit string dtc db "00",0; // Gen 2-digit date/time component dtt db "The date is: ",0; // Date string title tmt db "The time is: ",0; // Time string title date db "ddd, mmm 00, 0000.",0; // Date string time db "00, 00 minutes and 00 seconds.",0; // Time string segment .text; // Code Section global _arun,dtcMaker; // Function definitions _arun: { enter 0,0;pusha;pushf; // Function start routine. //----------------- Reading Date and storing it into long date string --------------------- // Date comes in 4 components. al=Weekday order # (Sun=0), cx=Year, dh=month, dl=day sm_d; // Get Date push edx;push ecx; // Save day,month,year data temporarely //------ Week day ------ and eax,0FFh;imul eax,4; // eax=today's order in (wkdays)=(al)*4 mov esi,wkdays;add esi,eax; // Make esi point to today's position in (wkdays) mov edi,date;mov eax,3;call copybytes; // edi point to (date) start, copy 3 bytes from esi to edi //------ Year ------ pop eax;and eax,0ffffh; // Read year into (eax) Keep only lower 16 bits mov [_o],eax;movs 'o',yr;call _om_fi;// Convert year to the string (yr) mov esi,yr;mov edi,date;add edi,13; // esi points to (yr), edi points to year positn in (date) mov eax,4;call copybytes; // Copy 4 bytes from (esi) to (edi) //------ Day ------ mov eax,[esp];and eax,0ffh; // Get a copy of day-month data from stack, Make eax=day mov edi,date;add edi,9;call dtcMaker; //------ Month ------ date2: pop eax;and eax,0ff00h;shr eax,8; // Pop day-month data from stack, make eax=month // Notice that January is month 1. In our string it's at position zero. sub eax,1;imul eax,4; // Make eax=This month's order in (months) mov esi,months;add esi,eax; // Make esi point to this month's position in (months) mov edi,date;add edi,5; // and edi point to month position in (date) mov eax,3;call copybytes; // Copy 3 bytes from esi to edi tm_s dtt;tm_sl date; // Display the (date) string //----------------- Reading Time and storing it into long time string --------------------- // Time comes in 4 components: ch=hours, cl=minutes, dh=seconds, dl=1/100 sec sm_t; // Get time push edx;push ecx; // Save minutes, seconds data temporarely //------ Hour ------ mov eax,ecx;and eax,0ff00h;shr eax,8;// Make eax=hour mov edi,time;call dtcMaker; // Make edi point to start of (time), call 2-digit maker //------ Minutes ------ pop eax;and eax,0ffh; // Make eax=minutes mov edi,time;add edi,4;call dtcMaker;// Make edi point to minutes place in (time), call functn. //------ Seconds ------ pop eax;and eax,0ff00h;shr eax,8; // Make eax=Seconds mov edi,time;add edi,19;call dtcMaker; // Make edi point to sec's place in (time), call function tm_s tmt;tm_sl time; // Display the (time) string popf;popa;leave; // Function end routine. mov eax,space; ret; } //-------------------------- Processing 2-digit date/time components -------------------------- dtcMaker: { // IN:eax=d/t Comp, edi points to where should be inserted mov [_o],eax;movs 'o',dtc; // Assign eax to [_o] and the 2-char string (dtc) to _os push eax;call _om_fi;pop eax; // and call om("fi) to convert to string. Don't lose eax mov esi,dtc; // esi points to (dtc) cmp eax,10;js dtc1; // Is the component a 2-digit one? If so: mov eax,2;call copybytes;jmp dtc2; // Copy 2 bytes from (esi) to (edi) and return dtc1: // Else if component was a 1 digit number: mov esi,dtc;add edi,1; // Put the number after the first zero at the assigned mov eax,1;call copybytes; // area to component in its string dtc2: ret; } < /xa.a> public class a : pcs { public override void init() { j=700;k=375;dm("s"); tia=toa="t"; base.init(); } public override void run() { os=" Using Interrupt 21h operating system functions to get system date and time"; cls="r0";tm();os="";tm();cls="b0"; os="Some useful operating system functions are accessable at Interrupt 21h. This "; os+="demonstration shows\nhow an assembly sub-program can obtain system's time and date"; os+=" using interrupt 21h functions, put\nthem into string formats and return them to the "; os+="calling PC# program."; tm();os="";tm();tm();cls="G3"; os="Received from Assembly Sub-Program:";tm();os="";tm();cls="S9"; os="xe a";sm("pn");tm(); // Call Assembly sub-program and display output. } } ==============================================================================================


============================================================================================= ======== LESSON 7 ======== Accessing WIN32 functions from Assembly ======================================= For version 4.34 and later We 'll show you here another method to access your assembly subroutines from PC#. In this method your PC# class sends the required parameters and receives the return value or values by files. Your assembly subroutine in this case can call any of the Windows API's which opens a great wealth of resources for you. We are going to develop all the tools required to allow you to use this method just as easy as you have been using the original one. However, we'll leave the automation for the next lesson. In this lesson, we will try to do everything from scratch. Here is how it is done: ======================= Our Assembler has the compiling tool that outputs an object file which is compatible with Microsoft win32 object files. We have found a linker which can link this object file with any Win32 DLL which your project requires. Here is our Linker's website: http://www.godevtool.com/GolinkFrame.htm A copy of this linker is available into subfolder "Langs\bin" of your working directory. Their documentations are available at subfolder "Langs\docs". The new compiling and linking tools: ==================================== Let us assume that your assembly file is named "xaw.a". You should convert it to NASM format as usual with: tonasm xaw.a This will generate "xaw.asm" Then compile it with: nasm -fwin32 xaw.asm This will generate "xaw.obj" Linking files: -------------- Before you link the file, you need to make file "dlls.txt" using notepad which contains a list of all the "dll" files which contain the functions you'll be using in your program. Each dll file should be on a seperate line. In the next example we'll be using "kernel32.dll" and "user32.dll". So your file contents should look like this: Kernel32.dll User32.dll After that link files with: golink /fo xe.exe /entry _arun /mix xaw.obj @dlls.txt to generate "xe.exe" About next example: ------------------- Function MessageBox() in "user32.dll" requires the following parameters: int WINAPI MessageBox( __in_opt HWND hWnd, __in_opt LPCTSTR lpText, __in_opt LPCTSTR lpCaption, __in UINT uType ); We are going to call this function from the Assembly function _arun: to display a greeting message on a dialog box. ============================================================================================== Example 8: Write an Assembly program which calls the Windows API function MessageBox() to display a greeting message. ============================================================================================== File "xaw.a": ============= extern MessageBoxA,ExitProcess; // Define necessary functions segment .data; // Data Section title db "Personal C Sharp",0; // Caption message db "Greeting from PC# Assembly sub-program.",0; // Message text segment .text; // Code section global _arun; _arun: { //------ Calling Message Box ------ push dword 0; // 4th par: Default type push dword title; // 3rd par: Caption push dword message; // 2nd par: Text push dword 0; // 1st par: Handle call MessageBoxA; // Call ANSI version of Message Box pop eax;pop eax;pop eax;pop eax; // Restore stack push dword 0;call ExitProcess; // Exit with code (0) } ============================================================================================== File "a.cs": ============ public class a : pcs { public override void init() { j=700;k=375;dm("s"); tia=toa="t"; base.init(); } public override void run() { os=" Calling the win32 function MessageBox() by our Assembly Language Sub-program"; cls="r0";tm();os="";tm();cls="b0"; os =" Function MessageBox() wich is contained into 'user32.dll' has been ";tm(); os =" called by our Assembly Language sub-program to generate a dialog box."; tm();tm("df"); // Display text and flush display os="xe";sm("pn"); // Call Assembly Sub-program } } ============================================================================================== Converting, compiling and linking: ---------------------------------- (1) Make file "dlls.txt" which contains "kernel32.dll", "user32.dll" as explained above. (2) From Command mode, enter the following: tonasm xaw.a [ENTER] nasm -fwin32 xaw.asm [ENTER] golink /fo xe.exe /entry _arun /mix xaw.obj @dlls.txt [ENTER] pcpr a [ENTER] ==============================================================================================


=============================================================================================== ANSI and Unicode Versions: -------------------------- Some DLL functions like CreateFile() and MessageBox() are available in two versions the ANSI version and the unicode version. They are identified by adding an upper case "A" for ANSI and "W" for unicode at the end of the function name. So, if you're interested in the ANSI version, you need to call CreateFileA() and MessageBoxA() in order to access the two functions. Can we guarantee that the WIN32 DLL functions abide with the standard convention? --------------------------------------------------------------------------------- The answer is NO. We can't guarantee that they keep the stack unchanged. So, we must do a stack backup before calls and a stack restoration after the calls. =============================================================================================== ======== LESSON 8 ======== Developing new tools for our new WIN32 Assembly =============================================== For version 4.35 and later In this lesson we'll develop the tools to make you able to easily display anything you like and to have a dependable means of 2-way communication with Personal C Sharp. You'll also be able to access most of the Assembly language functions which we have developed. The same automation which is available to the DOS version will be available to this version. Our Display Media: ------------------ The MessageBox which we have used in the previous example will be our display media. We're going to make an easy function which displays a string on a MessageBox if we make eax point to the string. The function will be named cm_d since it does exactly the same as the PC# method cm("d") The MessageBox displays strings only. If you like to display a number, convert it into a hex string first. Example: to display 255: mov eax,255;call tohexs;call cm_d; // Should display "FF" Our 2-way Communication with PC#: --------------------------------- Functions fm_r: and fm_w: will read and write messages from/to PC#. The parameters you supply to the functions are made to be flexible and easy to use. They are as follows: IN : esi=file name, edi= rd/wr buffer, eax=Number of bytes to read or write OUT: buffer loaded with file data or written into file, ao=Number of bytes read or written, edx=Error code: 0=No error, 1=open error, 2=read error, 3=write error, 4=close error Some helpful Assembly functions: -------------------------------- The following functions are available. They function exactly as they do in the DOS version: a) copybytes: Copies a number of bytes to a string starting at a specific location from a different location in same string or another one. IN: esi points to start of source data, edi points to start of destination data eax=Number of bytes to be copied. b) tohexs : Converts an integer to a hex string. IN: eax=Integer number. OUT: eax pointing to the resulting hex string. c) tohexn : Converts a hex string to an integer number. IN: eax points to the hex string. OUT: eax=Resulting integer number. d) tobins : Converts least significant byte of an integer to a binary string. IN: eax=Integer number. OUT: eax pointing to the resulting binary string. The ExitProcess function: ------------------------- We are still using function _arun: to contain our Assembly program in order to keep the same routine, but you should know that in this version this function did not start because it has been called by some other function. Therefore after it terminates the process will hang-up unless we terminate it by ourselves. The new function sm_e: will do this job. It uses the kernel32.dll function ExitProcess() internally. File pcsaw.a: ============= This file is similar to file "pcsa.a" in the DOS version. It contains the display, filing and all Assembly functions which you'll be accessing. It has been compiled with the win32 compiling tool and the object file "pcsaw.obj" has been added to the "dlls.txt" file. We're going to list the new added functions, but we must show the C++ win32 functions which they call internally first. Open file: VALUES WE'll SUPPLY ---------- ----------------------------------------------------- HANDLE WINAPI CreateFile( __in LPCTSTR lpFileName, esi points to File name __in DWORD dwDesiredAccess, 80000000h=(rd) / 40000000h=(wr) / c0000000h=(rd/wr) __in DWORD dwShareMode, 0=No other process can access file until closed. __in_opt LPSECURITY_ATTRIBUTES lpSec, 0=Default __in DWORD dwCreationDisposition, 3=Open existing file only / 2=Create new or overwrite __in DWORD dwFlagsAndAttributes, 80h=Normal File. __in_opt HANDLE hTemplateFile 0 ); RETURN VALUE: eax=Handle or (-1) meaning Error. We'll assign handle to ebx Read file: VALUES WE'll SUPPLY ---------- ----------------------------------------------------- BOOL WINAPI ReadFile( __in HANDLE hFile, ebx=Handle returned by Open file function. __out LPVOID lpBuffer, edi poits to Buffer (to read entire file content into) __in DWORD nNumberOfBytesToRd, eax=aos.Length or no. of bytes to be read __out_opt LPDWORD lpNumberOfBytesRd, ao=Actual no. of Bytes Read __inout_opt LPOVERLAPPED lpOverlapped 0 ); RETURN VALUE: eax. (eax=0 means "Error") Write file: VALUES WE'll SUPPLY ----------- ----------------------------------------------------- BOOL WINAPI WriteFile( __in HANDLE hFile, ebx=Handle returned by Open file function. __in LPCVOID lpBuffer, edi points to (Buffer to be written into file) __in DWORD nNumberOfBytesToWr, eax=aos.Length or no. of bytes to be written __out_opt LPDWORD lpNumbrOfBytesWrn, ao=Actual no. of Bytes written __inout_opt LPOVERLAPPED lpOverlapped 0 ); RETURN VALUE: eax. (eax=0 means "Error") Close file: VALUES WE'll SUPPLY ----------- ----------------------------------------------------- BOOL WINAPI CloseHandle( __in HANDLE hObject ebx=Handle returned by Open file function. ); and here are the new functions in file "pcsaw.a": ================================================= extern MessageBoxA,ExitProcess,CreateFileA,ReadFile,WriteFile,CloseHandle; segment .data; global ao,aoi; title db "Personal C Sharp",0; // Caption ao dd 0;aoi dd 255; //------------------------------------- Function cm_d: --------------------------------------- global cm_d; cm_d: { // IN:eax points to the message to be displayed enter 0,0; // Backup the stack push dword 0; // 4th par: Default type push dword title; // 3rd par: Caption push eax; // 2nd par: Text push dword 0; // 1st par: Handle call MessageBoxA; // Call ANSI version of Message Box leave; // Restore stack ret; } //------------------------------------- Function fm_r: --------------------------------------- global fm_r; fm_r: { // IN:eax=bytes to read,esi=File name,edi=buffer OUT:ao=byts read,edx=Error code // edx=Error code: 0=No error, 1=open error, 2=read error, 3=write error, 4=close error mov dword [aoi],eax; // Save (bytes to read) into [aoi] //------ Open File ------ enter 0,0; // Backup stack xor ecx,ecx; // Prepare a zero push ecx;mov eax,80h;push eax; // Supply par7,par6 mov eax,3;push eax;push ecx;push ecx; // Supply par5,par4,par3 push 80000000h;push esi; // Supply par2,par1 call CreateFileA; // Call function leave; // Restore stack mov ebx,eax;push ebx; // Copy returned handle to ebx and push into stack cmp eax,-1;jnz read; // If valid handle (not=-1) continue mov edx,1;pop ebx;ret; // Else return with (edx=1), fix stack //------ Read File ------ read: enter 0,0; // Backup stack xor ecx,ecx;push ecx; // Prepare a zero, Supply par5 push dword ao;push dword [aoi]; // Supply par4,par3 push edi;push ebx; // Supply par2,par1 call ReadFile; // Call function leave; // Restore stack cmp eax,0;jnz close; // If success (not=0) continue mov edx,2;pop ebx;ret; // Else return with (edx=2), fix stack //------ Close File ------ // Remember that the only par needed (Handle) has already been pushed in (at end of Open File) close: call CloseHandle; // Call function cmp eax,0;jnz return; // If success (not=0) continue mov edx,4;ret; // Else return with (edx=4) return: mov edx,0;ret; // If all OK return edx=0 } //------------------------------------- Function fm_w: --------------------------------------- global fm_w; fm_w: { // IN:eax=bytes to write,esi=File name,edi=buffer OUT:ao=byts written,edx=Error code // edx=Error code: 0=No error, 1=open error, 2=read error, 3=write error, 4=close error mov dword [aoi],eax; // Save (bytes to write) into [aoi] //------ Open File ------ enter 0,0; // Backup stack xor ecx,ecx; // Prepare a zero push ecx;mov eax,80h;push eax; // Supply par7,par6 mov eax,2;push eax;push ecx;push ecx; // Supply par5,par4,par3 push 40000000h;push esi // Supply par2,par1 call CreateFileA; // Call function leave; // Restore stack mov ebx,eax;push ebx; // Copy returned handle to ebx and push into stack cmp eax,-1;jnz write; // If valid handle (not=-1) continue mov edx,1;pop ebx;ret; // Else return with (edx=1), fix stack //------ Write File ------ write: enter 0,0; // Backup stack xor ecx,ecx;push ecx; // Prepare a zero, Supply par5 push dword ao;push dword [aoi]; // Supply par4,par3 push edi;push ebx; // Supply par2,par1 call WriteFile; // Call function leave; // Restore stack cmp eax,0;jnz close1; // If success (not=0) continue mov edx,3;pop ebx;ret; // Else return with (edx=3), fix stack //------ Close File ------ // Remember that the only par needed (Handle) has already been pushed in (at end of Open File) close1: call CloseHandle; // Call function cmp eax,0;jnz return1; // If success (not=0) continue mov edx,4;ret; // Else return with (edx=4) return1: mov edx,0;ret; // If all OK return edx=0 } //----------------------------------------- ExitProcess -------------------------------------- global sm_e; sm_e:{ // End process push dword 0;call ExitProcess; // Exit with code (0) } =============================================================================================== Automation of the Win32 Assembly's compiling and linking processes: =================================================================== (1) With the DOS version, you used to select a base name of one or more char's and add "a.a" to it to make the name of your Assembly file. Here you add "aw.a" to it to make the name of your win32 Assembly file. (2) With the DOS version, you can do all compiling and linking together with tool "pco". Here tool "pcow" does the same job. (3) With the DOS version, you can place your Assembly program at the top of your ".cs" file marking its borders with XML-Like tags. You can do the same with your win32 version. PC# software will be able to identify the win32 version by the added lower case "w" at the end of the file name. Windows API lists: ================== Here are some websites which can help you in finding information about win32 functions: General : http://pinvoke.net/default.aspx Kernel32: http://www.inf.unideb.hu/~fazekasg/english/Operating_Systems_I_II/Win_kernelref.pdf GDI32 : http://assembler-tutorials.pytalhost.de/tuts/gdiref.pdf USER32 : http://charon.bdeb.qc.ca/docs/asm/art_of_programming/userRef.pdf =============================================================================================== Example 9: In this example Miss PC# and Miss Assembly will check the new 2-way communication between them. Miss PC# will write a message to Miss Assembly into their communication file. Miss Assembly will read the message, display it into a MessageBox, and write a reply into same file. Miss PC# will read the file and display Miss Assembly's reply. =============================================================================================== < xaw.a> extern tohexs,tohexn,hexn,hexs,bins,bps,bpt,tobins,copybytes,space,star,nline; extern fm_r,fm_w,cm_d,sm_e; // Global data of file "pcsaw.a" segment .data; // Data Section fls db "pcscom.txt",0; // Communication file name aos1 times 256 db 0; // Receive buffer aos2 db "Hello Miss PC#. This is my reply. The MessageBox you requested has been made.",0; // Send buffer segment .text; // Code section global _arun; _arun: { mov esi,fls;mov edi,aos1;mov eax,100;call fm_r;// Read message in com file. mov eax,aos1;call cm_d; // and display it. mov esi,fls;mov edi,aos2;mov eax,77;call fm_w; // Write reply into com file. } call sm_e; // Exit Process < /xaw.a> public class a : pcs { public override void init() { j=700;k=375;dm("s"); // Resize form tia=toa="t"; // Use TextScreen for display base.init(); } public override void run() { os="";tm(); os=" Checking the 2-way communication between PC# and the new win32 Assembly sub-program"; cls="r0";tm();os="";tm();cls="b0"; os ="Hello, this is Miss PC#. I'll send a message to Miss Assembly and expect her to "; os+="confirm by displaying my message into a MessageBox and sending me a reply.";tm(); os="";tm(); os ="Miss Assembly, if you have received this message display it into a MessageBox and send"; os+=" me a reply.";fls="pcscom.txt";fm("W");// Send meesage via com file cls="G3";os="Hit [OK] to see her reply.";tm();os="";tm();tm("df"); os="xe";sm("pn"); // Call Assembly fls="pcscom.txt";fm("R"); // Receive incoming message and display it. cls="G3";os="Received from Miss Assembly:\n"+os;tm(); } } =============================================================================================== Compiling: ========== pcp a [ENTER] will compile and link all files generating the two executable files "a.exe" and "xe.exe". ===============================================================================================


=============================================================================================== USING WINDOWS ASSEMBLER: ======================== All examples of this chapter have been running fine for many years, but since the start of Windows 10, a growing number of problems have been appearing. DJGPP C-Compiler, NASM Assembler or GOLINK Linker may be found to be causing a security issue, to be incompatible with new CPU's or to be having any other problem which complicates their use in the new versions of Windows, but the one item which should work correctly all the time is Microsoft's Windows Macro Assembler which can be downloaded at: https://www.microsoft.com/en-us/download/details.aspx?id=12654 It requires the Visual Studio to use it which is fine. You can still access your resulting ".exe" file from a PC# class in the same universal manner which was explained in the chapter of "Accessing External Objects...". Here is how to do it again: You assign to (os) the '.exe' file name followed with all arguments exactly as you do it at command mode and call method sm("pn") to run it as a seperate process. To return a value to PC#, you have two choices: (1) If the assembly program can display the return value on the console, do so. Your PC# program will receive everything displayed assigned to (os) (2) Use communication file between the two languages as you did in Example 9. ===============================================================================================