Table of Contents
Object Identification Using Late-Bound Languages (like Python) is Straightforward
Python already makes it extremely easy to work with COM objects because it’s a late-typed language. In this example, we’ll see how the IID (or class ID number) is exposed.
from com3dx import * test_object = get3dxClient().ActiveEditor.Selection.Item(1).Value dir(test_object) # OUTPUT: ['AppendHybridShape', 'Application', 'Bodies', 'CLSID', 'GeometricElements', 'GetItem', 'HybridBodies', 'HybridShapes', 'HybridSketches', 'Name', 'Parent', '_ApplyTypes_', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_get_good_object_', '_get_good_single_object_', '_oleobj_', 'coclass_clsid'] print(test_object.CLSID) # OUTPUT: {49EC1846-E827-11D2-9265-006094B9A462}
Here, we’re defining a test object from the current selection in CATIA. Then, using Python’s built-in dir method, seeing which properties and methods are exposed in the interface. The one we’re interested in here is CLSID. The CLSID property of the object we’ve selected is 49EC1846-E827-11D2-9265-006094B9A462.
With this CLSID value in hand, we can query the windows registry to get its associated ProgID (or programmatic ID, the actual name of the object’s class used in code).
Searching the registry for this guid yields the ProgID HybridBody. Easy, right?
What if we want to get this value using an early-typed language, like C#?
It’s a little trickier for early-typed languages when we want to get the class ID / type during runtime, but it can be done.
First, let’s create a new model class to define the properties we’d like to retrieve:
public class COMIdModel { public string Type { get; set; } public Guid IID { get; set; } public List<string>? Methods { get; set; } = null; public List<string>? Properties { get; set; } = null; }
Now, let’s take a look at any object hierarchy in CATIA’s DSYAutomation.chm help file.
If we take a look at the inheritance hierarchy of any object in CATIA, we see that all objects inherit from System.IDispatch. This interface is language-agnostic, and allows programming tools and other applications to use the object’s methods and properties regardless of the language. This is important, because we’ll use methods from the IDispatch interface to retrieve the information we’re looking for.
If we take a look at the registry again, we see the IID for the IDispatch interface is 00020400-0000-0000-C000-000000000046. This is the same IID used for the IDispatch interface across all Windows systems. Now, we’ll use IDispatch’s IID to import the interface during runtime. Let’s start writing the class.
Writing the Class
using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; public class TestComInspector { // import the IDispatch interface, define methods we want to use from it [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00020400-0000-0000-C000-000000000046")] // IDispatch IID public interface IDispatch { [return: MarshalAs(UnmanagedType.Interface)] ITypeInfo GetTypeInfo(int iTInfo, int lcid); }
Start by importing InteropServices and InteropServices.ComTypes, which will allow us to work with these COM objects. As you can see, we’re using the IID for IDispatch to import that interface. There are various methods in the interface, but we only need to worry about the ones we’re going to implement, which in this case is ITypeInfo GetTypeInfo(int iTInfo, int lcid).
Next, we’ll start writing a method to use IDispatch to get the info we need.
private COMIdModel _queryComObject(object comObject) { COMIdModel returnModel = new COMIdModel(); if (comObject == null) return returnModel; if (comObject is IDispatch dispatch) // every object in CATIA inherits from IDispatch { ITypeInfo typeInfo = dispatch.GetTypeInfo(0, 0); // Assuming default locale typeInfo.GetDocumentation(-1, out string typeName, out _, out _, out _); // SET TYPE returnModel.Type = typeName;
We’re passing in an unknown object (of supertype object) into this method. Then, we create an empty return model to store the info we need. We should test that this unknown COM object indeed inherits from IDispatch (which it certainly will if it’s coming from CATIA). We’re storing the object’s type info in ITypeInfo typeInfo. Then, to get the type name, it’s as simple as calling the GetDocumentation method on the ITypeInfo object. If the type name is the only thing that we need, we can stop here. Next, we’ll look at how to get the IID of the COM object.
Getting the IID
For further information, we’ll need to use typeInfo to create a pointer to the structure in memory that represents the COM object we’re working with. Then, we’ll use InteropServices.Marshal to Marshal it. NOTE: This is somewhat dangerous! If we don’t properly release the pointer after we’ve retrieved the info we need, memory leaks will ensue. We’ll release the pointer at the end.
typeInfo.GetTypeAttr(out IntPtr typeAttrPtr); TYPEATTR typeAttr = Marshal.PtrToStructure<TYPEATTR>(typeAttrPtr); // SET IID returnModel.IID = typeAttr.guid;
Once the pointer has been marshaled, we can retrieve the IID of the object by looking at the guid property of our typeAttr. Again, if this is all the info we need, we can stop here (but not without releasing the pointer from memory first – for that, skip down to the end).
Getting the Properties and Methods
Now, let’s take a look at how to retrieve all of the properties and methods available for the COM object. We’ll get the names of the properties and methods, and store them in lists in our ComIdModel.
// GET METHODS List<string> methods = new List<string>(); for (int i = 0; i < typeAttr.cFuncs; i++) { typeInfo.GetFuncDesc(i, out IntPtr funcDescPtr); FUNCDESC funcDesc = Marshal.PtrToStructure<FUNCDESC>(funcDescPtr); typeInfo.GetDocumentation(funcDesc.memid, out string memberName, out _, out _, out _); Console.WriteLine($"Method: {memberName}"); methods.Add(memberName); typeInfo.ReleaseFuncDesc(funcDescPtr); } // SET METHODS LIST returnModel.Methods = methods; // GET PROPERTIES List<string> properties = new List<string>(); for (int i = 0; i < typeAttr.cVars; i++) { typeInfo.GetVarDesc(i, out IntPtr varDescPtr); VARDESC varDesc = Marshal.PtrToStructure<VARDESC>(varDescPtr); typeInfo.GetDocumentation(varDesc.memid, out string memberName, out _, out _, out _); Console.WriteLine($"Property: {memberName}"); properties.Add(memberName); typeInfo.ReleaseVarDesc(varDescPtr); } // SET PROPERTIES LIST returnModel.Properties = properties;
In this block of code, we’re creating additional pointers for each of the properties and methods present. After retrieving the name of each property and method, we release the pointer.
Now, our ComIdModel should have 4 pieces of information: the type name, the IID, a list of property names, and a list of method names!
Releasing the Pointer (VERY IMPORTANT!)
// release the typeAttrPtr typeInfo.ReleaseTypeAttr(typeAttrPtr); return returnModel;
After we’re done getting all the info we need, let’s release the pointer to typeAttr to avoid memory leaks (very bad). Then, we can return the ComIdModel to the method’s caller.
Looking at the Results
Looking at our return model, we can now see all of the information populated (even some methods exposed that aren’t included in the API documentation!).