Create Dynamic IExternalCommand class at runtime

Create Dynamic IExternalCommand class at runtime

NonicaTeam
Enthusiast Enthusiast
1,172 Views
7 Replies
Message 1 of 8

Create Dynamic IExternalCommand class at runtime

NonicaTeam
Enthusiast
Enthusiast

Good morning,

We have been trying to create a class dynamically with IExternalCommand implemented in order to give the new class different names at runtime but still execute our desired method. Our code is:

 

var ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(nombreassembly), AssemblyBuilderAccess.Run);
            
            var mb = ab.DefineDynamicModule(nombreassembly);

            var tb = mb.DefineType(nombreassembly);
            tb.AddInterfaceImplementation(typeof(IExternalCommand));

            foreach (var imethod in typeof(IExternalCommand).GetMethods())
            {
                var valueString = ((DescriptionAttribute)imethod.GetCustomAttribute(typeof(DescriptionAttribute))).Description;

                var method =
                  tb.DefineMethod
                  (
                    "@@" + imethod.Name,
                    MethodAttributes.Private | MethodAttributes.Static,
                    imethod.ReturnType,
                    new[] { tb }
                  );

                var thisParameter = Expression.Parameter(typeof(IExternalCommand), "this");

                var bodyExpression =
                  Expression.Lambda
                  (
                    Expression.Constant
                    (
                      Convert.ChangeType(valueString, imethod.ReturnType)
                    ),
                    thisParameter
                  );

                bodyExpression.CompileToMethod(method);

                var stub =
                  tb.DefineMethod(imethod.Name, MethodAttributes.Public | MethodAttributes.Virtual, imethod.ReturnType, new Type[0]);

                var il = stub.GetILGenerator();
                il.Emit(OpCodes.Ldarg_0);
                il.EmitCall(OpCodes.Call, method, null);
                il.Emit(OpCodes.Ret);

                tb.DefineMethodOverride(stub, imethod);
            }

            var fooType = tb.CreateType();
            var ifoo = (IExternalCommand)Activator.CreateInstance(fooType);

 

 

We managed to get the methods within the interface using ifoo at the end, but we have two issues:

- We don´t know how to edit the Execute method within the interface after implementation to add our code to that method. 

- We have a line throwing one issue when debugging. This line is: var valueString = ((DescriptionAttribute)imethod.GetCustomAttribute(typeof(DescriptionAttribute))).Description;

The error is: System.NullReferenceException: 'Object reference not set to an instance of an object.' Not sure which type should be set up in this case.

 

We would really appreciate you help into this.

Thank you

0 Likes
Accepted solutions (1)
1,173 Views
7 Replies
Replies (7)
Message 2 of 8

jeremy_tammik
Alumni
Alumni

To redefine the Execute method, i would assume that you would want to generate dynamic source code:

 

https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/dynamic-source-code-generat...

 

Why are you interested in the Description attribute? There is no need for it, is there?

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 3 of 8

NonicaTeam
Enthusiast
Enthusiast

Thanks @jeremy_tammik for your help!

 

We actually do not need to generate the code dynamically, we could use parameters to create the class with certain property values. We have been giving a try to CodeDom.

 

We first created a namespace. We try two things; bringing the methods from another class which implements IExternalCommand and Implementing IExternalCommand in the new class itself.

 

namespace NewCodeDomClassNamespace
{
    class NewCodeDomClass
    {
        CodeCompileUnit targetUnit;
        CodeTypeDeclaration targetClass;
        public NewCodeDomClass(string nameassembly)
        {
            targetUnit = new CodeCompileUnit();
            CodeNamespace samples = new CodeNamespace(nameassembly);
            samples.Imports.Add(new CodeNamespaceImport("Methods.ClassWithIExternalCommand"));
            targetClass = new CodeTypeDeclaration(nameassembly);
            targetClass.IsClass = true;
            targetClass.TypeAttributes = TypeAttributes.Public;
            targetClass.BaseTypes.Add(typeof(Autodesk.Revit.UI.IExternalCommand));
            samples.Types.Add(targetClass);
            targetUnit.Namespaces.Add(samples);            
        }
}
}

 

Then, OnStartup:

 

                NewCodeDomNamespace.NewCodeDomClass newclass = new NewCodeDomNamespace.NewCodeDomClass("NewClass");

                PushButtonData button0data = new PushButtonData("NewClass.NewClass",
                nombreboton, _path, "NewClass.NewClass");

 

However, when running the button, Revit throws an error saying that the class cannot be found in the add-in assembly.

 

Thanks

0 Likes
Message 4 of 8

jeremy_tammik
Alumni
Alumni

In that case, one of the arguments specifying the full class name may be incorrect. Maybe part of the class namespace prefix is missing. You can use some IL inspection tool to analyse your .NET assembly DLL and see exactly what full class names it defines:

  

https://duckduckgo.com/?q=il+decompiler 

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 5 of 8

ricaun
Advisor
Advisor

Why do you want to generate IExternalCommand dynamically?

 

I tried the CodeDom and didn't have any success.

 

By looking at the Assembly was not possible to find the dynamic class.

foreach (var item in Assembly.GetExecutingAssembly().GetTypes())
{
   Console.WriteLine(item);
}

 Probably Revit does something similar when the button is executed.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 6 of 8

NonicaTeam
Enthusiast
Enthusiast

Thank you very much @ricaun ! We also have been struggling the whole day using dotPeek with no success. Appreciate your comment.

We want to create a namespace and class dynamically because Revit journal files are logged using those when commands are executed. Our commands can be changed by the user and therefore we would like the Revit journal record to change with them.
EDIT:
We could not create classes using other methods from System. Reflection, maybe Revit blocks this behaviour.

0 Likes
Message 7 of 8

ricaun
Advisor
Advisor
Accepted solution

Hello @NonicaTeam I was able to create some assembly on the fly!

 

ricaun_0-1643659226763.gif

 

Here is the source code: https://github.com/ricaun/RevitAddin.CodeCompileTest

 

Have fun!

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 8 of 8

NonicaTeam
Enthusiast
Enthusiast

Thank you @ricaun !! That was really helpful, we really appreciate that you took the time to help us. 

0 Likes