April 2003 - Posts

An engineer on my team recently had to hunt down a bug in some reasonably stable, well-tested code (which was compiled for, and tested on, the v1.0 runtime, but now is being executed in a v1.1 CLR environment).

He narrowed it down to this:  the result of casting a NaN or Infinity value from System.Double to System.Int32 seems to be entirely different in v1.1!  Namely, 0x80000000 instead of 0x00000000.  See code, below.

Even if these operations fall into the realm of "undefined behaviour", I can't imagine how such a drastic change is justified.

using System;

class Class1
{
    [STAThread]
    static void Main(string[] args)
    {
        double d=1.0;
        int i=1;

        d = d/0.0;
        i = (int)(d);

        System.Console.WriteLine("1.0/0.0 (double)="+d+"  (int)="+i);

        d = -d;
        i = (int)(d);

        System.Console.WriteLine("-1.0/0.0 (double)="+d+"  (int)="+i);

        d = 0;
        d /= d;
        i = (int)(d);

        System.Console.WriteLine("0.0/0.0 (double)="+d+"  (int)="+i);

        d = 48000.0*48000.0;
        i = (int)(d);

        System.Console.WriteLine("(int overflow) 48000*48000 (double)="+d+"  (int)="+i);
    }
}
/*
C:\Temp>csc TomBug.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.


C:\Temp>TomBug.exe
1.0/0.0 (double)=Infinity  (int)=0
-1.0/0.0 (double)=-Infinity  (int)=0
0.0/0.0 (double)=NaN  (int)=0
(int overflow) 48000*48000 (double)=2304000000  (int)=-1990967296

C:\Temp>copy RunUnderEverett.exe.config .\TomBug.exe.config
        1 file(s) copied.

C:\Temp>type TomBug.exe.config

<configuration>
    <startup>
        <supportedRuntime version="v1.1.4322"/>
        <requiredRuntime version="v1.1.4322"/>
    </startup>
</configuration>

C:\Temp>TomBug.exe
1.0/0.0 (double)=Infinity  (int)=-2147483648
-1.0/0.0 (double)=-Infinity  (int)=-2147483648
0.0/0.0 (double)=NaN  (int)=-2147483648
(int overflow) 48000*48000 (double)=2304000000  (int)=-2147483648

*/

The saga continues.  Soon after I reported the terrible timing-sensitive bug in the VS automation layer, I got a friendly email from someone smarter than me, and with a microsoft.com email address.  That's the kind I like..!

I've forgotten how most of this COM/OLE goo works, but this kind soul reminded me that OLE message filters (ala IMessageFilter, but not the one you're thinking of!) are the key to avoiding call-rejected errors from COM plumbing.

Code follows.  Caveat emptor -- but it works for me!

using System;
using System.Runtime.InteropServices;

/*
This class implements an OLE message filter, appropriate for use with the VS automation clients.

Sample usage:

    [STAThread]
    private static void Main()
    {
        // We avoid instantiating the RCW as "new DTE()", because the both PIAs for v7.0 and v7.1 point 
        // to the same CLSID (VS 7.0's)... 
        Type latestDTE = Type.GetTypeFromProgID("VisualStudio.DTE"); //ver-indep progid
        EnvDTE.DTE dte = Activator.CreateInstance(latestDTE) as EnvDTE.DTE;

        // Register an OLE message filter to avoid "call rejected" exceptions
        MessageFilter.Register();

        // Drive the VS automation interface
        Installer1.AddToolBoxTab(dte); // or whatever

        // Unplug the message filter
        MessageFilter.Revoke();
    }
*/

class MessageFilter : IOleMessageFilter
{
    //
    // Public API

    public static void Register()
    {
        IOleMessageFilter newfilter = new MessageFilter(); 

        IOleMessageFilter oldfilter = null; 
        CoRegisterMessageFilter(newfilter, out oldfilter);
    }

    public static void Revoke()
    {
        IOleMessageFilter oldfilter = null; 
        CoRegisterMessageFilter(nullout oldfilter);
    }

    //
    // IOleMessageFilter impl
    
    int IOleMessageFilter.HandleInComingCall(int dwCallType, System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr lpInterfaceInfo) 
    {
        System.Diagnostics.Debug.WriteLine("IOleMessageFilter::HandleInComingCall");

        return 0//SERVERCALL_ISHANDLED
    }

    int IOleMessageFilter.RetryRejectedCall(System.IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
    {
        System.Diagnostics.Debug.WriteLine("IOleMessageFilter::RetryRejectedCall");

        if (dwRejectType == 2 ) //SERVERCALL_RETRYLATER
        {
            System.Diagnostics.Debug.WriteLine("Retry call later");
            return 99//retry immediately if return >=0 & <100
        }
        return -1//cancel call
    }

    int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
    {
        System.Diagnostics.Debug.WriteLine("IOleMessageFilter::MessagePending");

        return 2//PENDINGMSG_WAITDEFPROCESS 
    }

    //
    // Implementation

    [DllImport("Ole32.dll")]
    private static extern int CoRegisterMessageFilter(IOleMessageFilter newfilter, out IOleMessageFilter oldfilter);
}

[ComImport(), Guid("00000016-0000-0000-C000-000000000046"),    
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
interface IOleMessageFilter // deliberately renamed to avoid confusion w/ System.Windows.Forms.IMessageFilter
{
    [PreserveSig]
    int HandleInComingCall( 
        int dwCallType, 
        IntPtr hTaskCaller, 
        int dwTickCount, 
        IntPtr lpInterfaceInfo);

    [PreserveSig]
    int RetryRejectedCall( 
        IntPtr hTaskCallee, 
        int dwTickCount,
        int dwRejectType);

    [PreserveSig]
    int MessagePending( 
        IntPtr hTaskCallee, 
        int dwTickCount,
        int dwPendingType);
}

I used to think the System.String class was a little confusing -- a reference type which behaves like a value type, by virtue of the simple fact that all of its methods are "const".

Then I peeked under the hood of System.Enum...  why?  I can't remember.  But I'm really confused by what I'm seeing.

The MSDN docs that shipped w/ VS 7.0 state the following:  "Enum derives from ValueType, but is not a value type."  Hmm...  apparently that wasn't unclear enough, so they updated the docs for VS 7.1:  "Class Enum is derived from class ValueType; that is, Enum is itself a reference type, not a value type."

Ok, kids -- I enjoy a good joke, but that's too much.  What's the real story, here?

I whipped out ILDASM and a managed debugger, and I must say it looks to me like every enum I declare looks and feels just like a valuetype -- with pass by value semantics, the IL "valuetype" keyword, the whole nine yards.

But when I type my code more loosely, with variables and parameters typed as the abstract class System.Enum, I do indeed see pass by reference semantics (eg: the enum argument gets boxed before it's passed to a method).  So it seems the docs are correct.

But why?  This design decision doesn't make a whole lot of sense to me.

And what does it really mean to have an instance of a System.Enum-derived type?  The System.Enum class implements a number of interfaces, but the C# compiler complains when I try to cast a variable of an Enum-derived type to, say, IComparable.  (You get no such complaint when casting an ordinary value type, such as a structure.)  That seems to be just a compiler hiccup, because ILASM seems to treat enums and structs the same, in this regard.

Hmm... you know, now that I think about it, I guess even System.ValueType is not a really a valuetype, either.  This is making my head hurt...  but I feel I'm on the verge of an epiphany.

 

Everybody has been following along with my little toolbox saga, right?

It just doesn't let up.  Today it threw me another curve -- a timing bug! -- apparently it's failing in our test lab, on slow machines (think: sub-900 MHz laptops, w/ slow busses).

Sometimes I get a "call was rejected by the callee" exception, sometimes just silent failure (no exceptions, but no new toolbox items either).  When the exceptions do occur, it's never in a predictable spot (just one of the several calls in to VS).

The bug seems to manifest on both VS 7.0 and 7.1, but I'm not 100% sure of that.  I managed to repro on my high-end dev box, by cranking my SETI@Home client's process priority up to "high".  ;-)

Sprinkling around a few Thread.Sleep calls seems to fix the problem nicely... well, not "nicely", but what else can I do?

        // Get DTE from version-indep progid (points to newest)
        Type latestDTE = Type.GetTypeFromProgID("VisualStudio.DTE");
        EnvDTE.DTE dte = Activator.CreateInstance(latestDTE) as
            EnvDTE.DTE;

        // Repeat attempt to drive VS, with ever-increasing handicap
        for (int delayFudge=100; delayFudge < 5000; delayFudge += 1000)
        {
            try
            {
                AddToolboxItems(dte,delayFudge);
                break// exit loop
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message + "\nTrying again...");
            }
        }

At what point do I just give up, and look for a new job?

Recently I put together a series of small peer-to-peer demo apps...  too lazy to create yet another dialog/form with cryptic prompts for users to enter hostnames, port numbers, other endpoint info (these demos were going to be used by managers, after all!) I came up with the idea of using UDP multicast to advertise the peers' presence to each other.

Well, I'm sure I didn't invent this technique, but I couldn't seem to find much .NET-specific info out there...  So, I hashed out my experience in a short article.  Enjoy!

http://www.ondotnet.com/pub/a/dotnet/2003/04/14/clientserver.html

Or, just skip to the code...

http://www.ondotnet.com/dotnet/2003/04/14/examples/Listing1.cs

 

A few weeks ago, I blogged about how to programmatically add .NET UserControls to a VS Toolbox tab.

Today my teammates and I got around to testing/updating the code for VS 2003 -- as it happens, the code I presented in the blog is correct, but in practice getting it to work proved to be another royal p.i.t.a....

This time, the problem we ran into lied in how we were automating VS:  by instantiating a new instance of EnvDTE.DTE, from outside the devenv.exe process. 

For VS 7.0, we went about this by cavalierly instantiating the managed wrapper for the VS automation model:  dte = new EnvDTE.DTE();  No problem, right?

But guess what -- the managed wrapper for VS (EnvDTE.dll) that's shipping with the v1.1 framework is binary identical to the one in v1.0 framework!  No kidding!  They point to the same underlying CLSID, which, as it happens, is the old (VS 7.0) CLSID. 

All of the VS automation interfaces are the same, so I guess MS decided they didn't need to ship a new PIA for VS 7.1.  But the CLSIDs have changed, in order to support side-by-side coexistance of VS 7.0 and 7.1...  and the PIA contains references to the CLSIDs!  Kaboom.  For our little use-case, anyway.

In the end, we determined the best/easiest thing to do was to instantiate the RCW by going through the version-independent ProgID (which seems to correctly point to the latest):

            // Get DTE from version-indep progid (points to newest)
            Type latestDTE = Type.GetTypeFromProgID("VisualStudio.DTE");
            EnvDTE.DTE dte = Activator.CreateInstance(latestDTE) as EnvDTE.DTE;

Hope that helps somebody...

 

Those of you who follow my pathetic little blog know I do a lot of Tablet PC development these days -- and lead a team of really smart guys who do shape-recognition stuff.

As you can imagine, this type of programming involves a lot of 2D geometry.

One of my engineers hooked a profiler up to our unit-test, the other day, and was surprised to see that his processor was spending 75% of its time in System.Drawing.Point.get_X and .get_Y.

Even on Release builds.

So we wrote a little wrapper class, FastPoint, which exposes X and Y as public fields, rather than properties.  A few "using Point=FastPoint;"s later, 75% of our cycles just disappeared into thin air.

The UI on DevPartner's profiler is a bit convoluted... am I just using it wrong, or is the JIT really not inlining Point.get_X/Y? 

How trivial does a method need to be, before it's inlined?

using System;
using System.Drawing;

namespace Leszynski.Ink
{
    // Profiling shows a lot of time wasted in calls to Point.get_X/Y.  It's a shame that the CLR 
    // isn't able to inline these trivial little function calls. :(  So we use this "adapter" 
    // class, in order to gain access to the x/y members, as fields.
    internal struct FastPoint
    {
        internal readonly int X;
        internal readonly int Y;

        // Construction and conversion
        internal FastPoint( System.Drawing.Point p)
        {
            this.X = p.X;
            this.Y = p.Y;
        }
        internal FastPoint( int x, int y)
        {
            this.X = x;
            this.Y = y;
        }
        internal System.Drawing.Point ToPoint()
        {
            return new Point( this.X, this.Y);
        }

        // Canonical value-type comparison support
        public override bool Equals( Object obj) 
        {
            return ((obj is FastPoint) && (this == (FastPoint)obj));
        }
        public override int GetHashCode() 
        {
            return this.X.GetHashCode() ^ this.Y.GetHashCode();
        }
        public static bool operator==( FastPoint a, FastPoint b) 
        {
            return (a.X==b.X && a.Y==b.Y);
        }
        public static bool operator !=( FastPoint a, FastPoint b) 
        {
            return !(a==b);
        }
    }
}
More Posts