The return values of methods/functions in SOLIDWORKS API can be strings, objects and arrays.
This can be confusing for programmers that are just starting out. Or even for experienced devs.
In this blog post, I’ll explain every possible return value in the API.
Methods and functions are often used interchangeably. In C#, they have different meanings, so I will use the term methods in this post. SOLIDWORKS does the same in their API documentation.
You call a method to change a property of an object. Many methods will return something, but not all of them do.
If you call for example ISldWorks.CloseDoc, it will not return anything. In C# we call this void, but it has no special name in VBA.
That also means you don’t know if that method ran successfully. Did it close the document as you requested? It’s up to you to figure that out, or you have to trust SOLIDWORKS that it all worked as expected.
Methods that return simple value types are the easiest to work with. But they can mean different things. A string can be a name, a filepath or a selection string. So read the docs.
A boolean true will usually mean that the method ran successfully, although it sometimes means the opposite (read the docs). To avoid confusion, I prefer returning an enum value “Success” or “Failed” instead of returning a boolean.
Most programming languages have both integers and longs. An integer is used for smaller numbers (small is relative, it goes from -2,147,483,648 to 2,147,483,647 in C# and VBA and it uses 32 bits). If you need larger whole numbers, you use a long with 64 bits.
95% of all SOLIDWORKS APIs that return whole numbers will return integers. But there are a few annoying exceptions like the sketch segment ID. This caused me to write the following strange helper to get two longs for a sketch segment ID:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /// <summary> /// Get two longs from two integers or longs. /// </summary> /// <param name="sketchSegment"></param> /// <returns></returns> private static List<long> GetIds(ISketchSegment sketchSegment) { try { return ((int[]) sketchSegment.GetID()).Select(Convert.ToInt64).ToList(); } catch (Exception) { return ((long[]) sketchSegment.GetID()).ToList(); } } |
This method tries to convert two integers to longs. If that fails, SOLIDWORKS apparently gave us longs already. It also uses LINQ.
In VBA, you will get an error when the method returns a long and you try to put it into an integer.
The SOLIDWORKS API uses mostly integers for enum values. Since enums are integers underwater in C#, casting them is very easy but you need an explicit cast between brackets:
1 | var bodies = swPart.GetBodies2((int) swBodyType_e.swSolidBody, true); |
In VBA, casting between enums and integers is implicit:
1 2 | Dim swPart As PartDoc, bodies As Variant bodies = swPart.GetBodies2(swBodyType_e.swSolidBody, True) |
See IPartDoc.GetBodies2 in the docs.
There are some APIs that use enums and not integers. I have only seen them in the Document Manager API so far, for example in the method ISwDMApplication.GetDocument.
When you call ISldWorks.GetUserPreferenceStringValue, it returns a single string that represents multiple directory paths. The paths are separated by a semicolon ( ; ), but the documentation fails to mention that.
It’s good practice to add meaning to your primitives (aka strings, integers etc) to combat primitive obsession. Nick Chapsas also has a good video on the subject.
Does a string represent a path? Then create a FilePath object and convert the string to a path as soon as possible. Does a set of two integers represent a sketch segment ID? Then create a SketchSegmentID object to avoid creating methods that require two integer arguments.
Now, you can pass around this new object in your code. A method that expects a FilePath argument has more context than a method that expects a string.
The opposite of a value type is a reference type and the basis of all reference types is the object. Booleans, string and integers are value types and these things are passed around directly. Objects are not passed around, we only pass a reference (also known as a pointer) to that object.
Objects are generally more complex and have an internal state. The most-used objects are the SOLIDWORKS and ModelDoc2 objects. We can request (and sometimes edit) that state by checking its properties and we can change the state by calling methods. You cannot compare two objects directly, because if you do, you are usually only checking if the two pointers are the same.
The SOLIDWORKS API has 445 object types, so you can’t avoid them.
An example of a property returning an object is the SldWorks.ActiveDoc property, which returns a ModelDoc2 object or null if you have no files open.
The hardest thing when learning the SOLIDWORKS API is that many methods lazily return null when the input is not as expected. No error message, no help, no exception, just null. You’re on your own, dude.
To solve these issues, I usually:
SOLIDWORKS often chooses to return an array of values, for example an array of doubles to describe a position (even though we have IMathPoint for that also). When you call IDimension.GetValue3 and are only interested in a single configuration, you receive an array with one value.
Here’s an example where I convert a double array that represents a bounding box of a feature into a rectangle struct. A rectangle has more context than an ordinary array.
1 2 3 4 5 6 7 8 9 10 11 12 13 | /// <summary> /// Get the bounding box of a feature. /// </summary> /// <param name="feature"></param> /// <returns></returns> public static Rect GetBoundingBox(IFeature feature) { object boundingBox = null; var success = feature.GetBox(ref boundingBox); if (!success) throw new BoundingBoxNotFound(); var boundingBoxArray = (double[]) boundingBox; return new Rect(new Point(boundingBoxArray[0], boundingBoxArray[1]), new Point(boundingBoxArray[3], boundingBoxArray[4])); } |
A value array is still a type of object, so it can be null.
I strongly prefer getting an empty list back, but SOLIDWORKS will never do that. Instead, it returns null. That means you cannot directly iterate over the values with a foreach, you have to check for null first. Here I use such
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /// <summary> /// Get the lines in a view. /// </summary> /// <param name="view"></param> /// <returns></returns> public static double[] GetLinesInViewAsDoubleArray(IView view) { const int excludeCrossHatch = 1; view.GetPolylines6(excludeCrossHatch, out var polyLines); if (polyLines == null) { // Happens when the model has no bodies. throw new ViewIsEmpty(); } return (double[]) polyLines; } |
Generally, methods that return a 2D or 3D point (so a fixed-length array) will not return null but an array of zeroes instead. If the method returns a variable-length array, it can return null.
An example of a method returning an array of doubles is IAnnotation.GetPosition. You can cast it to a double array directly:
1 | var position = (double[]) annotation.GetPosition(); |
It’s a bit more work in VBA because variants are annoying:
1 2 3 4 5 6 7 8 9 10 | Public Function GetAnnotationPosition(swAnno As Annotation) As Double() Dim position(1) As Double Dim annotationPosition As Variant annotationPosition = swAnno.GetPosition position(0) = annotationPosition(0) position(1) = annotationPosition(1) GetAnnotationPosition = position End Function |
My favorite method to hate is IView.GetPolylines6. It has been deprecated, but I still use it because GetPolylines7 is slow.
This method takes returning an array to a whole new level. It returns a double array, but:
I have written an interpreter to be able to use the return values from this method, which I am not giving away 😉 But you bet it includes a whole bunch of unit tests.
Every now and then, a method has a strange return type. IModelDocExtension.GetPersistReference3 returns a single object, but that object contains an array of bytes of unknown length. A persistent ID is at least 16 bytes long, but it’s often 20 bytes or 50+ bytes.
To convert this object in C#, I use the following helper method:
1 2 3 4 5 6 7 8 9 | /// <summary> /// Convert the input (Solidworks uses a byte array in an object) to a real byte array. /// </summary> /// <param name="id"></param> /// <returns></returns> private static List<byte> CastToByteArray(object id) { return ((IEnumerable) id).Cast<byte>().ToList(); } |
You treat object arrays similar to reference type arrays, but you have to make sure you cast the whole return object to an object array first. Only after that can you cast each item to its specific type.
Just as I talked about before, SOLIDWORKS will return null and never an empty array. That’s why you always check for null first. In C#, lists are easier to work with than arrays so I convert them as soon as possible.
As an example, to get all annotations in a view:
1 2 3 4 5 6 7 8 9 10 | /// <summary> /// Get all annotations linked to a view. /// </summary> /// <param name="view"></param> /// <returns></returns> public static List<Annotation> GetAnnotationsFromView(IView view) { var annotations = view.GetAnnotations(); return annotations == null ? new List<Annotation>() : ((object[]) annotations).Cast<Annotation>().ToList(); } |
Here, I return an empty list if the annotations object is null. If not, I cast the whole object to an object array, then I cast each item in it to an Annotation using LINQ. The final step is converting the resulting enumerable to a list.
You can make it even shorter by using the null-coalescing operator:
1 2 3 4 5 6 7 8 9 | /// <summary> /// Get all annotations linked to a view. /// </summary> /// <param name="view"></param> /// <returns></returns> public static List<Annotation> GetAnnotationsFromView(IView view) { return ((object[]) view.GetAnnotations())?.Cast<Annotation>().ToList() ?? new List<Annotation>(); } |
A variant is anything that is not defined. It only exists in VBA, and I hate it. It is not the same as an array, which you will notice when you try to foreach through a variant. Every item in a variant is also a variant.
This function will compile and it works, but if you want to do more with the body objects, you better cast them to a Body2 object first.
With implicit casting:
1 2 3 4 5 6 7 8 | Private Function PrintBodyNames(swPart As PartDoc) Dim allBodies As Variant, singleBody As Variant allBodies = swPart.GetBodies2(swBodyType_e.swAllBodies, False) For Each singleBody In allBodies Debug.Print singleBody.Name Next End Function |
With an explicit cast:
1 2 3 4 5 6 7 8 9 10 | Private Function PrintBodyNames(swPart As PartDoc) Dim allBodies As Variant, singleBody As Variant allBodies = swPart.GetBodies2(swBodyType_e.swAllBodies, False) For Each singleBody In allBodies Dim swBody As Body2 Set swBody = singleBody Debug.Print swBody.Name Next End Function |
Every function in VBA has a return value. If you don’t define one, it will be a variant. See the two examples below.
If you don’t need a return value, the official route is to create a Sub, not a function. That being said, I usually create a function anyway because any sub can be used as a starting point of a macro. I prefer to only have the Main sub to avoid confusion. Having multiple subs has caused problems in the past because #TASK just picks a random sub when it has to run a macro on multiple files.
Since I prefer working with arrays over variants, I convert them as soon as possible. For fixed-length arrays like 3D points, you can set the array length when you define it. For variable-length arrays, this is a little harder.
Here’s my solution for converting a variant into a real object array. If you have a better solution, please let me know because this feels overly complex.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | Sub main() Dim swApp As SldWorks.SldWorks, swModel As ModelDoc2, swPart As PartDoc Set swApp = Application.SldWorks Set swModel = swApp.ActiveDoc Set swPart = swModel Dim bodies() As Body2 bodies = GetBodies(swPart) End Sub Private Function GetBodies(swPart As PartDoc) As Body2() Dim allBodies As Variant, singleBody As Variant allBodies = swPart.GetBodies2(swBodyType_e.swAllBodies, False) Dim bodiesArray() As Body2 ReDim bodiesArray(0) 'Give the array an initial length For Each singleBody In allBodies Dim swBody As Body2 Set swBody = singleBody Set bodiesArray(UBound(bodiesArray)) = swBody ReDim Preserve bodiesArray(UBound(bodiesArray) + 1) Next 'If the part has no bodies, the ubound remains at zero If UBound(bodiesArray) > 0 Then ReDim Preserve bodiesArray(UBound(bodiesArray) - 1) 'Remove the last emtpy item from the array End If GetBodies = bodiesArray End Function |
A method can return nothing or one item. If you really need to return multiple values, you could use out parameters. You initialize these values as zero or null, then pass them as arguments into a method. The method then sets these parameters to a useful value.
SOLIDWORKS does this a lot. An example is the ActivateDoc3 method, where the first three parameters are in parameters and the last one is an out parameter that holds the error status. If you call this method and it returns null as the active model, you can use the out parameter to find out what went wrong.
In VBA:
1 2 3 4 5 6 7 8 | Private Function ActivateDocument(swApp As SldWorks.SldWorks, path As String) As ModelDoc2 Dim errors As Long Set ActivateDocument = swApp.ActivateDoc3(path, True, swRebuildOnActivation_e.swRebuildActiveDoc, errors) If errors <> 0 Then Debug.Print "Cannot activate model " & path & ". Error code: " & errors End If End Function |
In C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /// <summary> /// Make a model the active model by its path (when it has been saved) or title (if not). /// </summary> /// <param name="pathOrTitle"></param> public static ModelDoc2 ActivateModel(string pathOrTitle) { var errors = 0; var swModel = (ModelDoc2) Core.SwApp.ActivateDoc3(pathOrTitle, true, (int) swRebuildOnActivation_e.swRebuildActiveDoc, ref errors); if (errors != 0) Log.Info($"Cannot activate model {pathOrTitle}. Error code: {errors}"); return swModel; } |
Out parameters are generally frowned upon since methods have one return value for a reason. And because out parameters aren’t used often, developers will have a harder time understanding what your code does.
If you need multiple return values, it usually means your method does two things, not one. A preferred solution is to return a custom object with multiple properties.
Technically, the last example isn’t even an out parameter in C#, even though the documentation says so. It is a ref parameter, which means it may have a useful value before the method is called. If it really was an out parameter, you could have called the method like this, without having to initialize the parameter:
1 2 | var swModel = (ModelDoc2) Core.SwApp.ActivateDoc3(pathOrTitle, true, (int) swRebuildOnActivation_e.swRebuildActiveDoc, out var errors); |
Subscribe to our newsletter and get our TimeSavers add-in for free.