VB structures and Marshalling

  • Thread starter Thread starter Mike
  • Start date Start date
M

Mike

Hi, I am trying to figure out why I have to do this:

DIM u as TUser

'---------- Why is this necessary?
Dim s(10) as TSecurityName
u.security = s
'----------

in order to do assignments like so?

u.security(0) = "Normal"
u.security(1) = "Extra"

Background:

I have c structures that in VB, it looks like this:

Type TUser
...
Security(1 To 10) As String * 32
...
End type

So the Tuser structure has a field Security which is array of 10 of
asciiz string of length 32.

In VB.NET, the structures I have is

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Structure TSecurityName
<MarshalAs(UnmanagedType.ByValTStr,sizeconst:=32)>
Public Name As String
End Structure

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Structure TUser
...
<MarshalAs(UnmanagedType.ByValArray, sizeconst:=10)>
Public Security() As TSecurityName
...
End Structure

I guess I am wondering why isn't the Security array "assignment" done
automatically by the marshalling? I can't I just use this simple
like so:

DIM u as TUser
u.security(0) = "Normal"

without any extra coding?

Thanks
 
Mike said:
Hi, I am trying to figure out why I have to do this:

DIM u as TUser

'---------- Why is this necessary?
Dim s(10) as TSecurityName
u.security = s
'----------

in order to do assignments like so?

u.security(0) = "Normal"
u.security(1) = "Extra"

Background:

I have c structures that in VB, it looks like this:

Type TUser
...
Security(1 To 10) As String * 32
...
End type

So the Tuser structure has a field Security which is array of 10 of
asciiz string of length 32.

In VB.NET, the structures I have is

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Structure TSecurityName
<MarshalAs(UnmanagedType.ByValTStr,sizeconst:=32)>
Public Name As String
End Structure

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Structure TUser
...
<MarshalAs(UnmanagedType.ByValArray, sizeconst:=10)>
Public Security() As TSecurityName
...
End Structure

I guess I am wondering why isn't the Security array "assignment" done
automatically by the marshalling? I can't I just use this simple
like so:

DIM u as TUser
u.security(0) = "Normal"

without any extra coding?


I'm not sure what the problem is and which extra coding you mean.

You can write:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure TUser
<MarshalAs(UnmanagedType.ByValArray, sizeconst:=10)> _
Public Security() As TSecurityName

Sub Init()
ReDim Security(9)
End Sub
End Structure

'...

Dim u As TUser
u.Init()
u.Security(0).Name = "Normal"


Armin
 
Armin said:
I'm not sure what the problem is and which extra coding you mean.

First, thanks for your input.

What I was doing was testing the I/O marshaling and in this case, the
output when creating a user:

DIM u as TUser
u.name = "Mike"
u.security(0) = "Normal" <---- Run Time Abort Here
... more u.xxxx assignments ....
If Not AddNewUser(u) then
.... some error ....
end if

There was an run time abort, something about u.security not assigned.
AddNewUser() is a DLLImport.

<DllImport("wcsrv2.dll", SetLastError:=True)>
Public Function AddNewUser(ByRef u As TUser) As Boolean
End Function

You can write:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Public Structure TUser
<MarshalAs(UnmanagedType.ByValArray, sizeconst:=10)> _
Public Security() As TSecurityName

Sub Init()
ReDim Security(9)
End Sub
End Structure

'...

Dim u As TUser
u.Init()
u.Security(0).Name = "Normal"

I figure this much. I can also do this:

DIM u as TUser
GetUserById(0,u)
u.name = "Mike"
u.security(0) = "Normal"
If Not AddNewUser(u) then
end if

Where GetUserById() is another DLL import.

What I missing is why is the marshaling "allocating" the space when
reading a Tuser auto-magically, but now when assigning it?

I would think:

DIM u as Tuser

would be sufficient because the structure has it defined that way:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Structure TUser
...
<MarshalAs(UnmanagedType.ByValArray, sizeconst:=10)>
Public Security() As TSecurityName
...
End Structure

therefore the internals should automatically allocate the array member
with a sizeconst of 10.

Its acting more like a pointer member.

What I am missing here?

--
 
Mike said:
What I am missing is why is the marshaling "allocating" the space when
reading a Tuser auto-magically, but not when assigning it?

I would think:

DIM u as Tuser

would be sufficient because the structure has it defined that way:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Structure TUser
...
<MarshalAs(UnmanagedType.ByValArray, sizeconst:=10)>
Public Security() As TSecurityName
...
End Structure

therefore the internals should automatically allocate the array member
with a sizeconst of 10.

Its acting more like a pointer member.

What I am missing here?

I ask because in our VB API documentation, we have many examples where
we have just

dim u as tuser
u.name = "example user"
u.security(0) = "default security"

and other structures where we don't has to tell people to "allocate"
or initialize it.

But if this is necessary under VB.NET, then I can live with that.
Just wondering why its not necessary when READING in the structure.

Dim U as Tuser
if GetUserByName("Mike",u) then
console.writeln("Mike's Security: {0}",u.security(0))
else
' mike not found
end if

It appears VB.NET is allocating it for you when the DLLImport call is
made.

--
 
Patrice said:
Are you sure the u.security array is initialized ? I would recommend
starting by giving us the exact error message you get.

An unhandled exception of type 'System.NullReferenceException'
occurred in TestNewUser1.exe

Additional information: Object reference not set to an instance of
an object.

The break is on the line below:

dim u as Tuser
u.name = "mike"
u.security(0) = "normal" <<-- break here
For now my guess would be that this is not related to marhsalling but that
the array is simply not initialized...

I think it is related because on INPUT (reading a TUser record) it is
not necessary to initialize (allocate the security array).

I don't think I have the proper structure and
MarshAs(UnManagedType.XXXX) correct to deal with the simple C construct

type struct _TUser {
...
char Security[10][32]
} TUser

or in VB:

Type Tuser
...
security(1 to 10) as String * 32
end type

--
 
Mike said:
What I was doing was testing the I/O marshaling and in this case, the
output when creating a user:

DIM u as TUser
u.name = "Mike"
u.security(0) = "Normal" <---- Run Time Abort Here

You should always mention error you get. I can not even compile this code,
so I can't follow you. It is impossible to assign a String to an item of
type TSecurityName. It must read

u.security(0).NAME = "Normal"

Your code can not even be compiled with Option Strict OFF.

... more u.xxxx assignments ....
If Not AddNewUser(u) then
.... some error ....
end if

There was an run time abort, something about u.security not assigned.

Probably a NullReferenceException because no array has been assigned to
u.security.

I would think:

DIM u as Tuser

would be sufficient because the structure has it defined that way:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Structure TUser
...
<MarshalAs(UnmanagedType.ByValArray, sizeconst:=10)>
Public Security() As TSecurityName
...
End Structure

therefore the internals should automatically allocate the array member
with a sizeconst of 10.

Its acting more like a pointer member.

What I am missing here?


The sizeconst is only there for the marshaller. If you assign an item to the
array in the usual managed code, you still must create an array before.


Armin
 
Mike said:
I ask because in our VB API documentation, we have many examples where
we have just

dim u as tuser
u.name = "example user"
u.security(0) = "default security"

and other structures where we don't has to tell people to "allocate"
or initialize it.

Are you mixing VB6 and VB.net code here? I can't figure out what is what.
But if this is necessary under VB.NET, then I can live with that.
Just wondering why its not necessary when READING in the structure.

Dim U as Tuser
if GetUserByName("Mike",u) then
console.writeln("Mike's Security: {0}",u.security(0))
else
' mike not found
end if

It appears VB.NET is allocating it for you when the DLLImport call is
made.

I can't test it and I don't know it by heart. Maybe the documentation says
something about it:
http://msdn.microsoft.com/en-us/library/sd10k43k.aspx


Armin
 
Armin said:
You should always mention error you get. I can not even compile this
code, so I can't follow you. It is impossible to assign a String to an
item of type TSecurityName. It must read

u.security(0).NAME = "Normal"

Sorry, I manually typed the above. Your syntax is correct.
The sizeconst is only there for the marshaller. If you assign an item to
the array in the usual managed code, you still must create an array before.

But this is not the case when reading in a TUser record, therefore the
marshaller must be allocating it for you.
Are you mixing VB6 and VB.net code here? I can't figure out what is
what.

Well, not missing, just reusing what is possible

Dim U as TUser < compiler should use the structure marshall
< construct
u.security(0) = "normal"

Why should u.security be initialized when the structure marshalling
has the fixed array size declared?

This can be shown when reading in the structure:

Dim U as Tuser
if GetUserByName("Mike",u) then
console.writeln("Mike's Security: {0}",u.security(0))
else
' mike not found
end if
I can't test it and I don't know it by heart.
Maybe the documentation says something about it:
http://msdn.microsoft.com/en-us/library/sd10k43k.aspx

Man, thanks, I've been at this for the last few days googling and
reading everything I can find, including the MSDN docs.

All examples are arrays of normal natural types where there is no need
to preallocate a subtype, i.e.

array of INTEGER (or some other natural compiler type)

what I have is an

Fixed Array of Fixed Array of Char (ASCIIZ STRING)

But the marshalling sizeconst is set for the array and the subtype so
I don't get it. Its find for reading but not writing.

--
 
Patrice said:
Ok I see now.

The VB "classic" definition is :

Security(1 To 10) As String * 32

The one used of VB.NET is :

Public Security() As TSecurityName

So the array is not initialized leading to a nullReference exception (that
is when you deal with an uninitialized object).

What if you use :

Public Security(9) As TSecurityName ' 0 to 9

Does it work ? You could also do that as part of the struct constructor
etc...

I explored this syntax and other statements. The compiler does not
like it.

error BC31043: Arrays declared as structure members cannot be
declared with an initial size.

I feel the solution is with the unManagedType.XXXXX declaration. I
don't have the right type to handle this in both directions.

Sure, if ultimately, we need to use an initializer then so be it,
but its so much easier if we don't have to :)

--
 
Mike said:
But this is not the case when reading in a TUser record, therefore the
marshaller must be allocating it for you.

Ok. I was referring to the NullRerenceException problem. My fault (or
misunderstanding).
Well, not missing, just reusing what is possible

Dim U as TUser < compiler should use the structure marshall
< construct
u.security(0) = "normal"

Why should u.security be initialized when the structure marshalling
has the fixed array size declared?

See my answer above: The sizeconst is only there for the marshaller. This
problem has nothing to do with marshalling at all. You can also write

dim b as byte()
b(7) = 12

This will cause the same exception.


All examples are arrays of normal natural types where there is no need
to preallocate a subtype, i.e.

array of INTEGER (or some other natural compiler type)

what I have is an

Fixed Array of Fixed Array of Char (ASCIIZ STRING)

But the marshalling sizeconst is set for the array and the subtype so
I don't get it. Its find for reading but not writing.

I am not able not follow you. Is the problem solved now?

I try to summarize:
- The marshalas attribute has no influence on usual, managed code. If you
want to access an item in an array, the array must be have been created
before. Accessing the array, be it a read or a write operation, fails.
- The marshaller returns the values from the unmanaged functions, including
the creation of the array. Consequently, you can access the array after the
umanaged function has been called.


Armin
 
Armin said:
- The marshaller returns the values from the
unmanaged functions, including the creation of the array.
Consequently, you can access the array after the umanaged function
has been called.

Addition: The marshaller also creates the array _before_ calling the
unmanaged function if you haven't done it yourself. Otherwise the unmanaged
function would fail.


Armin
 
Armin said:
Addition: The marshaller also creates the array _before_ calling the
unmanaged function if you haven't done it yourself. Otherwise the
unmanaged function would fail.

Ok, at least it explains what I am seeing.

Thanks for your input.

--
 
Thanks Patrice.

I have an RPC server API that supports the all major languages. A few
years back, a developer ported the "classic" VB prototype headers to
..NET and had the described marshalling construct for the array of
array instances. He provided examples where he illustrated reading of
structures with DLLImport unmanaged functions. He also had an example
of an update logic:

public function ChangeSecurity(byval name as string, _
byval sec as string) as boolean

Dim U as tuser
if getUserByName(name,u) then
u.security(0) = sec
return UpdateUser(u)
end if
return false
end function

He didn't have an example of adding a new user which was I adding to
the examples and thus discovered this issue. He never saw the problem
himself because he was referencing an unmanaged function which the
marshaller will initialize the structure. He's been out of touch
hence why I was asking here.

The port to VB.NET was done with a CPP2BAS.EXE utility he modified now
called CPP2VBNET. So with this discovery, the updated CPP2VBNET
utility needs to add some constructor or init function or both

sub init()
redim security(NUM_USER_SECURITIES)
end sub
sub new()
init()
end sub

for this and other similar special cases (two other structures).

At least I know now what needs to be done. :-)

Thanks for your input

--
 
Back
Top