Using the Windows API from within Axapta

Axapta

It seems that calling Windows API functions from Axapta is viewed as some sort of mysterious magic by most Axapta developers. Unfortunately, there are times when you need to do something that requires tight integration with functionality that exists outside of the system. While there are other methods of interfacing between bits of code, including COM, XML-RPC, or even .NET, using the Windows API can sometimes be very efficient, and powerful.

For beginner WinAPI coders who are also Axapta programmers, with at least some programming experience in C or C++, I felt it might be better to answer a question from my colleagues about calling WinAPI calls from Axapta, outside of those already wrapped by the WinAPI:: or WinGDI:: classes.

Axapta provides basic functionality to open a handle to a library (DLL file), and a handle to a function within that library. The functionality is handled within two classes, DLL:: and DLLFunction:: respectively, and allows you to call practically any piece of code within any dynamic link library.

The most common use for this is naturally to make calls into the Windows API, but there's no reason why you can't provide your own functions within a DLL and call them from Axapta, but be aware that the DLL file must be installed on either the client or server, depending on where the code will execute.

The DLL:: class opens a DLL file and acts as a handle for DLLFunction:: to use when defining handles for calls. If the DLL isn't already opened by Axapta internally (as would be the case with the standard Windows API DLL files), it will also silently handle the opening, initialisation, and closure of the DLL in conjunction with the garbage collector.

The DLLFunction:: class defines a handle to a function's symbol within the DLL, and defines the return value and the parameters of the function. This is important to define correctly, as defined by the C/C++ headers; otherwise you will head straight into problems with Axapta crashing, either during the call, or later on. Don't worry, this is easily determined, and I'll explain how this works. If you don't have the header files (*.h) with a windows compiler, you can look at Google Code Search.

Before going into detail with how this works, you might need to know what's going on behind the scenes, especially how functions call other functions from the computer's perspective. Since that's not really in scope here, I'll very quickly describe only the elements of this you need to know to make your calls work from Axapta. If you already know this, you can skip past it.

When you call a function, its parameters get pushed onto the stack, which is a FILO data model. The function being called then takes these items off the stack and can use them as variables. The calling function knows precisely how many items it needs to put onto the stack, and the function itself knows how many items it needs to take off the stack.

As you know, once a called function has done its work, it can return a value. The process works the same way in reverse, pushing the return value onto the stack for the caller to pop from the stack.

Sometimes an example helps, so let's consider the following simplistic example in C:

int foo(int bar, char* baz)
{
       printf("Qux: %s", baz);

       return (bar * 2);
}

void quux(void)
{
       int corge = foo(10, "Grault");
}

Consider when the quux() function calls foo(), it will push the value 10 and a pointer to the null-terminated string "Grault" onto the stack, and jump to the location in memory where the code for foo() exists. Subsequently, foo() will pop these two values from the stack to be able to use them. When foo() returns, it will push the value of 20 onto the stack, and quux() will pop that from the stack and use it as the value for corge.

Of course, there's a lot more going on in the stack, in fact there are a few stacks, but we don't need to cover that here.

To be able to call your function, you will need to know which DLL your function is in, and the real name of the function as exported by the DLL's symbols. I mention this because it seems obvious, but you should remember that Microsoft generally provide two versions of functions if they use strings, for ANSI and wide-characters, normally with an "A" or "W" suffix respectively. The headers for these functions often hide this fact, as does MSDN. Opt for the ANSI version - Axapta will convert strings based on the locale.

To cover the subject quickly, let's build a small example form that uses the Windows API to draw the appropriate icon for a specified file within a window pane.

Build a form with a Window control. In this example, I have named the window Icon. The window control needs have the width and height both set to 32 pixels (icon size), and the AutoDeclaration property enabled.

The form needs some code. Add some variables we will need to the class declaration of the form:

public class FormRun extends ObjectRun
{
    // DLL file handles
    DLL shellDLL; // shell32.dll
    DLL userDLL; // user32.dll

    // Function call handles
    DLLFunction extractAssociatedIconFunc;
    DLLFunction destroyIconFunc;
    DLLFunction drawIconFunc;

    // Our icon handle, taken from windows
    int hIcon;

    // These are from the windows headers, used later in the code!
    #define.DI_MASK(0x01)
    #define.DI_IMAGE(0x02)
    #define.DI_COMPAT(0x04)
}

We need to initialise many of these variables first. Using the init() function on the form, we can initialise the DLL handles, and the functions. The DLL handles only need the name of the DLL file, obviously, but the DLL function handles need to be configured with the primitive types that will be appearing on the stack. Even parameters or the return value are not used, you must specify them, or the stack will be out of synchronisation.

To help you define your primitive types, Axapta has a kernel enumerator called ExtTypes, which contains all the basic types you will need. You need to then take what's listed in the header file and determine what primitive type it is. For example, windows defines many functions which return a value of BOOL, however this is simply a type definition in C for an integer. It's your responsibility to match these values up logically by working through the definitions. Don't worry; I've done the hard work for you in this example!

Override your form's init() function with the following code:

public void init()
{
    ;

    // Initialise the form first
    super();

    // Set up our call for ExtractAssociatedIcon()
    extractAssociatedIconFunc =
        new DLLFunction(shellDLL, "ExtractAssociatedIconA");
    extractAssociatedIconFunc.returns(ExtTypes::DWord); // HICON
    extractAssociatedIconFunc.arg(ExtTypes::DWord,      // HINSTANCE hInst
                                  ExtTypes::String,     // LPTSTR lpIconPath
                                  ExtTypes::Pointer);   // LPWORD lpiIcon

    // We also need a function to destroy the icon handle once we've used it
    destroyIconFunc = new DLLFunction(userDLL, "DestroyIcon");
    destroyIconFunc.returns(ExtTypes::DWord);           // BOOL
    destroyIconFunc.arg(ExtTypes::DWord);               // HICON hIcon

    // This is our call for drawing the icon onto a window
    drawIconFunc = new DLLFunction(userDLL, "DrawIconEx");
    drawIconFunc.returns(ExtTypes::DWord);  // BOOL
    drawIconFunc.arg(ExtTypes::DWord,       // HDC hDC
                     ExtTypes::DWord,       // int xLeft
                     ExtTypes::DWord,       // int yTop
                     ExtTypes::DWord,       // HICON hIcon
                     ExtTypes::DWord,       // int cxWidth
                     ExtTypes::DWord,       // int cyWidth
                     ExtTypes::DWord,       // uint istepIfAniCur
                     ExtTypes::DWord,       // HBRUSH hbrFlickerFreeDraw
                     ExtTypes::DWord);      // UINT diFlags
}

When the form runs, we want the icon to be updated with an icon from a file. To do this, we will override the run method on the form. After the form has started normally, we will make a call to ExtractAssociatedIcon() to find our hIcon (icon handle) value. We'll save this value for later use. Once this is done, we'll need to draw the icon for the first time.

Axapta provides one additional bit of help here for complex structures containing primitive types, or any other place where you need to pass a pointer on the stack. The kernel class Binary:: lets you allocate memory in the system, and have it freed by the normal Axapta garbage collection routines. This of this as your answer to malloc(), and also your answer to creating and reading C-style structures.

Override your form's run() method with the following, and feel free to change the file to any file on your system:

public void run()
{
    Binary binary;
    ;

    // Run the form normally
    super();

    /* We need to build a binary item for two bytes (a short), since windows
     * has an "in/out" pointer for the icon information.
     */

    binary = new Binary(2); // 2 bytes = short

    // Call the ExtractAssociatedIcon() function
    hIcon = extractAssociatedIconFunc.call(NULL,
                                           "C:\\WINDOWS\\EXPLORER.EXE",
                                           binary);

    // Make sure we got an icon (we should, if the file exists)
    if (!hIcon) {
        throw error("Unable to get hIcon");
    }

    // Do the initial drawing of the icon
    this.drawIcon();
}

Since the Windows API will allocate stuff on its side, we need to clean up some memory when the form is closed. To do this, we call DestroyIcon() with the icon's handle. Override your form's close() method with the following snippet:

public void close()
{
    ;

    // If we have an icon handle, we need to clean up
    if (hIcon) {
        destroyIconFunc.call(hIcon);
    }

    // Close the form
    super();
}

Now the fun part: We need to grab the device context of the window, and use it to draw the icon on to the form. We will use the function DrawIconEx(). Note that we need to lock the device context of the window before passing it outside for the library to chew on. Add the following method to your form:

public void drawIcon()
{
    ;

    // Lock the device context of the window so we can use it
    Icon.lockDC();

    // Paint the icon (we'll use the window size to determine the icon size)
    drawIconFunc.call(Icon.hDC(), 0, 0, hIcon,
                      Icon.widthValue(),
                      Icon.heightValue(),
                      0, NULL,
                      (#DI_IMAGE | #DI_MASK | #DI_COMPAT));

    // Unlock the device context of the window
    Icon.unlockDC();
}

Finally, you will also need to override the function paint() on the Window control. The following code will be called by Axapta whenever Windows asks Axapta to paint the window. This allows you to redraw the icon when the form is covered or uncovered by another window in the foreground, for example. Without this, your icon will disappear if anything other than the mouse moves over it! Add this code to the Icon control:

public int paint()
{
    int ret;
    ;

    // Paint the window first
    ret = super();

    // Draw the icon
    element.drawIcon();

    return ret;
}

Compile and run the form, and you should have the correct icon for the file you specified. If you were too lazy to build it as we went, you can download my example and import it into your own Axapta system.

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

priyan on :

The author does not allow comments to this entry

Add Comment

E-Mail addresses will not be displayed and will only be used for E-Mail notifications.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

Textile-formatting allowed