Thursday, September 6, 2018

Still Making Plugins and Scripts Work with Art of Illusion

When a reader of Extending Art of Illusion contacted me about the possibility of updating the Art of Illusion scripts to Groovy, I became concerned. Firstly, because he wasn’t able to download the source code from the location specified in the book. Broken links on a webpage are one thing, but broken links in a book can be costly. Secondly, because I haven’t attempted to keep up with all of the changes to Art of Illusion that have taken place since the publication date of the book. There hasn’t been enough demand for the book to justify a revised edition. To address my concerns, I went back through the examples I included in the book—rebuilding with latest Java compiler and using Art of Illusion 3.03. With joy I’m able to say that they all still work. In spite of the book being seven years old, it remains useful anyone who is looking for instruction on how to create plugins for Art of Illusion.

I did find a couple of things that I would add to the book if I were to revise it. The first is that to use the examples you need to include the Bouy.jar in the list of linked libraries. I’m not sure if this is a result of a change or of an oversight on my part. The second is that I would include scripts written in Groovy in the book. I’ve somewhat corrected this by adding Groovy scripts to the .zip file that contains the examples. At this point, Art of Illusion still support BeanShell even as it is moving toward Groovy, so it is really up to the person writing the script to decide which one they want to use. There are a lot more similarities between BeanShell and Groovy than there are differences, so the only change I made to the axes script was to change the file extension from .bsh to .groovy

There were more differences with the Room script. At line 18 I removed the “private” specifier for the class. At line 105 I deleted “LayoutWindow layout”. At line 106 I removed the “final String” specifier because Groovy couldn’t see windowTitle as a global variable. And at line 116 I deleted “int roomCount = 0”. At 128 I rewrote the declaration as:

    float[] roomSmoothness;
    roomSmoothness = [0.0f, 0.0f, 0.0f, 0.0f]; 

Overall, it wasn’t difficult to make the changes, but it did take some effort and a little research to figure out the differences between the two scripting languages.


Monday, September 3, 2018

Using C++ Resource Files with Eclipse

Using a resource file in Eclipse is possible. While writing a blog about how to develop Windows programs using Eclipse and C++ I ran into a problem that I needed a tool for, namely to quote C++ code in the blog. So, I wrote a little program in C++ using the techniques I was discussing, but later I decided it would be an ideal project to rewrite using a resource file (.rc). There are a few things you have to do to get it to work.

First, Eclipse doesn’t provide the slick resource file editor that you find in Visual C++. This means that to edit the .rc file you will need to use a text editor. This isn’t particularly hard to do, but I did find myself questioning whether I was gaining anything by using the resource file over just making the calls I needed in the C++ code. One thing that I can say that I don’t like is that you must use #define in the header file rather than defining constants with static const or as an enum.

Second, the .rc file has to be compiled using a special compiler and then you have to link to the .o that is generated. In order to prevent yourself from having to remember to do that each time you modify the .rc file, you must create a pre-build step. Even then, changes to the .rc file won’t compile and link unless you’ve mode changes to the C++ source code.

As with the other blog post, you can download the Eclipse project for this program. It isn’t cleaned up, but it should provide you with an example of how to compile a resource file using Eclipse and MinGW. The command to run the winres must be supplied to Eclipse via the settings window of the C/C++ Build properties.

This command is:

c:\mingw\bin\windres.exe --include-dir="${ProjDirPath}" --input="${ProjDirPath}/res.rc" --output="${ProjDirPath}/res.o" --define RESLOC="${ProjDirPath}/resources.h"

Notice that I provide the project directory path for both the input and the output. For some reason, MinGW doesn’t seem to recognize –include-dir, but I populate it anyway. I also define a macro RESLOC. You will see this used within the .rc file where we include resources.h, since winres can’t find the local directory on its path.

#define Q(x) #x
#define QUOTE(x) Q(x)
#include QUOTE(RESLOC)

#define APSTUDIO_HIDDEN_SYMBOLS
#include "windows.h"
#undef APSTUDIO_HIDDEN_SYMBOLS

The rest is pretty much the same as you would expect for a resource file and in many cases you can copy on that has been created with Visual Studio, if you don’t want to edit it by hand.

The Eclipse Project is located at: http://www.timothyfish.com/Examples/Windows4Eclipse/BlogCodeFormat.zip

Sunday, September 2, 2018

Developing Windows Programs using Eclipse C++

Eclipse CDT (C/C++ Development Toolkit) is perfectly fine for developing Windows applications in C++. But you might be asking, why would I want to do that when Visual Studio is so readily available?

I started down this path because I spend most of my time with the Eclipse IDE (Integrated Development Environment) running on my machine and often with multiple workspaces on both Windows and Linux, but the C++ code I develop isn’t intended to run on Windows. But ever so often I will develop a simple tool with a Graphical User Interface (GUI). Since I’m familiar with Eclipse, I would prefer not to have to switch to another IDE just for a simple tool. But one thing you notice when you look at an IDE like Visual Studio is that it generates a significant amount of code for you and it makes use of various libraries, but how much of this do you really need?

I assume you’ve built with the Eclipse CDT. If not, follow this guide first:
https://www.codeproject.com/Articles/14222/C-Development-using-eclipse-IDE-Starters-guide

Also, if you want to skip ahead, the source projects are located in the following locations:

http://www.timothyfish.com/Examples/Windows4Eclipse/HelloMsg.zip

http://www.timothyfish.com/Examples/Windows4Eclipse/ExampleWin.zip

I started down this path because I spend most of my time with the Eclipse IDE (Integrated Development Environment) running on my machine and often with multiple workspaces on both Windows and Linux, but the C++ code I develop isn’t intended to run on Windows. But ever so often I will develop a simple tool with a Graphical User Interface (GUI). Since I’m familiar with Eclipse, I would prefer not to have to switch to another IDE just for a simple tool. But one thing you notice when you look at an IDE like Visual Studio is that it generates a significant amount of code for you and it makes use of various libraries, but how much of this do you really need?

One of my favorite books when I was younger was Programming Windows 3.1 by Charles Petzold. He’s updated it as the Windows OS has moved on, but that book is still in my library. The thing that surprised me when I first read it was that just to display a simple window on the screen required two pages of C code. In the fifth edition of Programming Windows he has that down to a much simpler program:

#include <windows.h>
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{

    MessageBox (NULL, TEXT ("Hello, Windows 98!"), 
                TEXT ("HelloMsg"), 0);
    return 0;
}

While that kind of program will let you test that you are able to compile and run a program from your IDE, it only hints at the more complicated nature of the Windows API (Application Programming Interface). For me, I not only wanted to know that I could write a program in Eclipse that would display a message box, but that I could use Eclipse for more useful programs as well. But I also wanted it to follow a more C++ oriented programming style.

But before we move farther, let’s compile and run a version of his C program from Eclipse. On my machine I have installed the following:

In the Eclipse C++ Perspective, create a new C++ Project.

Select C++ Managed Build and click Next.

Select Empty Project and give it a name. Click Next.

At this next window we could set some specifics, but we’ll ignore it for now. Click Finish.

Right click on the project name and create a new file.

Enter the following code and click the build button:


#include <windows.h>
int WINAPI WinMain (HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    PSTR szCmdLine,
                    int iCmdShow)
{
    MessageBox (NULL, TEXT ("Hello, World!"),
                TEXT ("HelloMsg"), 0);
    return 0;
}

If everything is installed correctly then this program should build, and you should be able to run it by clicking the Run Button. This will display something like the following:

But as I said before, this isn’t particularly useful other than to verify that your development environment is setup properly. What we would like to do is have a program that displays a window in which we specify what controls are used and where they are located on the page. We may even want to open another window.

Create another new project following the same steps as before. Call the project “ExampleWin” and add a file called “start.cpp” to the project.

Going back to Charles Petzold and Programming Windows, because he was writing in C, his WinMain consisted of a page of code that created a window and then went into a message loop. He also had one callback function WndProc that handled the WM_PAINT message by drawing text on the screen. It also handled WM_DESTROY by posting 0 to the message queue so that the loop in WinMain would terminate. We don’t see the code for it, but the MessageBox function is doing all of this in its code. So, there’s really no reason why we have to have such a long WinMain and since we’re working with C++ instead of C, we can create a class that handles it rather than just a function. We still have to put all the details somewhere, but we can keep WinMain simple. This is very useful if you are using a test environment that needs to replace WinMain with its own. It’s much easier to tell it how to replace one call than it is to tell it how to replace a page of code.

The code for start.cpp should look like this:

// *
// * StartApp.h
// *
// *  Created on: Sep 1, 2018
// *      Author: Timothy Fish
// *      Website: http://www.timothyfish.com
// *

#ifndef STARTAPP_H_
#define STARTAPP_H_
#include <windows.h>

class StartApp {
public:
    static StartApp& getInstance();
    virtual ~StartApp();

    int begin(HINSTANCE hInstance,
              HINSTANCE hPrevInstance,
              PSTR    lpCmdLine,
              int       nCmdShow);

    HINSTANCE getHInstance(){ return hInst; }
    void setHInstance(HINSTANCE h){ hInst = h; }
private:
    static StartApp* instance;
    bool running;

    HINSTANCE hInst;
    const char* className = "StartApp";
    const char* appName   = "Example Windows App";
    StartApp();
};

#endif /* STARTAPP_H_ */

Click the build button and you should expect to see the following error:

..\start.cpp:2:10: fatal error: StartApp.h: No such file or directory
 #include <StartApp.h>
          ^~~~~~~~~~~~

This is telling us that it doesn’t know where StartApp.h is. You could put it into quotes, but since some coding standards tell us not to do that sort of thing, let’s add the source directory to the include directories. To do that, Right click on the ExampleWin project name and select properties. Select C/C++ General and go to the Includes tab. Add ${ProjDirPath} to the GNU C++ Language all configurations. (You may add it to the other languages if you like, but we’re only using C++ here. Apply and close.

Now when you build you should get the following errors:

start.o: In function `WinMain@16':
C:\Users\timot\Eclipse4Windows\ExampleWin\Debug/../start.cpp:9: undefined reference to `StartApp::getInstance()'
C:\Users\timot\Eclipse4Windows\ExampleWin\Debug/../start.cpp:9: undefined reference to `StartApp::begin(HINSTANCE__*, HINSTANCE__*, char*, int)'
collect2.exe: error: ld returned 1 exit status

These are both linker errors. They exist because we haven’t added the functions to the StartApp.cpp file yet. To do this, create another new file StartApp.cpp and add the following code:

// *
// * StartApp.cpp
// *
// *  Created on: Sep 1, 2018
// *      Author: Timothy Fish
// *      Website: http://www.timothyfish.com
// *

#include <StartApp.h>

#include <iostream>
#include <windows.h>
#include <windowsx.h>

StartApp* StartApp::instance = 0;

StartApp& StartApp::getInstance(){

    if(instance == 0){
        instance = new StartApp();
    }

    return *instance;
}

StartApp::StartApp(): running(0), hInst(0) {
}

StartApp::~StartApp() {
}

int StartApp::begin(HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    PSTR    lpCmdLine,
                    int       nCmdShow)
{
    return 0;
}

Another thing that I would recommend doing at this point is add static linking of gcc by adding the following to you link command: -static -static-libgcc -static-libgcc. This will save you having to find the dll to run outside of Eclipse.

At this point, you should be able to build and run without errors. With multiple projects in Eclipse you may need to specify which one to run by selecting the project and then selecting Run As|Local C/C++ Application.

If you run it at this point, it will appear that it did nothing, since we haven’t yet created a window and we don’t have a message loop. If you like, you can run it using the debugger (click the bug button) and step through the code to verify that it is actually running something. Otherwise the code will run and exit without showing you anything. To correct this, we need to modify the begin function.

int StartApp::begin(HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    PSTR    lpCmdLine,
                    int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    if(running) return 0; // only run one copy

    MyRegisterClass(hInstance);

    if (!InitInstance (hInstance, nCmdShow))
    {
        std::cout<<"Start - GetLastError - InitInstance: "<<GetLastError()<<std::endl;
        return FALSE;
    }

    MSG msg;

    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

We are introducing some new functions here, so you will also need to modify StartApp.h to add the following as private member functions to the class:

    ATOM MyRegisterClass(HINSTANCE hInstance);
    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow);
    static LRESULT CALLBACK WndProc(HWND hwnd, 
                                     UINT message, 
                                     WPARAM wParam, 
                                     LPARAM lParam);

Building at this point should result in the following errors:

StartApp.o: In function `_::ZN8StartApp5beginEP11HINSTANCE(char *, int) static':
C:\Users\timot\Eclipse4Windows\ExampleWin\Debug/../StartApp.cpp:42: undefined reference to `StartApp::MyRegisterClass(HINSTANCE__*)'
C:\Users\timot\Eclipse4Windows\ExampleWin\Debug/../StartApp.cpp:44: undefined reference to `StartApp::InitInstance(HINSTANCE__*, int)'
collect2.exe: error: ld returned 1 exit status

We will implement MyRegisterClass and InitInstance in a moment, but first let’s look at begin.

    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

These like trick the compiler into thinking that these unused variables are actually used, so we don’t see warnings.

if(running) return 0; // only run one copy

This line prevents begin from being called twice.

    MSG msg;

    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;

This code is the standard message loop for a Windows program. We retrieve the message from the queue and pass it back to Windows. We keep doing this until we GetMessage returns 0. If we need to do something special within this loop we can, but we have no need to at this point.

Add the following code to StartApp.cpp:

ATOM StartApp::MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX windowClass; //window class

    windowClass.cbSize              = sizeof(WNDCLASSEX);
    windowClass.style               = CS_HREDRAW | CS_VREDRAW;
    windowClass.lpfnWndProc         = WndProc;
    windowClass.cbClsExtra          = 0;
    windowClass.cbWndExtra          = 0;
    windowClass.hInstance           = hInstance;
    windowClass.hIcon               = LoadIcon(0, IDI_APPLICATION);
    windowClass.hCursor             = LoadCursor(0, IDC_ARROW);
    windowClass.hbrBackground       = GetSysColorBrush(COLOR_WINDOW);
    windowClass.lpszMenuName        = 0;
    windowClass.lpszClassName       = className;
    windowClass.hIconSm             = LoadIcon(0, IDI_WINLOGO);
    return RegisterClassEx(&windowClass);
}

BOOL StartApp::InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance; // Store instance handle in our global variable
    RECT    windowRect;

    int width = 300;
    int height = 200;

    windowRect.left =(long)0;       //set left value to 0
    windowRect.right =(long)width;  //set right value to requested width
    windowRect.top =(long)0;        //set top value to 0
    windowRect.bottom =(long)height;//set bottom value to requested height

    AdjustWindowRectEx(&windowRect, WS_OVERLAPPEDWINDOW, FALSE, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);

    HWND hwnd = CreateWindowEx(0, className,  //class name
            appName,       //app name
            WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
            400, 300,                         //x and y coords
            windowRect.right - windowRect.left,
            windowRect.bottom - windowRect.top,//width, height
            0,                 //handle to parent
            0,                 //handle to menu
            hInstance,    //application instance
            0);                //no xtra params

    if (!hwnd)
    {
        std::cout<<"Start - GetLastError - CreateWindowEx: "<<GetLastError()<<std::endl;
        return FALSE;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    return TRUE;
}

LRESULT CALLBACK StartApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HINSTANCE hInstance;
    UNREFERENCED_PARAMETER(hInstance);

    switch (message)
    {
    case WM_CREATE:
        hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            UNREFERENCED_PARAMETER(hdc);
            // TODO: Add any drawing code that uses hdc here...

            EndPaint(hwnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}



Now, when you Build and Run you should see a small window appear on the screen.

While this is a small success, to be useful we need it to either provide information to the user or to request information from the user. As a proof of concept, lets display an edit box that accepts a character and displays the ASCII representation of it when the user clicks the Submit button. We need to add an edit box, a static, and a button to the window. We do this by modifying the InitInstance function. Add the following code to InitInstance just before ShowWindow:

    CreateWindow(TEXT("edit"), TEXT("A"),
                 WS_VISIBLE | WS_CHILD | SS_CENTER|WS_BORDER,
                 width/2-10, 20, 20, 25,
                 hwnd, (HMENU) IDM_CHAR, NULL, NULL);

    CreateWindow(TEXT("static"), TEXT("65"),
                 WS_VISIBLE | WS_CHILD | SS_CENTER,
                 width/2-10, 45, 20, 25,
                 hwnd, (HMENU) IDM_INT, NULL, NULL);

    CreateWindow(TEXT("button"), TEXT("Submit"),
            WS_VISIBLE | WS_CHILD ,
            200, 140, 80, 25,
            hwnd, (HMENU) IDM_SUBMIT, NULL, NULL);

Add the following to the private section of the StartApp class:

    enum ControlIds{
        IDM_CHAR = 100,
        IDM_INT,
        IDM_SUBMIT
    };

When you run this code you should see something like the following:

But while it looks like we would expect, nothing happens when we click the Submit button. To correct that, we need to add some code that handles the button clicks. Add the following code to the switch statement in WndProc:

    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        switch (wmId)
        {
        case IDM_SUBMIT:
        {
            char buf[5];

            GetDlgItemText( hwnd, IDM_CHAR, buf,2 );

            int asciiVal = int(buf[0]);
            itoa(asciiVal, buf, 10);

            SetDlgItemText( hwnd, IDM_INT, buf );
        }
            break;
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
        }
    }
        break;

Each time a button is clicked the WM_COMMAND message is sent with an Id in LOWORD(wParam). In the internal switch we check to see if that Id is IDM_SUBMIT, which is the id we gave the Submit button when we created it. When it is, we get the text from the Edit Box, which has Id IDM_CHAR, place it in buf, convert the first character to an int, then we convert that int to ASCII and place the value in buf. We set the text in the static with Id IDM_INT to the value in buf.

If you are able to do this much within the Eclipse IDE, you can do the rest by researching the specifics of the Windows API. You can download the Eclipse projects, which include the source code in this example plus some more code that implements a menu that calls an About Dialog that is implemented as a separate class.

You will notice that some of what we are doing here could be done using a resource file. I stayed away from resource files in this example because having additional files makes it harder to follow where things are coming from. To use a resource file you will need to call windres on the .rc file to generate a .o while that you can link with your program.

The source projects are located in the following locations:

http://www.timothyfish.com/Examples/Windows4Eclipse/HelloMsg.zip

http://www.timothyfish.com/Examples/Windows4Eclipse/ExampleWin.zip