开发者

What are WinRT language projections?

What are W开发者_开发技巧inRT language projections and what are they used for?


Windows Runtime Projections are the way that the Windows Runtime APIs are exposed in each language. This may be at compile time (as in C++) or at runtime (as in JavaScript) or a combination (as in C#). Each language decides how to present the WinRT APIs best. Most of the time it is a direct exposure, but other times there are wrappers or redirections that may take place. Delegates and events are a good example. In C# they show up as C# delegates/events and not as WinRT-specific types. Strings likewise are remapped to be the native language string type and not the underlying hstring type.


"Projections" in WinRT is another word for "Bindings".

The WinRT Language Projections are the WinRT Bindings for each Language that is supported.

For more information, check out:

WinRT Demystified - Miguel de Icaza


A language projection is a way to exposing the Windows Runtime API to you in a language-friendly manner.

For example, the underlying way to create a Windows.Globalization.Calendar object is to call:

IInspectable instance;
HRESULT hr = RoActivateInstance(StringToHSTRING("Windows.Globalization.Calendar"), out instance);
if (Failed(hr))
   throw new ComException(hr);

ICalendar calendar;
hr = instance.QueryInterface(IID_ICalendar, out calendar);
if (Failed(hr))
   throw new ComException(hr);

That's what most languages would call a "constructor". But most languages already have a syntax to "create an object".

If you're in C# you have:

Calendar calendar = new Calendar();

If you're in Pascal you have:

calendar: TCalendar;

calendar := TCalendar.Create;

So lets create a C#-like wrapper (or projection) around this:

class Calendar : Object
{
   private ICalendar _calendar;

   //constructor
   void Calendar() : base()
   { 
      IInspectable instance;
      HRESULT hr = RoActivateInstance(StringToHSTRING("Windows.Globalization.Calendar"), out instance);
      if (Failed(hr))
         throw new ComException(hr);
      ICalendar calendar;
      hr = instance.QueryInterface(IID_ICalendar, out calendar);
      if (Failed(hr))
         throw new ComException(hr);

      this._calendar = calendar;
   }
}

And now you can use your friendly C#-like projection:

Calendar cal = new Calendar();

Pascal version of constructors

Lets say you're using Delphi: you already have an idiom for creating objects. Lets convert the underlying plumbing to a friendly Pascal projection:

TCalendar = class
private
   FCalendar: ICalendar;
public
   constructor Create;
end;

constructor TCalendar.Create;
var
   instance: IInspectable;
   calendar: ICalendar;
   hr: HRESULT;
begin
   inherited Create;

   hr := RoActivateInstance(StringToHSTRING('Windows.Globalization.Calendar'), {out} instance);
   OleCheck(hr);

   hr = instance.QueryInterface(IID_ICalendar, {out} calendar);
   OleCheck(hr);

   FCalendar := calendar;
end;

And now we have our Delphi projection:

calendar: TCalendar;

calendar := TCalendar.Create;

Properties (use'em if you got'em)

In the underlying ICalendar interface, you have to get and set properties using methods:

  • get_Year
  • set_Year

If you blindly translated that to C# you could get:

C# property methods:

class Calendar : Object
{
   private ICalendar _calendar;

   public int get_Year() { return _calendar.get_Year(); }
   void set_Year(int value) { _calendar.set_Year(value); }
}

Pascal property methods:

TCalendar = class
public
   function get_Year: Integer;
   procedure set_Year(Value: Integer);
end;

But if your language supports them, you actually should expose these properties as actual "Properties". So we can project these properties using the property syntax native to our language:

C#:

class Calendar : Object
{
   private ICalendar _calendar;

   public int Year { 
         get { return _calendar.get_Year(); } 
         set { _calendar.set_Year(value); }
   }
}

Pascal:

TCalendar = class
public
   property Year: Integer read get_Year write set_Year;
end;

ITerators

The idea is to create a facade that looks and feels like your language, but behind the scenes it maps back to the underlying calls. It goes quite deep.

In WinRT, everything that is enumerable implements

  • IIterable<T>

But in C#, everything enumerable is supposed to start from:

  • IEnumerable

So the .NET library has an internal class that adapts an IIterable<T> and exposes it as an IEnumerable.

So rather than a method returning an IIterable<T>:

class Calendar : Object
{
   public IIterable<Datetime> Holidays()
   {
      return _calendar.Holidays();
   }
}

It returns an IEnumerable<T>:

class Calendar : Object
{
   public IEnumerable<DateTime> Holidays()
   {
       IIterable<DateTime> iter = _calendar.Holidays();

       //Create helper class to convert IIterable to IEnumerable
       IEnumerable<DateTime> enum = new IteratorToEnumeratorAdapter(iter);

       return enum;
   }
}

This way you can use your language's own:

  • foreach date in Holidays
  • for date in Holdays do

What's the Date?

In the WinRT, dates are represented as Windows.Foundation.DateTime:

class Calendar : Object
{
   //Windows.Foundation.DateTime
   Datetime Date { get { return _calendar.get_Date(); } set { _calendar.set_Date(value); }
}

But in other languages, we already have our own datetime classes:

  • C#: System.DateTimeOffset
  • Javascript: Date
  • C++: FILETIME
  • Delphi: TDateTime

So the projection does the work of converting the WinRT DateTime (an Int64 that is the number of 100ns intervals since January 1, 1601) to a C# DateTimeOffset:

class Calendar : Object
{
   //System.DateTimeOffset
   DateTimeOffset Date { 
       get { 
          Int64 ticks _calendar.get_Date().UniversalTime(); 
          DateTimeOffset dt = DateTimeOffset.FromFileTime(ticks);
          return dt;
       } 
       set { 
          Int64 ticks = value.ToFileTime();

          DateTime dt = new Windows.Foundation.DateTime();
          dt.UniversalTime = ticks;
          _calendar.set_Date(dt);
       }
}

and something similar to Delphi's TDateTime:

type
   TCalendar = class(TObject)
   private
      FCalendar: ICalendar;
      function getDate: TDateTime;
      procedure setDate(Value: TDateTime);
   public
      property Date: TDateTime read getDate write setDate;
   end;

   function TCalendar.GetDate: TDateTime;
   var
      ticks: Int64;
   const
      OA_ZERO_TICKS = Int64(94353120000000000);
      TICKS_PER_DAY = Int64(864000000000);
   begin
      ticks := FCalendar.get_Date().UniversalTime;
      Result := (ticks - OA_ZERO_TICKS) / TICKS_PER_DAY;
   end;

   procedure TCalendar.SetDate(Value: TDateTime);
   var
      ticks: Int64;
   const
      OA_ZERO_TICKS = Int64(94353120000000000);
      TICKS_PER_DAY = Int64(864000000000);
   begin
      ticks := (Value * TICKS_PER_DAY) + OA_ZERO_TICKS;
      FCalendar.set_Date(Round(ticks));
   end;    

tl;dr

A projection is a set of wrappers around WinRT to make it look as much like your native language as possible.

In C#, nobody actually writes the projected versions; the compiler and the runtime do all the work behind the scenes because it knows how to read the metadata.

For other languages, the translated code files are created either manually or automatically by an import tool.


The easiest way to clarify is that a language projection in WinRT is the "front end" whereas the Windows Runtime is the backend. Write from one of the three languages (JS, C#, VB), it behaves identically on the back end.

If you write your own 3rd Party WinRT component in C++ or C#, you can use it from JS, C# and VB without having to do any extra work.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜