This was inspired by Jim's IDispatch.Invoke interception @ https://forum.solidworks.com/thread/27197?tstart=60.
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:
[ComVisible(true)]
[Guid("00020400-0000-0000-c000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IDispatch3
{
}
Next, we need to generate an assembly, type and methods on the fly:
[ComVisible(true)]
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.Nop);
il.Emit(OpCodes.Ldstr, "hello_from_dynamic_testMethod");
il.EmitCall(OpCodes.Call, mi, null);
il.Emit(OpCodes.Ret);
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.