Tuesday, January 12, 2010

Dot NET 2.0 Generics and Windows 98

One of my customers asked me to write a .NET application which is capable to run on old Windows 98 machines. This request came to me last year but don't ask me about the reasons of doing such a thing :).
System-Bad-Image-Format-Exception

Only .NET 2.0 is running on Windows 98

But not any .NET 2.0 release will do so. You have to consider the first .NET 2.0 release (having no Service Packs) for building a Windows 98 capable application.

The plain .NET 2.0 Generics have bugs

It seems that the complex object structures based on multiple layers of generalization can't be digested by the original .NET 2.0. Of course, the first Service Pack is fixing this problem but .NET 2.0 SP1 doesn't run on Windows 98 !! That came as a big surprise to me because everywhere on the Microsoft site you can read that .NET 2.0 is the only version able to run on Windows 98. But the .NET 2.0 SP1 is still 2.0 (is not 2.1 or anything above !!). Anyway. These were the facts and I needed to deal with them.

System.BadImageFormatException

Here is a similar structure of what I wrote for my first .NET 2.0 Windows 98 application :).

C# .NET

1 public interface IDbItem<T>

2 {      

3     bool IsSimilar(T item);       

4 }

5 

6 public interface IDbItemManager<T>

7                         where T : IDbItem<T>

8 {

9     ReadOnlyCollection<T> Items { get; }

10 

11     event EventHandler<DbItemActionEventArgs<T>> ItemAdded;

12 

13     void Add(T item);

14     T NewItem();

15 }

So I have some contracts for a generic item (IDbItem) which is handled by a generic manager (IDbItemManager). Now here is the implementation for each of these contracts:

C# .NET

1public class DbItemBase<T> : IDbItem<T>

2 {

3     public virtual bool IsSimilar(T item)

4     {

5         return false;

6     }

7 }

8 

9 public abstract class DbItemManagerBase<TContract, TImplementation> : IDbItemManager<TContract>

10     where TContract : IDbItem<TContract>

11     where TImplementation : DbItemBase<TContract>, TContract, new()

12 {

13     public ReadOnlyCollection<TContract> Items

14     {

15         get { return this.AvailableItems.AsReadOnly(); }

16     }

17 

18     public event EventHandler<DbItemActionEventArgs<TContract>> ItemAdded;

19 

20     public virtual void Add(TContract item)

21     {

22         this.AvailableItems.Add(item);

23     }

;24 

25     public virtual TContract NewItem()

26     {

27         return new TImplementation();

28     }

29 }

Instantiating the DbItemManagerBase class on a .NET 2.0 Framework will end up with this error:

System.BadImageFormatException: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)

The workaround

The solution to this is rather simple: just remove the generalization from the IDbItem and DbItemBase and ajust the IDbItemManager and the DbItemManagerBase to reflect this change.

Note that you have to handle manually any required casting in the IDbItem methods that were using the generics!

C# .NET

1 public interface IDbItem

2 {      

3     bool IsSimilar(object item);       

4 }

5 

6 public interface IDbItemManager<T>

7                         where T : IDbItem

8 {

9     ReadOnlyCollection<T> Items { get; }

10 

11     event EventHandler<DbItemActionEventArgs<T>> ItemAdded;

12 

13     void Add(T item);

14     T NewItem();

15 }

16 

17 public class DbItemBase<T> : IDbItem

18 {

19     public virtual bool IsSimilar(object item)

20     {

21         // do the required item cast here...

22         return false;

23     }

24 }

25 

26 public abstract class DbItemManagerBase<TContract, TImplementation> : IDbItemManager<TContract>

27     where TContract : IDbItem

28     where TImplementation : DbItemBase<TContract>, TContract, new()

29 {

30     public ReadOnlyCollection<TContract> Items

31     {

32         get { return this.AvailableItems.AsReadOnly(); }

33     }

34 

35     public event EventHandler<DbItemActionEventArgs<TContract>> ItemAdded;

36 

37     public virtual void Add(TContract item)

38     {

39         this.AvailableItems.Add(item);

40     }

41 

42     public virtual TContract NewItem()

43     {

44         return new TImplementation();

45     }

46 }

2 comments:

escort said...

This won't succeed in actual fact, that is what I believe.

Dilbert said...

Nice article. I have started following your blog. Could you follow my blog too?
http://dotnetanalysis.blogspot.com/