在编译期间修改 IL:声明常量自定义结构的解决方法

发布于 2025-01-16 04:46:55 字数 3329 浏览 5 评论 0原文

我正在编写一个基于 Trits 而不是 Bits 的三元计算库。 Trits 可以具有三个值。有时表示为 0,1 和 2,有时表示为 -1、0 和 1。我将它们称为“下”、“中”和“上”。

在某些情况下,将这些自定义结构值作为常量很有用。目前,我正在使用静态只读值来解决它:

global using trit = Ternary3.Trit;
global using static Ternary3.Trit.Values;

public readonly partial struct Trit
{
    [SpecialName]
    private readonly sbyte value__;

    private Trit(byte value) => value__ = value;

    public readonly struct Values
    {
        public static readonly Trit down = new Trit(0);
        public static readonly Trit middle = new Trit(1);
        public static readonly Trit up = new Trit(2);
    }
}

这使得:

void MyMethod(bool b, trit t) => ...

...

MyMethod(true, up);

在某些情况下,必须使用实际常量。例如,在属性中。 一种更高级的解决方法是使用枚举和隐式转换:

global using trit = Ternary3.Trit;
global using static Ternary3.Trit.Values;

public readonly partial struct Trit
{
    ...

    public enum Values : byte
    {
        down,
        middle,
        up
    }

    public static implicit operator Trit(Trit.Values value) => new Trit((byte)value);
}

但是,这仍然不是真正的结构常量,在纯 C# 中无法实现。

但是,如果您使用 ildasm 进行反编译,请修改中间语言 (CIL) .field public static initonly valuetype Ternary3.Trit up.field public staticliteral valuetype Ternary3.Trit up = uint8(0x02),并使用ilasm重新编译,你会得到一个包含你想要的常量的dll。

现在的问题是:修改 CIL 的好方法是什么? 我找到了 InlineIL.Fody NuGet 包,但我找不到创建字段声明的方法,更不用说文字字段声明了。

我走在正确的轨道上吗?

编辑

作为管道胶带修复,我创建了一个 powershell 脚本,该脚本调用 ildasm,替换常量,然后调用 ilasm。这将创建一个在我测试过的任何运行时语言上按预期工作的库。然而,我仍在寻找更好的方法来操纵 il

param([string]$DllName);
$Ildasm = """C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\ildasm.exe"""
$CilName = "$DllName.il"
Start-Process $Ildasm -Argument "$DllName /OUT:$CilName"

# replace 
#.field public static initonly valuetype Ternary3.Trit down
#.field public static initonly valuetype Ternary3.Trit middle
#.field public static initonly valuetype Ternary3.Trit up
# by
#.field public static literal valuetype Ternary3.Trit down = uint8(0x00)
#.field public static literal valuetype Ternary3.Trit middle = uint8(0x01)
#.field public static literal valuetype Ternary3.Trit up = uint8(0x02)

$FixedCilName = "$DllName.fixed.il"
(Get-Content $CilName). replace(".field public static initonly valuetype Ternary3.Trit down", ".field public static literal valuetype Ternary3.Trit down = uint8(0x00)").replace(".field public static initonly valuetype Ternary3.Trit middle", ".field public static literal valuetype Ternary3.Trit middle = uint8(0x01)").  replace(".field public static initonly valuetype Ternary3.Trit up", ".field public static literal valuetype Ternary3.Trit up = uint8(0x02)") | Set-Content $FixedCilName
Remove-Item $CilName

$Ilasm = """C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ilasm.exe"""
Start-Process $Ilasm -Argument "/DLL ""$FixedCilName"" /OUTPUT=""$DllName"""

Remove-Item $FixedCilName

确实生成了某种正确的代码。 使用Ternary3 库

  • 编辑 *

我编写了一个小型自定义 Fody Code Weaver 来创建常量自定义结构。这种方法也有点工作,但它仍然有一个缺点,即无法在与实际代码相同的解决方案中进行单元测试:Visual Studio 会忽略 Fody 修改。

I'm writing a library for ternary computing, based on Trits instead of Bits. Trits can have three values. Sometimes represented as 0,1 and 2, sometimes as -1, 0 and 1. I've called them Down, Middle and Up.

In some scenario's, it's useful to have these custom struct values as constants. At this moment I'm working around it with static readonly values:

global using trit = Ternary3.Trit;
global using static Ternary3.Trit.Values;

public readonly partial struct Trit
{
    [SpecialName]
    private readonly sbyte value__;

    private Trit(byte value) => value__ = value;

    public readonly struct Values
    {
        public static readonly Trit down = new Trit(0);
        public static readonly Trit middle = new Trit(1);
        public static readonly Trit up = new Trit(2);
    }
}

Which enables:

void MyMethod(bool b, trit t) => ...

...

MyMethod(true, up);

In some cases, using an actual constant is a must. In attributes, for example.
A somewhat more advanced workaround is using an enum and an implicit cast:

global using trit = Ternary3.Trit;
global using static Ternary3.Trit.Values;

public readonly partial struct Trit
{
    ...

    public enum Values : byte
    {
        down,
        middle,
        up
    }

    public static implicit operator Trit(Trit.Values value) => new Trit((byte)value);
}

However, this still isn't a true struct constant, which cannot be achieved in pure c#.

If, however, you decompile using ildasm, modify the Intermediate Language (CIL) from
.field public static initonly valuetype Ternary3.Trit up to
.field public static literal valuetype Ternary3.Trit up = uint8(0x02), and recompile using ilasm, you get a dll that contains the constant you want.

Now the question: what would be a good way to modify the CIL?
I've found the InlineIL.Fody NuGet-package, but I cannot find a way to create a field declaration, let alone a literal field declaration.

Am I on the right track?

edit

As a duct tape fix I created a powershell script that calls ildasm, replaces the constant and then calls ilasm. This creates the library that works as expected on any runtime and language I've tested it against. However, I'm still looking for a better way to manipulate the il.

param([string]$DllName);
$Ildasm = """C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\ildasm.exe"""
$CilName = "$DllName.il"
Start-Process $Ildasm -Argument "$DllName /OUT:$CilName"

# replace 
#.field public static initonly valuetype Ternary3.Trit down
#.field public static initonly valuetype Ternary3.Trit middle
#.field public static initonly valuetype Ternary3.Trit up
# by
#.field public static literal valuetype Ternary3.Trit down = uint8(0x00)
#.field public static literal valuetype Ternary3.Trit middle = uint8(0x01)
#.field public static literal valuetype Ternary3.Trit up = uint8(0x02)

$FixedCilName = "$DllName.fixed.il"
(Get-Content $CilName). replace(".field public static initonly valuetype Ternary3.Trit down", ".field public static literal valuetype Ternary3.Trit down = uint8(0x00)").replace(".field public static initonly valuetype Ternary3.Trit middle", ".field public static literal valuetype Ternary3.Trit middle = uint8(0x01)").  replace(".field public static initonly valuetype Ternary3.Trit up", ".field public static literal valuetype Ternary3.Trit up = uint8(0x02)") | Set-Content $FixedCilName
Remove-Item $CilName

$Ilasm = """C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ilasm.exe"""
Start-Process $Ilasm -Argument "/DLL ""$FixedCilName"" /OUTPUT=""$DllName"""

Remove-Item $FixedCilName

It DOES produce sort-of-the right code.
Using the Ternary3 library

  • Edit *

I've written a small custom Fody Code Weaver to enable creating constant custom structs. This approach also sort-of works, but it still has the disadvantage of making it impossible to have unit tests in the same solution as the actual code: Visual Studio ignores Fody modifications.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文