Thursday, March 08, 2007

And so ends the debate of whether you can time an engine by ear or not.

 

3/8/2007 9:00:38 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Thursday, March 01, 2007

I've been writing apps in mostly C# for several years now.  Of course I've also been doing drivers and kernel work in C or C++, and that has only improved my ability to write, debug and understand managed code in ways that pure managed developers will never really get.  Hell, I pretty much believe that if your managed code doesn't require allowing unsafe code then it could probably be done better.

Well for a few months now I've been neck-deep in a C++ application.  It was started using MFC, which tends to give me a rash just by saying the letters, but it was a customer decision not mine.  At any rate, I was doing some work with a RAS class where I needed any number of consumers to be able to be notified of any changes in the RAS dial status (dialing, connecting, authenticating, etc.).

Imagine a C# class for RAS that will expose events for when the dial staus changes.  THe first thing you'd need is a delegate, right?

In C# you'd have this (assuming DialStatus is an enum):

delegate void DialStatusChange(DialStatus dialStatus);

Well the c++ I wrote (without even thinking about C#) looked eerily familiar:

typedef void(*DIAL_STATUS_DELEGATE)(DialStatus newStatus);

In C# you'd use the += or -= operators for adding an event handler to a classes event, so we get spoiled.  In VB, you call AddHandler with the event name and then the address of the handler.  Well my C++ had a private vector of function pointers (which the CF maintains internally):

std::vector <DIAL_STATUS_DELEGATE> ConnectionManager::m_statusCallbackList;

And then I added methods for adding and removing handlers to the class:

 void ConnectionManager::StatusAddHandler(DIAL_STATUS_DELEGATE callback)
 {
   m_statusCallbackList.push_back(callback);
 }
 
 void ConnectionManager::StatusRemoveHandler(DIAL_STATUS_DELEGATE callback)
 {
   std::vector<DIAL_STATUS_DELEGATE>::iterator iterator;
 
   for(iterator = m_statusCallbackList.begin() ; iterator != m_statusCallbackList.end() ; iterator++)
   {
     if((*iterator) == callback)
     {
        m_statusCallbackList.erase(iterator);
        break;
     }
   }
 }

And how about usage?  In C#, our event-exposing class would have a defined event:

 event DialStatusChange OnStatusChange;

and then when we want to raise the event (assume it's multicast) from our app, we'd do something like this:

 if (OnStatusChange != null)
 {
     foreach (DialStatusChange dsc in OnStatusChange.GetInvocationList())
     {
         dsc(newStatus);
     }
 }

Well my C++ didn't need the event declaration, but raising the "event" (which is simply calling a function pointer callback) looked like this:

 std::vector<DIAL_STATUS_DELEGATE>::iterator iterator;
 
 recheck2:
 for(iterator = m_statusCallbackList.begin() ; iterator != m_statusCallbackList.end() ; iterator++)
 {
   if(IsBadCodePtr((FARPROC)(*iterator)))
   {
      // invalid callback found (someone hooked us then died without unhooking)
      m_statusCallbackList.erase(iterator);
      goto recheck2;
   }
      (*iterator)(status);
 }

Now this was just off the top of my head, so maybe there are improvements that could be made, but it kind of surprised me how I'd taken the concepts I really learned in C# and translated them back to my C++ code.  So yes, writing C# can make you a better C++ developer.

3/1/2007 12:02:32 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Thursday, February 22, 2007

For all those naysayers who complain that we don't do enough free stuff for the community, here's another contribution:

We're now hosting the latest zlib source for Windows CE and a managed wrapper for it.  Click the bitmap or here for more info.

2/22/2007 11:49:30 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Tuesday, February 13, 2007

Now this is a great way to pay your bills.

 

What now indeed.  For you non-math people, the value is roughly $1.002. If you're interested in the story behind it (and the audio really is worth listening to) you can find it here.

2/13/2007 12:10:09 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2]  | 

After what seems like forever, the .NET Micro Framework is finally outDownload it here. Expect announcements over the next days from hardware vendors with development platforms.

2/13/2007 9:13:52 AM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Wednesday, February 07, 2007

Some days I really want to strangle Microsoft engineers.

 

For the past couple days I've been working on a project where I want a CE device to dial in to an XP desktop. In a perfect world, I'd just use RAS, create an entry, and go.  Well this isn't a perfect world.

 

Spent several hours yesterday trying different chipsets and PCs to finally determine that my device modem seems to be not-so-good and it won't negotiate at the default 56kbps that the chipset uses (than you Alex Feinman for the help).

 

The workaround is to manually slow it down with an AT command.  Using terminal on the device I just type it in and boom - it's set.  Next step was to try to repro that simple operation in code.  This is where the fun begins. Well this extra dialing data is set via the RasSetEntryProperties API in the lpb parameter.  The parameter docs say "Pointer to a buffer that contains device-specific configuration information. This is opaque TAPI device configuration information." 

 

It gives a reference to the TAPI lineGetDevConfig API for getting that data.  So we look over there and fine that it's a VARSTRING parameter.  Simple enough eh?  I fill in a VARSTRING with the command and send it in.  RasSetEntryProperties returns a success.

 

For fun I then open the connection via the UI's Network Connections.  Click on the "Configure" button and the shell completely locks up.  That can't be good.

 

So I figure, I'll see what the UI generates and use that to figure out what I screwed up.  I create a connection entry manually and add the info into the dialog.  Now clicking "Configure" works fine, so it's back to the code.

 

I call RasGetEntryProperties on the manually created entry to see what it looks like.  I see my data's in there, but it certainly isn't aligned as a VARSTRING and I have no idea what the "header" data is.  The length reported is also *way* bigger than my data.

 

  0x001291C0  30 00 00 00 78 00 00 00 10 01 00 00 00 4b 00 00  0...x........K..

  0x001291D0  00 00 08 00 00 00 61 00 74 00 2b 00 6d 00 73 00  ......a.t.+.m.s.

  0x001291E0  3d 00 76 00 33 00 32 00 2c 00 2c 00 31 00 34 00  =.v.3.2.,.,.1.4.

  0x001291F0  34 00 30 00 30 00 2c 00 32 00 38 00 38 00 30 00  4.0.0.,.2.8.8.0.

  0x00129200  30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  0...............

  etc.

 

Alright, so what's going on here?  Off to the Platform Builder source (and this is why every developer should get the PB eval and install it - the source is gold) to see how the default UI is doing it.  A little GREP on the dialog text and I find that the UI handling this is, not surprisingly NETUI.

 

So after a long while tracing through convoluted code (Source Insight is an invaluable tool here) it appears that you're supposed to call lineConfigDialogEdit and have TAPI give a dialog to fill this structure.  Well what if you don't want user interaction (like in my case where the user would have no idea what to enter)?  Or even more fun, what if it's a headless device?

 

Ok, so time to regroup.  Let's search all the PB source for anything calling lineSetDevConfig to see if there's something to base my work on.  Turns out that there are a few samples, but most of them just do a lineGetDevConfig, save that data somewhere, then send it back in - they don't actually modify it.

 

One exception is the sample RASENTRY app.  It calls lineGetDevConfig, and then appears to alter that config not through lineSetDevConfig but instead by calling lineDevSpecific.  It uses this to change the baud rate.  It's not exactly what I need (I need to prevent the modem from negotiating a v.90 or v.92 connection) but a start, right?

 

So a little work and I ended up with this (well more than this, but it's the heart of it):

 

            result = lineGetDevConfig(tapiId, config, DEV_CLASS_COMM_DATAMODEM);

            ucd.dwCommand = UNIMDM_CMD_CHG_DEVCFG;

            ucd.lpszDeviceClass = DEV_CLASS_COMM_DATAMODEM;

            ucd.lpDevConfig = config;

            ucd.dwOption = UNIMDM_OPT_CFGBLOB;

            ucd.dwValue = (DWORD)MY_CUSTOM_DIALUP_STRING;

            result = lineDevSpecific(hLine, 0, NULL, &ucd, sizeof(ucd));

 

I run it and all APIs return success.  Hooray!  Right?  Open the connection with the UI on the device and the "Extra Settings" box is still blank.  At this point I have to chalk it up to a huge WTF in the Microsoft code.

 

At this point I have no option but to go the route that the UNIMODEM dialer went (again in PB source) and set it in the registry at HKEY_LOCAL_MACHINE\Drivers\Unimodem\Init.  Some may argue that this is easier and I should have gone that route in the first place, but that just doesn't sit well with me and not just because it affects all Unimodem connections on the device. 

 

I strive to understand what is going on when I'm writing code because it not only makes it easier to debug aberrant behavior but it also helps make me a better developer all around. I hate black-box programming - that is programming where I don't fully understand how something works from end to end.  Somehow TAPI gets that data into the blob returned by RasGetEntryProperties, but I'll be damned if I can figure it out. 

 

The actual blob is created or copied to  HKCU\Comm\RasBook\<connection name>\DevCfg and it appears to be an extension of HKLM\Drivers\Unimodem\DevConfig, but they are slightly different and what those differences mean is totally undocumented.

2/7/2007 3:18:21 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  |