Thursday, March 10, 2011

Windows 7 SP1 Introduces Breaking Changes to MDAC

Wow, this one really took our office by surprise. A few of us here have updated to Windows 7 SP1. Some of the developers are also charged with maintaining some legacy VB6 applications that used ADODB to work with MS SQL Server databases.

If you compile this legacy code on Win7SP1, it will work *only* on Win7SP1 - any down stream OS versions will get some bad-juju runtime errors.

The issue is discussed in length at http://social.msdn.microsoft.com/Forums/en/windowsgeneraldevelopmentissues/thread/3a4ce946-effa-4f77-98a6-34f11c6b5a13

In short, it sounds like they made some changes to MDAC to make it play more friendly with x64 compilations, but in the process busted it for all older OSes. There's some work-arounds being discussed in the above post, but they have catches, like, if you update your legacy code (not that that's always a possibility,) you loose the ability to compile that code on non-Win7SP1 machines.  The only surefire fix right now to not compile your legacy code on Win7SP1 - uninstall SP1 for now until an update is released.

Useful tidbits I gleaned:

Update 2011-Apr-13: 

Thursday, December 11, 2008

Delayed Event Handlers and System.Threading.SynchronizationContext

I've found myself in situations where I'm on the receiving end of an API that for whatever reason will raise events in (relative) rapid succession.  It just so happens that I need to execute some code when that event is raised, but not with every event raise. 

For example, imagine you have a graphical mapping API.  Suppose that when the user pans the map, a "Panned" event is raised.  It turns out to be quite common that a user will pan the map over and over before they finish panning. The processing I need to perform on that Panned  event isn't terribly intensive, but it's not without cost; a cost that is too expensive to incur on every Panned event, to say nothing of the worthlessness of performing this processing before user has finished panning. 

A simple answer to that is some form of delayed event processing.  Due to different circumstances at different times I've found myself using two different types of delayed event handlers:

  1. Once the event is raised, it waits for a period of time, and then executes.
  2. Once the event is raised, it waits for a period of time to elapse in which no more of the events have been raised.

The first one is analogous to a timed-delay on a camera, or System.Windows.Forms.Timer, or System.Thread.Timer.  The second is more of "I'll wait for you to finish talking before I start talking" kind of thing. Basic implementation entails firing off an asynchronous method that performs the appropriate type of waiting, and once it's finished, the actual processing takes place. 

Before getting into the details of this implementation, let's talk about UI-thread synchronization.  If you've ever taken advantage of some thready goodness in order to fork off work, then you may have found out that you are not able to update the UI from the non-UI/main thread.  If you try to touch the UI thread, the Framework will throw an InvalidOperationException telling you: "Cross-thread operation not valid: Control 'TheTextBox' accessed from a thread other than the thread it was created on." If you've run into this, you've probably googled-up just a few pages about this exception, and as such, I will not rehash the details here. In short, ISynchronizeInvoke is your answer.  Lucky for almost all cases, System.Windows.Forms.Control implements ISynchronizeInvoke. It's common enough that in some of my code you'll see a "DoOnUIThread" method that I use in my non-UI-threaded code.  It's pretty simple, take a look:

private void DoOnUIThread(System.Threading.ThreadStart method) {
   if (this.InvokeRequired) { 
      this.Invoke(method, null);
   } else {
      method();
   }
}

Where "this" is a Control-derived control, such as a System.Windows.Forms.Form. It's pretty easy to modify this to match whatever delegate you need, and I can only assume there's a simple generic implementation that could accept any delegate type, I just haven't done that yet.  It's easy enough to use:

DoOnUIThread(delegate() {
   // My UI-bound code goes here, for example:
   _TextBox.Text = "Hello There.";
});

 

Unfortunately, there are cases where you're writing something that doesn't have access to a Control-based object.  For example, imagine you're implementing a class library that needs to do threaded operations.  In your unit tests everything seems to run great - your business objects instantiate, thread, calculate and populate.  But unless you also include tests that bind those objects to the UI, you may miss that you can easily get the "Cross-thread operation not valid:" InvalidOperationException only when you bind your business object to the UI. These situations are usually easily remedies by making your objects require an ISynchronizeInvoke object upon instantiation.  That ISynchronieInvoke object can be used to use a DoOnUIThread style method to make sure you don't run into that Cross-thread exception.

Even more unfortunate, there are cases where maybe your business objects are so entrenched, or it's simply impractical to require an ISynchronizeInvoke object to be specified upon instantiation. In these cases you're really at a loss for what to do when you need to do some work on a different thread that will eventually need to touch the UI thread.  If you can safely make the assumption that the object will normally be executing in the scope of the UI thread, then you can get your worker-threads back into the context of the UI thread by using System.Threading.SynchronizationContext.  If you make sure to grab a hold System.Threading.SychronizationContext.Current before forking off your worker thread, the worker thread will be able to get back to the UI thread to execute any UI-bound code.

Now let's look at an example of this in my implementation of a simple "timer" delayed event handler:

private System.Threading.ThreadStart _Click_DelayedEventHandler;
private System.Threading.SynchronizationContext _SynchronizationContext;
private void button1_Click(object sender, EventArgs e) {
   if (_Click_DelayedEventHandler != null) {
      return;// we're already waiting - bail out
   } else {
      // setup the 'wait until finished' anonymous method:
      _Click_DelayedEventHandler = delegate() {
         System.Threading.Thread.Sleep(1000);
      };
      AsyncCallback finishedWaiting = delegate(IAsyncResult ar) {
         // Take note that at this point you will still be executing on a threadpool thread,
         // not on the UI thread.
         SendOrPostCallback hello = delegate(object state) {
            MessageBox.Show(this, "Hello World", "Caption", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
         };
         _SynchronizationContext.Post(hello, null);

         // Make SURE to cleanup or this will be a one-trick pony
         _Click_DelayedEventHandler = null;
         _SynchronizationContext = null; // don't strictly need to set this to null...
      };

      // fire off our anonymous 'waiter' method on a thread-pool thread:
      _SynchronizationContext = SynchronizationContext.Current;
      _Click_DelayedEventHandler.BeginInvoke(finishedWaiting, null);
   }
}

To try this out, you should be able to create a new Windows Forms project, place a button on the form, hook up this method as the Click event handler. When you run, you should be able to click the button, and no matter how many times you click, you'll only get the "Hello World" message box after one second has elapsed since your first button click. So, what happens is, on the first click, it sets up a ThreadStart delegate, and executes it.  The only thing this does is wait for one second.  If any more button-clicks come in during this second, they're ignored. After the second, the ThreadStart finishes, and the 'finished' callback executes.  It's in this anonymous method that we place our real work.  In the finishedWaiting you can see the use of the UI SynchronizationContext - you create a SendOrPostCallback delegate and .Post it to the UI SynchronizationContext. At the end of finishedWaiting is very important cleanup code.  If you don't set the _Click_DelayedEventHandler back to null, this whole thing is just a one-trick pony.

 

Here's the more-useful "I'll wait for you to stop clicking" delayed event handler:

private System.Threading.ThreadStart _ButtonClick_DelayedEventHandler;
private DateTime _ButtonClick_LastChangeTime;
private object _ButtonClick_LastChangeLock = new object();
private System.Threading.SynchronizationContext _ButtonClick_SynchronizationContext;
private void button1_Click(object sender, EventArgs e) {
   if (_ButtonClick_DelayedEventHandler != null) {
      // we're already waiting...increment the 'last fired' indicator.
      lock (_ButtonClick_LastChangeLock) { _ButtonClick_LastChangeTime = DateTime.UtcNow; }
   } else {
      // setup the 'wait until finished' anonymous method:
      _ButtonClick_DelayedEventHandler = delegate() {
         // this will loop indefinately until the event hasn't been
         // raised for at least 1 second
         TimeSpan difference = TimeSpan.Zero;
         do {
            System.Threading.Thread.Sleep(100);

            DateTime lastChanged;
            lock (_ButtonClick_LastChangeLock) { lastChanged = _ButtonClick_LastChangeTime; }
            difference = DateTime.UtcNow.Subtract(lastChanged);
         } while (difference.TotalMilliseconds < 1000);
      };
      AsyncCallback finishedWaiting = delegate(IAsyncResult ar) {
         SendOrPostCallback hello = delegate(object state) {
            MessageBox.Show(this, "Hello World", "Caption", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
         };
         _ButtonClick_SynchronizationContext.Post(hello, null);


         // Make SURE to cleanup or this will be a one-trick pony.
         _ButtonClick_LastChangeTime = DateTime.MinValue;
         _ButtonClick_DelayedEventHandler = null;
         _ButtonClick_SynchronizationContext = null;
      };


      // fire off our anonymous 'waiter' method on a thread-pool thread:
      _ButtonClick_SynchronizationContext = SynchronizationContext.Current; // assumed to be the UI thread
      _ButtonClick_LastChangeTime = DateTime.UtcNow;
      _ButtonClick_DelayedEventHandler.BeginInvoke(finishedWaiting, null);
   }
}


To try this out, do the same steps as the first delayed event handler - hook this up to a button-click.  The difference you'll notice is that as long as you keep clicking the button at least once every second, the message box will not pop up.  It waits for you to finish hammering the button.  Again, it's very important that you cleanup at the end of finishedWaiting, and note that there is more to cleanup this time.

Anyway, feel free to customize to your needs, and I really hope these are useful to someone other than myself.

Thursday, October 16, 2008

Quick Highlighter - Quick and Easy HTML/CSS Code Highlightin

Hey everyone, just a quick post.  I recently learned about Quick Highlighter. It will take just about any piece of code you can throw at it and spit out snazzy HTML/CSS syntax highlighter code.  I just used it in my previous post. It's great.

I've used the CopySourceAsHtml AddIn for Visual Studio 2003/2005/2008, but I've always disliked it because my color scheme in Visual Studio doesn't translate well to HTML.  My blog has a dark-text-on-light-background scheme while I code in a light-text-on-dark-background scheme.  Plus, it doesn't help me when I want to post a piece of VB6 code, or code in any other language for that matter.

So, give it a try.  I like it. :)

Troubles Marshalling System.Guid to Visual Basic 6.0 (VB6)

I find myself in the <finger-quote>interesting</finger-quote> situation of having to interop a large portion of .NET code back into an older VB6 code base. As it turns out, I need to pass a System.Guid to VB6. Before exposing a property that returned a System.Guid I made sure that System.Guid was COM-Visible - it is:

[Serializable]
[ComVisible(true)]
public struct Guid : IFormattable, IComparable, IComparable<Guid>, IEquatable<Guid> {
   // remainder of metadata snipped for brevity
}

But let's get this point across right from the beginning: System.Guid is marked as being COM-Visible, but it is not usable in VB6! After adding a reference to mscorlib, VB6 will allow you to create a variable of type System.Guid:

Dim myGuid as mscorlib.Guid 

But you are unable to actually make use of 'myGuid' variable.  There is no intellisense pop-up when you type 'myGuid.', there's no way to create one or populate a declared mscorlib.Guid

I'm not entirely sure what the problem is, but I have to assume it has to do with Guid being a struct. Regardless of the reason for the problem, I still needed access to a System.Guid in VB6. Thankfully, all my problem requires is to fetch the Guid from a .NET object, and then pass that Guid to a stored procedure that's expecting a SQL Server "uniqueidentifier" data type.

I started searching. Talk about a tough problem to google for!  There's a lot of really good hits regarding Guid's an creating COM-visible classes/interfaces, but I found very little info on actually marshalling a System.Guid back-n-forth to/from .NET. Thankfully, I was able to find this post where someone is experiencing the exact same problem.  Unfortunately, there was no good answer. Someone did post a reply which didn't answer the original poster's question, but does include a link to an Adam Nathan blog post discussing customizing marshalling, which uses a System.Guid as an example. It's informative, but it doesn't offer any assistance with the problem.

For verbosity (and the morbidly curious,) let's look at a simple example illustrating the problem.  Here's an interface and a class that implements that interface, all exposed to COM. (This is in C#, but can just as easily be written in VB.NET.)

[Guid("BD95337B-0395-4773-B2FC-2BC812388BAB")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IGuidGetter {
   Guid GetSessionID();
}
 
[Guid("6AAFF9A3-875D-4955-AE09-634A88D9EC56")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[ProgId("JTPApp.GuidGetter")]
public class GuidGetter : IGuidGetter {
   public Guid GetSessionID() {
      return Guid.NewGuid();
   }
}

To use that class in VB6, we compile it, regasm it, and add the registered type lib as a reference to the VB6 project. We can now do something like:

Dim myGuidGetter As New GuidGetter
Dim myGuid As mscorlib.Guid

myGuid = guidGetter.GetSessionID()

Unfortunately, Line 4 will fail to compile, giving you a "Variable uses an automation type not supported in Visual Basic" error message.


Decorating the .NET interface and/or implementation with [MarshalAs(UnmanagedType.LPStruct)], while it does change the COM IDL (as noted and demonstrated in Adam Nathan's article,) doesn't make any difference from a VB6 standpoint. You always get the "Variable uses an automation type not supported in Visual Basic" error.


The really aggravating part for me is that I don't need to really use the Guid in VB6.  I simply need VB6 to retrieve it, and then stuff it into a stored procedure call! 

With this in mind, I tried returning the Guid as an object, but figured it would have problems because I would be boxing a value type in a reference type and then expecting VB6 to know how to properly unbox it. VB6 was able to retrieve the Guid into a Variant, but upon trying to set it as a parameter to the stored procedure, I received an AccessViolationException in my .NET code. Not pretty.

Thankfully, my work-around to the problem turns out to be pretty simple: Return the Guid as a string.  So, to follow through with the example code I started above, if you change it to look like:

[Guid("BD95337B-0395-4773-B2FC-2BC812388BAB")]  
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]  
[ComVisible(true)]  
public interface IGuidGetter {  
   string GetSessionID();  
}  
 
[Guid("6AAFF9A3-875D-4955-AE09-634A88D9EC56")]  
[ClassInterface(ClassInterfaceType.None)]  
[ComVisible(true)]  
[ProgId("JTPApp.GuidGetter")]  
public class GuidGetter : IGuidGetter {  
   public string GetSessionID() {  
      return Guid.NewGuid.ToString();  
   }  
} 

It will work.  You get the Guid, as a string, in VB6.  There's only one minor catch to getting SQL Server to be able to convert that string into a unique identifier, it must be enclosed in curly braces, like so:

  1. Dim sqlCommand As New ADODB.Command
  2. With sqlCommand
  3.    .ActiveConnection = dbConn
  4.    .CommandText = "A_Stored_Procedure"
  5.    .CommandType = adCmdStoredProc
  6.    .Parameters.Refresh
  7.           
  8.    Dim tmp As String
  9.    tmp = g_UserInfo.GetSessionID()
  10.    .Parameters("@sessionID").Value = "{" & tmp & "}"
  11.           
  12.    .Execute , , adExecuteNoRecords
  13. End With
  14. Set sqlCommand = Nothing

On line 10, you can see that I wrapped it in curly-braces.  So while this works for my scenario, there's no way, that I've figured out, to easily interop a System.Guid value back and forth between .NET and VB6.

Anybody have useful insight in how to get the back-n-forth marshalling to work?  I can't imagine I'm the only one who's ever needed to do this.

Sunday, July 20, 2008

Panographic Play using Autostitch

I've been playing with Autostitch for a while now. It's a proprietary, but free, tool for automatically stitching and blending photographs together into panographic mosaics.

For any of these pictures, click them and go download the original to see the full-size stitch.

Here's a link to the entire album:
Stitched Panographic Photos


Here's a few from the album:

This is the bridge over the Eagle River in Eagle River. Any family members recognize this stitch from the family blog? This is the raw stitch before cropping and editing.


Here's a shot from Winter Carnival 2008. This is the delt-sigs statue. Again, download the original full-size image and check out the detail. :) (This could use some contrast-adjusting...)


This is the lower Hungaria falls. This was a simple three-photo stitch. I had to rotate the pictures 90 degrees before stitching.


This is the spring 2008 run-off on the Tioga River in between Houghton and Marquette at a roadside park. It's just outside of Alberta by a few miles. I had photos to go in the bottom left of the photo, but autostitch didn't include them in the stitch for whatever reason.


This is another of the Tioga river.

There's more in the album, so click-through to see them all.

Tuesday, May 20, 2008

XPS Documents Opening With Firefox in Vista

So there I was, I had completed a transaction at a 'save this page for your records' page that I wanted to a PDF. Ooops...I don't have Acrobat on this laptop. Luckily I knew I had the XPS 'printer' installed by default. (It comes with Vista.)

So I print it to an .xps file. it appears to 'print' successfully. The problem is, when I doublt-click it to open it, it opens in Firefox! Firefox is set as my default browser, but I find this weird - I thought there was an XPS viewer application. I right-click the .xps file and choose "Open With" and then "XPS Viewer". Again, it tries to open in Firefox. Firefox doesn't understand the .xps extension and offers to save the file for me, or let's me open it with "XPSViewer.Document (default)". Huh. Ok. I tell Firefox to open it with the "XPSViewer.Document (default)". Firefox opens yet another tab with the .xps document, offering again to save/open it for me.




Okaaaaay.

I caught wind that you should try dropping the file into Internet Explorer. This works. IE recognizes the file and opens the XPS document for you. I also caught wind that this isn't just a Firefox problem, but rather a "default browser is not IE" problem. One solution to the problem is to re-associate the .xps file extension with IE instead of the "XPS Viewer" application. The other route (which I prefer) is to download the "XPS Essentials" pack from Microsoft. I think this pack was originally intended to add XPS functionality to XP and Server 2003, but it now supports Vista, and it appears to merge into Vista as some sort of update.

After I installed this update, double-clicking an .xps file still opened it in Firefox though! Come on! If I right-click on the .xps file, and choose "Open With", I now have a new item: "XPS Viewer EP". Ah-ha! This opens up the .xps file in it's own separate non-browser viewer. Perfect!

All that's left to do is make XPS Viewer EP the default app for .xps files. The first time I tried changing a file extension association in Vista, I went about it the way you do in XP - go to the Tools->Folder Options menu. But in Vista, the dialog that get for Folder Options doesn't have the familiar file associations tab that XP has. In Vista, you have to use the "Default Programs" Start menu item. (Just use the quick-search, or you should be able to find it under the Control Panel:



Just hit the ''Associate a file type or protocol with a program" item and you should be able to figure it out from there.

Friday, April 11, 2008

AnkhSVN and VS2008

I found this post by Damien Guard useful on getting AnkhSVN to work with VS2008.

Sorry for such a short post...

Wednesday, April 02, 2008

Rolling Back An SVN Repository

Yeesh...  Talk about a minor panic.

Don't ask me how, because I'm not entirely sure what I did to make it happen.  But, I thought I went to commit some code, and when I hit 'commit', instead of committing the code, it decided that I had flagged my entire working directory and the point in the repository for deletion. 

I really don't know what happened.

I thought I'd fix the problem by simply pulling out my stuff at a previous revision, and recommitting. This seemed problematic because I'd have to re-add the trunk, and there would still be a delete in the history, and I'm sure that could cause merge-hell in the future. 

Some quick googling turned up Tim Hatch's very simple instructions on how to rollback an SVN commit. Tim apparently accidentally checked some confidential information into his public repository and explains how to rollback a revision by doing something really dirty: manually modifying your repository.

I won't rehash what he explains, but know that his solution worked for our FSFS based repository.

 

Thanks Tim!