使用通过 .NET Core 和 Xamarin 从 IL 编译的程序集

Using assemblies compiled from IL with .NET Core & Xamarin

更新了适合我的解决方案。见本题底部。

上下文:
我需要一种方法来评估泛型类型的大小,以便计算适合特定字节大小的数组长度。基本上, sizeof 与 C/C++ 提供的类似。

C# 的 sizeofMarshal.SizeOf 不适合这个,因为它们有很多限制。

考虑到这一点,我用 IL 编写了一个程序集,它通过 sizeof 操作码实现了我正在寻找的功能。我知道它基本上评估为 IntPtr.Size 引用类型。

我为 .NET Standard & Core 复制了这个,引用了我认为是 mscorlib 的正确等价物。请注意,IL 编译正常,这个问题是关于另一个问题的。

代码:
Headers 每个目标框架:

.NET: (Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe)

.assembly extern mscorlib {}

.NET 标准:(从 nuget 中提取的 ilasm)

.assembly extern netstandard 
{ 
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89)
  .ver 0:0:0:0
}
.assembly extern System.Runtime
{
  .ver 0:0:0:0
}

.NET Core:(与标准的 ilasm 相同,尽管我已经对两者进行了测试)

.assembly extern System.Runtime
{
  .ver 0:0:0:0
}

来源:

.assembly Company.IL
{
  .ver 0:0:1:0
}
.module Company.IL.dll

// CORE is a define for mscorlib, netstandard, and System.Runtime
.class public abstract sealed auto ansi beforefieldinit 
  Company.IL.Embedded extends [CORE]System.Object
{
  .method public hidebysig specialname rtspecialname instance void 
    .ctor() cil managed 
  {
    .maxstack 8
    ldarg.0
    call instance void [CORE]System.Object::.ctor()
    ret
  }

  .method public hidebysig static uint32 
    SizeOf<T>() cil managed 
  {
    sizeof !!0
    ret
  }
}

问题:
当 .NET Core 或 Xamarin 应用程序引用以这种方式编译的任何 dll 时,我收到以下错误:

The type 'Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.

当此类 dll 被 .NET 项目或 .NET 标准库引用然后被 .NET 项目引用时,不会发生此问题。

我已经阅读了无数文章、帖子和存储库,详细介绍了不同版本和程序集的此错误。典型的解决方案似乎是添加对目标框架等效于 mscorlib(破坏可移植性)的显式引用。似乎缺少有关为 .NET Standard & Core 使用 IL 编译程序集的信息。

据我了解,.NET Standard 和 Core 使用外观来转发类型定义,因此它们可以由目标框架的运行时解析,从而实现可移植性。

我试过以下方法:

更新:
我尝试了 (following the build instructions here), yet couldn't configure my system to compile corefx with the build script or VS 2017. However, after digging through the code of System.Runtime.CompilerServices.Unsafe 中的解决方案,我发现了一个解决方案。

这似乎很明显,但我引用了错误版本的 System.Runtime

Headers 每个目标框架(从 corefx 复制):

.NET:

#define CORELIB "mscorlib"

.assembly extern CORELIB {}

.NET 标准:

#define CORELIB "System.Runtime"
#define netcoreapp

// Metadata version: v4.0.30319
.assembly extern CORELIB
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
  .ver 4:0:0:0
}

.NET 核心:

#define CORELIB "System.Runtime"

// Metadata version: v4.0.30319
.assembly extern CORELIB
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
  .ver 4:0:0:0
}

在所有源文件中,使用 CORELIB 来引用 mscorlib 中的类型(即 [CORELIB]System.Object)。

在 DotNet CoreFX 存储库中有一个很好的示例说明如何正确执行此操作。 System.Runtime.CompilerServices.Unsafe 是仅 IL 程序集,可由 .NET Core 和 Xamarin 使用。

https://github.com/dotnet/corefx/tree/master/src/System.Runtime.CompilerServices.Unsafe

有两种方法可以解决该问题:(i) 尝试从头开始在您的项目中重新创建所需的构建配置元素 - 这将非常耗时且非常复杂 - corefx 构建系统非常复杂,(ii)使用现有的构建基础设施并通过复制 System.Runtime.CompilerServices.Unsafe 项目、更改命名并用您的代码替换 IL 代码,在 .NET Core CoreFX 存储库中创建您的 IL 项目。在我看来,这是构建基于 IL 的程序集的最快方法,可以保证在所有目标上正常工作。

要在针对任何特定版本的 .NET Core 或 .NET Standard 构建程序集,只需在发布分支中创建它:release/2.0.0release/1.1.0

The type 'Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.

有一个选项可以尝试在触发它的引用程序集的项目中单独抑制该编译错误。将以下 属性 转换为新格式 csproj/vbproj 应该会抑制它:

<PropertyGroup>
    <_HasReferenceToSystemRuntime>true</_HasReferenceToSystemRuntime>
</PropertyGroup>

好的,这是我研究的结果(基于https://github.com/dotnet/corefx/tree/master/src/System.Runtime.CompilerServices.Unsafe and https://github.com/jvbsl/ILProj):

global.json:

{
  "msbuild-sdks": {
    "Microsoft.NET.Sdk.IL": "3.0.0-preview-27318-01"
  }
}

ILProj\ILProj.ilproj:

<Project Sdk="Microsoft.NET.Sdk.IL">

    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <MicrosoftNetCoreIlasmPackageVersion>3.0.0-preview-27318-01</MicrosoftNetCoreIlasmPackageVersion>
        <IncludePath Condition="'$(TargetFramework)' == 'netstandard1.0'">include\netstandard</IncludePath>
        <IncludePath Condition="'$(TargetFramework)' == 'netstandard2.0'">include\netstandard</IncludePath>
        <IncludePath Condition="'$(TargetFramework)' == 'netcoreapp1.0'">include\netcoreapp</IncludePath>
        <IncludePath Condition="'$(TargetFramework)' == 'netcoreapp2.0'">include\netcoreapp</IncludePath>
        <IlasmFlags>$(IlasmFlags) -INCLUDE=$(IncludePath)</IlasmFlags>
    </PropertyGroup>

</Project>

ILProj\include\netstandard\coreassembly.h:

#define CORE_ASSEMBLY "System.Runtime"

// Metadata version: v4.0.30319
.assembly extern CORE_ASSEMBLY
{
  .publickeytoken = ( B0 3F 5F 7F 11 D5 0A 3A )
  .ver 4:0:0:0
}

ILProj\Class.il

#include "coreassembly.h"

.assembly ILProj
{
  .ver 1:0:0:0
}

.module ILProj.dll

.class public abstract auto ansi sealed beforefieldinit ILProj.Class
  extends [CORE_ASSEMBLY]System.Object
{
  .method public hidebysig static int32 Square(int32 a) cil managed
  {
    .maxstack 2
    ldarg.0
    dup
    mul
    ret
  }
}

如果你很难把它们放在一起,那么看看这里的例子:https://github.com/Konard/ILProj

我学习了最近的 System.Runtime.CompilerServices.Unsafe 项目,并创建了具有多目标(.NET Core 6、NET Framework 4.8、netstandard2.0、netcoreapp3.1)和两种签名类型的工作示例

您需要创建文件global.json

{
    "msbuild-sdks": {
        "Microsoft.NET.Sdk.IL": "6.0.0"
    }
}

然后黑魔法开始了:

<Project Sdk="Microsoft.NET.Sdk.IL">
    <PropertyGroup>
        <TargetFrameworks>net6.0;netcoreapp3.1;netstandard2.0;net48</TargetFrameworks>
        <DebugOptimization>IMPL</DebugOptimization>
        <DebugOptimization Condition="'$(Configuration)' == 'Release'">OPT</DebugOptimization>
        <CoreCompileDependsOn>$(CoreCompileDependsOn);GenerateVersionFile</CoreCompileDependsOn>
        <IlasmFlags>$(IlasmFlags) -DEBUG=$(DebugOptimization)</IlasmFlags>
        <IsPackable>true</IsPackable>
        <ProduceReferenceAssembly>false</ProduceReferenceAssembly>
        <AssemblyVersion>1.0.0.0</AssemblyVersion>
        <FileVersion>1.0.0.0</FileVersion>
        <Version>1.0.0-beta.1</Version>
    </PropertyGroup>
    <PropertyGroup Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETCoreApp'">
        <ExtraMacros>#define netcoreapp</ExtraMacros>
        <CoreAssembly>System.Runtime</CoreAssembly>
        <_FeaturePublicSign>true</_FeaturePublicSign>
    </PropertyGroup>
    <PropertyGroup Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETStandard'">
        <CoreAssembly>netstandard</CoreAssembly>
        <_FeaturePublicSign>true</_FeaturePublicSign>
    </PropertyGroup>
    <PropertyGroup Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETFramework'">
        <CoreAssembly>mscorlib</CoreAssembly>
        <_FeaturePublicSign>false</_FeaturePublicSign>
    </PropertyGroup>

    <PropertyGroup Condition="'$(TargetFramework)'=='net6.0'">
        <_FeatureUsePopCount>true</_FeatureUsePopCount>
    </PropertyGroup>
    <PropertyGroup Condition="'$(TargetFramework)'!='net6.0'">
        <_FeatureUsePopCount>false</_FeatureUsePopCount>
    </PropertyGroup>

    <PropertyGroup Condition="'$(_FeaturePublicSign)'=='true'">
        <AssemblyOriginatorKeyFile>..\solar_pub.snk</AssemblyOriginatorKeyFile>
        <PublicSign>True</PublicSign>
    </PropertyGroup>

    <PropertyGroup Condition="'$(_FeaturePublicSign)'!='true'">
        <AssemblyOriginatorKeyFile>..\solar.snk</AssemblyOriginatorKeyFile>
        <DelaySign>false</DelaySign>
    </PropertyGroup>

    <ItemGroup Condition="'$(_FeatureUsePopCount)'=='true'">
        <Compile Include="FlagEnumUtil.PopCount.msil"/>     
    </ItemGroup>
    <ItemGroup Condition="'$(_FeatureUsePopCount)'!='true'">
        <Compile Include="FlagEnumUtil.NoPopCount.msil"/>       
    </ItemGroup>
    

    <ItemGroup>
        <!-- mscorlib is passed in as an explicit reference from C# targets but not via the IL SDK. -->
        <Reference Include="$(CoreAssembly)"
                   Condition="'$(TargetFrameworkIdentifier)' != '.NETStandard'" />
    </ItemGroup>

    <Target Name="GenerateVersionFile"
            DependsOnTargets="GetAssemblyVersion;ResolveReferences"
            Inputs="$(MSBuildAllProjects)"
            Outputs="'$(VersionFilePath)">
        <PropertyGroup>
            <IncludePath>$([MSBuild]::NormalizeDirectory('$(IntermediateOutputPath)', 'version'))</IncludePath>
            <IncludePathTrimmed>$(IncludePath.TrimEnd('\'))</IncludePathTrimmed>
            <IlasmFlags>$(IlasmFlags) -INCLUDE="$(IncludePathTrimmed)"</IlasmFlags>
            <VersionFilePath Condition="'$(VersionFilePath)' == ''">$([MSBuild]::NormalizePath('$(IncludePath)', 'version.h'))</VersionFilePath>
            <_AssemblyVersion>$(AssemblyVersion.Replace('.', ':'))</_AssemblyVersion>
            <_coreAssemblyName Condition="'%(ReferencePath.FileName)' == '$(CoreAssembly)'">%(ReferencePath.FusionName)</_coreAssemblyName>
            <_assemblyNamePattern><![CDATA[[^,]+, Version=(?<v1>[0-9]+)\.(?<v2>[0-9]+)\.(?<v3>[0-9]+)\.(?<v4>[0-9]+), .*PublicKeyToken=(?<p1>[0-9a-f]{2})(?<p2>[0-9a-f]{2})(?<p3>[0-9a-f]{2})(?<p4>[0-9a-f]{2})(?<p5>[0-9a-f]{2})(?<p6>[0-9a-f]{2})(?<p7>[0-9a-f]{2})(?<p8>[0-9a-f]{2})]]></_assemblyNamePattern>
            <_coreAssemblyVersion>
                $([System.Text.RegularExpressions.Regex]::Replace(
                $(_coreAssemblyName),
                $(_assemblyNamePattern),
                '${v1}:${v2}:${v3}:${v4}'))
            </_coreAssemblyVersion>
            <_coreAssemblyVersionTrimmed>$(_coreAssemblyVersion.Trim())</_coreAssemblyVersionTrimmed>
            <_coreAssemblyPublicKeyToken>
                $([System.Text.RegularExpressions.Regex]::Replace(
                $(_coreAssemblyName),
                $(_assemblyNamePattern),
                '${p1} ${p2} ${p3} ${p4} ${p5} ${p6} ${p7} ${p8}').ToUpperInvariant())
            </_coreAssemblyPublicKeyToken>
            <_VersionFileContents>
                <![CDATA[
#define CORE_ASSEMBLY "$(CoreAssembly)"
#define ASSEMBLY_VERSION "$(_AssemblyVersion)"
#define CORE_ASSEMBLY_VERSION "$(_coreAssemblyVersionTrimmed)"
#define FILE_VERSION "{string('$(FileVersion)')}"
#define INFORMATIONAL_VERSION "{string('$(InformationalVersion)')}"
$(ExtraMacros)
// Metadata version: v4.0.30319
.assembly extern CORE_ASSEMBLY
{
  .publickeytoken = ($(_coreAssemblyPublicKeyToken) )
  .ver CORE_ASSEMBLY_VERSION
}
 ]]>
            </_VersionFileContents>
        </PropertyGroup>

        <Message Importance="high" Text="Building:$(TargetFramework) $([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) CoreAssembly $(CoreAssembly)#$(_coreAssemblyVersionTrimmed) PublicSign=$(_FeaturePublicSign) PopCount=$(_FeatureUsePopCount)"/>

        <WriteLinesToFile
          File="$(VersionFilePath)"
          Lines="$(_VersionFileContents)"
          Overwrite="true"
          WriteOnlyWhenDifferent="true" />

        <ItemGroup>
            <FileWrites Include="$(VersionFilePath)" />
        </ItemGroup>
    </Target>

    <!-- Decompile the ILResourceReference to get native resources. -->
    <Target Name="SetILResourceReference"
            BeforeTargets="DisassembleIlasmResourceFile"
            Condition="'@(ResolvedMatchingContract)' != ''">
        <ItemGroup>
            <ILResourceReference Include="@(ResolvedMatchingContract)" />
        </ItemGroup>
    </Target>
</Project>

我们来解释一下这个项目文件

  1. DebugOptimization - 我们仅针对发布使用 OPT 优化
  2. CoreCompileDependsOn - 我们需要使用主程序集名称和版本生成 version.h 文件(在目标 GenerateVersionFile 中生成)
  3. IlasmFlags - 用于将 DebugOptimization 传递给 ilasm 工具
  4. IsPackable - 不工作,但它必须创建 nuget 包
  5. ProduceReferenceAssembly - 我们需要禁用 ref 程序集搜索 .NET 6
  6. 然后我们需要为不同的框架定义一些编译时常量
  7. 特殊文件version.h看起来像一个C++头文件并且可以包含一些宏,我们用它来掩码real mscorlib/System.运行时程序集名称。 MSBuild 中的 .NET 内联代码在生成的 version.h 文件中检测平台和 fil 宏值(这是在最近的
  8. 我为不同的源包含规则使用了两个扩展名(所有*.il 文件都是自动编译的,所有*.msil 文件都是手动包含的)

您可以在以下位置查看整个项目(包括泛型、枚举和测试):

https://github.com/AlexeiScherbakov/Solar.IL