Calling from VB.net to a Dll Win32 function that uses safearray pointer
-
Hi, I'am updating and old VB6 project to VB.net 2008. My old project uses a Win32 Dll and TLB file to shared types. From VB6 a reference is created to TLB file and interop works fine. From VB.net I can import TLB file successfully and types are accessibles, but not the functions. Using 'Declare Function' sentences to import DLL methods, I can access to DLL functions. My Win32 DLL has methods that uses LPSAFEARRAY pointer as input parameters. This is the declaration of one of this functions on DLL:
extern NOMANGLE short CCONV EncenderLEDs(LPSAFEARRAY *CBLeds, bool Estado);
To reference this from VB.net I write
Public Declare Function EncenderLEDs Lib "MonAuto.dll" (<MarshalAs(UnmanagedType.SafeArray)> ByRef CBLeds() As CBTipo, ByVal Estado As Boolean) As Integer
CBTIPO is defined on DLL as:
typedef struct{
short Carta;
short Borna;
} CBTipo;The type is correctly visible at VB.net after TLB is imported. I test the function with next code:
Dim CBLeds() as CBTipo
Redim CBLeds(4)
CBLeds(0).Borna = 0
CBLeds(0).Carta = 0
EncenderLEDs(CBLeds, False)An exception is reported: (Exception of HRESULT: 0x8002802B (TYPE_E_ELEMENTNOTFOUND)) But if I use any single Type to create the array: Int32, Double, Single, byte, ... and modify the declaration function definition on Vb.net like:
Public Declare Function EncenderLEDs Lib "MonAuto.dll" (<MarshalAs(UnmanagedType.SafeArray)> ByRef CBLeds() As Integer, ByVal Estado As Boolean) As Integer
and modify the test in the same way: <pre>Dim CBLeds() as Integer Redim CBLeds(4) CBLeds(0).Borna = 0 CBLeds(0).Carta = 0 EncenderLEDs(CBLeds, False)</pre> Then the call is processed good. The question is, How must be defined a DLL Win32 function on VB.net that uses a LPSAFEARRAY * as input parameter, to send a userdefined type array ? I've tried:
Public Declare Function EncenderLEDs Lib "MonAuto.dll" (<MarshalAs(UnmanagedType.SafeArray, SafeArraySubType:=VarEnum.VT_USERDEFINED)> ByRef CBLeds() As CBTipo, ByVal Estado As Boolean) As Integer
and fails too. Regards in advance
-
Hi, I'am updating and old VB6 project to VB.net 2008. My old project uses a Win32 Dll and TLB file to shared types. From VB6 a reference is created to TLB file and interop works fine. From VB.net I can import TLB file successfully and types are accessibles, but not the functions. Using 'Declare Function' sentences to import DLL methods, I can access to DLL functions. My Win32 DLL has methods that uses LPSAFEARRAY pointer as input parameters. This is the declaration of one of this functions on DLL:
extern NOMANGLE short CCONV EncenderLEDs(LPSAFEARRAY *CBLeds, bool Estado);
To reference this from VB.net I write
Public Declare Function EncenderLEDs Lib "MonAuto.dll" (<MarshalAs(UnmanagedType.SafeArray)> ByRef CBLeds() As CBTipo, ByVal Estado As Boolean) As Integer
CBTIPO is defined on DLL as:
typedef struct{
short Carta;
short Borna;
} CBTipo;The type is correctly visible at VB.net after TLB is imported. I test the function with next code:
Dim CBLeds() as CBTipo
Redim CBLeds(4)
CBLeds(0).Borna = 0
CBLeds(0).Carta = 0
EncenderLEDs(CBLeds, False)An exception is reported: (Exception of HRESULT: 0x8002802B (TYPE_E_ELEMENTNOTFOUND)) But if I use any single Type to create the array: Int32, Double, Single, byte, ... and modify the declaration function definition on Vb.net like:
Public Declare Function EncenderLEDs Lib "MonAuto.dll" (<MarshalAs(UnmanagedType.SafeArray)> ByRef CBLeds() As Integer, ByVal Estado As Boolean) As Integer
and modify the test in the same way: <pre>Dim CBLeds() as Integer Redim CBLeds(4) CBLeds(0).Borna = 0 CBLeds(0).Carta = 0 EncenderLEDs(CBLeds, False)</pre> Then the call is processed good. The question is, How must be defined a DLL Win32 function on VB.net that uses a LPSAFEARRAY * as input parameter, to send a userdefined type array ? I've tried:
Public Declare Function EncenderLEDs Lib "MonAuto.dll" (<MarshalAs(UnmanagedType.SafeArray, SafeArraySubType:=VarEnum.VT_USERDEFINED)> ByRef CBLeds() As CBTipo, ByVal Estado As Boolean) As Integer
and fails too. Regards in advance
Very beautiful question.:thumbsup: LPSAFEARRAY is a pointer itself, so LPSAFEARRAY* is a pointer to pointer declaration. This is what I would try:
Public Declare Function EncenderLEDs Lib "MonAuto.dll" (cbLeds() As IntPtr, estado As Boolean) As Integer)
Your first array initialization is ok, but in order to pass it to that function, you should first pin the objects in the managed heap, create the IntPtr array with their addresses and pass this array. This is very similar to another question I answered in C# forum[^] some time ago, so let me bring it to you with some of the required changes. It is still C#. I will not convert it to VB becouse I do not feel comfortable enough with this language, and I could end up with a little mess.
GCHandle[] handleArray = new GCHandle[cbLeds.Length];
for (int i=0; i<cbLeds.Length; i++)
handleArray[i] = GCHandle.Alloc(cbLeds[i], GCHandleType.Pinned);IntPtr[] pointers = (from GCHandle handle in handleArray select
handle.GetAddrOfPinnedObject()).ToArray();int ret = EncenderLEDs(pointers, false)
// Release the handles
foreach (GCHandle handle in handleArray)
handle.Free();Please, let us know if it worked.
-
Very beautiful question.:thumbsup: LPSAFEARRAY is a pointer itself, so LPSAFEARRAY* is a pointer to pointer declaration. This is what I would try:
Public Declare Function EncenderLEDs Lib "MonAuto.dll" (cbLeds() As IntPtr, estado As Boolean) As Integer)
Your first array initialization is ok, but in order to pass it to that function, you should first pin the objects in the managed heap, create the IntPtr array with their addresses and pass this array. This is very similar to another question I answered in C# forum[^] some time ago, so let me bring it to you with some of the required changes. It is still C#. I will not convert it to VB becouse I do not feel comfortable enough with this language, and I could end up with a little mess.
GCHandle[] handleArray = new GCHandle[cbLeds.Length];
for (int i=0; i<cbLeds.Length; i++)
handleArray[i] = GCHandle.Alloc(cbLeds[i], GCHandleType.Pinned);IntPtr[] pointers = (from GCHandle handle in handleArray select
handle.GetAddrOfPinnedObject()).ToArray();int ret = EncenderLEDs(pointers, false)
// Release the handles
foreach (GCHandle handle in handleArray)
handle.Free();Please, let us know if it worked.
No good result. The translated code to VB is:
Dim CBLeds(4) As CBTipo
CBLeds(0).Borna = 0 CBLeds(0).Carta = 0 Dim handleArray(4) As GCHandle For i As Integer = 0 To CBLeds.Length - 1 handleArray(i) = GCHandle.Alloc(CBLeds(i), GCHandleType.Pinned) Next i Dim pointers() As IntPtr = (From handle As GCHandle In handleArray Select handle.AddrOfPinnedObject()).ToArray() Dim ret As Integer = EncenderLEDs(pointers, False) ' Release the handlesforeach (GCHandle handle in handleArray) handle.Free(); For Each handle As GCHandle In handleArray handle.Free() Next
When I Call the DLL function, the process operates normally (no exception appears) but DLL function not executes correctly. Below you can see the code at EncenderLeds function. I'm getting the Ubound and Lbound of array to verify that this is passed good. The return of SafeArrayGetLBound is DISP_E_BADINDEX 8002000B The return of SafeArrayGetUBound is DISP_E_BADINDEX 8002000B So execution is failed.
NOMANGLE short CCONV EncenderLEDs(LPSAFEARRAY *CBLeds, bool Estado)
{
CBTipo HUGEP *Leds;
long LBound, UBound;
//USHORT MatLeds[128];
//DWORD ReceivedBytes;
DWORD BytesWritten;
DATA Adat[256];
short ActAdat=0;
short nLed;
int j;
USHORT ErrorCnt;
char Cadena[200];for(unsigned char i=0;i<128;++i) { Adat\[i\].Address = 0; Adat\[i\].Data = 0; Adat\[128+i\].Address = 0; Adat\[128+i\].Data = 0; } int k,l; k=SafeArrayGetLBound(\*CBLeds,1,&LBound); l=SafeArrayGetUBound(\*CBLeds,1,&UBound); sprintf(Cadena,"Valores %x %x %d %d",k,l,LBound,UBound); MessageBox(NULL,Cadena,"Titol",0); return 0;
}
Any Idea what is happen?
-
No good result. The translated code to VB is:
Dim CBLeds(4) As CBTipo
CBLeds(0).Borna = 0 CBLeds(0).Carta = 0 Dim handleArray(4) As GCHandle For i As Integer = 0 To CBLeds.Length - 1 handleArray(i) = GCHandle.Alloc(CBLeds(i), GCHandleType.Pinned) Next i Dim pointers() As IntPtr = (From handle As GCHandle In handleArray Select handle.AddrOfPinnedObject()).ToArray() Dim ret As Integer = EncenderLEDs(pointers, False) ' Release the handlesforeach (GCHandle handle in handleArray) handle.Free(); For Each handle As GCHandle In handleArray handle.Free() Next
When I Call the DLL function, the process operates normally (no exception appears) but DLL function not executes correctly. Below you can see the code at EncenderLeds function. I'm getting the Ubound and Lbound of array to verify that this is passed good. The return of SafeArrayGetLBound is DISP_E_BADINDEX 8002000B The return of SafeArrayGetUBound is DISP_E_BADINDEX 8002000B So execution is failed.
NOMANGLE short CCONV EncenderLEDs(LPSAFEARRAY *CBLeds, bool Estado)
{
CBTipo HUGEP *Leds;
long LBound, UBound;
//USHORT MatLeds[128];
//DWORD ReceivedBytes;
DWORD BytesWritten;
DATA Adat[256];
short ActAdat=0;
short nLed;
int j;
USHORT ErrorCnt;
char Cadena[200];for(unsigned char i=0;i<128;++i) { Adat\[i\].Address = 0; Adat\[i\].Data = 0; Adat\[128+i\].Address = 0; Adat\[128+i\].Data = 0; } int k,l; k=SafeArrayGetLBound(\*CBLeds,1,&LBound); l=SafeArrayGetUBound(\*CBLeds,1,&UBound); sprintf(Cadena,"Valores %x %x %d %d",k,l,LBound,UBound); MessageBox(NULL,Cadena,"Titol",0); return 0;
}
Any Idea what is happen?
The fact that you do not get a runtime exception while running it makes me think that the function is correctly imported, and the IntPtr array along with all the other stuff regarding the GCHandle structure is fine as well. However, looking at the documentation for SafeArrayGetLBound function, I think we were missing something important. It says the array descriptor must have been created with SafeArrayCreate function. I am not used to manage this kind of pointers so I cannot be sure of this, but maybe we should also import SafeArrayCreate function and use it to create the array descriptor before passing it to the EncenderLEDs function. Does it make sense?
-
The fact that you do not get a runtime exception while running it makes me think that the function is correctly imported, and the IntPtr array along with all the other stuff regarding the GCHandle structure is fine as well. However, looking at the documentation for SafeArrayGetLBound function, I think we were missing something important. It says the array descriptor must have been created with SafeArrayCreate function. I am not used to manage this kind of pointers so I cannot be sure of this, but maybe we should also import SafeArrayCreate function and use it to create the array descriptor before passing it to the EncenderLEDs function. Does it make sense?
-
May be logic, but calling DLL from VB6 no safearray creation is needed. I will continue investigating. If I find a solution, I tell you. Regards.
-
Yes, you are right, but remember arrays in VB6 were allocated as SAFEARRAY under the covers, and that is quite a different thing than .NET arrays, and it seems you are using VB.NET.
Sure. But the DLL is now compiled on VS2008. Reading about safearrays and thinking about your suggest, I've find an assembly named system.visualstudio.ole.interop on VS2008 help that implements Safearray struct. I'll try to use it, but from now, I can't load the assembly. It doesn't appear on Net list references, but it is present on Windows\assembly. If I achieve to load it, I tell you about. Regards.