AnsweredAssumed Answered

Dynamic Command and Menu Callbacks

Question asked by chris misztur on Jun 29, 2010
Latest reply on Aug 27, 2012 by jason van clark

This was inspired by Jim's IDispatch.Invoke interception @


swApplication.SetAddinCallbackInfo(0, targetObject, Cookie);

is very limiting, since all callback methods must be contained in targetObject.  I tried creating a class that implements IDispatch, hoping to intercept the Invoke method and dispatch my callbacks at runtime.  That did not work.


After some trial and error I finally got it to work with System.Reflection.Emit.


First, we need to define the interface that made all this possible:

    public interface IDispatch3


Next, we need to generate an assembly, type and methods on the fly:

    public class CodeGenerator
        public object tbBakedInstance;

        public CodeGenerator()
            Guid g = Guid.NewGuid();
            AssemblyName asmname = new AssemblyName();
            asmname.Name = "temp" + g;
            AssemblyBuilder asmbuild = System.Threading.Thread.GetDomain().DefineDynamicAssembly(asmname, AssemblyBuilderAccess.Run);
            ModuleBuilder modbuild = asmbuild.DefineDynamicModule("test");

            TypeBuilder tb = modbuild.DefineType("testType", TypeAttributes.Public, null, new Type[] { typeof(IDispatch3) });
            MethodBuilder mb = tb.DefineMethod("testMethod", MethodAttributes.Public, typeof(void), null);
            MethodInfo mi = typeof(System.Diagnostics.Debug).GetMethod("WriteLine", new Type[] { typeof(string) });
            ILGenerator il = mb.GetILGenerator(256);
            il.Emit(OpCodes.Ldstr, "hello_from_dynamic_testMethod");
            il.EmitCall(OpCodes.Call, mi, null);
            tbBaked = tb.CreateType();

            tbBakedInstance = Activator.CreateInstance(tbBaked);

What we just did is create an assembly at runtime, containing a testType Type.  This type contains one void method named testMethod.  This method just writes to our Debug console so that we know it was called.  Last we instantiate a public field tbBakedInstance that will be of our testType.


Next, we call set our callback info:

myCode = new CodeGenerator();
callme = myCode.tbBakedInstance;
_swApplication.SetAddinCallbackInfo(0, callme, Cookie);

Now our target object for SW callbacks is our tbBakedInstance.


And finally, we build our command tabs and menus:

_CommandGroup.AddCommandItem2("Command3", -1, "Trigger Command 3", "ToolTip for Command3", 3, "testMethod", "Command2Enable", 103, (int)(swCommandItemType_e.swMenuItem | swCommandItemType_e.swToolbarItem));

The above command item has our dynamic testMethod as our callback.


We run our addin, click our command button 3 times and we're all good:



What does this do for me?

I can sew random public void methods all over my code to be my command/menu callbacks.  Then I tag them with an attribute.  When my addin loads I can then iterate my types, pull all those tagged methods out as MethodInfo.  Then, I can create a dynamic type.  In my dynamic type I can create a dynamic method for each one of the tagged methods.  The dynamic method's IL will basically invoke my tagged method, acting as a proxy between SW and my tagged methods that do the real work.