Dynamic Command and Menu Callbacks

This was inspired by Jim's IDispatch.Invoke interception @ https://forum.solidworks.com/thread/27197?tstart=60.


<p>swApplication.SetAddinCallbackInfo(0, targetObject, Cookie);</p>

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:

<p>[ComVisible(true)]<br>    [Guid("00020400-0000-0000-c000-000000000046"),<br>    InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]<br>    public interface IDispatch3<br>    {<br>    }</p>

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

<p>[ComVisible(true)]<br>    public class CodeGenerator<br>    {<br>        public object tbBakedInstance;<br><br>        public CodeGenerator()<br>        {<br>            Guid g = Guid.NewGuid();<br>            AssemblyName asmname = new AssemblyName();<br>            asmname.Name = "temp" + g;<br>            AssemblyBuilder asmbuild = System.Threading.Thread.GetDomain().DefineDynamicAssembly(asmname, AssemblyBuilderAccess.Run);<br>            ModuleBuilder modbuild = asmbuild.DefineDynamicModule("test");<br><br>            TypeBuilder tb = modbuild.DefineType("testType", TypeAttributes.Public, null, new Type[] { typeof(IDispatch3) });<br>            MethodBuilder mb = tb.DefineMethod("testMethod", MethodAttributes.Public, typeof(void), null);<br>            MethodInfo mi = typeof(System.Diagnostics.Debug).GetMethod("WriteLine", new Type[] { typeof(string) });<br>            ILGenerator il = mb.GetILGenerator(256);<br>            //il.Emit(OpCodes.Nop);<br>            il.Emit(OpCodes.Ldstr, "hello_from_dynamic_testMethod");<br>            il.EmitCall(OpCodes.Call, mi, null);<br>            il.Emit(OpCodes.Ret);<br>            tbBaked = tb.CreateType();<br><br>            tbBakedInstance = Activator.CreateInstance(tbBaked);<br>        }<br>    }</p>

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:

<p>myCode = new CodeGenerator();<br>callme = myCode.tbBakedInstance;<br>_swApplication.SetAddinCallbackInfo(0, callme, Cookie);</p>

Now our target object for SW callbacks is our tbBakedInstance.

And finally, we build our command tabs and menus:

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

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.

SolidworksApi macros