Chapter 6: Developing and Using Assembly Subroutines

image from book  Download CD Content

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.

Listing 6.1: A function that multiplies an integer by 5
image from book
 .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 
image from book
 

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.

Listing 6.2: A C++ program that uses the mul5 procedure
image from book
 // 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;  } 
image from book
 

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.

image from book
Fig. 6.1: Window of an application that multiplies an integer by a constant

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.

Listing 6.3: An assembly function that passes a character string to the main program
image from book
 ;---------------- 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 
image from book
 

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).

Listing 6.4: A console application that calls the strshow function
image from book
 // 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;  } 
image from book
 

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.

image from book
Fig. 6.2: Window of an application that displays a string received from an assembly function

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.

Listing 6.5: An assembly function that copies a string to the main application
image from book
 ;-----------------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 
image from book
 

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).

Listing 6.6: A console application that displays a copy of a string
image from book
 // 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;  } 
image from book
 

The window of the application is shown in Fig. 6.3.

image from book
Fig. 6.3: Window of an application that displays a copy of a string

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.

Listing 6.7: A function that returns a substring to the C++ program
image from book
 ;------------------- 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 
image from book
 

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).

Listing 6.8: The ID_PartStr menu item selection handler
image from book
 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; 
image from book
 

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.

Listing 6.9: A C++ program that displays a substring
image from book
 #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;  } 
image from book
 

The window of this application is shown in Fig. 6.4.

image from book
Fig. 6.4: Window of an application that displays a part of a string from a C++ .NET program

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.

Listing 6.10: A function that searches for a character in a string
image from book
 ; -------------------- 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 
image from book
 

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).

Listing 6.11: An on-button-clicked event handler in a C++ application
image from book
 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);  } 
image from book
 

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.

image from book
Fig. 6.5: Window of an application that searches for a character in a string

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).

Listing 6.12: A function that searches for the maximum element in a floating-point array
image from book
 ;-------------------------------- 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 
image from book
 

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.

Listing 6.13: A C++ program that displays the maximum value
image from book
 #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;  } 
image from book
 

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).

Listing 6.14: Displaying the lines
image from book
 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); 
image from book
 

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.

image from book
Fig. 6.6: Window of an application that looks for the maximum element in a floating-point array

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.

Listing 6.15: Inverting an integer in an assembly procedure
image from book
 ;-------------------- 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 
image from book
 

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.

Listing 6.16: A console application that demonstrates processing the elements of a structure in an assembly procedure
image from book
 // 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; 
image from book
 

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.

image from book
Fig. 6.7: Window of an application that demonstrates the work with an element of a structure

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.

Listing 6.17: A function processing the elements of a union
image from book
 ;------------------------- 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 
image from book
 

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.

Listing 6.18: The main fragments of a C++ program that uses the uex function
image from book
 // 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);  } 
image from book
 

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.

image from book
Fig. 6.8: Window of an application that demonstrates manipulations with the elements of a union

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.



Visual C++ Optimization with Assembly Code
Visual C++ Optimization with Assembly Code
ISBN: 193176932X
EAN: 2147483647
Year: 2003
Pages: 50
Authors: Yury Magda

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net