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



更新了适用于我的解决方案。请参阅此问题的底部。

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

C#的sizeof和Marshal.SizeOf不适合这样做,因为它们有很多局限性。

考虑到这一点,我在IL中编写了一个程序集,它通过sizeof操作码实现了我想要的功能。我知道它本质上是通过引用类型计算为IntPtr.Size

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

代码:
每个目标框架的标头:

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

.assembly extern mscorlib {}

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

.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核心:(与标准的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
}
}

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

类型"Object"是在未引用的程序集中定义的。必须添加对程序集"System.Runtime,Version=0.0.0.0,区域性=中性,PublicKeyToken=null。

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

我读过无数的文章、帖子和存储库,用不同的版本和程序集详细描述了这个错误。典型的解决方案似乎是添加对目标框架的mscorlib等价物的显式引用(破坏可移植性)。似乎缺乏关于使用IL编译的.NET Standard&果心

据我所知,.NET标准&核心使用转发类型定义的facade,以便它们可以由目标框架的运行时解析,从而实现可移植性。

我试过以下几种:

  • System.Runtime的显式版本
  • 正在使用完全相同的引用分解从C#编译的.NET核心库。奇怪的是,它们似乎针对显式版本(如.NET Core 2.0中的System.Runtime 4.2)
  • 使用Emit API动态生成代码。编译到内存似乎是可行的,但不是一个选项,因为我也针对Xamarin.iOS(仅AOT)。从磁盘引用这种动态编译的程序集会导致与手动编译程序集相同的错误

更新:
我尝试了Jacek的答案中的解决方案(按照此处的构建说明),但无法将我的系统配置为使用构建脚本或VS 2017编译corefx。然而,在深入研究了System.Runtime.CompilerServices.Unsafe的代码后,我发现了一个解决方案。

这可能看起来很明显,但我引用了错误版本的System.Runtime

每个目标框架的标头(从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)使用现有的构建基础设施,并通过复制sizeof0项目在.NET Core corefx repo中创建IL项目,更改命名并用您的代码替换IL代码。在我看来,这是构建基于IL的程序集的最快方法,它将保证与所有目标正常工作。

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

类型"Object"是在未引用的程序集中定义的。必须添加对程序集"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和https://github.com/jvbsl/ILProj):

global.json:

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

ILProjILProj.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'">includenetstandard</IncludePath>
<IncludePath Condition="'$(TargetFramework)' == 'netstandard2.0'">includenetstandard</IncludePath>
<IncludePath Condition="'$(TargetFramework)' == 'netcoreapp1.0'">includenetcoreapp</IncludePath>
<IncludePath Condition="'$(TargetFramework)' == 'netcoreapp2.0'">includenetcoreapp</IncludePath>
<IlasmFlags>$(IlasmFlags) -INCLUDE=$(IncludePath)</IlasmFlags>
</PropertyGroup>
</Project>

ILProjincludenetstandardcoreassembly.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
}

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

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

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

最新更新