What are WinRT language projections?
"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.
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.