Passing arrays from vb .NET to ActiveX control (*.ocx)
I have an application written in Visual Basic 6 that uses an ActiveX
control written in Visual C++ 6. I'm moving the visual basic 6
application to visual basic .NET (2003). The problem is when I try to
pass an array from the visual basic code as a parameter of a function
of the control.
In Visual Basic 6 the code is something like this:
Dim dummy(10) as Long
'...
'some code
'...
myActiveXControl.myFunction(dummy(0))
myFunction expects a reference of the first element of an array of
Integers. Passing the first element of the array works fine in visual
basic 6.
But if I try the above code in vb .NET, changing the definition of the
array for this one (type Long in vb 6 is Integer in vb .NET) :
Dim dummy(10) as Integer
it does not work
Any help would be appreciated
Thanks in advance
[983 byte] By [
Urlik] at [2007-12-28]
You should just pass the array variable directly, not a specific element.
Also, is your C++ control using a C-Style or a SafeArray? Depending on this you might need to tweak some things to make things work.
Hope this helps,
Thanks for your answer.
The C++ control is using a C-Style array, so it expects only a reference to the first position of the array. In vb 6 passing the first position worked fine.
I cannot pass the array variable directly. Maybe it has something to do with marshaling or with tweaking the MSIL of the interop assembly a bit, but I'm new to .NET, so I don't have any idea of how to do this.
Urlik
Can you post the declaration of the function that you are calling? If you are using a dll to "import" the function, could you post the ILDASM output for this function?
Hope this helps,
I'm trying to use the control inside a vb .NET form. Everything works well except when a function of the control needs an array passed as parameter.
I've searched for the function definition in the Object Browser and here's the definition:
Public Overridable Sub myFunction(ByRef ar As Integer)
The most annoying part is that inside a vb 6 form it works fine, so I thought that it was a common migration issue... : (
I cannot modify the C++ ActiveX control, so I need to find a way to pass these arrays correctly.
Any idea? my active X control has a function that need a pointer to array type long as one of the parameters, that is , the implementation in the activeX (VC++ net) is something like this
int getbuf(long *buf, long size)
{
......
}
But in the VB.NET it does not work when I supply to it the first item of long array, only the first item is accessible. I can modify the active-X control but I don't now what to change that it will work.
Yes, in VB6 it work fine!
Waiting to any help
In order to help you I need you to post the declaration of the interop function that you are using and also the IDL file.
I have been able to pass .Net arrays into C++ functions, but for that to work correctly the Interop functions needs to be correctly declared.
Hope this helps,
I need to see the actual output of the ILDASM tool in the .Net SDK (justfor that function). The OB doesn't show the attributes, and I need to see what marshalling attributes have been applied.
Can you also post the C++ header and the IDL header?
As to how you can fix this, well there are a couple of options. The simplest one is to modfity the interop library to use the right signature. Other possibilities would be to write marshalling code to pack the array and pass it down.
Hope this helps,
Here's the declaration of one of these C++ functions that expects and array as parameter
My C++ control is called Meshplot.In the C++ code the function is declared as follows:
In the odl file:
[id(179)] void GetSelected(long* ids);
In the .h file of the main control class:
afx_msg void GetSelected(long FAR* ids);
In the dispatch map in the *cpp file:
DISP_FUNCTION(CMeshplotCtrl, "GetSelected", GetSelected, VT_EMPTY, VTS_PI4)
In the *.cpp code the function looks like this :
void CMeshplotCtrl::GetSelected(long FAR* ids) {
I've found 2 references in the application, one with the name AxInterop.MESHPLOTLib.dll and another one with the name Interop.MESHPLOTLib.dll
In the AxInterop.MESHPLOTLib.dll I've found the following (I don't know exactly which one has to be modified):
.method public hidebysig newslot virtual
instance void GetSelected(int32& ids) cil managed
{
// Code size 33 (0x21)
.maxstack 3
IL_0000: ldarg.0
IL_0001: ldfld class [Interop.MESHPLOTLib]MESHPLOTLib._DMeshplot AxMESHPLOTLib.AxMeshplot::ocx
IL_0006: brtrue.s IL_0014 IL_0008: ldstr "GetSelected"
IL_000d: ldc.i4.0
IL_000e: newobj instance void [System.Windows.Forms]System.Windows.Forms.AxHost/InvalidActiveXStateException::.ctor(string,
valuetype [System.Windows.Forms]System.Windows.Forms.AxHost/ActiveXInvokeKind)
IL_0013: throw
IL_0014: ldarg.0
IL_0015: ldfld class [Interop.MESHPLOTLib]MESHPLOTLib._DMeshplot AxMESHPLOTLib.AxMeshplot::ocx
IL_001a: ldarg.1
IL_001b: callvirt instance void [Interop.MESHPLOTLib]MESHPLOTLib._DMeshplot::GetSelected(int32&)
IL_0020: ret
} // end of method AxMeshplot::GetSelected
In the Interop.MESHPLOTLib.dll I've found the following:
.method public hidebysig newslot abstract virtual
instance void GetSelected(int32& ids) runtime managed preservesig internalcall
{
.custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = ( 01 00 B3 00 00 00 00 00 )
} // end of method _DMeshplot::GetSelectedand this
.method public hidebysig newslot virtual
instance void GetSelected(int32& ids) runtime managed preservesig internalcall
{
.custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = ( 01 00 B3 00 00 00 00 00 )
.override MESHPLOTLib._DMeshplot::GetSelected
} // end of method MeshplotClass::GetSelected
Ulrik,
The problem begins here:
[id(179)] void GetSelected(long* ids)
If you could change the ODL declaration to the following:
[id(179)] void GetSelected([in,out,size_is(10)] long ids[]);
And then recreate and reimport the ActiveX control, things should just work.
Note: you should only use the "in" keyword if you are sending data to the function; likewise,
you should only use the "out" keyword if you are expecting the function to return data in the array.
If you are not able to modify the ActiveX control for whatever reason, then you
should still do the above steps, but just keep the modified interop libraries
that were generated.
Using the modified libraries in your project should allow you to pass the Integer array directly.
If that doesn't work, then post the same ILDASM content that you provided, but do so for the modified |
interop libraries.
Hope this helps,
Avraham,
You should do something similar to what I suggested Ulrik to try. In your case you need to modify the IDL declaration to be:
int getbuf([in,out,size_is(size)] long *buf, long size)
Again, only use the "in" modifier if you are sending data to the function; likewise, only use the "out" modifier if you are receiving data from the function.
Hope this helps,
I've tried to modify the ODL declaration as you said and still have the same problem.
The AxInterop.MESHPLOTLib.dll is exactly the same.
The Interop.MESHPLOTLib.dll is a bit different:
.method public hidebysig newslot abstract virtual
instance void GetSelected([in][out] int32& ids) runtime managed preservesig internalcall
{
.custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = ( 01 00 B3 00 00 00 00 00 )
} // end of method _DMeshplot::GetSelected
and
.method public hidebysig newslot virtual
instance void GetSelected([in][out] int32& ids) runtime managed preservesig internalcall
{
.custom instance void
[mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32)
= ( 01 00 B3 00 00 00 00 00 )
.override MESHPLOTLib._DMeshplot::GetSelected
} // end of method MeshplotClass::GetSelected
Try changing the declaration to the following:
[id(179)] void GetSelected([in,out,size_is(cElements)] long ids[], long cElements);
You should see a rather different signature with this declaration.
Also compile a vb project that has the following function in it:
Sub GetSelected(<[In](), Out(), MarshalAs(UnmanagedType.LPArray, SizeConst:= 10)> ByRef ids As Integer())
ILDASM both functions and compare them.
At this point you have 2 options: if it is possible to add the extra parameter to your C++ code, you should just use that library and modify your code accordingly.
If not, you will need to save the output of ILDASM for the interop library that you just posted above to a text file and then modify the declaration to match the one for the vb project with the function above.
You will then need to ILASM the assembly to get a proper interop - library.
Hope this helps,
Hello Abel,
I've tried the 2 solutions:
Adding the extra parameter in the C++ code and the declaration as you said, the problem remains the same. The resulting declaration is:
In the Interop.MESHPLOTLib.dll:
.method public hidebysig newslot abstract virtual
instance void GetSelected([in][out] int32& ids,
int32 n) runtime managed preservesig internalcall
In the AxInterop.MESHPLOTLib.dll:
.method public hidebysig newslot virtual
instance void GetSelected(int32& ids,
int32 n) cil managed
I've compiled also a vb project with this function:
Sub GetSelected(<[In](), Out(), MarshalAs(UnmanagedType.LPArray, SizeConst:= 10)> ByRef ids As Integer())and the ILDASM is:
.method public instance void GetSelected([in][out] int32[]& marshal([10]) ids) cil managed
And then I've tried to modify my interop library with this declaration, then I pass the array to the function, not the first element, but I get an error:
"unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in mscorlib.dll
Additional information: Tipo incorrecto"
Allright, so try modifying the ILDASM of the "original" interop libraries to be:
In the Interop.MESHPLOTLib.dll:
.method public hidebysig newslot abstract virtual
instance void GetSelected([in][out] int32[]& marshal([10]) ids) runtime managed preservesig internalcall
In the AxInterop.MESHPLOTLib.dll:
.method public hidebysig newslot virtual
instance void GetSelected(int32[]&) cil managed
Once you have done that, ILASM the code back into dlls, and remove and re-add the two references from your project. Hopefully that should work,
If that doesn't work, you will need to handcraft the interface on the managed side and apply the correct attributes directly.
Hope this helps,