KnowDotNet

Using Com Interop to do the "Impossible"

by William Ryan

If you are reading this article, you obviously have an interest in learning .NET or learning more about it.  Well, this article isn't really about learning .NET although that will be the side effect of (at least I hope it will be).  The main thing I want to show is how to get around limitation of the current CLS.  You see, .NET is a wonderful environment and is nothing short of amazing in many regards.  However, it's only been out for a little over a year and nothing is fully mature after only a year (if you ask my mom, some things don't mature even after 31 years, but that's a different story).  If you look at all of the things .NET does, it's hard to believe it was ever made in the first place.  For one thing, being able to write code in C# or VB.NET and have it compile to almost identical IL is pretty amazing.  The new features of Visual Studio are pretty cool too.  So as you can see, Microsoft had a lot of work to do when making .NET.  And they still do.  As such, many things aren't included yet.  In 10 years, there will probably be a CLS class for just about everything, but that's not the case now.  And for the next few years, there are still going to be some limitations.

If you need to use functionality that isn't yet provided, get ready to jump into the Windows API.  Now, I don't profess to be an expert on the Win32 API, but I'm no novice either.  I've done more than my share of writing COM and COM+ components, and that's probably the main reason I love .NET so much.  But let's face it, many many things haven't been incorporated into .NET yet, so much so that entire books (like .NET Framework Solutions, In Search of the Lost Win32 API by John Paul Mueller - which is a MUST have in any programming library) have been written on the subject.  So let's walk through a little example.

Let's say that your boss calls you into the office and wants some modifications done to your new .NET app.  Users are having some trouble with the UI and are complaining it's hard to use.  Since every programmer out there is an expert on Usability changing the UI to accomdate the users is out of the question.  After all, the UI is so simple to use that the dumb users must simply learn how to use it better.  You can all the other programmers agree on this so you need to look to another solution.  The next thing you look to is building a better Help system.  However, the technical writer says that every feature is documented and that when a MessageBox pops up, the users often click through it, forget what the message said and can't remember anything to put in the Help search menu.  This is also creating a problem because no one knows what the problem is since the MessageBox is no longer showing itself.  What the boss wants is something that will let the users pull up help related to the problem each time a dialog box pops up telling the users they did something wrong.  At this point, it becomes obvious that your UI is probably not as intuitive as you once thought and that your annoying MessageBoxes aren't going to take the place of an easy to navigate the system.  But these problems exist all over the system and re-writing it is out of the question right now.  You think back to that stupid
Don't Make Me Think book you saw at the store and wish you would have read it.  However it's too late for that so you have to come up with something quick.  Aha, I can embed a help button in my MessageBoxes and then I can fire help from there.  This is perfect because you don't have to admit that DialogBoxes are totally annoying and that you were wrong about them, and you can still keep the MessageBox visible while the user looks for help.  Only one problem, there is no Help button on MessageBoxes.  Huh, I'm a real programmer and I'll simply subclass the MessageBox  class.  But then you realize that it's a Sealed Class and you can't Inheritfrom it.  Some guy on the newsgroups points out that you can go under the code into IL and wipe out the Sealed directive, but you don't know IL so that's not an approach.  Desperately, you think hard about what you can do.  "I remember back in C++ I could do this, I think I could in VB6 too. This Object Oriented Stuff and Shared Methods is for the birds you say.  Then you try to good old MsgBox function thinking that will fix it.  But no, it sure won't.  What to do now?

Well, thanks to .NET's System.Runtime.Interop Libraries, you can dig into the Windows API and use the MessageBoxEx function, and if you do everything correctly, you'll be able to add a MessageBox with a Help Button, use Windows to identify that Help button as a sender of the HelpRequested Event, then use the System.Process class to start Internet Explorer and pass it the URL of the Help file your technical writer so proudly created.  If you make a class out of this MessageBox, you can call it something like NewMessageBox and make the appropriate changes in your code and have enough time to practice up on your new XBOX game Street Vol. 2.

Ok, off we go (please note that I'm using C# solely for this example.  While I hate VB.NET vs. C# debates, I think C# and its support of unsafe code makes it the preferable language when using COM and interop code.  VB.NET can handle most of it most of the time, but anytime you need unsafe blocks you have to go to C# anyway, so C# is probably the language of choice for this type of stuff):

First, add the namespace constants so you can use an abbreviated syntax...(these are in addition to the ones that C# adds by default)

using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Reflection;
using System.Runtime;
using System.Resources;


Now, we are going to use the MessageBoxEx API as I mentioned, here's the declaration:

//I origninally learned this in John Paul Mueller's
//book,  .NET Framework Solutions, In Search of the Lost
// Win32 API - I've modified it some but he's the guy
//who is owed the credit!
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MessageBoxEx(
    IntPtr hWnd,
    [MarshalAs(UnmanagedType.LPTStr)]String Message,
    [MarshalAs(UnmanagedType.LPTStr)]String Header,
    UInt32 Type,
      UInt16 LanguageID);


At this point, we've set up the function so that we can call it, passing in the appropriate structs and enums essentially the same way we would a standard function.  Now, in order to use the API, you can either read through the documentation and remember the HEX and Octal representations of everything, or you can use Enums and the like.  The ability to read Hex on the fly has never been one of my stronger points, or Octal or Binary for that matter, so I'll defer to the latter.

/// <summary>
///
These are all of the buttons that you can add to your new MessageBox
/// class.  Notice the last one  - Help.  This is a prime example of a
/// time when you need to hit the API if you want to add some pretty
/// common and simple functionality
/// </summary>
public class MBButton
{
  
public const UInt32 MB_OK =                0x00000000;
  
public const UInt32 MB_OKCANCEL=             0x00000001;
  
public const UInt32 MB_ABORTRETRYCANCEL =          0x00000002;
  
public const UInt32 MB_YESNOCANCEL =          0x00000003;
  
public const UInt32 MB_YESNO =             0x00000004;
  
public const UInt32 MB_RETRYCANCEL =       0x00000005;
  
public const UInt32 MB_CANCELTRYCONTINUE =    0x00000006;
  
public const UInt32 MB_HELP =             0x00004000;
}


/// <summary>
///
This is a list of the possible Icon types, also
/// natively in Windows, however, a few of them aren't in the
/// framework YET
/// </summary>
///
<param name="sender"></param>
///
<param name="e"></param>
public class MBIcon
{
  
public const UInt32 MB_ICONHAND =       0x00000010;
  
public const UInt32 MB_ICONQUESTION =    0x00000020;
  
public const UInt32 MB_ICONEXCLAMATION=   0x00000030;
  
public const UInt32 MB_ICONASTERISK =    0x00000040;
  
public const UInt32 MB_USERICON =       0x00000080;
  
public const UInt32 MB_ICONWARNING =    MB_ICONEXCLAMATION;
  
public const UInt32 MB_ICONERROR =       MB_ICONHAND;
  
public const UInt32 MB_ICONINFORMATION =  MB_ICONASTERISK;
  
public const UInt32 MB_ICONSTOP    =     MB_ICONHAND;
}
//Notice that since we defined a few of these already we can immediately use them
//as we are doing for MB_ICONWARNING and the rest.  These ones use the same
//values as the first, so you can use the named constant instead of
//trying to remember their values


/// <summary>
///
Here are the default buttons, once again pretty standard stuff
/// </summary>
///
<param name="sender"></param>
///
<param name="e"></param>
public class MBDefButton
{
  
public const UInt32 MB_DEFBUTTON1 =       0x00000000;
  
public const UInt32 MB_DEFBUTTON2 =       0x00000100;
  
public const UInt32 MB_DEFBUTTON3 =       0x00000200;
  
public const UInt32 MB_DEFBUTTON4 =       0x00000300;
            
}


public class MBModal
{
  
public const UInt32 MB_APPLMODAL    =    0x00000000;
  
public const UInt32 MB_SYSTEMMODAL    =    0x00001000;
  
public const UInt32 MB_TASKMODAL    =    0x00002000;
}

public class MBSpecial
{
  
public const UInt32 MB_SETFOREGROUND =          0x00010000;
  
public const UInt32 MB_DEFAULT_DESKTOP_ONLY =       0x00020000;
  
public const UInt32 MB_SERVICE_NOTIFICATION_NT3x =    0x00040000;
  
public const UInt32 MB_TOPMOST =             0x00040000;
  
public const UInt32 MB_RIGHT =             0x00080000;
  
public const UInt32 MB_RTLEADING =             0x00100000;
  
public const UInt32 MB_SERVICE_NOTIFICATION =       0x00200000;
}

public enum MBReturn
{
    IDOK =    1,
    IDCANCEL =    2,
    IDABORT =    3,
    IDRETRY =    4,
    IDIGNORE =    5,
    IDYES     =    6,
    IDNO     =    7,
    IDCLOSE     =    8,
    IDHELP =    9,
    IDTRYAGAIN =10,
    IDCONTINUE =11,
    IDTIMEOUT = 32000
}



Now up until this point all we've done is declare the fuction and set up constants and enums that will allow us to call the function with various properties.  One of the cool things about .NET (well, it was true in C++ too) is the ability to pass in multiple values separated by a "|" pipe characeter so that you can pass in multiple values (flags) when you make a call.  Many times these different values compliment each other and if you could only pass one in at a time, they would have to be viewed as mutually exlcusive, which would be really lame.  In this case (you'll see in the call in a minute) that we can Call the MessageBox with the MB_CANCELTRYCONTINUE flag AS WELL AS THE HELP flag.   Now, the standard usage of this call would be something like this:  We'd have a button of some sort that the user would click, when it was clicked, the MessageBox would be shown.  Once it was shown, the user could do the usual stuff, continue, quit etc, or they could click the Help button.  If they click Help then the Form's HelpRequested Event is raised and whatever is set to handle it will occur.  Let's go with the standard usage and I'll show you how to apply this to the problem I posited earlier:

private void button1_Click(object sender, System.EventArgs e)
{
    MBReturn SomeResult;
      
//Since the SomeResult is a result which waits for the return of the Function
      //I can just call it within the switch and make it not only more clear and
      //concise, but save a few lines in the process.  More importantly, notice the
      //use of the Bit Flags which this API allows us to pass in with the Pipe | seperator

      switch(SomeResult = (MBReturn)MessageBoxEx(this.Handle, "Sample Message.",
    "SampleBox", MBButton.MB_CANCELTRYCONTINUE | MBButton.MB_HELP |
    MBIcon.MB_ICONEXCLAMATION | MBModal.MB_SYSTEMMODAL |
    MBDefButton.MB_DEFBUTTON4 | MBSpecial.MB_TOPMOST, 0))
   {
      
case MBReturn.IDCANCEL:
          MessageBox.Show("They Hit 'Return'");
        
break;
      
case MBReturn.IDTRYAGAIN :
          MessageBox.Show("They Hit 'Return'");
        
break;
      
case MBReturn.IDCONTINUE:
          MessageBox.Show("They Hit 'Continue'");
        
break;
      
case MBReturn.IDABORT:
          MessageBox.Show("Abort, but we didn't give this option so won't be chosen");
        
break;
      
case MBReturn.IDHELP:
          MessageBox.Show("This is where this could be cool");
        
break;
      
default:
          MessageBox.Show("Not sure what they chose");
        
break;
         }
}


Then, to react to the HelpRequested:

this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.Form1_HelpRequested);

private void Form1_HelpRequested(object sender, System.Windows.Forms.HelpEventArgs hlpevent)
{
    Process.Start("iexplore.exe" , "hepltopic.html");
  
//Or
   MessageBox.Show("The user requested Help: \r\n" +
       "\r\n Sender:  " + sender.ToString() +
       "\r\n Mouse Position: " + hlpevent.MousePos,
       "Help Requested",
       MessageBoxButtons.OK,
       MessageBoxIcon.Information);
          hlpevent.Handled =
true;  
}

Now, this is all precepitated upon the notion that the user will click a button.  If they could click a  button for help, then they could just use the F1 Key or select Help from the Menu.  This does nothing to address the fact that we sent an annoying DialogBox to bark at them in the first place because as developers, we didn't stop them from doing what they aren't supposed to do (as you can see, I have pretty strong feelings about using DialogBoxes to tell users they did something wrong as opposed to making an intuitive UI that allows users to figure things out easily and doesn't allow them to make mistakes only to have an obnoxious dialog box rub it in their face - but I'd rather discuss the merits of C# vs. VB.NET or Windows vs. Linux than discuss good UI's with many other developers)

So, instead of firing everything at with the button, let's say that we had an exception handler that caught a System.IOException:

try
{
  
//Something failed like a file wasn't found
  //and our annoying box is going to be shown.
}
catch(System.IO.IOException ex)
{
    MessageBox.Show("You are stupid!  Read the Manual and do it right",
       "My UI's Are the Best", MessageBoxButtons.AbortRetryIgnore,
       MessageBoxIcon.Error);

    System.Windows.Forms.HelpEventArgs hlpevent =
new System.Windows.Forms.HelpEventArgs(Cursor.Position);
    Form1_HelpRequested(
this, hlpevent); //Lazy way of rasing the event  
}


Then use the same Form1_HelpRequested handler as above.  Now, once you catch the event, you can handle it any way you want.  In this instance, I set the MB_SYSTEMMODAL and MB_TOPMOST to make it modal and make it stay on top, but I didn't have to do that. Similarly I could have chosen any button to be the default, but in this example, I wanted the Help button to be b/c I wanted to highlight using the Help button which can't be done with the traditional .NET MessageBox class.  You can see that there are many other flags that you can play with and I'd encourage you to use them.  However, remember that depending on how you set up the call, the user may cause more errors or get lost so you need to be careful and not assume that because you are a UI genius, that the user will not be able to mess things up.

This is just one example of what you can do with API Calls and what's missing, and as you can see by the amount of code it takes, things can get complex very quickly and there often isn't a whole lot of documentation on the subject.  Most people would have created their own form and put a Help button on it rather than go through all of this, although there are certianly shortcomings with that approach too (for instance, the sender of the Event is Form1 in this example, but if you make a dialog form, it would be that form.  There are other reasons for using this approach (like adding in your own pictures to name one of the cooler ones) but my goal isn't to make a case for using the WinAPI to call a MessageBox - after all MessageBox.Show does the job quite well.  Rather I wanted to show how to make an API call, set up the enums and other declarations to take full advantage of it, and remind you that there are often many ways to skin  a cat- so look around before thinking one way is the 'best'.

I'll be posting another sequel to this one soon which goes into the MessageBoxEx api in depth showing how to add a custom icon and a few other things.  I also expect to write some more on the cooler API functionality I've come across recently.  If you have anything in particular that you'd like to see, please feel free to write me atme