Skip to content

Commit

Permalink
Embedded CsWinRt Option (#1011)
Browse files Browse the repository at this point in the history
  • Loading branch information
j0shuams authored Nov 12, 2021
1 parent 39fbcdf commit d568019
Show file tree
Hide file tree
Showing 60 changed files with 1,988 additions and 7 deletions.
103 changes: 103 additions & 0 deletions docs/embedded.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Embedded C#/WinRT Support

## Overview

**Note**: Embedded support is currently **in preview**.

Embedded support is a C#/WinRT feature that allows you to compile the c# projection for the component, within the library/app itself.

This is done by setting the build property `CsWinRTEmbedded` to `true` in the app or library's project file.

Enabling embedded support compiles the sources for WinRT.Runtime with the project.
For any types from the component which you want to embed in your project, they must be specified using `CsWinRTIncludes`.
The specified types will be projected and embedded into the project's *.dll*.
This means projects using embedded support no longer have a dependency on a projection for the component, `WinRT.Runtime.dll` or `Microsoft.Windows.SDK.NET.dll` in the case of the Windows SDK projection.

For an example, you can look at the [sample](https://github.com/microsoft/CsWinRT/tree/master/src/Samples/TestEmbedded).

Embedded support introduces new features to the C#/WinRT toolchain:
* Decreased component/app size: The developer only needs to include the minimum necessary parts of the Windows SDK required by their component or app, reducing the size of the output .dll significantly.
* Downlevel support: App developers on .NET 5+ can target Windows 7 (`net5.0-windows`) and light-up on Windows 10
(and Windows 11) when consuming an embedded .NET 5+ library.
* Removes the need for multi-targeting: Library authors can support all .NET Standard 2.0 app consumers, including .NET 5+, without the need for multi-targeting. App consumers are able to target any .NET Standard 2.0 compatible TFM (e.g. `netcoreapp3.1`, `net48`, and `net5.0-windows`).

It is important to remember that embedded support constrains the scope of Windows Runtime types to your binary.

## Scenarios

This feature allows C# apps and libraries to target `net5.0` (and above), .NET Framework 4.6.1+, `netcoreapp3.1`, and `netstandard2.0` while also using the Windows SDK.
Moreover, a library can target `netstandard2.0` and be able to run on NetFX, Net Core and Net 5.

Note: the `netstandard2.0` generated projection is not optimized for .NET 5 and won't be as performant.

## Usage

Using embedded support allows you to target non-Windows specific TFMs and older versions of .NET.
You will need to set a few properties:
* `CsWinRTEmbedded` - must be set to `true` to include the sources
* `CsWinRTIncludes` - Specify the types to be projected; error `CS0246` will highlight any missing namespaces
* `CsWinRTWindowsMetadata` - must be set to the Windows SDK release to use for Windows APIs
* `LangVersion` - must be set to `9` because the CsWinRT generated sources use C# 9

Targeting `netstandard2.0` requires adding the following two package references:

```xml
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="System.Runtime.Extensions" Version="4.3.1" />
```

Here is an example project file for a library cross-targeting and embedding a C#/WinRT generated projection.

```csproj
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0-windows;net5.0-windows;netstandard2.0</TargetFrameworks>
<Platforms>x64;x86</Platforms>
</PropertyGroup>

<PropertyGroup>
<LangVersion>9</LangVersion>
<CsWinRTEmbedded>true</CsWinRTEmbedded>
<CsWinRTWindowsMetadata>10.0.19041.0</CsWinRTWindowsMetadata>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="$(CsWinRTVersion)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\path\CppwinrtComponent\CppwinrtComponent.vcxproj" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="System.Runtime.Extensions" Version="4.3.1" />
</ItemGroup>

<PropertyGroup>
<CsWinRTIncludes>
CppwinrtComponent;
Windows.Devices.Geolocation;
Windows.Foundation;
</CsWinRTIncludes>
</PropertyGroup>

</Project>
```

You will need to enable Windows Desktop Compatible for all your referenced C++/WinRT components -- either via the properties or in the `.vcxproj` file.

If you try to use an embedded projection of a native (C++/WinRT) component, via ProjectReference, then you may encounter a runtime error `CLASS_NOT_REGISTERED`.
You can fix this by unloading the native component project in Visual Studio, and adding the following:

```vcxproj
<PropertyGroup Label="Configuration">
<DesktopCompatible>true</DesktopCompatible>
</PropertyGroup>
```

Alternatively, you can right-click the C++ project in Visual Studio, select Properties > General > Project Defaults,
and set Windows Desktop Compatible to Yes for all configurations/platforms.

If you find any other issues using embedded support, please [file an issue]()!
75 changes: 75 additions & 0 deletions nuget/Microsoft.Windows.CsWinRT.Embedded.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!--
***********************************************************************************************
Copyright (C) Microsoft Corporation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<PropertyGroup>

<!-- Activate the internal version of the runtime layer -->
<DefineConstants>$(DefineConstants);EMBED</DefineConstants>
<!-- Path to runtime sources, must update if their location changes in the .nuspec -->
<CsWinRTEmbeddedTFMNet5OrGreater Condition="$([MSBuild]::GetTargetFrameworkVersion('$(TargetFramework)')) >= 5">true</CsWinRTEmbeddedTFMNet5OrGreater>
</PropertyGroup>

<ItemGroup>
<CsWinRTEmbeddedSourcesAny Include="$(CsWinRTPath)embedded\any\*.cs"/>
<CsWinRTEmbeddedSourcesNet5 Include="$(CsWinRTPath)embedded\net5.0\*.cs"/>
<CsWinRTEmbeddedSourcesNet2 Include="$(CsWinRTPath)embedded\netstandard2.0\*.cs"/>
</ItemGroup>

<!-- Copy sources that are for any framework -->
<Target Name="CsWinRTAddEmbeddedRuntime_AnySources"
AfterTargets="CsWinRTGenerateProjection"
BeforeTargets="CsWinRTIncludeProjection">

<Copy SourceFiles="@(CsWinRTEmbeddedSourcesAny)"
DestinationFolder="$(CsWinRTGeneratedFilesDir)"
UseHardlinksIfPossible="false"
SkipUnchangedFiles="true"/>

</Target>

<!-- Copy netstandard2.0 sources -->
<Target Name="CsWinRTAddEmbeddedRuntime_Net2Sources"
Condition="'$(CsWinRTEmbeddedTFMNet5OrGreater)' != 'true'"
AfterTargets="CsWinRTGenerateProjection"
BeforeTargets="CsWinRTIncludeProjection">

<Copy SourceFiles="@(CsWinRTEmbeddedSourcesNet2)"
DestinationFolder="$(CsWinRTGeneratedFilesDir)"
UseHardlinksIfPossible="false"
SkipUnchangedFiles="true"/>

</Target>

<!-- Copy net5.0 sources -->
<Target Name="CsWinRTAddEmbeddedRuntime_Net5Sources"
Condition="'$(CsWinRTEmbeddedTFMNet5OrGreater)' == 'true'"
AfterTargets="CsWinRTGenerateProjection"
BeforeTargets="CsWinRTIncludeProjection">

<Copy SourceFiles="@(CsWinRTEmbeddedSourcesNet5)"
DestinationFolder="$(CsWinRTGeneratedFilesDir)"
UseHardlinksIfPossible="false"
SkipUnchangedFiles="true"/>

</Target>


<!-- Prevent collisions of WinRT.Runtime types -->
<Target Name="RemoveWinRTRuntimeReference"
Inputs="@(RuntimeCopyLocalItems)"
AfterTargets="ResolvePackageAssets"
Outputs="@(RuntimeCopyLocalItems)">

<ItemGroup>
<Reference Remove="WinRT.Runtime" />
<RuntimeCopyLocalItems Remove="@(RuntimeCopyLocalItems)" Condition="'%(DestinationSubPath)' == 'WinRT.Runtime.dll'"/>
<ResolvedCompileFileDefinitions Remove="@(ResolvedCompileFileDefinitions)" Condition="'%(HintPath)' == '$(CsWinRTPath)lib\net5.0\WinRT.Runtime.dll'"/>
</ItemGroup>

</Target>

</Project>
16 changes: 16 additions & 0 deletions nuget/Microsoft.Windows.CsWinRT.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<file src="Microsoft.Windows.CsWinRT.props" target="build"/>
<file src="Microsoft.Windows.CsWinRT.targets" target="build"/>
<file src="Microsoft.Windows.CsWinRT.Authoring.targets" target="build"/>
<file src="Microsoft.Windows.CsWinRT.Embedded.targets" target="build"/>
<file src="Microsoft.Windows.CsWinRT.IIDOptimizer.targets" target="build"/>
<file src="Microsoft.Windows.CsWinRT.Prerelease*.targets" target="build"/>
<file src="Microsoft.Windows.CsWinRT.Authoring.Transitive.targets" target="build"/>
Expand All @@ -36,6 +37,21 @@
<file src="$winrt_host_arm$" target ="runtimes\win-arm\native"/>
<file src="$winrt_host_arm64$" target ="runtimes\win-arm64\native"/>
<file src="$winrt_shim$" target ="lib\net5.0\"/>

<!-- Add the WinRT.Runtime sources to the pkg for embedded scenarios -->

<file src="..\src\WinRT.Runtime\*.cs" exclude="..\src\WinRT.Runtime\*.net*.cs" target="embedded\any\" />
<file src="..\src\WinRT.Runtime\*net5*.cs" target="embedded\net5.0\" />
<file src="..\src\WinRT.Runtime\*netstandard2.0*.cs" target="embedded\netstandard2.0\" />

<file src="..\src\WinRT.Runtime\Interop\*.cs" exclude="..\src\WinRT.Runtime\Interop\*.net*.cs" target="embedded\any\" />
<file src="..\src\WinRT.Runtime\Interop\*net5*.cs" target="embedded\net5.0\" />
<file src="..\src\WinRT.Runtime\Interop\*netstandard2.0*.cs" target="embedded\netstandard2.0\" />

<file src="..\src\WinRT.Runtime\Projections\*.cs" exclude="..\src\WinRT.Runtime\Projections\*.net*.cs" target="embedded\any\" />
<file src="..\src\WinRT.Runtime\Projections\*net5*.cs" target="embedded\net5.0\" />
<file src="..\src\WinRT.Runtime\Projections\*netstandard2.0*.cs" target="embedded\netstandard2.0\" />

<file src="$guid_patch$" target ="build\tools\IIDOptimizer"/>
</files>
</package>
10 changes: 8 additions & 2 deletions nuget/Microsoft.Windows.CsWinRT.targets
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<TrackFileAccess Condition="'$(CsWinRTComponent)' != 'true'">false</TrackFileAccess>
</PropertyGroup>

<Import Project="$(MSBuildThisFileDirectory)Microsoft.Windows.CsWinRT.Embedded.targets" Condition="'$(CsWinRTEmbedded)' == 'true'"/>

<!-- Remove WinRT.Host.dll and WinRT.Host.Shim.dll references -->
<Target Name="CsWinRTRemoveHostingDllReferences" AfterTargets="ResolvePackageAssets" BeforeTargets="ResolveLockFileAnalyzers" Outputs="@(Reference)">
<PropertyGroup>
Expand Down Expand Up @@ -99,14 +101,18 @@ Copyright (C) Microsoft Corporation. All rights reserved.
-input $(CsWinRTInteropMetadata)
-include WinRT.Interop
</CsWinRTIncludeWinRTInterop>
<CsWinRTEmbeddedParam Condition="'$(CsWinRTEmbedded)' == 'true'">-embedded</CsWinRTEmbeddedParam>
<CsWinRTExeTFM Condition="$([MSBuild]::GetTargetFrameworkVersion('$(TargetFramework)')) >= 5">$(TargetFramework)</CsWinRTExeTFM>
<CsWinRTExeTFM Condition="'$(CsWinRTExeTFM)' == ''">netstandard2.0</CsWinRTExeTFM>
<CsWinRTParams Condition="'$(CsWinRTParams)' == ''">
$(CsWinRTCommandVerbosity)
-target $(TargetFramework)
-target $(CsWinRTExeTFM)
$(CsWinRTWindowsMetadataInput)
-input @(CsWinRTInputs->'"%(FullPath)"', ' ')
-output "$(CsWinRTGeneratedFilesDir.TrimEnd('\'))"
$(CsWinRTFilters)
$(CsWinRTIncludeWinRTInterop)
$(CsWinRTEmbeddedParam)
</CsWinRTParams>
</PropertyGroup>
<MakeDir Directories="$(CsWinRTGeneratedFilesDir)" />
Expand All @@ -131,7 +137,7 @@ $(CsWinRTIncludeWinRTInterop)
<Compile Include="$(CsWinRTGeneratedFilesDir)*.cs" Exclude="@(Compile)" />
</ItemGroup>
</Target>

<Import Project="$(MSBuildThisFileDirectory)Microsoft.Windows.CsWinRT.Prerelease.targets" Condition="Exists('$(MSBuildThisFileDirectory)Microsoft.Windows.CsWinRT.Prerelease.targets')"/>
<Import Project="$(MSBuildThisFileDirectory)Microsoft.Windows.CsWinRT.Authoring.targets" Condition="'$(CsWinRTComponent)' == 'true'"/>
<Import Project="$(MSBuildThisFileDirectory)Microsoft.Windows.CsWinRT.IIDOptimizer.targets" Condition="'$(CsWinRTIIDOptimizerOptOut)' != 'true'"/>
Expand Down
7 changes: 7 additions & 0 deletions src/Samples/TestEmbedded/C++ Components/Alpha/Alpha.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include "pch.h"
#include "Alpha.h"
#include "Class.g.cpp"

namespace winrt::Alpha::implementation
{
}
3 changes: 3 additions & 0 deletions src/Samples/TestEmbedded/C++ Components/Alpha/Alpha.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
EXPORTS
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE
18 changes: 18 additions & 0 deletions src/Samples/TestEmbedded/C++ Components/Alpha/Alpha.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "Class.g.h"

namespace winrt::Alpha::implementation
{
struct Class : ClassT<Class>
{
Class() = default;
};
}

namespace winrt::Alpha::factory_implementation
{
struct Class : ClassT<Class, implementation::Class>
{
};
}
13 changes: 13 additions & 0 deletions src/Samples/TestEmbedded/C++ Components/Alpha/Alpha.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Alpha
{
[default_interface]
runtimeclass Class
{
Class();
}

interface IAlpha
{
Int32 Five();
}
}
Loading

0 comments on commit d568019

Please sign in to comment.