英文:
Injecting method calls using Mono.Cecil draws InvalidProgramException in constructor
问题
我使用Mono.Cecil在构造函数中注入了两个方法调用。但不知何故,构造始终失败,并显示以下错误:
InvalidProgramException: Invalid IL code in Networking.ServerController:.ctor (): IL_0028: callvirt 0x0600017f
基本上我所做的是为每个stfld
注入一个方法调用,同时将方法的名称(不包括类名)和当前字段值(装箱后)作为对象传递给以下方法(从两个上层的基类中继承):
protected void LocalNetworkVariableChange(string variableName, object value) {
// 无论我放什么都会失败
}
注入前的IL代码:
IL_0000: ldarg.0
IL_0001: newobj System.Void System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj System.Void System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call System.Void Networking.PeerController`1<Networking.ServerController>::.ctor()
IL_001c: nop
IL_001d: ret
注入后:
IL_0000: ldarg.0
IL_0001: newobj System.Void System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj System.Void System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call System.Void Networking.PeerController`1<Networking.ServerController>::.ctor()
IL_001c: nop
IL_0000: ldarg.0
IL_0000: ldstr "_connectedPlayersByClientId"
IL_0000: ldfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_0000: callvirt System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.String,System.Object)
IL_0000: ldarg.0
IL_0000: ldstr "_connectedPlayersByPlayerId"
IL_0000: ldfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0000: callvirt System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.String,System.Object)
IL_001d: ret
(!)指令前面的标签在通过Mono.Cecil写出指令到文件时是惰性更新的。这就是为什么它们似乎具有错误的偏移量并且都映射到IL_0000
。
最终的IL代码如下:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 62 (0x3e)
.maxstack 8
IL_0000: ldarg.0
IL_0001: newobj instance void [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj instance void [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call instance void class Networking.PeerController`1<class Networking.ServerController>::.ctor()
IL_001c: nop
IL_001d: ldarg.0
IL_001e: ldstr "_connectedPlayersByClientId"
IL_0023: ldfld [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_0028: callvirt instance void Networking.NetworkBehaviour::LocalNetworkVariableChange(string,
object)
IL_002d: ldarg.0
IL_002e: ldstr "_connectedPlayersByPlayerId"
IL_0033: ldfld [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0038: callvirt instance void Networking.NetworkBehaviour::LocalNetworkVariableChange(string,
object)
IL_003d: ret
} // end of method ServerController::.ctor
这两个字段如下:
[NetworkVariable(SyncOnChange = false)]
private readonly Dictionary<ushort, ConnectedPlayer> _connectedPlayersByClientId = new
Dictionary<ushort, ConnectedPlayer>();
[NetworkVariable(SyncOnChange = false)]
private readonly Dictionary<NuID, ConnectedPlayer> _connectedPlayersByPlayerId = new
Dictionary<NuID, ConnectedPlayer>();
有任何想法吗?
英文:
I use Mono.Cecil to inject two method calls into a constructor. Somehow the construction always fails with the following error:
InvalidProgramException: Invalid IL code in Networking.ServerController:.ctor (): IL_0028: callvirt 0x0600017f
What I basically do is to inject a method call to the following method (from two base-classes above) for every stfld
while passing the name of the method (without class) and the current field value (boxed) as object:
protected void LocalNetworkVariableChange(string variableName, object value) {
// Fails no matter what i put here
}
The IL code before injection:
IL_0000: ldarg.0
IL_0001: newobj System.Void System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj System.Void System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call System.Void Networking.PeerController`1<Networking.ServerController>::.ctor()
IL_001c: nop
IL_001d: ret
After Injection:
IL_0000: ldarg.0
IL_0001: newobj System.Void System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj System.Void System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call System.Void Networking.PeerController`1<Networking.ServerController>::.ctor()
IL_001c: nop
IL_0000: ldarg.0
IL_0000: ldstr "_connectedPlayersByClientId"
IL_0000: ldfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_0000: callvirt System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.String,System.Object)
IL_0000: ldarg.0
IL_0000: ldstr "_connectedPlayersByPlayerId"
IL_0000: ldfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0000: callvirt System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.String,System.Object)
IL_001d: ret
(!) The labels in front of the instructions are updated lazy when writing out the instructions to file via Mono.Cecil. This is why the seem to have a wrong offset and map all to IL_0000
.
The final IL code looks like this:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 62 (0x3e)
.maxstack 8
IL_0000: ldarg.0
IL_0001: newobj instance void [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj instance void [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call instance void class Networking.PeerController`1<class Networking.ServerController>::.ctor()
IL_001c: nop
IL_001d: ldarg.0
IL_001e: ldstr "_connectedPlayersByClientId"
IL_0023: ldfld [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_0028: callvirt instance void Networking.NetworkBehaviour::LocalNetworkVariableChange(string,
object)
IL_002d: ldarg.0
IL_002e: ldstr "_connectedPlayersByPlayerId"
IL_0033: ldfld [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0038: callvirt instance void Networking.NetworkBehaviour::LocalNetworkVariableChange(string,
object)
IL_003d: ret
} // end of method ServerController::.ctor
The two fields look like this:
[NetworkVariable(SyncOnChange = false)]
private readonly Dictionary<ushort, ConnectedPlayer> _connectedPlayersByClientId = new
Dictionary<ushort, ConnectedPlayer>();
[NetworkVariable(SyncOnChange = false)]
private readonly Dictionary<NuID, ConnectedPlayer> _connectedPlayersByPlayerId = new
Dictionary<NuID, ConnectedPlayer>();
Any idea?
答案1
得分: 1
第一个问题是box System.Collections.Generic.Dictionary...
。你不能将一个引用类型进行装箱。
第二个问题是ldfld
。根据文档:
在顺序执行时,堆栈的转换行为如下:
- 将一个对象引用(或指针)推送到堆栈上。
- 从堆栈中弹出对象引用(或指针);找到对象中指定字段的值。
- 将字段中存储的值推送到堆栈上。
换句话说,ldfld
需要知道要从中读取字段的对象,并且它会从堆栈中消耗掉该对象参数。
因此,你需要:
ldarg.0 // 加载方法调用的 'this'
ldstr ... // 加载要传递给方法的字符串
ldarg.0 // 加载 ldfld 的 'this'
ldfld ... // 加载要传递给方法的字典
callvirt ...
英文:
The first problem is that box System.Collections.Generic.Dictionary...
. You can't box a reference type.
The second problem is the ldfld
. From the docs:
> The stack transitional behavior, in sequential order, is:
>
> 1. An object reference (or pointer) is pushed onto the stack.
> 2. The object reference (or pointer) is popped from the stack; the value of the specified field in the object is found.
> 3. The value stored in the field is pushed onto the stack.
In other words, ldfld
needs to know the object to read the field from, and it consumes that object parameter from the stack.
So you need:
ldarg.0 // Load the 'this' for the method call
ldstr ... // Load the string to pass to the method
ldarg.0 // Load the 'this' for the ldfld
ldfld ... // Load the dictionary to pass to the method
callvirt ...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论