Reputation: 4551
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?
Upvotes: 0
Views: 101
Reputation: 42330
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:
- An object reference (or pointer) is pushed onto the stack.
- The object reference (or pointer) is popped from the stack; the value of the specified field in the object is found.
- 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 ...
Upvotes: 1