It started as a windows application. I am porting it to Mac OSX. It is actually a pair of applications. A client and server where the server has high privilege context, like a service on the windows side.
The client has it's main which does not return which causes the problem of using the default message handler. it does not return either. I can't run it on a timer, because the main will not return and I do not think good things would happen when a timer event never exits.
Currently I am running my client as a thread with 1 meg of stack space. I hope this does not break any of the API calls, being on a thread opposed to the main one. When the client exits, I needed to cause the whole application to exit in a graceful manner. I think I have this solved, although in a few days things might start blowing up again. For the client:
OSStatus MyThreadFunction(void* Para)
{
static ClientCore Core;
CoreRef = &Core;
Core.DoRun();
Running = false;
return 0;
}
static void MainEventLoop()
{
RgnHandle cursorRgn;
Boolean gotEvent;
EventRecord event;
cursorRgn = NULL;
while(Running)
{
// Get event and wait up to 1 second. The value is in 'ticks' and 60 ticks = 1 second.
gotEvent = WaitNextEvent(everyEvent, &event, 60, cursorRgn);
if (gotEvent)
{
if ( event.what == kHighLevelEvent )
{
AEProcessAppleEvent(&event);
}
}
}
}
So this pump will wait up to 1 second before going through the while condition. So at worst case, if the threaded Core.DoRun(); returns, The app will exit in 1 second later.
The server on the other hand does have a Run() method which returns rather quickly and is meant to be called every second. It has a similar event pump, and runs on the same thread.
static void MainEventLoop()
{
RgnHandle cursorRgn;
Boolean gotEvent;
EventRecord event;
cursorRgn = NULL;
while(true)
{
// Get event and wait up to 1 second. The value is in 'ticks' and 60 ticks = 1 second.
gotEvent = WaitNextEvent(everyEvent, &event, 60, cursorRgn);
if (gotEvent)
{
if ( event.what == kHighLevelEvent )
{
AEProcessAppleEvent(&event);
}
}
else
{
// Exit the MainEventLoop
break;
}
}
}
Server Loop:
while( true )
{
SCResult = PKC.Run();
if ( ( PKRESULTNORMAL == SCResult ) && ( false == Interupt) )
{
// The system console gripes about windows server stuff when there is
// no server. This may be the cause.
if ( true == MiscInfo::qqBool( MiscInfo::QISINTERACTIVE, false) )
{
MainEventLoop();
}
else
{
// Sincee were no using our event pump, we will use this to wait 1 second.
SLEEP(1000);
}
}
else
{
break;
}
} // End While
It also hooks into a bunch of the signals and is launched by launchd. I use the MainEventLoop function as my 1 second timer unless there is no user and the I use SLEEP(1000) which is a macroed version of a tcl_sleep function that uses MS instead of seconds.
Now a lot of people who have seen my various posts about this and that and keep wondering why don't I do it "The Apple Way". There are several reasons. 1. This app runs on multiple platforms: Windows, OSX, and Linux. This puts architectural restrictions on how I do things. I just can't re-write that client call that does not return and turn it into one that does. Id have to make big changes on a lot of platforms to do that.
This application, between the client and server has well over 200 source files. All written by me. Out of those 200 source files, I would guess that 190 of them are identical on all platforms. That means if I fix a bug or add a feature, every platform gets it fixed or added.
My code, unlike a lot of multi-platform code is not full of a bunch of #ifdef OSX or #ifdef WIN32, etc.. Of those kinds of #if defs, there is a grand total of 6 out of 200 source files on all platforms.
So I may seem stubborn or rigid, but I have my reasons. Certain things have to work a certain way otherwise it causes huge problems.
The problem here, which
may be solved is I had two functions which did not return that HAD to be called. My worry on the client is that being in a thread some API functions might get wonky. Not mine, but apples. Take this for example:
Name = SCDynamicStoreCopyConsoleUser(NULL, NULL, NULL);
That is a totally legal way to call that function. And yes I did CFRelease(Name); so I was not leaking memory. That function could not be called once every second for more then 2 days without crashing. It crashed because it tried to allocate memory internally, didn't get it, and then wrote to it and boom. I put a try/catch around it. No go still blew up. This is what I mean by Apple APIs going whacky. I did solve the problem by doing what apple SHOULD have done, but didn't. Here is the solution that can run forever and never crash.
bool UserInfo::IsCurrentUserInteractive()
{
LOG(5, "UserInfo::IsCurrentUserInteractive Enter");
bool RetVal = true;
CFStringRef Name = NULL;
SCDynamicStoreContext *context = NULL;
SCDynamicStoreRef DStore = NULL;
bool StoreExcept = false;
try
{
DStore = SCDynamicStoreCreate(NULL, CFSTR("scClient"), NULL, context);
}
catch(...)
{
StoreExcept = true;
LOG(1, "UserInfo::IsCurrentUserInteractive SCDynamicStoreCreate threw an exeption");
}
// The local store may be null, but this is a valid input value for SCDynamicStoreCopyConsoleUser.
// of course it may have the same problem.
if ( NULL == DStore )
{
LOG(1, "UserInfo::IsCurrentUserInteractive SCDynamicStoreCreate returned NULL");
}
else
{
// This is just test code. I want to know if this condition is possible.
// I am certain it is not, but sometimes the Mac does weird things.
if ( true == StoreExcept )
{
LOG(1,"UserInfo::IsCurrentUserInteractive exception thrown, but value was returned.");
}
}
// This call has crashed before
try
{
Name = SCDynamicStoreCopyConsoleUser(DStore, NULL, NULL);
}
catch(...)
{
LOG(1, "UserInfo::IsCurrentUserInteractive SCDynamicStoreCopyConsoleUser threw an exception. Exit true");
}
if (NULL == Name)
{
RetVal = false;
}
else
{
// This is only done once, during object construction.
// The user name can not change without this program exiting and restarting.
if ( true == RefreshUserName )
{
RefreshUserName = false;
char *bytes = NULL;
bytes = (char *)CFStringGetCStringPtr(Name, kCFStringEncodingMacRoman);
if ( NULL != bytes )
{
mm_UserName = (const char *)bytes;
}
else
{
// It would be intresting to observe how often this happens.
// I am guessing for usernames, the above code will never return null.
LOG(2, "UserInfo::IsCurrentUserInteractive() CFStringGetCStringPtr returned null.");
char *Buffer = new char[1025];
bzero(Buffer, sizeof(Buffer));
if( true == CFStringGetCString(Name, Buffer, 1024, kCFStringEncodingMacRoman) )
{
mm_UserName = Buffer;
}
delete Buffer;
}
}
CFRelease(Name);
}
if (NULL != DStore)
{
CFRelease(DStore);
}
LOG(5, "UserInfo::IsCurrentUserInteractive Exit");
return RetVal;
}
I hope people can appreciate why I do things a certain way and not always the official Apple way.