.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Passing Late-bound Variant arrays to AutoCAD

10 REPLIES 10
SOLVED
Reply
Message 1 of 11
mleslie00
2074 Views, 10 Replies

Passing Late-bound Variant arrays to AutoCAD

 

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.

 

 

10 REPLIES 10
Message 2 of 11
norman.yuan
in reply to: mleslie00

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

Drive CAD With Code

EESignature

Message 3 of 11
mleslie00
in reply to: mleslie00

 

 

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

 

Message 4 of 11
barts007
in reply to: mleslie00

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)

Message 5 of 11
mleslie00
in reply to: barts007

 

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!

 

 

Message 6 of 11
barts007
in reply to: mleslie00

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.

Message 7 of 11
gpseismic
in reply to: mleslie00

Public Function MarshalVariantStringArray(ByVal ObjIn AsObject) _
As <Runtime.InteropServices.MarshalAsAttribute( _
Runtime.InteropServices.UnmanagedType.SafeArray, _
SafeArraySubType:=Runtime.InteropServices.VarEnum.VT_BSTR)> Object

ReturnObjIn
End Function

how to write them by c#?
Message 8 of 11
mleslie00
in reply to: mleslie00

[return: Runtime.InteropServices.MarshalAsAttribute(Runtime.InteropServices.UnmanagedType.SafeArray, SafeArraySubType = Runtime.InteropServices.VarEnum.VT_BSTR)]
public object MarshalVariantStringArray(object ObjIn)
{

ReturnObjIn();

}
Message 9 of 11
gpseismic
in reply to: mleslie00

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;

}

Message 10 of 11
mleslie00
in reply to: mleslie00

ObjectList
Access: Input-only

Type: Variant (array of Arc, Circle, Ellipse, Line, LWPolyline, Spline objects)

The array of objects forming the closed coplanar face to be made into a region.



This indicates that the AddRegion method expects a SAFEARRAY of AutoCAD objects, probably insisting on them being type IAcadEntity. The function that you got from me is specific to an array of strings. You will probably have to change the SafeArraySubType = System.Runtime.InteropServices.VarEnum.VT_BSTR to s different value.

The choices for this are listed here:
https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshalasattribute.safearray...
Message 11 of 11
mleslie00
in reply to: mleslie00

While the canonical explanation of how array marshaling works is here:
https://msdn.microsoft.com/en-us/library/z6cfh6e6(v=vs.110).aspx

What I think you really want is this:
http://vault.lozanotek.com/archive/2005/09/24/3614.aspx

Note the extra attribute,
[return:MarshalAs(UnmanagedType.SafeArray,
SafeArraySubType=VarEnum.VT_DISPATCH,
SafeArrayUserDefinedSubType=typeof(Person))]
Person[] CreatePeople (int count);

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report

”Boost