Using ASM with C++ Builder
Author : Dik Takken Page : 1
USING ASSEMBLY LANGUAGE WITH C++ BUILDER
If you want to use assembly language in C++ builder, there are some tricks you need to know about. Some years back, when I tried to figure this out with an old C++ compiler, it took me days before it finally worked. Let me make this easy for you. Just read this page and you know all you need to know.
In Windows 95/98 and Windows NT your computer is running in what the intel guys call "Protected Mode". Modern computers run fastest in this mode, and assembly programming is easier than it was in Read Mode operating systems like MS-DOS. Especially when you use C++Builder!
Allright, first of all, there are two ways of using assembly in C++ Builder. You can use it in-line, or in a seperate unit. I always create seperate units, because it is faster, you have more control over the code and there are no restrictions on instruction sets and registers.
I think it's not very likely, but say you want to create an assembly sub-routine that multiplies to floating point numbers and returns the result to the caller. First of all you need a way to pass the arguments, two floats, to the assembly routine. Well, C++ builder pushes these arguments on the stack in reverse order (C Calling Convention). You can tell the assembler about the existence of these arguments by using the following syntax on your sub-routine declaration:
MySubroutine PROC C Arg1:DWORD, Arg2:DWORD
Now the assembler knows the name of the sub-routine (MySubroutine), the type of programming language that is calling the sub-routine (C), and the names and types of the arguments (Arg1:DWORD, Arg2:DWORD). Now you can just use the names Arg1 and Arg2, the assembler will automatically replace these names with stack-adresses. The next thing we need to do is to make the sub-routine public, so the linker can see it. This is done by the PUBLIC directive. Now C++ Builder, like most other C++ compilers, has the odd habit of putting an underscore in front of the name of every C++ subroutine when compiling the source code to assembly. We can tell the assembler to compensate for this by putting a language identifier after the PUBLIC directive. This is how you make the sub-routine visible to the linker:
PUBLIC C MySubroutine
Ok, the assembler knows what we want to do, the linker knows about the sub-routine, bot the compiler doesn't know a thing yet. We need to declare the assembly routine. Here is how you can do this:
extern "C" float MySubroutine(float Arg1,float Arg2);
Hey, but how are we going to return the result to the caller? Well, C++ expects us to put the data somewhere, it depends of the type of data (int,float,char *,...). In this case, we need to return a floating point value, and C++ expects it to be on the top of the stack of the FPU processor. For other data types, look at the list below:
int -> EAX
unsigned int -> EAX
float -> ST(0)
char -> AL
unsigned char -> AL
short int -> AX
unsigned short int -> AX
pointer -> EAX
Ok, the code looks like this:
; MyCode.asm
.586p
model flat
locals
.DATA
.CODE
PUBLIC C MySubroutine
MySubroutine PROC C Arg1:DWORD, Arg2:DWORD
fld Arg1
fmul Arg2
ret
ENDP
END
And here is how you call it:
extern "C" float MySubroutine(float Arg1,float Arg2);
....
float Result = MySubroutine(FirstNumber,SecondNumber);
What if you want the subroutine to store the result in a public C++ variable? Well, I never figured out how to do that. You see, C++ renames all your variables to some random sequence of characters. There is no way of finding out what the name of your C++ variable will be in assembly. However, there is a little trick to do this. You don't create your variable in C++, but in your assembly code. You can still access it in C++ like any other variable by declaring it external. This is what it looks like:
; MyCode.asm
.586p
model flat
locals
.DATA
PUBLIC C Result
Result dd 0
.CODE
PUBLIC C MySubroutine
MySubroutine PROC C Arg1:DWORD, Arg2:DWORD
fld Arg1
fmul Arg2
fstp Result
ret
ENDP
END
And here is how you call it:
extern float Result;
extern "C" void MySubroutine(float Arg1,float Arg2);
....
MySubroutine(FirstNumber,SecondNumber);
....
Ok, but what if I want the sub-routine to store the result at a specified address? Well, you just pass a pointer to the assembly routine. The rest is quite straight foreward:
; MyCode.asm
.586p
model flat
locals
.DATA
.CODE
PUBLIC C MySubroutine
MySubroutine PROC C Arg1:DWORD, Arg2:DWORD,Address:DWORD
fld Arg1
fmul Arg2
fstp [Address]
ret
ENDP
END
And here is how you call it:
float Result;
extern "C" void MySubroutine(float Arg1,float Arg2,float *Address);
....
MySubroutine(FirstNumber,SecondNumber,&Result);
....
Ok, now comes the biggest trick of all assembly tricks:
This is the easiest way of debugging your assembly code ever. You write your assembly unit and add it to a C++ Builder project, just like an ordinary CPP file. You write a program that calls the assembly sub-routine. Open the assembly unit and place breakpoints at some cruicial points. Now you run the program until a breakpoint is triggered. Return to the C++ Builder IDE. You will see an arrow in front of the line in the assembly file where execution has paused. Now you can just move your mouse pointer to any variable or CPU register and it will show you is's value! You can also open the CPU Window of C++ Builder to follow the execution one instruction at a time.
That's it! I hope you find this information very useful and if not, thanks for reading.
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请
点击举报。