I have been attempting to pass a string array contained in a variant to the AutoCAD method AcadPlot.SetLayoutsToPlot. I am using late binding without a Interop assembly because we have several versions of AutoCAD in this company and I would like to support all of them from the same code base.
The method wants a variant that contains a string array passed byref. The Microsoft documentation on COM interop from VB.NET makes it sound like the compiler will marshal it correctly by default, but it always throws a COMException (Exception from HRESULT: 0x80070057 (E_INVALIDARG)).
The stack trace shows it is trying to anyway:
StackTrace:
at Microsoft.VisualBasic.CompilerServices.LateBinding.InternalLateCall(Object o, Type objType, String name, Object[] args, String[] paramnames, Boolean[] CopyBack, Boolean IgnoreReturn)
at Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateCall(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack, Boolean IgnoreReturn)
But no matter how I code it I cannot get this variant array passed to AutoCAD.
I have tried:
Imports System.Runtime.InteropServices
' VB default marshalling
Dim layoutarr = New Object() {"Layout1"}
doc.Plot.SetLayoutsToPlot(layoutarr)
' using a VariantWrapper
Dim layoutarr = New Object() {"Layout1"}
Dim wrapper As New System.Runtime.InteropServices.VariantWrapper(layoutarr)
doc.Plot.SetLayoutsToPlot(wrapper)
'making a method and using a custom marshaling attribute
Dim layoutarr = New Object() {"Layout1"}
doc.Plot.SetLayoutsToPlot(MarshalVariantStringArray(layoutarr))
Public Function MarshalVariantStringArray(ByVal ObjIn As Object) As <MarshalAsAttribute(UnmanagedType.SafeArray, SafeArraySubType:=VarEnum.VT_BSTR)> Object
Return ObjIn
End Function
'using reflection to invoke the method
'(not sure why that would help, but it was worth a try)
Dim layoutarr = New Object() {"Layout1"}
Dim argarr(0 To 0) As Object
argarr(0) = layoutarr
doc.Plot.GetType().InvokeMember("SetLayoutsToPlot", Reflection.BindingFlags.InvokeMethod, Nothing, doc.Plot, argarr)
Does anyone know what to do in this situation? As intensively as AutoCAD ActiveX uses variant arrays, I know this problem is going to come up again and again for me as I try to migrate code over to .NET.
Thanks.
Solved! Go to Solution.
Solved by mleslie00. Go to Solution.
I think in your all attempts you did the same thing: trying to pass an ARRAY of object, instead of required AN object that is an array of string.
This is how crapy VB.NET tries to to more things behind of scene for user. If you set Option Strict on, the error would have been caught at compiling time:
Dim something = New Object() {...}
is equivalent to
Dim layoutarr As object()
layoutarr=New Object(){...}
Hence the error: object array with one element is not the same as a single object
While in your cose you should have done this:
Dim layoutarr As object=new String(){....}
I think even this might work"
Dim layoutarr As String()=New String(){...}
Personally, if I have to write with VB.NET, I never omit the As part in the Dim statement, just make the declaration code unmistakenly clear. Whether Option Strict is on or not
If you use C#, you would not have run into this type of mistake.
Norman Yuan
To let everyone know how this ended up and what I learned:
'marshals correctly when using Interop.AutoCAD.dll
Dim plt As AutoCAD.AcadPlot = Layout.Document.Plot
plt.SetLayoutsToPlot(layoutarr)
'does not work without Interop.AutoCAD.dll
Dim plt As Object= Layout.Document.Plot
doc.Plot.SetLayoutsToPlot(layoutarr)
'this works with no interop .dll
Dim plt As Object= Layout.Document.Plot
plt.SetLayoutsToPlot(MarshalVariantStringArray(layoutarr))
Public Function MarshalVariantStringArray(ByVal ObjIn AsObject) _
As <Runtime.InteropServices.MarshalAsAttribute( _
Runtime.InteropServices.UnmanagedType.SafeArray, _
SafeArraySubType:=Runtime.InteropServices.VarEnum.VT_BSTR)> Object
ReturnObjIn
End Function
In summary, VB is using the type information in the Interop.AutoCAD.dll to know how to marshal the string array into a variant. In the absence of that information, the default marshalling does not take care of it, but you can use attributes to tell the compiler what you are really trying to do.
Also for reasons that I do not really understand, the VariantWrapper object did not help the situation. AutoCAD still saw that as an invalid parameter.
Maybe this will help other lunatics like me that have an ongoing vested interest in using late binding.
Mike Leslie
Hello,
Mike’s solution looked very promising. However, when I applied it for coping blocks I get “Invalid object array” error.
Dim objCollection(0) As Object objCollection(0) = SourceFile.Blocks.Item(BlockEntity.EffectiveName) SourceFile.CopyObjects(objCollection, TargetFile.Blocks)
Does anyone know of any solution for calling CopyObjects in late bind?
Please note that the above work in an early bind situation in which objCollection is defined as AcadBlock:
(Dim objCollection(0) As AcadBlock)
I replicated your error (CopyObjects working correctly early bound, failing late bound), but I was not able to find a solution.
I could not get the MarshalAttibutes to translate this to what AutoCAD wanted. I had thought that passing the object(s) as an IUnknown or an IDispatch inside a SafeArray made the most sense, but it just wasn't happening.
There really should be a way to do this, but I could not find it.
In theory, you could use the methods in the Marshal class to manually allocate a SAFEARRAY and to put a pointer to your object into it, but that is too involved and low-level for me to mess with right now. I've spent too much time on this maddening problem as it is!
Another way to solve would be to use SendCommand to send lisp statements to do the CopyObjects for you. See:
http://www.theswamp.org/index.php?topic=34676.msg399205#msg399205
I bet it would work, but I hate having to use multiple languages just to get something done!
Thanks for the efforts on this issue.
I also hate having to use multiple languages when simple tasks should be doable with one.
I’ll try the LISP suggestion. But I wonder if CopyObjects would work in C#; it would be a more elegant solution to combine VB.NET with C# or to use C# only.
i create region in late-bound. this is code, it is show " Invalid Object Array". how can i do?
[return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.SafeArray, SafeArraySubType = System.Runtime.InteropServices.VarEnum.VT_BSTR)]
public object MarshalVariantStringArray(object ObjIn)
{
return ObjIn;
}
private dynamic createCircleRegion
{
dynamic[] circleObj=new dynamic[1];
double[] calpt = new double[3];
calpt[0] = 0; calpt[1] = 0; calpt[2] = 0;
circleObj[0] = AcadDoc.ModelSpace.AddCircle(cirPt, circleR);
object[] regionObject;
regionObject = (object [])AcadDoc.ModelSpace.AddRegion(MarshalVariantStringArray(circleObj));//this row line is problem.
dynamic profileRegionObject = (dynamic)(regionObject[0]);
circleObj[0].Delete();
return profileRegionObject;
}