The previous chapter looked at general principles of creating assembly module interfaces with a C++ .NET program and the main standards and conventions used in programs. Now, we will look at using parameters in calls to assembly functions more closely. There are two main methods for passing data to a function for further processing: by value and by reference. First, we will consider passing parameters by value.
In this case, the called function receives a copy of the variable, and the copy is lost when the function returns control. The variable in the calling program does not change. Consider a simple example of a console application, in which the called function multiplies an integer parameter by five and returns the result to the main program via the EAX register. We will assume that the main program and the called function in the examples in this and the following chapters use the stdcall convention.
The assembly function that multiplies numbers is shown in Listing 6.1.
.686 .model flat public _mul5@4 .code _mul5@4 proc push EBP mov EBP, ESP mov EAX, DWORD PTR [EBP+8] mov ECX, 5 mul ECX pop EBP ret _mul5@4 endp end
The source code of the function is straightforward. The Visual C++ .NET main program is written as a console application, and its source code is shown in Listing 6.2.
// This is the main project file for VC++ application project // generated by using an Application Wizard. #include "stdafx.h" #using <mscorlib.dll> extern "C" int _stdcall mul5(int i1); using namespace System; int _tmain() { // TODO: Please replace the sample code below with your own. int i1, i5; String *Si1, *Si5; Console::Write("Enter integer value:"); Si1 = Console::ReadLine(); i1 = Convert::ToInt32(Si1); i5 = mul5(i1); Si1 = Convert::ToString(i1); Si5 = Convert::ToString (i5); Console::Write("Entered integer = "); Console::WriteLine (Si1); Console::Write("Multiplying i1 x 5 = "); Console::WriteLine (Si5); Console::ReadLine(); return 0; }
After mul5 procedure is called with the statement i5=mul5(i1) , the value of the i1 variable does not change. This is evident in Fig. 6.1, which shows the application window.
Passing parameters by value in function calls is inconvenient when processing arrays of numeric and character data.
To process such data, pointers are usually used in function calls, and passing parameters in such a manner is called passing by reference.
Now we will look at using pointer parameters for processing strings and arrays in C++ .NET with assembly functions more closely. It is well known that a pointer is a variable that contains the memory address of another variable. The address of a string or array is the address of its first element. The addresses of subsequent elements are computed by adding the value equal to the size of the element of the string or array. For ASCII strings, which we will consider, the address of the next element is one greater than the address of the previous element. For an integer array, the address of the next element is four greater than the address of the previous item.
Another important note: In all the examples in this chapter, null- terminated strings are used in string operations.
The following example illustrates how a character string can be passed to the main program from an assembly module and displayed in the program s window. The source code of the assembly function is shown in Listing 6.3.
;---------------- strshow.asm ----------- .686 .model flat public _strshow@0 .data TESTSTR DB "THIS STRING GOES FROM ASM PROCEDURE !", 0 .code _strshow@0 proc mov EAX, offset TESTSTR ret _strshow@0 endp end
The function is very simple. It does not take any parameters and returns the address of the TESTSTR string in the EAX register. The source code of the C++ .NET console application that calls the strshow function is also easy to analyze (Listing 6.4).
// This is the main project file for VC++ application project // generated by using an Application Wizard. #include "stdafx.h" extern "C" char* _stdcall strshow(void); #using <mscorlib.dll> using namespace System; int _tmain () { Console::Write (strshow()); Console::ReadLine(); return 0; }
The string is output to the application window with the Console::Write statement. The Write method of the System::Console class takes the pointer to the string buffer returned by the strshow function as an argument.
The window of the application is shown in Fig. 6.2.
Another method for passing a string or array to the main program involves copying the string to the main program s memory buffer. The source code of an assembly function that does this is shown in Listing 6.5.
;-----------------copystr.asm------------------ .686 .model flat public _copystr@4 .data TESTSTR DB "TEST STRING IS COPIED FROM ASM PROCEDURE !", 0 LENSTR EQU $TESTSTR .code _copystr@4 proc push ESI push EDI push EBP mov EBP, ESP cld mov ECX, LENSTR mov ESI, offset TESTSTR mov EDI, DWORD PTR [EBP+16] rep movsb pop EBP pop EDI pop ESI ret 4 _copystr@4 endp end
The parameter of this function is the address of the memory buffer of the calling program, to which the string must be copied. The buffer is assumed to be large enough to hold the whole string. The string length in bytes is put to the ECX register. The ESI register contains the address of the TESTSTR source string, and the EDI register contains the address of the target string in the main program. Copying is done with the rep movsb command.
The C++ .NET console application that displays a copy of a string can be coded as follows (Listing 6.6).
// This is the main project file for VC++ application project // generated by using an Application Wizard. #include "stdafx.h" extern "C" void _stdcall copystr(char* s1); #using <mscorlib.dll> using namespace System; int _tmain() { // TODO: Please replace the sample code below with your own. char buf[64]; copystr(buf); Console::WriteLine(buf); Console::ReadLine(); return 0; }
The window of the application is shown in Fig. 6.3.
When linking the application in Visual C++ .NET, always include the file of the object module written in the assembler into your project.
We will continue by considering a few more examples that demonstrate techniques of passing parameters and processing data in assembly functions.
It is often necessary to pass to the main program a part of a string (a substring) starting from a certain position, rather than the whole string. The next example illustrates how this can be done.
Suppose an assembly module contains a character string, and you want to pass to the main program a substring starting from a certain position. In this case, the assembly function takes the offset from the beginning of the string as a parameter and returns the address of the first element of the extracted substring.
The source code of the assembly function (named strpart ) is shown in Listing 6.7.
;------------------- strpart.asm ------------------- .686 .model flat public _strpart@8 .data .code _strpart@8 proc push EBP mov EBP, ESP mov ECX, DWORD PTR [EBP+12] mov EAX, DWORD PTR [EBP+8] add EAX, ECX pop EBP ret 8 _strpart@8 endp end
Choose a standard variant of a procedure-oriented Windows application as a template for the C++ .NET main program. After the Application Wizard generates the frame, make necessary changes and additions to the source text and add the Return Part of String item to the menu. Bind the ID_PartStr identifier to it. When this menu item is selected, the source string, substring, and offset in the initial string will be displayed in the application window.
In the declaration section of the WinMain main program, create a reference to the external procedure:
extern "C" char* _stdcall strpart(char *ps, int off);
The parameters of the strpart function are the address of the source string (ps) and the offset from its beginning (off) .
The application uses a few more variables , which are shown below:
char src[] = "STRING1 STRING2 STRING3 STRING4 STRING5"; char *dst; int off, ioff; char buf[4];
where
src string is a source string for processing.
dst string is the destination string.
off integer is the offset from the beginning of the source string.
buf string and the ioff integer are used by the sprintf function to format the output.
In the WndProc callback function, create a menu item selection handler ID_PartStr (Listing 6.8).
case ID_PartStr: hdc = GetDC(hWnd); GetClientRect(hWnd, &rect); off = 10; dst = strpart(src, off); ioff = sprintf(buf, "%d", off); TextOut(hdc,(rect.right - rect.left)/4, (rect.bottom - rect.top)/4, "Source:", 7); TextOut(hdc, (rect.right - rect.left)/3, (rect.bottom - rect.top)/4, src, strlen(src)); TextOut(hdc, (rect.right - rect.left)/4, (rect.bottom - rect.top)/3, "Dest:", 5); TextOut(hdc, (rect.right - rect.left)/3, (rect.bottom - rect.top)/3, dst, strlen(dst)); TextOut(hdc, (rect.right - rect.left)/4, (rect.bottom - rect.top)/3 + 30, "Offset:", 7); TextOut(hdc, (rect.right - rect.left)/3, (rect.bottom - rect.top)/3 + 30, buf, ioff); ReleaseDC(hWnd, hdc); break;
The TextOut function outputs text to the client area. Its first parameter is the device context descriptor of the display. The device context descriptor is returned by the GetDC function.
To output the text to the client area of the window, access the coordinates of this area with the GetClientRect function.
The sprintf function is used to format the off integer when displaying it. The prototype of this function is described in the stdio.h file, so it is necessary to add the following line to the declaration section of the WinMain function:
#include <stdio.h>
The application window will look better if you change the standard white background to gray:
wcex.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
The full source code of the application is shown in Listing 6.9.
#include "stdafx.h" #include "Return Part of String in C.NET.h" #define MAX_LOADSTRING 100 #include <stdio.h> HINSTANCE hInst; TCHAR szTitle[MAX_LOADSTRING]; TCHAR szWindowClass[MAX_LOADSTRING]; // References to the functions declared in this module ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance (HINSTANCE, int); LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About (HWND, UINT, WPARAM, LPARAM); extern "C" char* _stdcall strpart(char *ps, int off); int APIENTRY _tWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { MSG msg; HACCEL hAccelTable; LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING), LoadString (hInstance, IDC_RETURNPARTOFSTRINGINCNET, szWindowClass, MAX_LOADSTRING); MyRegisterClass (hInstance); if (!Initlnstance (hlnstance, nCmdShow)) { return FALSE; } hAccelTable = LoadAccelerators (hInstance, (LPCTSTR)IDC_RETURNPARTOFSTRINGINCNET); while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator (msg.hwnd, hAccelTable, &msg) { TranslateMessage (&msg); DispatchMessage (&msg); } } return (int)msg.wParam; } ATOM MyRegisterClass (HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW CS_VREDRAW; wcex.lpfnWndProc = (WNDPROC) WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon (hInstance, (LPCTSTR)IDI_RETURNPARTOFSTRINGINCNET); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)GetStockObject (GRAY_BRUSH); wcex.lpszMenuName = (LPCTSTR)IDC_RETURNPARTOFSTRINGINCNET; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon (wcex.hInstance, (LPCTSTR)IDI_SMALL); return RegisterClassEx (&wcex); } BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HOC hdc; RECT rect; char src[] = "STRING1 STRING2 STRING3 STRING4 STRING5"; char *dst; int off, ioff; char buf[4]; switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); switch (wmId) { case IDM_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_EXIT: DestroyWindow(hWnd); break; case ID_PartStr: hdc = GetDC(hWnd); GetClientRect(hWnd, &rect); off = 10; dst = strpart(src, off); ioff = sprintf(buf,"%d",off); TextOut(hdc, (rect.right - rect.left)/4, (rect.bottom - rect.top)/4, "Source:", 7); TextOut(hdc, (rect.right - rect.left)/3, (rect.bottom - rect.top)/4, src, strlen(src)); TextOut(hdc, (rect.right - rect.left)/4, (rect.bottom - rect.top)/3, "Dest:", 5); TextOut(hdc, (rect.right - rect.left)/3, (rect.bottom - rect.top)/3, dst, strlen(dst)); TextOut(hdc, (rect.right - rect.left)/4, (rect.bottom - rect.top)/3 + 30, "Offset:", 7); TextOut(hdc, (rect.right - rect.left)/3, (rect.bottom - rect.top)/3 + 30, buf, ioff); ReleaseDC(hWnd, hdc); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK LOWORD (wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; }
The window of this application is shown in Fig. 6.4.
The next example demonstrates how a string element can be found. An assembly function passes the main program the position of the first occurence of the sought element in the string (if it finds any) or zero otherwise . The function s parameters are the string address and a character to search for.
The source code of the assembly function (named charpos ) is shown in Listing 6.10.
; -------------------- charpos. asm ------------------- .686 .model flat public _charpos@8 .data .code _charpos@8 proc push EBX push EBP mov EBP, ESP mov EBX, DWORD PTR [EBP+12] xor EAX, EAX mov AL, BYTE PTR [EBP+16] mov ECX, 1 next_check: cmp AL, [EBX] je quit cmp BYTE PTR [EBX], 0 jne inc_cnt jmp not_found quit: mov EAX, ECX pop EBP pop EBX ret 8 inc_cnt: inc ECX inc EBX jmp next_check not_found: xor ECX, ECX jmp quit _charpos@8 endp end
Parameters of the charpos function are the string address located in the address stored in [EBP+12] and the character to search for in [EBP+16] . The first element of the string has the number 1. Therefore, the counter is initialized to this value:
mov ECX, 1
The string address is put to the EBX register, and the character to search for is put to the AL register. Then the character whose address is in the EBX register is compared with the contents of the AL register. Depending on the result, the function jumps to an appropriate branch:
cmp AL, [EBX] je quit cmp BYTE PTR [EBX], 0 jne inc_cnt jmp not_found
If the sought character is found, its number is written to the ECX register. If the last element is null, i.e., the end-of-string character is found, the contents of the ECX register is reset to zero. If the sought character is not found, but the end of the string has not been encountered yet, the registers EBX and ECX are incremented, and the loop resumes from the next_check label:
jmp next_check
As usual, the procedure returns the result in the EAX register and frees up the stack with the ret 8 command.
Select the dialog-based application template for the C++ program. Put three edit controls, three static text controls, and a button onto the main form. Bind the src and cSrc variables of the cstring type to the Source and Character edit controls and the iPos integer variable to the Number edit control. Write an event handler for clicking the Button button (Listing 6.11).
void GetNumberOfCharinStringforCNETDlg: :OnBnClickedButton1 () { // TODO: Add your control notification handler code here. CString s1; CString c1: char *pc1: UpdateData(TRUE); s1 = src; c1 = cSrc; pc1 = c1.GetBuffer(8); iPos = charpos(s1.GetBuffer(16), *pc1); UpdateData(FALSE); }
If the character is found, the Number edit control will contain the number of the character; otherwise, it will contain zero.
The application window is shown in Fig. 6.5.
The next example demonstrates how to search for the maximum element in a floating-point array and display it on the screen. Let the array size be equal to nine. Develop a classic procedure-oriented application in Visual C++ .NET. In such an application, there are usually two interrelated pieces of code: the WinMain main procedure, which registers the window class and initializes an application window instance, and a callback function (a window procedure).
The search for the maximum element in a floating-point array is done with the maxreal assembly function (Listing 6.12).
;-------------------------------- maxreal. asm ----------------------- .686 .model flat public _maxreal@8 .data MAXREAL DD 0 .code _maxreal@8 proc push EBX push EBP mov EBP, ESP mov EBX, DWORD PTR [EBP+12] mov EDX, DWORD PTR [EBP+16] mov ECX, 1 finit fld DWORD PTR [EBX] NEXT_CMP: add EBX, 4 fcom DWORD PTR [EBX] fstsw AX sahf jnc CHECK_INDEX fld DWORD PTR [EBX] CHECK_INDEX: cmp ECX, EDX je FIN inc ECX jmp NEXT_CMP FIN: fwait fstp DWORD PTR MAXREAL mov EAX, offset MAXREAL pop EBP pop EBX ret 8 _maxreal@8 endp end
This function uses mathematical coprocessor commands. To extract parameters, the EBP register is used. The array address is passed via [EBP+i2] , and the array size is passed via [EBP+16] . The current maximum value is stored in the local variable MAXREAL .
After processing the array, the address of the maximum element is put to the EAX register:
fstp DWORD PTR MAXREAL mov EAX, offset MAXREAL
Use the Application Wizard to develop a common 32-bit Windows application that uses the result of the assembly function. The source code of the WinMain procedure and the callback function is shown in Listing 6.13.
#include "stdafx.h" #include "Find Max Value in Array of Reals.h" #define MAX_LOADSTRING 100 // Global variables HINSTANCE hInst; TCHAR szTitle[MAX_LOADSTRING]; TCHAR szWindowClass[MAX_LOADSTRING]; // Declarations of this module's functions ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM); extern "C" float* _stdcall maxreal(float *px, int sx); int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { MSG msg; HACCEL hAccelTable; LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_FINDMAXVALUEINARRAYOFREALS, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // Initializing the application if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_FINDMAXVALUEINARRAYOFREALS) // A message processing loop while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; } // A window class registration function ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW CS_VREDRAW; wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_FINDMAXVALUEINARRAYOFREALS); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); wcex.lpszMenuName = (LPCTSTR)IDC_FINDMAXVALUEINARRAYOFREALS; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL); return RegisterClassEx(&wcex); } BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL), if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HOC hdc; char buf [16]; float xarray[9] = {12.43, 93.54, 2 3.1, 23.59, 16.09, 10.67, -54.7, 11.49, 98.06}; float *xres; int cnt; switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); switch (wmId) { case IDM_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam) } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Our handler's code is here TextOut(hdc, 30, 80, "ARRAY: ", 7); for (cnt = 0; cnt < 9; cnt ++) { gcvt(xarray[cnt], 6, buf); TextOut(hdc, 100 + cnt*50, 80, buf, 5); } TextOut(hdc, 30, 100, "MAXIMUM: ", 9); xres = maxreal (xarray, 9); gcvt(*xres, 5, buf); TextOut(hdc, 220, 100, (LPCTSTR)buf , 5); EndPaint (hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam) } return 0; } LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; }
The program outputs two lines to the application window work area. One of them contains all elements of the array, and the other shown below contains the maximum element. Displaying is done with the WM_PAINT handler using the TextOut function.
Declare the following variables in the WndProc callback function:
char buf[16]; float xarray[9] = {12.43, 93.54, 23.1, 23.59, 16.09, 10.67, 54.7, 11.49, 98.06}; float *xres; int cnt;
The buf string is used to store the result of conversion of a floating-point number to text, and the xarray floating-point array contains nine elements. Also, xres is a pointer to a floating-point number, and cnt is a counter for displaying the nine elements of the array.
Displaying the lines is implemented with the following code in the WM_PAINT handler (Listing 6.14).
TextOut(hdc, 30, 80, "ARRAY: ", 7); for (cnt = 0; cnt < 9; cnt++) { gcvt(xarray[cnt], 6, buf); TextOut(hdc, 100 + cnt*50, 80, buf, 5); } TextOut(hdc, 30, 100, "MAXIMUM: ", 9); xres = maxreal(xarray, 9); gcvt(*xres, 5, buf); TextOut(hdc, 220, 100, (LPCTSTR)buf, 5);
The first line of this code is a call to the TextOut function whose parameters are the device context ( hdc ), the horizontal and vertical coordinates in the client rectangle, the pointer to the text string, and the number of items to output.
To display numbers, you must convert the array to a sequence of strings. Conversion of a floating-point number to a string can be done with the gcvt function whose parameters are a floating-point number, a number of characters to output, and the pointer to a buffer for storing the result of conversion. The for statement is used to display all nine elements of the array.
Displaying the maximum element is done in a similar manner. However, you should first call the maxreal function:
xres = maxreal(xarray, 9);
Since xres is a pointer, the correct call to the gcvt function is the following:
gcvt(*xres, 5, buf);
After this statement is executed, the maximum element is put to the buf variable and displayed with the TextOut function.
Finally, be sure to add a declaration of the assembly function to the source code of your program:
extern "C" float* _stdcall maxreal(float *px, int sx);
The window of the application is shown in Fig. 6.6.
Structures and unions are very important data types in C++ .NET. Structures are arrays or vectors consisting of closely related elements, but, unlike arrays or vectors, they can contain elements of different types.
Unions are similar to structures. Using unions, it is possible to store elements of different types in a continuous fragment of the system memory. Structures and unions are used in most spreadsheet and database applications. Using assembly functions when working with such data types makes it possible to increase the speed of applications and to decrease the load on the operating system as a whole. The remainder of this chapter will demonstrate how to work with structures and unions by using functions from separately compiled assembly modules. We will begin with structures.
A structure is declared with the struct keyword. The next example uses the intstr structure that has three integer fields:
struct intstr { int i1; int i2; int i3; };
When declaring a structure, no variable is created; only the data types contained in this structure are defined. To declare a variable of the intstr type (we will call it ist ), write the following line:
struct intstr ist
The ist variable is an example of the intstr structure. To manipulate with the elements of a structure, it is convenient to use a pointer to the structure. Consider an example, in which the i2 element of the intstr structure will be inverted in an assembly procedure. The source code of the procedure is shown in Listing 6.15.
;-------------------- negint.asm -------------------- .686 .model flat public _negint@4 .code _negint@4 proc push EBP mov EBP, ESP mov EAX, DWORD PTR [EBP+8] neg EAX pop EBP ret 4 _negint@4 endp end
The procedure takes one parameter, an integer, and returns its inverted value in the EAX register. The source text of a console application that uses this procedure is shown in Listing 6.16.
// This is the main project file for VC++ application project // generated by using an Application Wizard. #include "stdafx.h" #using <mscorlib.dll> extern "C" int _stdcall negint(int i1); using namespace System; int _tmain() { // TODO: Please replace the sample code below with your own. struct intstr { int i1; int i2; int i3; }; struct intstr ist, *pist; pist = &ist; String *s; Console::Write("Enter i2: "); s = Console::ReadLine(); pist->i2 = Convert::ToInt32(s); pist->i2 = negint(pist->i2); s = Convert::ToString(pist->i2); Console::WriteLine("New value of i2= {0}", s); Console::WriteLine("Press any key to exit..."); Console::ReadLine(); return 0;
The intstr structure contains three integer elements: i1 , i2 , and i3 . The lines
struct intstr ist, *pist; pist = &ist;
declare the ist variable and the pist pointer to the structure. The pist pointer is assigned the address of the ist structure instance. To access a structure element, i1 , for example, you can use one of the following expressions:
ist.i1 pist->i1
Inverting the i2 element is done with the following statement:
pist->i2= negint(pist->i2);
When using an external assembly module, you should declare the function it contains as external:
extern "C" int _stdcall negint(int i1);
The window of the application is shown in Fig. 6.7.
Now, we will consider using assembly functions for manipulations with the elements of unions. I will use the union
union test_union { int i; char c; };
that consists of two elements. Declaring a union does not create any variables, so to create an instance of a union variable, use, for example, the following statement:
union test_union tu, *ptu;
The compiler allocates for a union as much memory as the largest element takes. In our case, four bytes are allocated. A union allows you to treat the same sequence of bits in various ways. Consider an example in which an assembly function first processes a character and then an integer. The source code of the function (named uex ) is shown in Listing 6.17.
;------------------------- uex.asm ------------------ .686 .model flat public _uex@8 .code uex.asm- _uex@8 proc push EBP mov EBP, ESP mov EDX, DWORD PTR [EBP+12] ; Load the indicator parameter to EDX. cmp EDX, 0 ; Is the 1st parameter integer? je int_exe ; If yes, invert the number. cmp EDX, 1 ; Is the 1st parameter a character? je char_exe ; If yes, convert it to the upper case. xor EAX, EAX ; The 2nd parameter is out of range. ; Write 0 to EAX 0 and return. jmp ex int_exec: mov EAX, DWORD PTR [EBP+8] ; Put the first parameter to EAX neg EAX ; as integer and invert it. jmp ex char_exec: xor EAX, EAX ; Zeroing EAX mov AL, BYTE PTR [EBP+8] ; Put the character to AL cmp AL, 97 ; and analyze it. jb ex cmp AL, 122 ja ex sub AL, 32 ; If the character is alphabetic, ex: ; convert it to the upper pop EBP ; case. ret 8 _uex@8 endp end
The uex function takes two integers as parameters. The second parameter located at the [EBP+i2] address can be either one or zero. It is an indicator for the type of the first parameter passed via [EBP+8] . If the second parameter is equal to one, the first parameter is an integer. If the second parameter is equal to zero, the first parameter is a character.
Thus, the first parameter is processed differently, depending on its type. If it is an integer, the function returns its inversion; if it is a character, the function converts it to the upper case.
Develop a C++ .NET dialog-based application. Put two edit controls, two static text labels, and one button on the main form. Bind the iEdit integer variable to one edit control and the cEdit character variable to the other. The on-button-clicked handler should output the values of the variables to the edit controls. The C++ source code of the handler with the uex external function declaration is shown in Listing 6.18.
// Union_in_ASM_procDlg.cpp : implementation file #include "stdafx.h" #include "Union_in_ASM_proc.h" #include "Union_in_ASM_procDlg.h" #include ".\union_in_asm_procdlg.h" extern "C" int_stdcall uex(int i1, int id); . . . void CUnion_in_ASM_procDlg::OnBnClickedButton1() { // TODO: Add your control notification handler code here. union test_union { int i; char c; }; union test_union tu, *ptu; ptu = &tu; ptu->i = 56; iEdit = uex(ptu->i, 0); ptu->c = 'r'; cEdit = (char)uex(ptu->c, 1); UpdateData(FALSE); }
The main program uses the test_union union. After the initialization, the elements of the union are assigned the numeric value ˆ’ 56 and the character 'r' , respectively. The uex function processes these elements according to their types. For the numeric value ˆ’ 56 , the following statements are executed:
ptu->i = 56; iEdit = uex(ptu->i, 0);
If the union element is the ˜ r character, the following statements are executed:
ptu->c = 'r'; cEdit = (char)uex(ptu->c, 1);
The rest of the program s code is simple and does not need additional explanation. The window of the application is shown in Fig. 6.8.
This completes the discussion of interfaces of separately compiled assembly modules and Visual C++ .NET programs. All examples in this chapter can be easily modified for use in your work.