Cascading .NET Versioning In The Path

by Jon Davis 16. December 2007 15:21

The .NET Framework v3.0 and v3.5 both broke some versioning rules, and I suppose there was good reason. Version 3.0 was just version 2.0 plus a few new DLLs for WPF, WCF, WF, and CardSpace. But those new technologies were huge, huge enough to give the .NET Framework a new version number. Meanwhile, though, the C# language and .NET Framework 3.5 forked off cosistency again, in a way that was unnecessary, and with double the damage. C# 3.0 runs on .NET 3.5 -- that is way too confusing. C# 3.0 compiles to the .NET 2.0 CLR. That is way too confusing, too. .NET Framework 3.5 builds on top of .NET Framework 3.0 and 2.0. That would make sense, except that v3.0 and v2.0 are sort of seperate entities.

If you open up C:\Windows\Microsoft.NET\Framework, you'll find each of these .NET Framework versions deployed in their own directory..

08/29/2007  12:30 AM    <DIR>          v1.0.3705
08/29/2007  01:01 AM    <DIR>          v1.1.4322
12/15/2007  03:13 PM    <DIR>          v2.0.50727
11/02/2006  08:15 AM    <DIR>          v3.0
11/19/2007  09:49 PM    <DIR>          v3.5
08/28/2007  05:22 PM    <DIR>          VJSharp

Incidentally, notice that v3.0 and v3.5 don't have build numbers, while the others do. Refer back to my rant about inconsistency. (Personally, I never liked the build numbers being there anyway.) Meanwhile, VJSharp is completely unversioned.

Then there are tons of utility files that do not belong in this directory at all, and Microsoft allowed a mess to be made in here:

08/29/2007  05:39 AM    <DIR>          1028
08/29/2007  06:58 AM    <DIR>          1030
08/29/2007  02:57 AM    <DIR>          1031
08/29/2007  03:10 AM    <DIR>          1035
08/29/2007  04:13 AM    <DIR>          1036
08/29/2007  06:05 AM    <DIR>          1040
08/29/2007  03:55 AM    <DIR>          1042
08/29/2007  07:27 AM    <DIR>          1043
08/29/2007  06:31 AM    <DIR>          1044
08/29/2007  05:16 AM    <DIR>          1046
08/29/2007  03:39 AM    <DIR>          1049
08/29/2007  03:23 AM    <DIR>          1053
08/29/2007  04:33 AM    <DIR>          2052
08/29/2007  04:53 AM    <DIR>          3082
11/01/2006  11:33 PM            72,704 NETFXSBS10.exe
02/20/2003  06:44 PM            36,354 NETFXSBS10.hkf
09/18/2006  02:32 PM            41,392 netfxsbs12.hkf
11/19/2007  09:09 PM            16,896 sbscmp10.dll
11/19/2007  09:09 PM            16,896 sbscmp20_mscorwks.dll
11/19/2007  09:09 PM            16,896 sbscmp20_perfcounter.dll
11/01/2006  11:33 PM             5,120 sbs_diasymreader.dll
11/01/2006  11:33 PM             5,120 sbs_iehost.dll
11/01/2006  11:33 PM             5,120 sbs_microsoft.jscript.dll
11/01/2006  11:33 PM             5,632 sbs_microsoft.vsa.vb.codedomprocessor.dll
11/01/2006  11:33 PM             5,120 sbs_mscordbi.dll
11/01/2006  11:33 PM             5,120 sbs_mscorrc.dll
11/01/2006  11:33 PM             5,120 sbs_mscorsec.dll
11/01/2006  11:33 PM             5,120 sbs_system.configuration.install.dll
11/01/2006  11:33 PM             5,120 sbs_system.data.dll
11/01/2006  11:33 PM             5,120 sbs_system.enterpriseservices.dll
11/01/2006  11:33 PM             5,120 sbs_VsaVb7rt.dll
11/01/2006  11:33 PM             5,120 sbs_wminet_utils.dll
11/19/2007  09:09 PM            16,896 SharedReg12.dll

Now we're one step closer to looking like the infamous Windows Registry. Each Microsoft employee or department gets to put his own file or entry wherever he wants, see? I wonder how many Microsoft employees have decided to move their office desks to the middle of the front lobby, besides the nice front desk lady(ies).

Anyway, the reason why I posted here is because, ironically, the .NET Framework does not appear in the PATH for some strange reason. I've always had to manually add "DOTNET", pointing to C:\Windows\Microsoft.NET\Framework\v1.1.4322 or ...\v2.0.50727 to my environment variables, then add %DOTNET% to my PATH. I used this regularly for tools like my own AssemblyLister so I could easily pre-JIT my assemblies for faster boot time (even if the trade-off is slower runtime performance). But this broke with v3.0 and it is still broken in v3.5, because there is no CLR and Framework directory that runs .NET Framework v3.5. .NET Framework v3.5, like v3.0, is just some add-on assemblies to v2.0. But if I were to reference only the v2.0 directory, I would only have the v2.0 framework (plus the GAC, which, fortunately, contains the v3.0 and v3.5 DLL assemblies, but not their utilities).

Fortunately, you can cascade the .NET versions in the PATH. I don't know why I didn't do this a long time ago, but the PATH environment variable, which is a semi-colon delimited list of directories in which to search for files in the file system shell without referencing their complete paths, is already a cascading list. In other words, when searching for a file using the PATH, the first directory listed is scanned first, then the second, and so on.

One thing I like about the .NET Framework versioning that Microsoft claimed that they are committed to when they were working on v2 was that the .NET Framework will try to be forwards-compatible and will always be backwards-compatible to assemblies targeting different versions of the CLR. This means that a v1.1 assembly can be run in a v2.0 CLR, and a v2.0 assembly might be able to run in a v1.1 CLR. In the end, it's just MSIL that the CLR breaks down into machine code at runtime (unless it's pre-JITted).

So as long as you're using an environment (such as cmd.exe or PowerShell) that splits the PATH string into multiple directores, recursively finding more %variables%, and scans them one by one in cascading order, you can effectively use a single environment variable to reference all of your .NET Framework directories at the same time, with the newer .NET Framework files taking priority over the older files. To do this, just add each .NET Framework version, starting with v3.5, then v3.0, then v2, then v1.1 (if v1.1 is installed), into a DOTNET environment variable, and then add %DOTNET% to your path.

  • DOTNET = C:\Windows\Microsoft.NET\Framework\v3.5; C:\Windows\Microsoft.NET\Framework\v3.0; C:\Windows\Microsoft.NET\Framework\v2.0.50727; C:\Windows\Microsoft.NET\Framework\v1.1.4322; C:\Windows\Microsoft.NET\Framework\v1.0.3705
  • PATH (virtually on %PATH%, not literally) = %PATH%;%DOTNET% 

By "virtually on %PATH%, above, all I mean is that you would replace %PATH% with what it already is, then append ";%DOTNET%".

If I wanted to use the 64-bit versions of the .NET Framework, I could do the same, using C:\Windows\Microsft.NET\Framework64\, but meanwhile also adding the x86 paths for compatibility.

  • DOTNETX64 =
     
    C:\Windows\Microsoft.NET\Framework64\v3.5; C:\Windows\Microsoft.NET\Framework64\v3.0; C:\Windows\Microsoft.NET\Framework64\v2.0.50727; C:\Windows\Microsoft.NET\Framework\v3.5; C:\Windows\Microsoft.NET\Framework\v3.0; C:\Windows\Microsoft.NET\Framework\v2.0.50727; C:\Windows\Microsoft.NET\Framework\v1.1.4322; C:\Windows\Microsoft.NET\Framework\v1.0.3705
     
    - or -

    C:\Windows\Microsoft.NET\Framework64\v3.5; C:\Windows\Microsoft.NET\Framework64\v3.0; C:\Windows\Microsoft.NET\Framework64\v2.0.50727; %DOTNET%
  • PATH = %PATH%;%DOTNETX64%

Unfortunately, though, this brings another issue into the mix. Which one do you want in your PATH, %DOTNET% or %DOTNETX64%? This is an important question. I do ASP.NET development in 32-bit and I debug my apps in explicit x86 CPU build mode (because Visual Studio doesn't let me perform edit-and-continue tasks in 64-bit, which is already the default environment for the "Any CPU" build mode). But without compiling to 64-bit, CLR assemblies can hit the RAM registry space ceiling, quickly running out of RAM (OutOfMemoryException or something similar), and this has happened to me already while building a search engine server based on Lucene.NET.

To be honest, I'm still not sure. I'm going to try using the 64-bit path variable (%DOTNETX64%) for now and see if it brings me any problems. I think Windows is defaulting to that one already. Meanwhile, though, I can still continue to target x86 CPUs in my builds.

So to test this out, I try where in Vista to see the cascading effect. (Note that any time you change the PATH environment variable, or any environment variable(s), you must close the command shell window and restart it before the variables will propogate. They will not propogate into the GUI shell until you log out and log back in.)

C:\Users\Jon>where ngen
C:\Windows\Microsoft.NET\Framework64\v2.0.50727\ngen.exe
C:\Windows\Microsoft.NET\Framework\v2.0.50727\ngen.exe
C:\Windows\Microsoft.NET\Framework\v1.1.4322\ngen.exe

So it seems to work, which is nice.

Meanwhile, I still need an environment variable to single directory for my primary CLR, which contains ngen.exe and csc.exe and other CLR essentials.

  • CLRDIR = C:\Windows\Microsoft.NET\Framework64\v2.0.50727

There's one last change I need to make, though. It might make more sense to change the environment variable %DOTNET% to %NETFXX86%, and then change %DOTNETX64% to %NETFX%. This way, apps that target a %NETFX% environment variable can properly target the environment's complete and CPU-targeted environment rather than just focus solely on x86 compatibility.

So, here's what I have in the end:

  • NETFXX86 =
     
    C:\Windows\Microsoft.NET\Framework\v3.5;
    C:\Windows\Microsoft.NET\Framework\v3.0;
    C:\Windows\Microsoft.NET\Framework\v2.0.50727;
    C:\Windows\Microsoft.NET\Framework\v1.1.4322;
    C:\Windows\Microsoft.NET\Framework\v1.0.3705
      
  • NETFXX64 =
     
    C:\Windows\Microsoft.NET\Framework64\v3.5;
    C:\Windows\Microsoft.NET\Framework64\v3.0;
    C:\Windows\Microsoft.NET\Framework64\v2.0.50727
     
  • NETFX = %NETFXX64%; %NETFXX86%
     
  • DOTNET = %NETFX%
     
  • CLRDIR = C:\Windows\Microsoft.NET\Framework64\v2.0.50727
      
  • PATH =
     
    C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin; %SystemRoot%\system32; %SystemRoot%; %SystemRoot%\System32\Wbem; %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\; C:\Program Files (x86)\Microsoft SQL Server\90\Tools\binn\; C:\Program Files (x86)\Microsoft SQL Server\80\Tools\Binn\; C:\Program Files\Microsoft SQL Server\90\DTS\Binn\; C:\Program Files\Microsoft SQL Server\90\Tools\binn\; C:\Program Files (x86)\Microsoft SQL Server\90\DTS\Binn\; C:\Program Files (x86)\Microsoft SQL Server\90\Tools\Binn\VSShell\Common7\IDE\; C:\Program Files (x86)\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies\; C:\Program Files (x86)\QuickTime\QTSystem\; C:\Program Files (x86)\Common Files\Adobe\AGL; C:\Windows\SUA\common\; C:\Windows\SUA\usr\lib\; C:\Program Files\Microsoft Network Monitor 3\; E:\filez\dev\IronPython-1.1; C:\Program Files (x86)\GnuWin32\bin; C:\ruby\bin; C:\MinGW\bin; C:\cygwin\bin;
    %NETFX%

Now I can use "native" .NET apps like csc.exe or ngen.exe, and have all tools and assemblies on hand, without manually loading the SDK command shell.

I created an EXE to auto-configure this: SetNetFxEnvVars.exe [source] Note that it will require an immediate reboot. Note also that if you're using Internet Explorer, in order to run the EXE you must download it and then unblock it (right-click it, choose Properties, then click Unblock).

kick it on DotNetKicks.com

Currently rated 3.0 by 2 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , , , , , ,

Software Development | Microsoft Windows

Windows Communication Framework (WCF): Beware the fake IDisposable implementation !!

by Jon Davis 23. May 2007 19:07

Yeesh. My fascination with WCF became red-faced shame overnight.

We’re using WCF client/server both on a server, so an ASP.NET web app can query a custom indexing service. Since this was a fresh project with no legacy constraints, I opted to use WCF rather than remoting to..., well, to drink the kool-aid I suppose, but I thought the argument made at the AZGroups presentation that “you shouldn’t have to worry about the plumbing” was compelling. (Now that the solution is almost fully baked, I am really annoyed I went down this path simply because of the hassle I went through in having to manually populate the original strong types in a shared codebase between client and server. IMO, DataContract-driven proxy code is only useful for third parties.)

An initial WCF implementation with a simple loop of create, invoke, and drop scope a WCF client that used named pipes to a WCF service was freezing up after 12 iterations. Executing manually, roughly one iteration per second, it froze up on the 50th or so iteration.

Turned out I wasn’t calling Close() and should have been. *blush* Of course. But I looked for Dispose() to see if I could use the using() statement, and it wasn’t there. Or, wasn’t explicit, one must cast to IDisposable first before calling its Dispose() method.

Fixing that, now I was getting exceptions on Close() / Dispose() if the server had returned a Fault message. Buried deep in the far back of the WCF book I’m reading--and actually I had to use Reflector to figure this out before I looked in the book to see if I was right--is a brief mention not to use the using() statement with WCF clients, and don’t call Dispose(), either, but to call Close() manually. Dispose() on WCF clients actually call Close() internally. But just don’t expect the CLR / compiler to pick that up, and you shouldn’t always call Close(), either, but rather Abort(). Confused yet?

As I posted in Microsoft.public.windows.developer.winfx.indigo,

IDisposable was always percieved to be the happy, safe haven for getting rid of objects that use unmanaged resources. If something implemented IDisposable, Dispose() was always callable. Not so anymore.

((IDisposable)client).Dispose() can only be called on a WCF client if Close() can be called, because internally it calls Close(). Close() cannot be called unless basically it's in the Open state; otherwise, you have to execute Abort() instead, which is not a memeber of IDisposable. This means that, even though the object does indeed implement IDisposable, its *SUPPORT* for IDisposable is 100% dependent upon the caller evaluating the State of the object to determine whether or not it's open. In other words, Microsoft has established a new precedent: IDisposable mandates extraneous state-checking code before its IDisposable implementation is usable, and the only thing you can do about it is wrap it.

I might've opted to create a new interface, IReallyDispose, but then I'd still have to implement it. I could create an abstract class, WcfDisposable, but C# doesn't support multiple inheritance. The best I can do is put a sticky note on my computer monitor that reads: "WCF client objects don't REALLY implement IDisposable unless they're Open!" Then I can only hope that I'll pay attention to my stickynote when I'm going about WCF coding.

Does anyone else besides me find this to be unacceptably stupid and messy? I really *WANT* to like WCF. I love greenfield projects that use promising new technology, but when new technology abandons key design patterns like this, it really gets under my skin.

Discussing the matter further, ..

This isn't about the object not being able to Close(). I don't mind Close() raising exceptions. The core problem is that IDisposable throws an exception just because the object is in a "Faulted" state, while the object retains unmanaged resources!! IDisposable is generic and agnostic to connections/sockets/pipes/channels/streams, so I disagree when most people say "Dispose() and Close() are one and the same", because they're not. What Dispose() is supposed to do is safely unload unmanaged resources, whether that means to Close() or not. WCF shouldn't implement IDisposable if IDisposable.Dispose() will ever throw exceptions. I don't care if Dispose() calls Close(), it should wrap that call with ...

void IDisposable.Dispose()
{
if (this.State == CommunicationState.Closing ||
this.State == CommunicationState.Closed ||
this.State == CommunicationState.Faulted)
{
this.Abort();
}
else
{
this.Close();
}
}

Instead, Reflector says it's implemented as such:

	void IDisposable.Dispose()
	{
	this.Close();
	}
	

Since IDisposable has compile-time support for managing resources with Dispose, including the using() statement, this implementation is garbage.

There should be a working IDisposable.Dispose() that clears out unmanaged resources if you are *NOT* working in a transaction and have nothing to "abort" except the open connection itself. IMO, outside of a transaction, disposal of any object is an "abortion".

The bug in the design isn't just faulty Dispose(), but that IDisposable was implemented in the first place. The practice we are told to use is to ignore it, and to call Close() or Abort() ourselves. Therefore, it's not disposable, it's only Closable/Abortable, depending on state. Why, then, did they implement IDisposable?

Where does Microsoft stand on this? Well, according to this forum post [link], they couldn't figure out what to do themselves, so they released it with it with no real solution. Literally, "for good or for ill we have landed where we have", which was to try{} to Close, catch{} to Abort. Oh, nice planning. My respect for Microsoft just went down about 50 points.

Currently rated 5.0 by 3 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , ,

Software Development


 

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

About the author

Jon Davis (aka "stimpy77") has been a programmer, developer, and consultant for web and Windows software solutions professionally since 1997, with experience ranging from OS and hardware support to DHTML programming to IIS/ASP web apps to Java network programming to Visual Basic applications to C# desktop apps.
 
Software in all forms is also his sole hobby, whether playing PC games or tinkering with programming them. "I was playing Defender on the Commodore 64," he reminisces, "when I decided at the age of 12 or so that I want to be a computer programmer when I grow up."

Jon was previously employed as a senior .NET developer at a very well-known Internet services company whom you're more likely than not to have directly done business with. However, this blog and all of jondavis.net have no affiliation with, and are not representative of, his former employer in any way.

Contact Me 


Tag cloud

Calendar

<<  October 2018  >>
MoTuWeThFrSaSu
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar