FS2004 GDI+ draw on ELEMENT_STATIC_IMAGE

#1
Hello,

I stumbled over a little problem with my first trial standing on my own feet with GDI+ in FS9 - the gauge stays empty - no ERROR, no fault, it simply doesn't draw anything (although I added some draw code)...

Some general information:
I use Dai's latest gauge.h from sd2gau29 (fsxgauges_sp2.h) with some modifications of mine. The IDE and compiler setup (MS VS 2008) worked with other FS projects (normal gauges, modules w/ and w/o FSUIPC useage) so far. I also have received and compiled the GDI+ template from Bill (thank you very much again!) and it works in FS too. Now I have started my own GDI project with inspiration from Bill's - and I can not see any obvious reason why it should not work, thats why I have to ask here if you could point out to me whats fishy.

Setup is easy:
  • One "master" .cpp #including the subgauges in classic multigauge cluster style with one .h (GDIPlusTest.cpp and GDIPlusTest.h).
  • There is only one subgauge .cpp for my "Hello GDI+ World" :D gauge (GDIPlusTest.Hello.cpp).
  • Resources are 2 bitmaps (24bpp, 100x100) one for the Background STATIC_IMAGE (all 0x010101), one for the canvas - the STATIC on which I try to draw on (pure black) and of course the resource file script.
  • In the build targets (both debug & release) under Linker Options there is gdiplus.lib added.

It all compiles well (beside the 3 warnings not to use the deprecated fopen and fprintf but rather the mem safe versions _s...), loads in FS and gives me the output in my hand made debug file dump - the only thing it doesn't do is show anything...

Here you can see from my debug dump that the loading, initialization and closing works (compare to the code blocks from where these prompts are written). The pelement data seems to be correct too. As you can see from this dump: PANEL_SERVICE_DISCONNECT is never triggered, thats why I moved the close_gdiplus() method call to PANEL_SERVICE_PRE_KILL
Code:
####################
New Session
####################
Gauge: Hello
2010-08-01T20:20:59.718Z  Service ID: PANEL_SERVICE_CONNECT_TO_WINDOW
2010-08-01T20:20:59.718Z  Gauge: Hello opened, gdiplusUsers 1
2010-08-01T20:20:59.718Z  Service ID: PANEL_SERVICE_PRE_QUERY
2010-08-01T20:20:59.718Z  Service ID: PANEL_SERVICE_POST_QUERY
2010-08-01T20:21:00.093Z  Service ID: PANEL_SERVICE_PRE_INSTALL
2010-08-01T20:21:00.093Z  Service ID: PANEL_SERVICE_POST_INSTALL
2010-08-01T20:21:00.109Z  Service ID: PANEL_SERVICE_PRE_INITIALIZE
2010-08-01T20:21:00.109Z  Service ID: PANEL_SERVICE_POST_INITIALIZE
2010-08-01T20:21:00.109Z  Service ID: PANEL_SERVICE_PANEL_OPEN
2010-08-01T20:21:00.687Z  Service ID: PANEL_SERVICE_PRE_INITIALIZE
2010-08-01T20:21:00.687Z  Service ID: PANEL_SERVICE_POST_INITIALIZE
2010-08-01T20:21:00.687Z  Service ID: PANEL_SERVICE_PRE_UPDATE
2010-08-01T20:21:00.687Z  Service ID: PANEL_SERVICE_POST_UPDATE
2010-08-01T20:21:02.468Z  Service ID: PANEL_SERVICE_PRE_GENERATE
2010-08-01T20:21:02.468Z  Service ID: PANEL_SERVICE_POST_GENERATE
2010-08-01T20:21:02.468Z  Service ID: PANEL_SERVICE_PRE_DRAW
2010-08-01T20:21:02.468Z  pelement final dim.x 83, final dim.y 83
2010-08-01T20:21:02.468Z  pelement source dim.x 83, source dim.y 83
2010-08-01T20:21:02.468Z  pelement element_type 0 (ELEMENT_TYPE_STATIC_IMAGE)
2010-08-01T20:21:02.468Z  pelement position.x 166, position.y 0
2010-08-01T20:21:02.468Z  pelement hdc (so 4) 3001449a
2010-08-01T20:21:02.468Z  Service ID: PANEL_SERVICE_POST_DRAW
2010-08-01T20:21:05.828Z  Service ID: PANEL_SERVICE_PRE_DRAW
2010-08-01T20:21:05.828Z  Service ID: PANEL_SERVICE_POST_DRAW
[....]
2010-08-01T20:21:31.250Z  Service ID: PANEL_SERVICE_PRE_UPDATE
2010-08-01T20:21:31.250Z  Service ID: PANEL_SERVICE_POST_UPDATE
2010-08-01T20:21:31.250Z  Service ID: PANEL_SERVICE_PRE_DRAW
2010-08-01T20:21:31.250Z  Service ID: PANEL_SERVICE_POST_DRAW
2010-08-01T20:21:31.281Z  Service ID: PANEL_SERVICE_PRE_UPDATE
2010-08-01T20:21:31.281Z  Service ID: PANEL_SERVICE_POST_UPDATE
2010-08-01T20:21:31.296Z  Service ID: PANEL_SERVICE_PRE_DRAW
2010-08-01T20:21:31.296Z  Service ID: PANEL_SERVICE_POST_DRAW
[....]
2010-08-01T20:21:40.015Z  Service ID: PANEL_SERVICE_PRE_DRAW
2010-08-01T20:21:40.015Z  Service ID: PANEL_SERVICE_POST_DRAW
2010-08-01T20:21:40.031Z  Service ID: PANEL_SERVICE_PRE_UPDATE
2010-08-01T20:21:40.031Z  Service ID: PANEL_SERVICE_POST_UPDATE
2010-08-01T20:21:45.875Z  Service ID: PANEL_SERVICE_PANEL_CLOSE
2010-08-01T20:21:45.875Z  Service ID: PANEL_SERVICE_PRE_KILL
2010-08-01T20:21:45.875Z  Gdiplus Shutdown
2010-08-01T20:21:45.875Z  gdiplusUsers 0
Attached are the contents of the 3 files I think you might want to see.

I hope someone can help me, as it would be nice if I could use GDI+ in my gauges as well (reminds me sooo much of PostScript I once could write by hand...)

Thank you in advance,
Jakob

(GDIPlusTest.cpp)
Code:
// GDIPlusTest.cpp
// Copyright (c) 2010 Jakob Klein, Project Open Sky.  All Rights Reserved.

#include "..\inc\fsxgauges_sp2_jk.h"

#include <stdio.h>
#include <time.h>
#include <stdarg.h>
#include <windows.h>
#include <Gdiplus.h>

using namespace Gdiplus;

#include "GDIPlusTest.h"

/////////////////////////////////////////////////////////////////////////////
// Global GDI+ Variables
/////////////////////////////////////////////////////////////////////////////
GdiplusStartupInput     gdiplusStartupInput;
ULONG_PTR               gdiplusToken;
int                     gdiplusUsers = 0;
bool                    gdiplus_initialized = false;


/////////////////////////////////////////////////////////////////////////////
// Hello GDI+ World
/////////////////////////////////////////////////////////////////////////////
#define		GAUGE_NAME			"Hello"
#define		GAUGEHDR_VAR_NAME		gaugehdr_hello
#define		GAUGE_W				100

#include "GDIPlusTest.Hello.cpp"


/////////////////////////////////////////////////////////////////////////////
// Gauge table entries
/////////////////////////////////////////////////////////////////////////////
GAUGE_TABLE_BEGIN_JK(MODULE_INIT_NONE, MODULE_DEINIT_NONE)
	GAUGE_TABLE_ENTRY(&gaugehdr_hello)
GAUGE_TABLE_END()
(GDIPlusTest.h)
Code:
// GDIPlusTest.h
// Copyright (c) 2010 Jakob Klein, Project Open Sky.  All rights reserved.
//

#define         VERSION_MAJOR         	1
#define         VERSION_MINOR         	0
#define         VERSION_BUILD          	0

// magic to get the preprocessor to do what we want
#define		lita(arg) #arg
#define		xlita(arg) lita(arg)
#define		cat3(w,x,z) w##.##x##.##z##\000
#define		xcat3(w,x,z) cat3(w,x,z)
#define		VERSION_STRING xlita(xcat3(VERSION_MAJOR,VERSION_MINOR,VERSION_BUILD))

#ifndef		VS_VERSION_INFO
#define		VS_VERSION_INFO		0x0001
#endif

////////////////////////////////////
//
// Common Defines
//
#define ULONG_PTR			DWORD
#define GAUGE_CHARSET        	  	DEFAULT_CHARSET
#define GAUGE_FONT_DEFAULT      	"Courier New"
#define GAUGE_WEIGHT_DEFAULT    	FW_NORMAL


/////////////////////////////////////////////////////////////////////////////
//
// Hello Bitmaps
//
#define		BMP_HELLO_SMALL_BACKGROUND	    	0x0100
#define     	BMP_HELLO_SMALL_CANVAS      		0x0104
and the suspected heart of the problem: the Hello subgauge:
(GDIPlusTest.Hello.cpp)
Code:
// GDIPlusTest.Hello.cpp
// Copyright (c) 2010 Jakob Klein, Project Open Sky.  All rights reserved.

// Set up gauge header
char hello_gauge_name[]	        = GAUGE_NAME;
extern PELEMENT_HEADER          hello_list;
extern MOUSERECT                hello_mouse_rect[];


/////////////////////////////////////////////////////////////////////////////
// Gauge Specific Declarations
//
GAUGE_CALLBACK hello_callback;

float   hello_width = 100.0f;
float   hello_height = 100.0f;

FILE*   debug_file;
bool    debug_output_done = false;


/////////////////////////////////////////////////////////////////////////////
// GDI+ Defines
//
Color   black       (0x01, 0x01, 0x01);
Color   transBlack  (0, 0, 0);
Color   white       (0xFF, 0xFF, 0xFF);
Color   red         (0xFF, 0, 0);
Color   green       (0, 0xFF, 0);
Color   blue        (0, 0, 0xFF);
Color   cyan        (0, 0xFF, 0xFF);
Color   magenta     (0xFF, 0, 0xFF);

Pen     whitePen    (white, 1);

SolidBrush  whiteBrush  (white);

/////////////////////////////////////////////////////////////////////////////
// Mouse Rectangle Macro
//
MOUSE_BEGIN( hello_mouse_rect, NULL, 0, 0 )
MOUSE_END


/////////////////////////////////////////////////////////////////////////////
// Gauge Header Macro
//
GAUGE_HEADER_FS700(GAUGE_W, hello_gauge_name, &hello_list, \
						hello_mouse_rect, hello_callback, 0, 0, 0);


/////////////////////////////////////////////////////////////////////////////
// GDI+ Canvas Static
//
MAKE_STATIC (
	hello_canvas,
	BMP_HELLO_SMALL_CANVAS,
	NULL,
	NULL,
	IMAGE_USE_TRANSPARENCY | IMAGE_USE_BRIGHT | IMAGE_CREATE_DIBSECTION,
	0,
	0,0
)


PELEMENT_HEADER hello_canvas_plist[] =
{
    &hello_canvas.header,
    NULL
};


/////////////////////////////////////////////////////////////////////////////
// Background Static
//
MAKE_STATIC (
	hello_background,
	BMP_HELLO_SMALL_BACKGROUND,
	&hello_canvas_plist,
	NULL,
	IMAGE_USE_TRANSPARENCY,
	0,
	0,0
)

PELEMENT_HEADER		hello_list	= &hello_background.header;


/////////////////////////////////////////////////////////////////////////////
// Set Render Quality Routine
//
void set_render_quality(Graphics& graphics)
{
    graphics.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBicubic);
    graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias);
    graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
}

/////////////////////////////////////////////////////////////////////////////
// Custom Debug Printer
//   useage analouge to printf (fprintf(debug_file, format, ...))
//   outputs timestamped text to debug file.
//
void dprintf( const char* format, ... )
{
    if (debug_file)
    {
        va_list     args;
        
        SYSTEMTIME now;
        GetSystemTime(&now);

        fprintf(debug_file, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ  ", now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond, now.wMilliseconds );
        va_start(args, format);
        vfprintf(debug_file, format, args);
        va_end(args);
        fprintf(debug_file, "\n");
    }
}

/////////////////////////////////////////////////////////////////////////////
// GDI Plus Startup Method and User Registration
//
bool start_gdiplus()
{
    gdiplusUsers ++;
    if (!gdiplus_initialized)
    {
        if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) == 0)
        {
            gdiplus_initialized = true;
            dprintf("Gauge: %s opened, gdiplusUsers %d", GAUGE_NAME, gdiplusUsers);
        }
    }
    return gdiplus_initialized;
}

/////////////////////////////////////////////////////////////////////////////
// GDI Plus Closedown Method with user unlisting
bool close_gdiplus()
{
    if (gdiplus_initialized)
        gdiplusUsers--;

    if (gdiplus_initialized && gdiplusUsers <= 0)
    {
        GdiplusShutdown(gdiplusToken);
        gdiplus_initialized = false;
        dprintf("Gdiplus Shutdown");
    }
    
    dprintf("gdiplusUsers %d", gdiplusUsers);

    return !gdiplus_initialized;
}

/////////////////////////////////////////////////////////////////////////////
// Main Gauge Callback Routine
//   Switches service dependent actions.
//
void FSAPI hello_callback( PGAUGEHDR pgauge, SINT32 service_id, UINT32 extra_data )
{
    if (!debug_file)
    {
        debug_file = fopen("G:\\Eigene Dokumente\\Aircraft Modeling\\Programming\\POSKY Gauges\\debug.txt", "a");
        fprintf(debug_file, "####################\nNew Session\n####################\nGauge: %s\n", GAUGE_NAME);
    }

    switch (service_id)
    {
    case PANEL_SERVICE_PRE_DRAW:
        dprintf("Service ID: PANEL_SERVICE_PRE_DRAW");
        if (!gdiplus_initialized)
            start_gdiplus();

        if (gdiplus_initialized)
        {
            PELEMENT_STATIC_IMAGE pelement = (PELEMENT_STATIC_IMAGE)(pgauge->elements_list[0]->next_element[0]);

            if (pelement)
            {
                HDC hdc = pelement->hdc;
                PIXPOINT dim = pelement->image_data.final->dim;

                if (hdc)
                {
                    //Beep(1000,200);
                    Graphics    graphics(hdc);

                    FLOAT64 dx = dim.x / hello_width;
                    FLOAT64 dy = dim.y / hello_height;

                    set_render_quality(graphics);

                    if (!debug_output_done)
                    {
                        dprintf("pelement final dim.x %d, final dim.y %d", dim.x, dim.y);
                        dprintf("pelement source dim.x %d, source dim.y %d", pelement->image_data.source->dim.x, pelement->image_data.source->dim.y);
                        dprintf("pelement element_type %d", pgauge->elements_list[0]->next_element[0]->element_type);
                        dprintf("pelement position.x %d, position.y %d", pelement->position.x, pelement->position.y );
                        dprintf("pelement hdc (so %d) %x", sizeof(hdc), hdc);

                        debug_output_done = true;
                    }

                    graphics.ScaleTransform(dx, dy);
                    
                    // Do Drawing
                    graphics.Clear(black);
                    // Draw white X
                    graphics.DrawLine(&whitePen,0,0,100,100);
                    graphics.DrawLine(&whitePen,0,100,100,0);
                    // Draw Inset Filled Box
                    Point box[5] = { Point(10,10), Point(10,90), Point(90,90), Point(90,10), Point(10,10) };

                    graphics.FillPolygon(&whiteBrush, box, 5);

                }
                // Set region "offscreen" to trigger redraw in FS.
                SET_OFF_SCREEN(pelement);
            }
        }
        break;

    case PANEL_SERVICE_PRE_KILL:
        dprintf("Service ID: PANEL_SERVICE_PRE_KILL");
        close_gdiplus();
        fclose(debug_file);
        debug_file = NULL;
        break;

    case PANEL_SERVICE_CONNECT_TO_WINDOW:
        dprintf("Service ID: PANEL_SERVICE_CONNECT_TO_WINDOW");
        start_gdiplus();
        break;

    case PANEL_SERVICE_DISCONNECT:
        dprintf("Service ID: PANEL_SERVICE_DISCONNECT");
        break;
    }
}


/////////////////////////////////////////////////////////////////////////////
#undef GAUGE_NAME
#undef GAUGEHDR_VAR_NAME
#undef GAUGE_W
 
Last edited:
#2
I've checked the probably most obvious now (haven't thought of it before as I'm completely new to GDI+):

The Status Enum return of my GDI+ calls - and the return isn't 0 (=Gdiplus::Status::Ok) as expected. ScaleTransform and Clear seems to work fine (Status::Ok), while all "graphical" operations return 2 "Status::InvalidParameter" - Still have to research into that what this means - as all function parameters seem kosher to me and the compiler doesn't throw type incompatibility warnings/errors either.

Here are the corresponding lines to one FS frame:
Code:
2010-08-03T07:28:20.984Z  Service ID: PANEL_SERVICE_PRE_DRAW
2010-08-03T07:28:20.984Z  graphics scale transform status 0
2010-08-03T07:28:20.984Z  graphics clear status 0
2010-08-03T07:28:20.984Z  graphics fill rectangle status 2
2010-08-03T07:28:20.984Z  graphics draw line status 2
2010-08-03T07:28:20.984Z  graphics draw line status 2
2010-08-03T07:28:20.984Z  graphics fill polygon status 2
2010-08-03T07:28:20.984Z  Service ID: PANEL_SERVICE_POST_DRAW
Maybe someone with more expierence with GDI+ from FS or Win Dev can shed some light on this?

Thanks,
Jakob
 
#3
Seems the MSDN Documentation on this Error is abundant :D:

On the respective functions' page there is a link to the status enum page which gives the following statement:

InvalidParameter
Indicates that one of the arguments passed to the method was not valid.

Now I have gained a lot of insight...

Jakob
 
#4
InvalidParameter
Indicates that one of the arguments passed to the method was not valid.
I see your debug log show this:

"graphics fill rectangle status 2"

But I can't see any fill rectangle commands in your code. Are you sure you posted the current version ?
 
#5
Oh yes sorry, forgot to amend the source in my posting before,

I changed the draw code bellow "// Do Drawing" in the hello_callback callback function slightly while adding the code to dump the status code (dprintf("%d", (Gdiplus::Status)<draw call>)) to this: (added a rectangle fill larger than my canvas...)

Code:
// Do Drawing
dprintf("graphics clear status %d", graphics.Clear(black));
dprintf("graphics fill rectangle status %d", graphics.FillRectangle(&whiteBrush,-500,-500,500,500));
dprintf("graphics draw line status %d", graphics.DrawLine(&whitePen,0,0,100,100));
dprintf("graphics draw line status %d", graphics.DrawLine(&whitePen,0,100,100,0));

Point box[5] = { Point(10,10), Point(10,90), Point(90,90), Point(90,10), Point(10,10) };
dprintf("graphics fill polygon status %d", graphics.FillPolygon(&whiteBrush, box, 5));
As you will have noticed, the code in the original post is shortened a little - if you run it it would not print all service IDs to the file as posted in my dump - I took out the dprintf statements for all service_id cases in which nothing else but dprintf (e.g. PRE_INSTALL) happens as this won't influence the things we are talking about and makes the source a bit shorter for web posting...

Jakob
 
Last edited:
#6
Maybe this helps you too:

As I have the understanding that GDI+ is a state machine (not to the extent as PS or OpenGL as you have to still supply Pens and so for each call but its probably more a state machine than anything else), I had the idea that maybe up to clear everything works, but then something is corrupted and the draw calls are not really the source of the problem.

So I tested with another changed section bellow "// Do Drawing", which clears, draws, clears, transforms, draws: (tempx,tempy are int, greenPen is analouge to whitePen, just to have a visual clue what line it is *if* I would see anything)


Code:
dprintf("graphics clear status %d", graphics.Clear(black));
dprintf("graphics origin status %d", graphics.GetRenderingOrigin(&tempx, &tempy));
dprintf("graphics origin x %d, y %d", tempx, tempy);
dprintf("graphics draw line status %d", graphics.DrawLine(&whitePen, tempx, tempy, tempx + 100, tempy + 100));
dprintf("graphics clear status %d", graphics.Clear(black));
dprintf("graphics scale transform status %d", graphics.ScaleTransform(dx, dy));
dprintf("graphics fill rectangle status %d", graphics.FillRectangle(&whiteBrush,-500,-500,500,500));
dprintf("graphics draw line status %d", graphics.DrawLine(&greenPen,0,0,100,100));
dprintf("graphics draw line status %d", graphics.DrawLine(&greenPen,0,100,100,0));

Point box[5] = { Point(10,10), Point(10,90), Point(90,90), Point(90,10), Point(10,10) };

dprintf("graphics fill polygon status %d", graphics.FillPolygon(&whiteBrush, box, 5));
Which gives a Status::Ok (0) for all non graphical calls, while all graphicals return Status::InvalidParameter (2), which supports the hypothesis that the problem really has got to do something with the draws and not a "general GDI+ problem" after the first clear or draw:

Code:
2010-08-03T07:49:28.875Z  Service ID: PANEL_SERVICE_PRE_DRAW
2010-08-03T07:49:28.875Z  pelement final dim.x 83, dim.y 83
2010-08-03T07:49:28.875Z  pelement source dim.x 83, dim.y 83
2010-08-03T07:49:28.875Z  pelement element_type 0
2010-08-03T07:49:28.875Z  pelement position.x 166, position.y 0
2010-08-03T07:49:28.875Z  pelement hdc (so 4) 4B013923
2010-08-03T07:49:28.875Z  graphics clear status 0
2010-08-03T07:49:28.875Z  graphics origin status 0
2010-08-03T07:49:28.875Z  graphics origin x 0, y 0
2010-08-03T07:49:28.875Z  graphics draw line status 2
2010-08-03T07:49:28.875Z  graphics clear status 0
2010-08-03T07:49:28.875Z  graphics scale transform status 0
2010-08-03T07:49:28.875Z  graphics fill rectangle status 2
2010-08-03T07:49:28.875Z  graphics draw line status 2
2010-08-03T07:49:28.875Z  graphics draw line status 2
2010-08-03T07:49:28.875Z  graphics fill polygon status 2
2010-08-03T07:49:28.875Z  Service ID: PANEL_SERVICE_POST_DRAW
Jakob

p.s. I have also tried to substitute the unreliable "service_id initialization" of GDI+ from the gauge callback with a module_init and deinit one but that changed nothing - it worked to the same extent - no errors, GDI+ is initialized but still won't draw anything, so i reverted to the state as given in the tread above, so it is easier if we have the same code to talk about :D
 
Last edited:
#7
Have you tried changing this:

graphics.FillRectangle(&whiteBrush,-500,-500,500,500);

into this:

graphics.FillRectangle(&whiteBrush,-500.0f,-500.0f,500.0f,500.0f);
 
#8
Thank you very much, will try it. In the GdiPlusGraphics.h header there are overloaded functions for REAL (=GDI+ typedef float), int and some point/size structs... So I assumed that int will be ok (and the compiler does aswell as he neither complains about incompatible types nor implicit cast...), maybe I should also try explicitly converting my static values to (REAL) (int)s?

I shall report back, as soon as I've tried what Ihave in mind now, thanks for the input once again!

Jakob
 
Last edited:
#9
First I've tried floats, rebranded floats ;) (REAL) int and INT to no avail, so I started my search now one level deeper, IN GDI+ itself.

Looked into the definition of "DrawLine" as one arbitrary example for the draw functions that fail on me. Found that all the overloaded 'DrawLine's are wrappers for the function GdipDrawLine with the signature:

Code:
GpStatus WINGDIPAPI GdipDrawLine(GpGraphics *graphics, GpPen *pen, REAL x1, REAL y1, REAL x2, REAL y2)
So I looked up the definition of that function in the hope to find some clue whats wrong in my gauge. And I think I've found it:

Code:
GpStatus WINGDIPAPI GdipDrawLine(GpGraphics *graphics, GpPen *pen, REAL x1,
    REAL y1, REAL x2, REAL y2)
{
    INT save_state;
    GpPointF pt[2];
    GpStatus retval;

    if(!pen || !graphics)
        return InvalidParameter;

    pt[0].X = x1;
    pt[0].Y = y1;
    pt[1].X = x2;
    pt[1].Y = y2;

    save_state = prepare_dc(graphics, pen);

    retval = draw_polyline(graphics, pen, pt, 2, TRUE);

    restore_dc(graphics, save_state);

    return retval;
}
In the light of this I can close my search in on the pen or graphics - atleast one of those pointers must be NULL, and as Pen is simpler and checked already in all kinds of variations I suspect that the creation of the Gdiplus::Graphics fails, as I use a standard constructor I don't have much information about the creation process yet, but I'll figure it out.

I'll look into this, I have the feeling we're slowly getting closer ;)

Jakob
 
Last edited:
#10
Oh seems I'm on the right path! For the first time I see *something* - I can see the X and the inset box - only as black silhouettes while the background looks like a sieve (?) - the color should not be too hard to get done once I'm on the right track now (optimism never let me down so far ;)).

-- see attached picture --
(the BG is the background file)

On the right you can see Bill's GDI Template with some fixes made by me as there was a ResetTransform in a loop, which also resets the global ScaleTransform! This means the first iteration is executed with a different transformation than the rest - resulting is size jumps which where noticeable when the gauge bitmap size varies much from the actual on-screen size. Better use Save and Load GraphicsState to revert to a former state, reset undoes everything! (Just like in PS where you can load and set drawing and path states, is a really cool but complex feature there as PS supports nested states and commands can be abbreviated in a user defined way resulting in "space code")

And here is proof that finally my draws return a Status::Ok (0):
Code:
2010-08-03T19:39:42.484Z  Service ID: PANEL_SERVICE_PRE_DRAW
2010-08-03T19:39:42.484Z  graphics scale transform status 0
2010-08-03T19:39:42.484Z  graphics clear status 0
2010-08-03T19:39:42.484Z  graphics fill rectangle status 0
2010-08-03T19:39:42.484Z  graphics draw line status 0
2010-08-03T19:39:42.484Z  graphics draw line status 0
2010-08-03T19:39:42.484Z  graphics fill polygon status 0
2010-08-03T19:39:42.484Z  Service ID: PANEL_SERVICE_POST_DRAW
The solution path I'm on right now is rather complex too. I had to look into both Pen and Graphics documentation (MSDN #1, #2), and learned about the Graphics wrapper class that is MUST be destroyed BEFORE the device context handle (HDC) is released (from my insight I strongly suppose that happens in FS somewhen after PRE_DRAW, and before or while POST_DRAW, as FS provides the hdc already for us in PRE_DRAW for the gauge element of choice, thus it locks the DC for us, gives us the handle we can work with it and FS unlocks it again for us...).

This might get rather technical, but I hope it can prevent someone else to go through the same thing again:

The root of the problem was that I inherited the definition of "Graphics graphics(hdc);" as an autovariable (dynamically created and unloaded on the stack) in a block (if (hdc) { ... }) from Bill's GDI_Template, normally autovariables (the Graphics in our case) will be destroyed at the end of the block when the variable runs out of scope, but this is a bit variable and well hidden from the programmer - if something is critical there is no other way to make sure it is done but to make it your self!

So what I did, as we're in C++ anyways, I dynamically create the Graphics on the heap rather than stack, with "new" and force destroy it by "delete" after my drawing is done, this way I make sure that the "Graphics" is destroyed at a well known and defined time (BEFORE my processing of PRE_DRAW is finished) and not somewhere transparent to the programmer - hidden processes and "side effects" always introduce a lot of problems hiding the real reason very well (been there, done that...).

This ofcourse changes a little bit the rest of the code as my graphics is now of type "Graphics*" (a pointer) rather than "Graphics", thus one has to use "->" (indirect member operator) instead of "." (member operator), but should not be a problem for everyone at least half way familiar with C and/or C++.

Now the code in PANEL_SERVICE_PRE_DRAW looks like this and produces no more "InvalidParameters" (Pens and Brushes are also on the Heap now thats why I don't have to use the address operator (&) any more, they are dynamically created (new) when GDI+ is loaded, destroyed (delete) when GDI+ is shut down; Colors are still on the Stack, maybe I'll change that to be more consistent):
Code:
    case PANEL_SERVICE_PRE_DRAW:
        dprintf("Service ID: PANEL_SERVICE_PRE_DRAW");
        if (!gdiplus_initialized)
            start_gdiplus();
        else if (!debug_output_done)
            dprintf("GDIPlus open!");

        if (gdiplus_initialized)
        {
            PELEMENT_STATIC_IMAGE pelement = (PELEMENT_STATIC_IMAGE)(pgauge->elements_list[0]->next_element[0]);

            if (pelement)
            {
                HDC hdc = pelement->hdc;
                PIXPOINT dim = pelement->image_data.final->dim;

                if (hdc)
                {
                    //Beep(1000,200);
                    Graphics    *graphics;
                    graphics    = new Graphics(hdc);

                    REAL dx = dim.x / hello_width;
                    REAL dy = dim.y / hello_height;

                    //set_render_quality(graphics);

                    dprintf("graphics scale transform status %d", graphics->ScaleTransform(dx, dy));

                    if (!debug_output_done)
                    {
                        int tempx, tempy;

                        dprintf("pelement final dim.x %d, dim.y %d", dim.x, dim.y);
                        dprintf("pelement source dim.x %d, dim.y %d", pelement->image_data.source->dim.x, pelement->image_data.source->dim.y);
                        dprintf("pelement element_type %d", pgauge->elements_list[0]->next_element[0]->element_type);
                        dprintf("pelement position.x %d, position.y %d", pelement->position.x, pelement->position.y );
                        dprintf("pelement hdc (so %d) %X", sizeof(hdc), hdc);
                        dprintf("graphics origin status %d", graphics->GetRenderingOrigin(&tempx, &tempy));
                        dprintf("graphics origin x %d, y %d", tempx, tempy);

                        debug_output_done = true;
                    }

                    // Do Drawing
                    dprintf("graphics clear status %d", graphics->Clear(black));
                    dprintf("graphics draw line status %d", graphics->DrawLine(greenPen, 0, 0, 100, 100));
                    dprintf("graphics draw line status %d", graphics->DrawLine(whitePen, 0, 100, 100, 0));

                    Point box[5] = { Point(10, 10), Point(10, 90), Point(90, 90), Point(90, 10), Point(10, 10) };

                    dprintf("graphics fill polygon status %d", graphics->FillPolygon(whiteBrush, box, 5));

                    delete graphics;
                }

                SET_OFF_SCREEN(pelement);
            }
        }
        break;
I will play with it a little more and when it works (with color and without the sieve mode ;)), I will post my new code complete with elaborate annotations here as this might be useful for others as well.

Grazie mille, thank you again for your help,
Jakob
 

Attachments

Last edited:
#11
Well, since you can understand C++ code ( real C++ code, not "C-gauges" code...). I suggest having a look at an entirely different approach, here:

http://code.msdn.microsoft.com/ESPDrawingGaugesGDI

It's from the ESP SDK, but can be applied to FSX as well.

I wrote it, it's taken from the source code of the F/A-18 we did for Microsoft a while ago, it has been a little bit simplified by Microsoft, but the main concepts are there, and it's a way more functional object-oriented approach, were both the gauges and their relationship with GDI+ are wrapped in classes.
 
Last edited:
#12
Thank you very very much, will look into that, with that continuous mixed use of C and C++ I'm just happy that I'm not completely rusted in understanding the fundamentals ;). Back in good times I was as flexible to go from some basics in asm on one end via C, C++, Perl to BASH and other scripts on the other in minutes...

Jakob
 
Last edited:
#13
Looks really very nice and promising, I just have to give a little thought on all the inlines, virtuals and templates (never liked that templates need the actual definition and not only the declaration in the header too, really feels unnatural to me and confused me quite a bit for years now... :D) and have to see how I can get simconnect out and FSUIPC in.

Give me some time and I'll report back.

Jakob
 
Last edited:

n4gix

Resource contributor
#14
I'm delighted that you are making such progress! Do realize that my "GDI Template" is (1) a very simple example providing some idea of structure and function, but (2) is not really intended for the advanced programmer... :)

Maybe once you get a good handle (pun intended!) on the process, you might put together a better "C++/GDI+ Template" project... :teacher:
 
#15
Bill thank you very much, if I get it to work I'll definitely think about writing a little guided template for GDI+ with C++ (as GDI+/C++/SimConnect is already covered by Umberto's ESP article, I may write one for plain GDI+/C++ and maybe one which is using FSUIPC instead of SimConnect...), but first I have to make it work :D!

Umberto, I have now completed reading the whole source of your example, let me say it is very nice solution and pretty straight forward, if you take the time to read it completely :D I think I've also spotted all parts that need adaption for FS2004 and removing SimConnect seems to be pretty easy.

I see you use the C++ standard library auto_ptr<Type T> template to make sure that the Gdiplus::Graphics get destroyed, a very very nice solution, I probably would not thought of in first place... I always understood that template as a "heap version of autovariables", which seems to be a good approximation but only representing a small aspect. I see now with the overloaded -> operator it is really intuitive to use! I just never thought of a situation before where I would need that. Until now I accepted that I have to manage the heap myself... :eek:

A thing that is not completely clear to me yet: I have not seen anywhere that you instantiate a (global) object of the base nor derived classes explicitly - that makes sense as the function Callback() is static anyways (Class method). And also I see you have a neat way of calling the Base Class' static Callback() template with a derived class as type specifier, resulting in the different behavior because of the child's different constructors. But when are these Constructors called? More generally: I don't know whats the lifetime of the children classes? Have they the same (universal) lifetime as the module (because of the "inline" in the Callback template)? Does each child maintain its own unique set of members (except statics of course), and how can I access them as I don't have a address pointer like when I instantiate them myself explicitly?

I hope my questions are not to stupid, if so, please excuse!

I will start now adapting the code for FS2004, I will keep you informed!

Thank you very much again,
Jakob
 
#16
I see you use the C++ standard library auto_ptr<Type T> template to make sure that the Gdiplus::Graphics get destroyed, a very very nice solution, I probably would not thought of in first place...
Yes auto_ptr are very convenient.

I love when I can dispel the pupular myth of "in C++ you have to manage pointers and risk killing yourself", which was probably spread by Java/C# programmers who don't know the C++ Standard Library well enough...

The only thing to take care of, when using autopointers, is that you can't put them inside a smart container (like a vector), because containers are designed to store only "dumb" objects.

A thing that is not completely clear to me yet: I have not seen anywhere that you instantiate a (global) object of the base nor derived classes explicitly
What triggers its creation, is the reference to the template in the GAUGE_HEADER macro, here:

Code:
GDIPlus_Gauge::Callback<OpaqueGauge>
In the MS version of the sample, OpaqueGauge is a class derived from GDIPlus_Gauge but, it's not really important here, the class are basically the same, save for a member. This triggers the creation of the Gauge Callback, but the real black magic, is happening here, in its declaration:

Code:
inline void FSAPI GDIPlus_Gauge::Callback(GAUGEHDR* gau, int serviceId, unsigned)
{
  switch(serviceId)
  {
  case PANEL_SERVICE_CONNECT_TO_WINDOW:
    assert(gau->user_data == 0);
    gau->user_data = reinterpret_cast<UINT32>(new T);
    reinterpret_cast<T*>(gau->user_data)->mCanvas = reinterpret_cast<PELEMENT_STATIC_IMAGE>( gau->elements_list[0] );
    break;

  case PANEL_SERVICE_DISCONNECT:
    assert(gau->user_data);
    delete reinterpret_cast<T*>(gau->user_data);
    gau->user_data = 0;
    break;

  default:
    reinterpret_cast<T*>(gau->user_data)->DoCallback(gau, serviceId);
    break;
  }
}
Most important lines are:

Code:
  case PANEL_SERVICE_CONNECT_TO_WINDOW:
    gau->user_data = reinterpret_cast<UINT32>(new T);

  case PANEL_SERVICE_DISCONNECT:
    delete reinterpret_cast<T*>(gau->user_data);
    gau->user_data = 0;

   default:
    reinterpret_cast<T*>(gau->user_data)->DoCallback(gau, serviceId);
Here's the object lifetime: it's created on the heap with "new T" so, it's a pointer to a C++ object of the type "GDIPlus_Gauge" and it's stored inside the C gauge "user_data" parameter. This was freely available so, we use it to store the pointer to the C++ object, inside the C gauge.

If, instead, our callback is in any other cycle than CONNECT_TO_WINDOW or DISCONNECT, we are in the "default" case of the switch statement so, we just make a function call of the DoCallback, using the gau->user_data pointer we stored previously.

During the DISCONNECT cycle, we kill the C++ object so, that's concludes its lifetime.

Note that, as I've said, Microsoft shortened that code for the example's sake but, my original version was more generic. Here, they put actual drawing code inside the base class PANEL_SERVICE_PRE_DRAW and some init code in the PANEL_SERVICE_POST_INSTALL. Also, some of their GDI+ objects (pens, images, brushes, etc.) are stored as members of the *base* class.

My original approach was more modular: I kept the base class free of anything related to the Gauge I was programming, and used Virtual function declared in the base class like this:

Code:
virtual void OnDraw(Gdiplus::Graphics& g) = 0;
virtual void OnUpdate( void ) = 0;
virtual void OnGenerate( Gdiplus::Graphics& g) = 0;
Note they are declared as abstract functions so, you are forced to implement them in the instantiated gauge object. These are called in the respective cycle of the base class so, OnDraw would be called after all the generic code in the PANEL_SERVICE_PRE_DRAW cycle, OnUpdate after the generic code in the PANEL_SERVICE_PRE_UPDATE cycle, and OnGenerate() after the PANEL_SERVICE_POST_INSTALL cycle.

This way, you keep separate the *base* class from your specific gauge implementation. Everything that is generic stays in the base class, and customization is added with the virtual function.

A specific gauge made this way, would then look very clean in code, like this:

Code:
class mfd : public GDIPlus_gauge
{
public:
  mfd();

protected:
  virtual void OnGenerate(Gdiplus::Graphics& g);
  virtual void OnDraw(Gdiplus::Graphics& g);
  virtual void OnUpdate( void );
};

mfd::mfd()
  : GDIPlus(GAUGE_W, GAUGE_H, BMP_MFD_BACKGROUND, BMP_MFD_BACKGROUND, PointF(GAUGE_W/2, GAUGE_H/2), false, 2 )
{};

void mfd::OnGenerate(Gdiplus::Graphics& g)
{
  // things to do during initialization
}


void mfd::OnUpdate()
{
  // things to do every 18 hz
};

void mfd::OnDraw(Gdiplus::Graphics& g)
{
  // things do draw at every frame
}
I've removed the redundant bit, like mouse pointers, headers, to make it more readable. As you can see, you are getting a ready to use Graphics context in every virtual function call you implement, which is obtained because the Graphic context that was obtained in the base class, is passed through as a parameter when calling the virtual function from the base class, like this:

Code:
// PANEL_SERVICE_PRE_DRAW
OnDraw(*mGraphics);

// PANEL_SERVICE_PRE_UPDATE
OnUpdate(*mGraphics);

// PANEL_SERVICE_POST_INSTALL
OnGenerate(*mGraphics);
Hope you'll get the idea...it was basically a way to convert something that was thought as pure C into object oriented code. Which is interesting because, if you *really* think about it, the whole concept of how SDK Gauges works, it's a way to simulate object orientation and templates, with a language that doesn't support any of it!
 
Last edited:
#17
What triggers its creation, is the reference to the template in the GAUGE_HEADER macro, here:

Code:
GDIPlus_Gauge::Callback<OpaqueGauge>
This is what I have thought, but as these techniques are beyond my everyday code set in C++ I wasn't quite sure. (Uh Java is something I'd rather not comment on - but I can say I do like the coffee, or the island :D)

Code:
  case PANEL_SERVICE_CONNECT_TO_WINDOW:
    gau->user_data = reinterpret_cast<UINT32>(new T);

  case PANEL_SERVICE_DISCONNECT:
    delete reinterpret_cast<T*>(gau->user_data);
    gau->user_data = 0;

   default:
    reinterpret_cast<T*>(gau->user_data)->DoCallback(gau, serviceId);
Ah yes, sorry :eek: strange how I could overlook something like this, especially since you (or MS) describe in the accompanying .html that you "store the object reference as an opaque pointer in the gauge USER data"...

Proves as long as you haven't read something at least 3 times there will always be something obvious escaping you :D

---

Regarding the rest, I will have to print that out and read it over (and over and over) again, but I think I have a general feeling now where you solution is heading. The more I look into the more I'm impressed by it's beauty :)

Thank you very much again,
Jakob

btw., Yes I can see how the SDK tries to emulate some OOP approaches and patterns in C - I generally think this is not the worst thing to do, especially in the light of the time from where the FS roots come from and still live on in some parts of it. Everyday OOP was still in its child shoes or simply not performant enough for resource intensive computation as simulation back at the time. I'm myself neither a hardcore "high" nor "low language" proponent, I think each language has its purpose and some complex model structures and advanced OOP Patterns are better served with a high abstraction language like C++, but for other performance critical parts I'm more pro C or sometimes even inline asm (althogh I have to have all my references ready when I have to do this ;))...
 
Last edited:
#18
Finally managed to rewrite your C++ example for FS9, using Dai's and Arno's updated gauge.h - had to rewrite the gauges.h a little bit as it wasn't optimized for anything but "one-object build'n'link" projects what the default gauges basically are after they #include all sub c/cpps into the main one...

The problem was they had function bodies (definitions) in the header, which naturally will provoke "unresolved function name conflicts" if built in a multi-object project with a two step build and link (as each object will be offering the same function at linking not simply using it...).

I solved it now by moving all function bodies in the header to a separate .cpp, thus having all other objects including the gauge.h (each childgauge due to inheritance) use the same functions offered by one dedicated object file...

So it compiles now error and warning less, but FS will stop loading when I select a panel with this gauge... I have to track down the error, I think it has either got to do with the slight differences between FSX/ESP and FS9 when they send which service_id in the callback (thus resulting in a double initialization or smth. similar) or there is another fault.

Anyway, I have a list of ideas to test next, I will keep you updated on the progress, once it works I will release my template which is otherwise complete.

Jakob
 
Top