Loading a PNG dynamically in docklet

I am developing a docklet for stardock using it's SDK.

When the dock starts (during onCreate), I setup a timer to update the icon every second (since it will be a clock docklet).

This timer fires a function called UpdateClockData which in turn retrieves the current time and creates an Overlay Image to combine it with the clock background...

 
Gdiplus::Image *CreateClockOverlayImage(struct tm *tmCurrentTime, PDOCKLET_DATA lpData)
{
// Some initialization stuff
CHAR szBuffer[PRINT_BUFFER];
Gdiplus::Bitmap *imageReturn = new Bitmap(128, 128, PixelFormat32bppARGB);
Gdiplus::Status result;
Gdiplus::Graphics graphics(imageReturn);
graphics.SetInterpolationMode(InterpolationModeHighQuality);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);

// Set the folder name where icons reside
char szFolderName[MAX_PATH];
_tcscpy_s(szFolderName, lpData->szFolderName);

// Set the full path to the image repository
char szImagePath[MAX_PATH];
DockletGetRelativeFolder(lpData->hwndDocklet, szImagePath);
strcat_s(szImagePath, szFolderName);
strcat_s(szImagePath, "");

// Retrieve the image (of the digit to be shown)
char szImageName[MAX_PATH];

// Hour's 1st digit
int iHH1 = (lpData->bShow12HourFormat && (tmCurrentTime->tm_hour > 12)) ? (tmCurrentTime->tm_hour-12)/10 : tmCurrentTime->tm_hour/10;
sprintf_s(szImageName, "%s%d.png", szImagePath, iHH1);

// Open the PNG file
wchar_t *pFile;
pFile = ConvertToWCharString(szImageName);

Gdiplus::Bitmap *bitmap;
bitmap = Gdiplus::Bitmap::FromFile(pFile, FALSE);
result = bitmap->GetLastStatus();

// Draw the PNG
if (result == Gdiplus::Ok)
graphics.DrawImage(bitmap, Gdiplus::Rect(26, 31, bitmap->GetWidth()/2, bitmap->GetHeight()/2));
else
{
char szLabel[LABEL_SIZE];
sprintf_s(szLabel, "Error loading digit: %S, %d", pFile, result);
DockletSetLabel(lpData->hwndDocklet, szLabel);
}
delete bitmap;

// Ohter digits processed the same way ...

return imageReturn;
}

This code is called from my update tread which is invokead on each timer event:

 
DWORD WINAPI UpdateClockDataThread(LPVOID lpInput)
{
PDOCKLET_DATA lpData = (PDOCKLET_DATA) lpInput;

if (!lpData)
return 0;
// Retrieve the time & date parameters
_tsetlocale(LC_ALL, _T(""));
time_t currentTime = time(NULL);
struct tm tmCurrentTime;
localtime_s(&tmCurrentTime, ¤tTime);

// Set the docklet label to current date & time
char szLabel[LABEL_SIZE];
strftime(szLabel, sizeof(szLabel), "%x, %X", &tmCurrentTime);
DockletSetLabel(lpData->hwndDocklet, szLabel);

//Create an overlay image and set it on top of our loaded file image.
DockletSetImageOverlay(lpData->hwndDocklet, CreateClockOverlayImage(&tmCurrentTime, lpData));

//Exit the thread
CloseHandle(lpData->hUpdateThread);
lpData->hUpdateThread = NULL;
ExitThread(0);
return 0;
}

The problem I am facing is that whenever the docklet loads with ObjectDock for the fist time (during session start - since object dock is configured to start automatically), I am getting the "Error loading digit:..." error, with result = 2, which means "invalid parameter". If I close object dock, and open it again, then the docklet loads the PNG files without problems, configuring stardock not to open automatically, will work too.

Only if objectdock is started automatically, then de docklet will not load well.

You can download my (under development docklet) from:

http://www.feterspace.com.mx/downloads/DigitalClock.zip

So you can test what I have tried to explain.

Am I doing something wrong here? It is odd that removing the docklet and adding it again, or simply restarting objectdock will not yield the error. I have checked on the image path and it is not wrong!

Any tips?

#504046

On first glance, you are doing something very dangerous:

You create a thread and dispose it in regular intervals. Windows does not like this at all, especially during system bootup where some speed optimizations let apps run in an environment that is not completely set-up.

Do a proper worker thread that keeps alive for the life-time of the docklet instead.

Also, all UI must be done from within the creation thread, calling DockletWhatEver functions from a non-UI thread is asking for trouble.

By all means, create the image in the worker thread, send a WM_USER + X message back to the main UI thread (to your docklet's hWnd) and wake the endless loop of your worker thread in a response to the WM_TIMER messages of a one and only timer that you set up during OnDockletCreate (or whatever API that was).

#504070

thanks herd for taking the time to look at my post.

You create a thread and dispose it in regular intervals. Windows does not like this at all, especially during system bootup where some speed optimizations let apps run in an environment that is not completely set-up.

I based my code on the WeatherDocklet Sample included in ObjectDock's SDK. In this sample they do as I am doing... during OnCreate a Timer is being SetUp wihch will trigger a USER message to the UI Thread. Whenever this message is received a function is called:

 
void UpdateClockData(DOCKLET_DATA *lpData)
{
if(lpData->hUpdateThread)
return;

//Create a thread to update the weather as to not lock up the program during any slow updates.
DWORD dwNewThreadId;
lpData->hUpdateThread = CreateThread(NULL, 0, UpdateClockDataThread, (VOID *) lpData, 0, &dwNewThreadId);
}

which in my case was set up to call UpdateClockDataThread.

Then in the SDK they do as I did in my code... do stuff that will eventually update the UI of the Docklet. like calling DockletSetLabel, DockletSetImageFile or DockletSetImageOverlay.

On the OnCreate method, a timer is being setup:

// Update every second
SetTimer(lpData->hwndDocklet, UPDATE_CLOCK_TIMER, 1000, NULL);

which I understand sends a message to the main thread evey second in order to update the clock display. Then on the OnProcessMessage,

VOID CALLBACK OnProcessMessage(PDOCKLET_DATA lpData, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
bool dataChanged = false;
switch (uMsg)
{
case WM_TIMER:
if (wParam == UPDATE_CLOCK_TIMER)
dataChanged = true;
break;
}
if (dataChanged)
UpdateClockData(lpData);

return;
}

Do a proper worker thread that keeps alive for the life-time of the docklet instead.

Also, all UI must be done from within the creation thread, calling DockletWhatEver functions from a non-UI thread is asking for trouble.

By all means, create the image in the worker thread, send a WM_USER + X message back to the main UI thread (to your docklet's hWnd) and wake the endless loop of your worker thread in a response to the WM_TIMER messages of a one and only timer that you set up during OnDockletCreate (or whatever API that was).

Would you mind to share some mock code here... I think I am not getting what you mean? I thought that doing UI stuff at the worker thread was supposed to aleviate the main thread from slow updates.

Would you mind to show me a sample on how to acomplish

#504093

Do a proper worker thread that keeps alive for the life-time of the docklet instead.

Do you mean to call CreateThread() during onCreate and then simply put it to sleep via SuspendThread()?

Also, all UI must be done from within the creation thread, calling DockletWhatEver functions from a non-UI thread is asking for trouble.

This mean, in the case of this clock docklet, to get the current time & call the bitmap stuff from within the UpdateClockData itself (or an utility function called from it), but not do so from the worker thread... in the case of a clock... that would mean, do everything from the main thread, am I correct?

By all means, create the image in the worker thread, send a WM_USER + X message back to the main UI thread (to your docklet's hWnd) and wake the endless loop of your worker thread in a response to the WM_TIMER messages of a one and only timer that you set up during OnDockletCreate (or whatever API that was).

This is the stuff that is more obscure to me... how do I do that? I believe that I should raise a WM_USER + X message from the worker thread (no idea on how this is done), so the OnProcessMessage will catch it and run some logic ...

 
case WM_USER + X:???
UpdateWeather(lpData);
break;

(like updating the UI), here I will enable the worker thread ... but, for what reason, like calling a web service or something like that? But, then again, if the result of that work (at the worker thread) means to modify the UI, then I will not do it there? Then how the UI gets updated?

#504094

Ok, aside from the issues I have been told by herd, I found that the problem I am facing with loading the PNG files upon startup of objectdock is still present.

What I did was to "move" the code from UpdateClockDataThread to the UpdateClockData function (the one being called when the timer event gets fired).

Thus, this time there are no Threads being spawned, everything is done from the UI Thread... and the problem persists.

When the docklet is loaded (with ObjectDock) upon start of the session user, the Bitmap::FromFile() method fails with error 2 "Invalid Parameter". If I simply close (or configure not to autostart) the dock, and open it again, then it will run without problems, but letting the dock to auto-start yields the error.

I believe this has notting to to with the threading problems herd described, because this time, everything is being run from the UI thread, no worker threads, nothing.

Just to clarify, this is the modified UpdateClockData funtion:

VOID UpdateClockData(PDOCKLET_DATA lpData)

{

/*

if (lpData->hUpdateThread)

return;



// Create a thread to update the volume as to not lock up the program during any slow updates.

DWORD dwNewThreadId;

HANDLE aThread;



aThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) UpdateClockDataThread, (VOID *) lpData, 0, &dwNewThreadId);

if (aThread != NULL)

lpData->hUpdateThread = aThread;

else

lpData->hUpdateThread = NULL;

*/



if (!lpData)

return;



// Retrieve the time & date parameters

_tsetlocale(LC_ALL, _T(""));



time_t currentTime = time(NULL);

struct tm tmCurrentTime;

localtime_s(&tmCurrentTime, ¤tTime);



// Set the docklet label to current date & time

char szLabel[LABEL_SIZE];

strftime(szLabel, sizeof(szLabel), "%x, %X", &tmCurrentTime);

DockletSetLabel(lpData->hwndDocklet, szLabel);



//Create an overlay image and set it on top of our loaded file image.

DockletSetImageOverlay(lpData->hwndDocklet, CreateClockOverlayImage(&tmCurrentTime, lpData));

}

What does the "Invalid parameter" means to the FromFile() method?

#504108

Whoa lots of stuff.

I'd wildly guess that the repetitiveness is of some problem here.

Firstly, does your docklet run in AveDesk or RKLauncher? These two are compatible implementations where you can test this erratic behaviour against.

Secondly, whats the point in loading the same image(s) every second again?

Load them once in onCreate and combine them later, maybe this gets you on the right track.

I'm sure you need to catch up some reading on MSDN: MsgWaitForMultipleObjects, RaiseEvent, EnterCriticalSection and PostMessage APIs are there for a purpose.

I read somewhere that GDIPlus (be it the C++ backend you are using or the .Net target) does not really load the image into memory but instead opens it like a page file, mapping its contents into memory by page faults. The guys over at Konfabulator sweated some serious blood with similar issues until someone came over with the clever idea of manually reading the file into a HGlobal, doing a CoCreateStreamOnHGlobal on it and doing an Image::FromStream over the result. Sounds cruel? You bet it is. It's Microsoft we are talking here.

The only docklet sources that survived my HD crash are just too complex to show off, sorry.

hth,

herd

#504183

herd,

Ok, I have tried your suggestion... I have created a couple of Bitmap arrays that got populated with the digits images at Docklet startup (OnCreate)... the icons are opened and pointers to them are stored in the array, then during UpdateClockData() I just simply combine the bitmap needed.

So long so good... but, the problem still arises. During startup of the docklet, the images fail to load (again "Invalid Parameter" is being returned from Bitmap::FromFile().

Whoa lots of stuff.

I'd wildly guess that the repetitiveness is of some problem here.

Firstly, does your docklet run in AveDesk or RKLauncher? These two are compatible implementations where you can test this erratic behaviour against.

Secondly, whats the point in loading the same image(s) every second again?

Load them once in onCreate and combine them later, maybe this gets you on the right track.

I'm sure you need to catch up some reading on MSDN: MsgWaitForMultipleObjects, RaiseEvent, EnterCriticalSection and PostMessage APIs are there for a purpose.

I read somewhere that GDIPlus (be it the C++ backend you are using or the .Net target) does not really load the image into memory but instead opens it like a page file, mapping its contents into memory by page faults. The guys over at Konfabulator sweated some serious blood with similar issues until someone came over with the clever idea of manually reading the file into a HGlobal, doing a CoCreateStreamOnHGlobal on it and doing an Image::FromStream over the result. Sounds cruel? You bet it is. It's Microsoft we are talking here.

The only docklet sources that survived my HD crash are just too complex to show off, sorry.

hth,

herd

Could you elaborate a bit more on the stuff you have described (the Konfabulator thing)?

Thanks again for your support.

#504243

herd, I have a question here... in a docklet scenario should I initializa gdiplus like ina regular app?

I have added

to OnCreate

	Gdiplus::Status result;
GdiplusStartupInput gdiplusstartupinput;
result = GdiplusStartup(&m_gdiplustoken, &gdiplusstartupinput, NULL);
if (result != Gdiplus::Ok)
{
char szLabel[LABEL_SIZE];
sprintf_s(szLabel, "Error: GdiplusStartup, %d", result);
DockletSetLabel(lpData->hwndDocklet, szLabel);
}

to OnDestroy...
GdiplusShutdown(m_gdiplustoken);

Running this code shows that there are no error during Gdiplus initialization... what's more, I have changed the code that loads the images to:

	wchar_t *pFile = ConvertToWCharString(szImage);
HRESULT hr = SHCreateStreamOnFile(pFile, STGM_READ, &pInputStream);
Gdiplus::Bitmap *bitmap = Gdiplus::Bitmap::FromStream(pInputStream);
result = bitmap->GetLastStatus();
if (result == Gdiplus::Ok)
lpData->gloss = bitmap;
else
{
lpData->gloss = NULL;

char szLabel[LABEL_SIZE];
sprintf_s(szLabel, "Error: %S, %d", pFile, result);
DockletSetLabel(lpData->hwndDocklet, szLabel);
}

I do not know if this is what you meant... but the problem is still present... as in the FromFile() code before. Simply closing & opening OD will let the docklet run fine.

#504277

I finally found the root cause of the error... by using the following code to open de PNG image I was able to track the specific cause of the "invalid parameter" error returned by Bitmap::FromFile (or FromStream):

hr = SHCreateStreamOnFileEx(pFile, STGM_READ, FILE_SHARE_READ, FALSE, NULL, &pInputStream);
if (hr != S_OK)
{
char szLabel[LABEL_SIZE];
sprintf_s(szLabel, "Error: %S, hr[%llx]", pFile, hr);
DockletSetLabel(lpData->hwndDocklet, szLabel);
}
else
{
bitmap = Gdiplus::Bitmap::FromStream(pInputStream);
result = bitmap->GetLastStatus();
if (result == Gdiplus::Ok)
lpData->gloss = bitmap;
else
{
lpData->gloss = NULL;
char szLabel[LABEL_SIZE];
sprintf_s(szLabel, "Error: %S, result[%d]", pFile, result);
DockletSetLabel(lpData->hwndDocklet, szLabel);
}
}

what I found was that upon initial start of the docklet (while being loaded during system startup, the SHCreateStreamOnFileEx method was returning with error 0x80070003 which essentially means "PATH NOT FOUND". So I did a quick test by appending "C:Program~1StardockObjectDock to the path returned by DockletGetRelativeFolder... as in the following snippet:

DockletGetRelativeFolder(lpData->hwndDocklet, szImagePath);
sprintf_s(szFullPath, "C:Archiv~1StardockObjectDock%sicons", szImagePath);

where "icons" is a folder under the "DigitalClock" directory where the Docklet resides.

This time, the docklet worked just fine.

So, it seems that during initial load, DockletGetRelativeFolder() is retrieving a folder that is "out of context" that is, is not related to the running instance of ObjectDock and as such, the path is invalid. After closing OD and reopening it, the path is correctly set relative to the running path of OD's running process.

Have you found something like this in the past? What would you recommend me to do in order not to hardcode the path in the docklet code?

I tried using _getcwd() but it behaves similarly, upon automatic startup... in vista it will retrieve "C:WindowsSystem32" and under XP it will point to the user's profile directory "C:Documents and SettingsUserX", so it won´t help in this case.

#504284

Well... finally I nailed the bug! This remebers me about RTMF... there is a DockletLoadGDIPlusImage() function in the OD SDK that deals exactly with those issues... the problem with not having the right path.

I am ashamed.... but this whole exercise let me get into gdiplus a bit more and you pointed me on reading about multhithreaded programs in windows... so, all in all, it was great to have you helping me out.

Thanks again.

Please take a look at my docklet at wincustomize.com or at the link in my thread here at aqua-soft,. and tell me what you think about it.

#504297

Good luck with your docklet.

Always a pleasure to be here.

#504307