By Xiaodong Liang
Recently, we got a strange issue. With a specific dataset, we opened all files in cascade views, then activated each document and ran an external rule. The rule is quite simple. It just gets the document ComponentDefinition and access parameters.
Dim compDef = ThisApplication.ActiveDocument.ComponentDefinition
For most documents, it works well. but for specific assembly document, it throws an exception that says: Member not found.
We doubted ComponentDefinition / Parameters have been broken when activating the document at his side, so we tested with a VBA code. But the code tells the ComponentDefinition / Parameters are always valid. While the issue seems not always reproducible.
Finally, our expert of iLogic Mike Deck shared the insights:
It is caused by internal .NET behavior to do with COM interop. Here’s a simplified rule that will reproduce it:
- Create a new part
- Run the rule in the part
- Create a new assembly
- Run the rule in the assembly
The order matters: it won’t break if you run it in the assembly first.
Here’s what’s happening: ActiveDocument returns a Document object. There is no ComponentDefinition property on the Document object. So VB.NET tries to do late binding. When you run the rule on a Document that is also a PartDocument, it will find a ComponentDefinition property with a DispId on that object. Then later you run the rule on a Document that is also an AssemblyDocument. It also has a ComponentDefinition property, but with another DispId.
For some reason, VB.NET will fail to find that property. it might probably cache the first DispId (from PartDocument), and associates it with the Document interface. Then it thinks that it can always use that DispId to access ComponentDefinition on a Document. But that DispId will fail if the object is an AssemblyDocument.
It seems like an issue of Microsoft .NET. So in such case, there is a workaround. In VB.NET code, never try to access the ComponentDefinition on a Document object. Instead, always cast it to a PartDocument or AssemblyDocument first. Here’s sample code to get the UserParameters:
Dim doc = ThisApplication.ActiveDocument
Dim params As Parameters = Nothing
If doc.DocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
Dim assemDoc As AssemblyDocument = doc
params = assemDoc.ComponentDefinition.Parameters
ElseIf doc.DocumentType = DocumentTypeEnum.kPartDocumentObject Then
Dim partDoc As PartDocument = doc
params = partDoc.ComponentDefinition.Parameters
End If
Dim userparams As UserParameters = params.UserParameters
It requires a lot more than a single line of code, but it avoids the problem.