Friday, March 4, 2016

Creating delegate declared as internal in thirdparty library

Creating delegate declared as internal in thirdparty library (like mscorlib.dll) can be tricky, especially if it takes or returns arguments (classes) also declared as internal. It is pretty easy to use prepared delegate value to replace it somewhere in library internals using reflection, but attaining such delegate for your own method is completely different beast.

Lets look for example at SocketHandler delegate (mscorlib.dll):

internal delegate SocketHandler SocketHandlerFactory(
  Socket socket,
  SocketCache socketCache,
  String machineAndPort);

Beside the internal delegate itself (which we can't reference), we also have issue with SocketCache argument and SocketHandler return types both declared as internal (so we can't declare method to suit this delegate).
Long story short, the only way I found to achieve this is using DynamicMethod class which allows to declare a new method, with arguments we get from original delegate using reflection, emit some IL code to invoke our hardcoded method with untyped (object) arguments, and create delegate based on provided type.
Below there are some cuts from source code which should give you the general idea (yeah, I'm to lazy to provide full working example or fix variables names for you). Let's say we have object of SocketCache type (which has _handlerFactory delegate field we want to replace):


var sct = socketCache.GetType();
// get field info
var miHandlerFactory = sct.GetField("_handlerFactory"
  BindingFlags.NonPublic | BindingFlags.Instance);
// get variable original value  (delegate)
var oldHandlerFactory = miHandlerFactory.GetValue(socketCache);
// get type of delegate
var hft = oldHandlerFactory.GetType();
// get MethodInfo for delegate method
var hfmi = hft.GetProperty("Method").GetGetMethod()
  .Invoke(oldHandlerFactory, null) as MethodInfo;

// we will create delegate bound to our SocketFactoryInterceptor object
var sfit = typeof (SocketFactoryInterceptor);

// create dynamic method with our object as owner and a copy of all arguments of delegate
DynamicMethod dm = new DynamicMethod("Handler", hfmi.ReturnType,
  new []{sfit}.Concat(hfmi.GetParameters().Select(p=>p.ParameterType))
  .ToArray(), sfit, true);

var ilg = dm.GetILGenerator();
// load all provided arguments onto stack
foreach (var a in dm.GetParameters().Select((p,i)=>i)) 
  ilg.Emit(OpCodes.Ldarg, a);
// invoke our method (which can declare argument as simple untyped objects)
ilg.EmitCall(OpCodes.Call, sfit.GetMethod("CreateSocketHandler"), null);
// we need that cast for our predefined method to be able to return simple object type
ilg.Emit(OpCodes.Castclass, hfmi.ReturnType);
ilg.Emit(OpCodes.Ret);

// interceptor class will do all the stuff (call original handler and do our logic), so we just need
// to pass control to our existing method - as little IL as possible
var sfi = new SocketFactoryInterceptor() { transportSink = ns, 
  socketCache = socketCache, miHandlerFactory = miHandlerFactory, 
  handlerDelegate = (Delegate) oldHandlerFactory, dynamicMethod = dm, };
// create delegate based on delegate type information we got from variable
var dlg = sfi.ourDelegate = dm.CreateDelegate(hft, sfi);

// replace variable value with our new delegate
miHandlerFactory.SetValue(socketCache, dlg);

1 comment:

  1. Useful blog u have here. For fixing systems I would recommend Wurth Malaysia as they have a dedicated team.

    ReplyDelete