Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customize .NET Portable Class Library Profiles?

I need to add and/or modify profiles to allow more classes and members to be shared in PCL (many of them are built-in in framework, such as Thread.Sleep). What's the best way to do this? Are there any tools to help that?



PS: I'm not seeking an anwser to tell me NO or STOP. I want to have compile-once DLLs that may be shared in different environment. No per-platform binary, no recompile, no ifdef.


Following is what I got so far:

Requirement:

  • Target environements: Silverlight 5 and .NET Framework 4.5.
  • Purpose of PCLs: shared infrastructure by RIA client and ASP.NET server (without WCF)
  • What's lacking in default profiles: XPath, Thread methods, DynamicMethod/ILGenerator

PCL profiles: under Reference Assemblies\Microsoft\Framework.NETPortable:

  • All assemblies are stubs, with "Retargetable" attribute set.
  • All assemblies have flags = 0x171: 0x001 is signed, 0x100 is retargetable, and 0x070 is undefined in AssemblyNameFlags (appears to have no effect)
  • All references between assemblies are with "Retargetable" attribute as well.
  • All assemblies with Silverlight support are versioned 2.0.5.0.
  • Built PCL binaries contain two references for each of referred assemblies (ex: mscorlib 2.0.5.0 retargetable + mscorlib 4.0)

Customization attempt #1

  • Profile: Silverlight 5 + .NET Framework 4.5 (profile 24)
  • Copy SL5 mscorlib.dll to profile 24
  • Mark SL5 mscorlib.dll as retargetable (change to delay signed)
  • ReSharper: failed to resolve all extension methods, error in generic type/value match
  • Build: success, Run: success

Customization attempt #2

  • Profile: Silverlight 5 + .NET Framework 4.5 (profile 24)
  • Copy all SL5 DLLs to profile 24
  • Mark all SL5 DLLs as retargetable (change to delay signed)
  • Mark all references between SL5 DLLs' as retargetable
  • ReSharper: failed to resolve all extension methods, error in generic type/value match
  • Build: success, Run: success

Customization attempt #3

  • Profile: Silverlight 4 + .NET Framework 4.0.3 (profile 18)
  • Copy SL4 mscorlib.dll to profile 18
  • Mark SL4 mscorlib.dll as retargetable (change to delay signed)
  • ReSharper: success
  • Build: success, Run: success

Customization attempt #4

  • Profile: Silverlight 4 + .NET Framework 4.0.3 (profile 18)
  • Copy all SL4 DLLs to profile 18
  • Set .NET runtime version of all SL4 DLLs to v4 (original DLLs have that, unknown of effects)
  • Mark all SL4 DLLs as retargetable (change to delay signed)
  • Mark all references between SL4 DLLs' as retargetable
  • ReSharper: success
  • Build: success, Run: success

Customization attempt #5 inherit #4

  • Profile: Silverlight 4 + .NET Framework 4.0.3 (profile 18)
  • Add SL4's System.Numerics (included in other SL profiles) to RedistList\FrameworkList.xml
  • Add SL4's System.Xml.XPath (not included in any SL profiles) to RedistList\FrameworkList.xml
  • Result: unable to resolve System.Numerics and System.Xml.XPath from default PCL references
  • Fixes: reference both DLLs manually - unable to force them to be retargetable, though VS wouldn't compile with non-retargetable System.Numerics or System.Xml.XPath due to the noted problem below

Notes:

  • Compile error: "... defined in an assembly that is not referenced, You must add a reference to assembly". Happens after all assemblies are made retargetable but one of references between them is not changed to "retargetable"

It works to a certain degree, but quite troublesome to customize existing referenced DLLs or add new ones, nor can one easily validate PCL code after overriding referenced DLLs (if possible at all).

like image 462
AqD Avatar asked May 04 '13 16:05

AqD


2 Answers

Since you won't take NO or STOP as an answer let me try to explain why this is a bad idea. The short answer is: if PCL doesn't expose an API it's usually because it won't work.

First of all PCL doesn't have it's own set of APIs. PCL just exposes the intersections of APIs between a given set of platforms you want to target. Now, there are cases where an API level intersection would yield more APIs than what PCL exposes. There are several reaons why this might be the case.

  1. The API isn't available on all platforms
  2. The API is available but doesn't actually work
  3. The API is defined in different assemblies

The first one should be obvious. PCL itself isn't an actual platform but only exposes what's there. So we can't give you APIs that don't actually exist across all the platforms you target. After all, we expose the intersection.

The second one sounds a bit strange but actually does happen. Take, for example, file IO on Windows Phone 7. Althought the File class is technically available on the Windows Phone, it's documented as

This type is present to support the .NET Compact Framework infrastructure in Silverlight for Windows Phone, and it is not intended to be used in your application code.

You may say "what do I care?" and simply try it but then you would find out that the security model on the phone would prevent you from accessing the files you are looking for. So exposing this API in PCL would not be helpful. In fact, our team believes this actually would hurt you because it leads you down a non-portable path.

The third issue around APIs being implemented in different assemblies is a bit more involved. In order to understand why this is an issue you need consider how the CLR handles APIs in general. The CLR doens't have the concept of loading indiviual types as, for example, Java does. In .NET types are implemented in assemblies and in order to use them ("load them"), you need to load the assembly that defines the type. Under the covers, type references include both, the namespace qualified type name as well as the assembly the type is defined in. In general, types that have the same namespace qualified name but live in different assemblies are considered different. For example, the type MyNamespace.MyType, Assembly1 and MyNamespace.MyType, Assembly2. Please note that assemblies themselves have a notion of a fully qualified name as well; it includes the assembly name and the public key token. This allows two companies to produce both an assembly called "Foo" and not confuse the CLR (assuming, of course, they are signed with different keys). So in essence loading type requires a few steps: finding the assembly the type is defined in, loading that assembly, and then loading the type.

Usally, different .NET platforms use different keys for platform assemblies, for example, mscorlib. Now you may wonder how you can use types from Silverlight on the .NET Framework. The reason for that is that CLR has the concept of assembly unfification. Assembly unfication allows the .NET Framework to treat references to Silverlight's mscorlib as references to the desktop version. This is valid because Silverlight's mscorlib was designed to be a subset of the .NET Framework version (for a particular combination of versions that is).

Although this may sound like the silver bullet to bridge all paltform differences, it's actually not. Over time, different platforms chose different assembly factorings. Take, for example, ICommand. It's available in .NET 4 and Silverlight 4. However, in WPF it's implemented in PresentationCore.dll while Silverlight put it in System.Windows.dll. To understand why PCL doesn't expose ICommand when you target .NET 4 and Silverlight 4 let's see what would happen if PCL would expose it.

In PCL, we have to put ICommnad in some assembly. We could either chose to use the Silverlight assembly or the full framework one. No matter which one we would pick, the type would not resolve on the other platform as PresenationCore.dll only exists in .NET 4 and System.Windows.dll only exists in Silverlight 4.

We solved this problem by allowing references for ICommand in System.Windows.dll to succeed on the full framework. How did we do this? The answer is type forwarding. Type forwarding allows an assembly to say "I define a type Foo". When the CLR tries to load the type Foo from that assembly, the assembly actually says "no, no -- the type Foo is actually defined in this other assembly Bar". In other words, assembly Foo contains something like a pointer to Bar's version of the type. We call those pointers type forwarding entries.

This concept allowed us to solve the ICommand mismatch by adding a System.Windows.dll to the full framework that contains a type forward to the actual implementation. PCL now gives you ICommand in System.Windows.dll and can be sure type load requests can succeed on both, .NET Framework as well as Silverlight. This, however, requires to target at least .NET Framework 4.5 as the previous versions didn't had the type forward.

For all APIs that should be exposed but aren't we are working together with the platform owners to close the gaps. We have basically two strategies for that:

  1. We ask platform owners to add missing APIs or type forwards
  2. We ship a portable implementation in an out-of-band fashion. Take for example Async or HttpClient.

However, "just" addding it to PCL doesn't work.

EDIT: If you miss a feature in PCL you can always unblock yourself. Our tester Daniel has written a blog post that shows a few techniques you can use.

like image 139
Immo Landwerth Avatar answered Nov 11 '22 04:11

Immo Landwerth


NO. STOP.

OK, since that's not what you want to hear, continue at your own risk. :) This certainly isn't supported, and IANAL so I don't know if it would be allowed by the license agreement either.

It sounds like the main problem you have with your existing solutions is that you can't pick individual APIs to add to the portable profile. To do that, you could use ildasm on the existing reference assemblies, then add in the APIs you want (probably by copying them from the results of running ildasm on another reference assembly), and then use ilasm to create your own versions of the reference assemblies with those additional APIs.

You'll need to delay sign and/or disable strong name key verification for the keys for the assemblies you modify this way.

Another option is to use type-forwarding, as described in my answer here. In that case you would end up with your main code being shareable as is, with a dependency on a DLL that would be different for each platform.

like image 41
Daniel Plaisted Avatar answered Nov 11 '22 05:11

Daniel Plaisted