Marshalling Question: Array of Complex Structs?

C

calenlas

Hi all,

I'm taking my first steps into C# <--> C++ DLL Interop and
unfortunately I've run into (what seems to be) a very complicated case
as my first task. Perhaps someone here can help me.

I need to pass an array of RADIO_INFO2 structures to be filled by a
function in the DLL. This is how the structure is defined in the C++
example that comes with the DLL:

#pragma pack(1) // set byte packing
typedef struct {
unsigned __int32 bLength;
char szSerNum[9];
char szProdName[9];
unsigned __int64 MinFreq;
unsigned __int64 MaxFreq;
struct {
unsigned __int32 ExtRef:1;
unsigned __int32 FMWEnabled:1;
unsigned __int32 Reserved:30;
} Features;
} RADIO_INFO2;
#pragma pack() // set back the default packing

As you can see, there are a few obvious difficulties. First, the
nested Features struct. I think I've solved this by just defining
Features as a UInt32 and I'll handle the bit masking manually if I
need to. The second are the two char arrays. The DLL wants two 9
byte buffers to fill with character data while the marshalling code
wants to pass references. This brings me to the third problem, the
size of the structure. In C++ it's 42 bytes long: 32bits + 9bytes +
9bytes + 64bits + 64bits + 32bits = 42bytes. However, due to the
reference vs. value array problem the Marshalling code is coming up
with 32bytes: 32bits + 32bit (ref) + 32bit (ref) + 64bits + 64bits +
32bits = 32 bytes. The following is my best shot at describing this
structure in C#.

[StructLayout(LayoutKind.Sequential, Pack = 1,
CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
public Byte[] szSerNum;
public Byte[] szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;
};

As stated above, this is the struct that gives me a
Marshalling.SizeOf() of 32 bytes. If, instead of the two Byte arrays,
I provide 18 individual Bytes, I get a matching size of 42 bytes, but
that seems like a heavy-handed work-around. Surely there's a Right
Way to do this.

Additionally, if instead of Byte arrays I define them as Char arrays,
I get an error from the Marshalling code complaining that the array
types don't match.

Just for reference, the actual call that consumes this struct looks
like:

[DllImport("WRG315API.DLL")]
public static extern UInt32 GetRadioList(ref RADIO_INFO2[]
lpRadioInfo,
UInt32 BufferSize,
ref UInt32 InfoSize);

Thanks in advance for any help that is offered.
Todd Pafford
(e-mail address removed)
 
N

Nicholas Paldino [.NET/C# MVP]

Todd,

You can just tell the marshalling layer to embed the string into the
struct by value:


[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szSerNum;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;
};

If you are using unsafe code, then you could use the fixed keyword as
well to fix the character array to a certain size, but what's above should
work just fine.

--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)


Hi all,

I'm taking my first steps into C# <--> C++ DLL Interop and
unfortunately I've run into (what seems to be) a very complicated case
as my first task. Perhaps someone here can help me.

I need to pass an array of RADIO_INFO2 structures to be filled by a
function in the DLL. This is how the structure is defined in the C++
example that comes with the DLL:

#pragma pack(1) // set byte packing
typedef struct {
unsigned __int32 bLength;
char szSerNum[9];
char szProdName[9];
unsigned __int64 MinFreq;
unsigned __int64 MaxFreq;
struct {
unsigned __int32 ExtRef:1;
unsigned __int32 FMWEnabled:1;
unsigned __int32 Reserved:30;
} Features;
} RADIO_INFO2;
#pragma pack() // set back the default packing

As you can see, there are a few obvious difficulties. First, the
nested Features struct. I think I've solved this by just defining
Features as a UInt32 and I'll handle the bit masking manually if I
need to. The second are the two char arrays. The DLL wants two 9
byte buffers to fill with character data while the marshalling code
wants to pass references. This brings me to the third problem, the
size of the structure. In C++ it's 42 bytes long: 32bits + 9bytes +
9bytes + 64bits + 64bits + 32bits = 42bytes. However, due to the
reference vs. value array problem the Marshalling code is coming up
with 32bytes: 32bits + 32bit (ref) + 32bit (ref) + 64bits + 64bits +
32bits = 32 bytes. The following is my best shot at describing this
structure in C#.

[StructLayout(LayoutKind.Sequential, Pack = 1,
CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
public Byte[] szSerNum;
public Byte[] szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;
};

As stated above, this is the struct that gives me a
Marshalling.SizeOf() of 32 bytes. If, instead of the two Byte arrays,
I provide 18 individual Bytes, I get a matching size of 42 bytes, but
that seems like a heavy-handed work-around. Surely there's a Right
Way to do this.

Additionally, if instead of Byte arrays I define them as Char arrays,
I get an error from the Marshalling code complaining that the array
types don't match.

Just for reference, the actual call that consumes this struct looks
like:

[DllImport("WRG315API.DLL")]
public static extern UInt32 GetRadioList(ref RADIO_INFO2[]
lpRadioInfo,
UInt32 BufferSize,
ref UInt32 InfoSize);

Thanks in advance for any help that is offered.
Todd Pafford
(e-mail address removed)
 
C

calenlas

Thanks for the quick response, Nicholas. Indeed, the MarshalAs()
directive fixes the string sizing and typing issue. This is a great
help.

Oddly, when I pass an array of ten of these structs as refs to the
DLL, I get back an array of length 1 (I've only got one radio so this
is ok), but that one struct contains only the initialization values I
assigned all the elements in my original array. It's as if the array
was passed by reference and so could be modified, but each element of
the array cannot be modified. Does this make sense according to the
marshalling rules? Certainly, the problem could be in the DLL in
which case I can take it up with the manufacturers, but could this
behavior be a result of a problem with my definitions?

Thanks,
Todd


Todd,

You can just tell the marshalling layer to embed the string into the
struct by value:

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szSerNum;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;

};

If you are using unsafe code, then you could use the fixed keyword as
well to fix the character array to a certain size, but what's above should
work just fine.

--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)


I'm taking my first steps into C# <--> C++ DLL Interop and
unfortunately I've run into (what seems to be) a very complicated case
as my first task. Perhaps someone here can help me.
I need to pass an array of RADIO_INFO2 structures to be filled by a
function in the DLL. This is how the structure is defined in the C++
example that comes with the DLL:
#pragma pack(1) // set byte packing
typedef struct {
unsigned __int32 bLength;
char szSerNum[9];
char szProdName[9];
unsigned __int64 MinFreq;
unsigned __int64 MaxFreq;
struct {
unsigned __int32 ExtRef:1;
unsigned __int32 FMWEnabled:1;
unsigned __int32 Reserved:30;
} Features;
} RADIO_INFO2;
#pragma pack() // set back the default packing
As you can see, there are a few obvious difficulties. First, the
nested Features struct. I think I've solved this by just defining
Features as a UInt32 and I'll handle the bit masking manually if I
need to. The second are the two char arrays. The DLL wants two 9
byte buffers to fill with character data while the marshalling code
wants to pass references. This brings me to the third problem, the
size of the structure. In C++ it's 42 bytes long: 32bits + 9bytes +
9bytes + 64bits + 64bits + 32bits = 42bytes. However, due to the
reference vs. value array problem the Marshalling code is coming up
with 32bytes: 32bits + 32bit (ref) + 32bit (ref) + 64bits + 64bits +
32bits = 32 bytes. The following is my best shot at describing this
structure in C#.
[StructLayout(LayoutKind.Sequential, Pack = 1,
CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
public Byte[] szSerNum;
public Byte[] szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;
};
As stated above, this is the struct that gives me a
Marshalling.SizeOf() of 32 bytes. If, instead of the two Byte arrays,
I provide 18 individual Bytes, I get a matching size of 42 bytes, but
that seems like a heavy-handed work-around. Surely there's a Right
Way to do this.
Additionally, if instead of Byte arrays I define them as Char arrays,
I get an error from the Marshalling code complaining that the array
types don't match.
Just for reference, the actual call that consumes this struct looks
like:
[DllImport("WRG315API.DLL")]
public static extern UInt32 GetRadioList(ref RADIO_INFO2[]
lpRadioInfo,
UInt32 BufferSize,
ref UInt32 InfoSize);
Thanks in advance for any help that is offered.
Todd Pafford
(e-mail address removed)
 
N

Nicholas Paldino [.NET/C# MVP]

Todd,

C style arrays don't have any information about the length of the array,
so the marshaling layer doesn't know how much data to marshal back to the
calling code. If you are marshaling data one way, then you don't have to
worry about this, since arrays in .NET have length information attached to
them, so the marshaling layer can figure out how much data to send to
unmanaged code.

Automation doesn't have this problem since it uses SAFEARRAYs which have
the length contained in them.

In order to get around this, you will need to allocate a block of
unmanaged memory for your structures, marshal the structures to that block,
make the call, passing the block to the function (you would have to change
your declaration for this), and then reverse the process when the call
returns.

If you are using unsafe code, you can do this differently, as you could
declare the array on the stack, or fix the pointer to the array of
structures (if you can't declare it on the stack) and then pass that.
However, you would have to change your structure declaration to embedded
byte arrays, as the strings would not be marshalled (you would be passing a
pointer).


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)


Thanks for the quick response, Nicholas. Indeed, the MarshalAs()
directive fixes the string sizing and typing issue. This is a great
help.

Oddly, when I pass an array of ten of these structs as refs to the
DLL, I get back an array of length 1 (I've only got one radio so this
is ok), but that one struct contains only the initialization values I
assigned all the elements in my original array. It's as if the array
was passed by reference and so could be modified, but each element of
the array cannot be modified. Does this make sense according to the
marshalling rules? Certainly, the problem could be in the DLL in
which case I can take it up with the manufacturers, but could this
behavior be a result of a problem with my definitions?

Thanks,
Todd


Todd,

You can just tell the marshalling layer to embed the string into the
struct by value:

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szSerNum;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;

};

If you are using unsafe code, then you could use the fixed keyword as
well to fix the character array to a certain size, but what's above
should
work just fine.

--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)


I'm taking my first steps into C# <--> C++ DLL Interop and
unfortunately I've run into (what seems to be) a very complicated case
as my first task. Perhaps someone here can help me.
I need to pass an array of RADIO_INFO2 structures to be filled by a
function in the DLL. This is how the structure is defined in the C++
example that comes with the DLL:
#pragma pack(1) // set byte packing
typedef struct {
unsigned __int32 bLength;
char szSerNum[9];
char szProdName[9];
unsigned __int64 MinFreq;
unsigned __int64 MaxFreq;
struct {
unsigned __int32 ExtRef:1;
unsigned __int32 FMWEnabled:1;
unsigned __int32 Reserved:30;
} Features;
} RADIO_INFO2;
#pragma pack() // set back the default packing
As you can see, there are a few obvious difficulties. First, the
nested Features struct. I think I've solved this by just defining
Features as a UInt32 and I'll handle the bit masking manually if I
need to. The second are the two char arrays. The DLL wants two 9
byte buffers to fill with character data while the marshalling code
wants to pass references. This brings me to the third problem, the
size of the structure. In C++ it's 42 bytes long: 32bits + 9bytes +
9bytes + 64bits + 64bits + 32bits = 42bytes. However, due to the
reference vs. value array problem the Marshalling code is coming up
with 32bytes: 32bits + 32bit (ref) + 32bit (ref) + 64bits + 64bits +
32bits = 32 bytes. The following is my best shot at describing this
structure in C#.
[StructLayout(LayoutKind.Sequential, Pack = 1,
CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
public Byte[] szSerNum;
public Byte[] szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;
};
As stated above, this is the struct that gives me a
Marshalling.SizeOf() of 32 bytes. If, instead of the two Byte arrays,
I provide 18 individual Bytes, I get a matching size of 42 bytes, but
that seems like a heavy-handed work-around. Surely there's a Right
Way to do this.
Additionally, if instead of Byte arrays I define them as Char arrays,
I get an error from the Marshalling code complaining that the array
types don't match.
Just for reference, the actual call that consumes this struct looks
like:
[DllImport("WRG315API.DLL")]
public static extern UInt32 GetRadioList(ref RADIO_INFO2[]
lpRadioInfo,
UInt32 BufferSize,
ref UInt32 InfoSize);
Thanks in advance for any help that is offered.
Todd Pafford
(e-mail address removed)
 
C

calenlas

Nicholas,

Thanks so much for your help. You've put me back on track with a very
time-sensitive project. I'd buy you a beer if I could. :)

In short, I got it working pretty easily once I decided to switch over
to an "unsafe" environment and do it all manually. This is something
I've never done before in C#, but I have plenty of experience with C/C+
+ so the pointer arithmetic is second nature to me.

For the sake of anyone who stumbles upon this thread in the future
while trying to solve a similar problem, here's my solution.

First, the struct definition. Declared "unsafe" with "fixed" byte
arrays so I don't have to worry about unicode vs. ansi characters. Of
course, I've used no managed types within the struct. (I used longs
instead of Int64s, for example.)


[StructLayout(LayoutKind.Sequential, Pack=1)]
public unsafe struct RADIO_INFO2_unsafe
{
public uint bLength;
public fixed byte szSerNum[9];
public fixed byte szProdName[9];
public long MinFreq;
public long MaxFreq;
public uint Features;
};


My DLL entry point is defined as:


[DllImport("WRG315API.DLL", EntryPoint="GetRadioList")]
public unsafe static extern
uint GetRadioList_unsafe(
RADIO_INFO2_unsafe* lpRadioInfo,
uint BufferSize,
uint* InfoSize);


Finally, the following is the code used to call the DLL function.


public unsafe static String[] TestGetRadioList_unsafe()
{
// define our buffer using stackalloc
uint numRadios = 10;
uint radioSize = (uint)(sizeof(RADIO_INFO2_unsafe));
uint bufferSize = radioSize * numRadios;
RADIO_INFO2_unsafe* radiosBuffer
= stackalloc RADIO_INFO2_unsafe[(int)numRadios];

// initialize the buffer (the DLL needs the bLength
// member to be initialized to the size of the struct
RADIO_INFO2_unsafe* radioPtr
= (RADIO_INFO2_unsafe*)radiosBuffer;
for (uint i = 0; i < numRadios; i++, radioPtr++ )
{
(*radioPtr).bLength = radioSize;
}

// make the DLL call
uint radioInfoSize = radioSize;
uint numRadiosFromCall
= GetRadioList_unsafe(radiosBuffer,
bufferSize,
&radioInfoSize);

// Parse the output
// This is just a bit of code that digests the radio info
// returned from the DLL, packages it up in a String[],
// and returns it to the caller for display to the user.
// Of course, since I specified byte arrays in my struct,
// the RadioInfoToString_unsafe() method called below has
// to convert those byte[]s to strings. No big deal, but
// a little extra work.
String[] radioStrings
= new String[numRadiosFromCall < numRadios ?
(int)numRadiosFromCall :
(int)numRadios];
for (int i = 0; i < numRadiosFromCall && i < numRadios; i+
+)
{
radioStrings
= RadioInfoToString_unsafe(radiosBuffer);
}
return radioStrings;
}


Thanks again, and I hope this helps others.
Todd Pafford





Todd,

C style arrays don't have any information about the length of the array,
so the marshaling layer doesn't know how much data to marshal back to the
calling code. If you are marshaling data one way, then you don't have to
worry about this, since arrays in .NET have length information attached to
them, so the marshaling layer can figure out how much data to send to
unmanaged code.

Automation doesn't have this problem since it uses SAFEARRAYs which have
the length contained in them.

In order to get around this, you will need to allocate a block of
unmanaged memory for your structures, marshal the structures to that block,
make the call, passing the block to the function (you would have to change
your declaration for this), and then reverse the process when the call
returns.

If you are using unsafe code, you can do this differently, as you could
declare the array on the stack, or fix the pointer to the array of
structures (if you can't declare it on the stack) and then pass that.
However, you would have to change your structure declaration to embedded
byte arrays, as the strings would not be marshalled (you would be passing a
pointer).

--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)


Thanks for the quick response, Nicholas. Indeed, the MarshalAs()
directive fixes the string sizing and typing issue. This is a great
help.
Oddly, when I pass an array of ten of these structs as refs to the
DLL, I get back an array of length 1 (I've only got one radio so this
is ok), but that one struct contains only the initialization values I
assigned all the elements in my original array. It's as if the array
was passed by reference and so could be modified, but each element of
the array cannot be modified. Does this make sense according to the
marshalling rules? Certainly, the problem could be in the DLL in
which case I can take it up with the manufacturers, but could this
behavior be a result of a problem with my definitions?
Thanks,
Todd
Todd,
You can just tell the marshalling layer to embed the string into the
struct by value:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szSerNum;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;
};
If you are using unsafe code, then you could use the fixed keyword as
well to fix the character array to a certain size, but what's above
should
work just fine.
--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Hi all,
I'm taking my first steps into C# <--> C++ DLL Interop and
unfortunately I've run into (what seems to be) a very complicated case
as my first task. Perhaps someone here can help me.
I need to pass an array of RADIO_INFO2 structures to be filled by a
function in the DLL. This is how the structure is defined in the C++
example that comes with the DLL:
#pragma pack(1) // set byte packing
typedef struct {
unsigned __int32 bLength;
char szSerNum[9];
char szProdName[9];
unsigned __int64 MinFreq;
unsigned __int64 MaxFreq;
struct {
unsigned __int32 ExtRef:1;
unsigned __int32 FMWEnabled:1;
unsigned __int32 Reserved:30;
} Features;
} RADIO_INFO2;
#pragma pack() // set back the default packing
As you can see, there are a few obvious difficulties. First, the
nested Features struct. I think I've solved this by just defining
Features as a UInt32 and I'll handle the bit masking manually if I
need to. The second are the two char arrays. The DLL wants two 9
byte buffers to fill with character data while the marshalling code
wants to pass references. This brings me to the third problem, the
size of the structure. In C++ it's 42 bytes long: 32bits + 9bytes +
9bytes + 64bits + 64bits + 32bits = 42bytes. However, due to the
reference vs. value array problem the Marshalling code is coming up
with 32bytes: 32bits + 32bit (ref) + 32bit (ref) + 64bits + 64bits +
32bits = 32 bytes. The following is my best shot at describing this
structure in C#.
[StructLayout(LayoutKind.Sequential, Pack = 1,
CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
public Byte[] szSerNum;
public Byte[] szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;
};
As stated above, this is the struct that gives me a
Marshalling.SizeOf() of 32 bytes. If, instead of the two Byte arrays,
I provide 18 individual Bytes, I get a matching size of 42 bytes, but
that seems like a heavy-handed work-around. Surely there's a Right
Way to do this.
Additionally, if instead of Byte arrays I define them as Char arrays,
I get an error from the Marshalling code complaining that the array
types don't match.
Just for reference, the actual call that consumes this struct looks
like:
[DllImport("WRG315API.DLL")]
public static extern UInt32 GetRadioList(ref RADIO_INFO2[]
lpRadioInfo,
UInt32 BufferSize,
ref UInt32 InfoSize);
Thanks in advance for any help that is offered.
Todd Pafford
(e-mail address removed)
 
N

Nicholas Paldino [.NET/C# MVP]

Todd,

You could have used Int64 in the structure declaration. "long" is a
managed type, nothing more than an alias in the language for Int64. There
is no difference.

Also, why did you change the types? You had unsigned values before, and
now they are signed. I would think you would want to preserve those (you
can use ulong or UInt64).

Finally, I'm not sure you have to declare InfoSize as a pointer, I think
you can still use "ref" even though it is unsafe (I don't see why you can't)
and the marshaller should correctly marshal the value back and forth.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Nicholas,

Thanks so much for your help. You've put me back on track with a very
time-sensitive project. I'd buy you a beer if I could. :)

In short, I got it working pretty easily once I decided to switch over
to an "unsafe" environment and do it all manually. This is something
I've never done before in C#, but I have plenty of experience with C/C+
+ so the pointer arithmetic is second nature to me.

For the sake of anyone who stumbles upon this thread in the future
while trying to solve a similar problem, here's my solution.

First, the struct definition. Declared "unsafe" with "fixed" byte
arrays so I don't have to worry about unicode vs. ansi characters. Of
course, I've used no managed types within the struct. (I used longs
instead of Int64s, for example.)


[StructLayout(LayoutKind.Sequential, Pack=1)]
public unsafe struct RADIO_INFO2_unsafe
{
public uint bLength;
public fixed byte szSerNum[9];
public fixed byte szProdName[9];
public long MinFreq;
public long MaxFreq;
public uint Features;
};


My DLL entry point is defined as:


[DllImport("WRG315API.DLL", EntryPoint="GetRadioList")]
public unsafe static extern
uint GetRadioList_unsafe(
RADIO_INFO2_unsafe* lpRadioInfo,
uint BufferSize,
uint* InfoSize);


Finally, the following is the code used to call the DLL function.


public unsafe static String[] TestGetRadioList_unsafe()
{
// define our buffer using stackalloc
uint numRadios = 10;
uint radioSize = (uint)(sizeof(RADIO_INFO2_unsafe));
uint bufferSize = radioSize * numRadios;
RADIO_INFO2_unsafe* radiosBuffer
= stackalloc RADIO_INFO2_unsafe[(int)numRadios];

// initialize the buffer (the DLL needs the bLength
// member to be initialized to the size of the struct
RADIO_INFO2_unsafe* radioPtr
= (RADIO_INFO2_unsafe*)radiosBuffer;
for (uint i = 0; i < numRadios; i++, radioPtr++ )
{
(*radioPtr).bLength = radioSize;
}

// make the DLL call
uint radioInfoSize = radioSize;
uint numRadiosFromCall
= GetRadioList_unsafe(radiosBuffer,
bufferSize,
&radioInfoSize);

// Parse the output
// This is just a bit of code that digests the radio info
// returned from the DLL, packages it up in a String[],
// and returns it to the caller for display to the user.
// Of course, since I specified byte arrays in my struct,
// the RadioInfoToString_unsafe() method called below has
// to convert those byte[]s to strings. No big deal, but
// a little extra work.
String[] radioStrings
= new String[numRadiosFromCall < numRadios ?
(int)numRadiosFromCall :
(int)numRadios];
for (int i = 0; i < numRadiosFromCall && i < numRadios; i+
+)
{
radioStrings
= RadioInfoToString_unsafe(radiosBuffer);
}
return radioStrings;
}


Thanks again, and I hope this helps others.
Todd Pafford





Todd,

C style arrays don't have any information about the length of the
array,
so the marshaling layer doesn't know how much data to marshal back to the
calling code. If you are marshaling data one way, then you don't have to
worry about this, since arrays in .NET have length information attached
to
them, so the marshaling layer can figure out how much data to send to
unmanaged code.

Automation doesn't have this problem since it uses SAFEARRAYs which
have
the length contained in them.

In order to get around this, you will need to allocate a block of
unmanaged memory for your structures, marshal the structures to that
block,
make the call, passing the block to the function (you would have to
change
your declaration for this), and then reverse the process when the call
returns.

If you are using unsafe code, you can do this differently, as you
could
declare the array on the stack, or fix the pointer to the array of
structures (if you can't declare it on the stack) and then pass that.
However, you would have to change your structure declaration to embedded
byte arrays, as the strings would not be marshalled (you would be passing
a
pointer).

--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)


Thanks for the quick response, Nicholas. Indeed, the MarshalAs()
directive fixes the string sizing and typing issue. This is a great
help.
Oddly, when I pass an array of ten of these structs as refs to the
DLL, I get back an array of length 1 (I've only got one radio so this
is ok), but that one struct contains only the initialization values I
assigned all the elements in my original array. It's as if the array
was passed by reference and so could be modified, but each element of
the array cannot be modified. Does this make sense according to the
marshalling rules? Certainly, the problem could be in the DLL in
which case I can take it up with the manufacturers, but could this
behavior be a result of a problem with my definitions?
Thanks,
Todd

On Jan 14, 2:06 pm, "Nicholas Paldino [.NET/C# MVP]"
Todd,
You can just tell the marshalling layer to embed the string into
the
struct by value:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet =
CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szSerNum;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;

If you are using unsafe code, then you could use the fixed keyword
as
well to fix the character array to a certain size, but what's above
should
work just fine.
I'm taking my first steps into C# <--> C++ DLL Interop and
unfortunately I've run into (what seems to be) a very complicated
case
as my first task. Perhaps someone here can help me.
I need to pass an array of RADIO_INFO2 structures to be filled by a
function in the DLL. This is how the structure is defined in the
C++
example that comes with the DLL:
#pragma pack(1) // set byte packing
typedef struct {
unsigned __int32 bLength;
char szSerNum[9];
char szProdName[9];
unsigned __int64 MinFreq;
unsigned __int64 MaxFreq;
struct {
unsigned __int32 ExtRef:1;
unsigned __int32 FMWEnabled:1;
unsigned __int32 Reserved:30;
} Features;
} RADIO_INFO2;
#pragma pack() // set back the default packing
As you can see, there are a few obvious difficulties. First, the
nested Features struct. I think I've solved this by just defining
Features as a UInt32 and I'll handle the bit masking manually if I
need to. The second are the two char arrays. The DLL wants two 9
byte buffers to fill with character data while the marshalling code
wants to pass references. This brings me to the third problem, the
size of the structure. In C++ it's 42 bytes long: 32bits + 9bytes +
9bytes + 64bits + 64bits + 32bits = 42bytes. However, due to the
reference vs. value array problem the Marshalling code is coming up
with 32bytes: 32bits + 32bit (ref) + 32bit (ref) + 64bits + 64bits +
32bits = 32 bytes. The following is my best shot at describing this
structure in C#.
[StructLayout(LayoutKind.Sequential, Pack = 1,
CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
public Byte[] szSerNum;
public Byte[] szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;
};
As stated above, this is the struct that gives me a
Marshalling.SizeOf() of 32 bytes. If, instead of the two Byte
arrays,
I provide 18 individual Bytes, I get a matching size of 42 bytes,
but
that seems like a heavy-handed work-around. Surely there's a Right
Way to do this.
Additionally, if instead of Byte arrays I define them as Char
arrays,
I get an error from the Marshalling code complaining that the array
types don't match.
Just for reference, the actual call that consumes this struct looks
like:
[DllImport("WRG315API.DLL")]
public static extern UInt32 GetRadioList(ref RADIO_INFO2[]
lpRadioInfo,
UInt32 BufferSize,
ref UInt32
InfoSize);
Thanks in advance for any help that is offered.
Todd Pafford
(e-mail address removed)
 
C

calenlas

Good points, Nicholas. The issues you bring up can be explained
simply by the fact that I was working very fast and loose. Having
tried several unsuccessful approaches I was excited to be hot on the
trail of the correct approach. Little things like signed/unsigned
ints sometimes get lost in that sort of excitement. :) (I guess
that's testiment to our geekiness when we get excited over this sort
of thing.) Also, by this point I was tired of trying to figure out
exactly what the Marhsalling code would do to my types and just
figured I'd handle everything manually. It's good to know that in the
"unsafe" environment I can still rely on the Marshaller to handle some
things for me.

Todd

Todd,

You could have used Int64 in the structure declaration. "long" is a
managed type, nothing more than an alias in the language for Int64. There
is no difference.

Also, why did you change the types? You had unsigned values before, and
now they are signed. I would think you would want to preserve those (you
can use ulong or UInt64).

Finally, I'm not sure you have to declare InfoSize as a pointer, I think
you can still use "ref" even though it is unsafe (I don't see why you can't)
and the marshaller should correctly marshal the value back and forth.

--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)


Nicholas,
Thanks so much for your help. You've put me back on track with a very
time-sensitive project. I'd buy you a beer if I could. :)
In short, I got it working pretty easily once I decided to switch over
to an "unsafe" environment and do it all manually. This is something
I've never done before in C#, but I have plenty of experience with C/C+
+ so the pointer arithmetic is second nature to me.
For the sake of anyone who stumbles upon this thread in the future
while trying to solve a similar problem, here's my solution.
First, the struct definition. Declared "unsafe" with "fixed" byte
arrays so I don't have to worry about unicode vs. ansi characters. Of
course, I've used no managed types within the struct. (I used longs
instead of Int64s, for example.)
[StructLayout(LayoutKind.Sequential, Pack=1)]
public unsafe struct RADIO_INFO2_unsafe
{
public uint bLength;
public fixed byte szSerNum[9];
public fixed byte szProdName[9];
public long MinFreq;
public long MaxFreq;
public uint Features;
};
My DLL entry point is defined as:
[DllImport("WRG315API.DLL", EntryPoint="GetRadioList")]
public unsafe static extern
uint GetRadioList_unsafe(
RADIO_INFO2_unsafe* lpRadioInfo,
uint BufferSize,
uint* InfoSize);
Finally, the following is the code used to call the DLL function.
public unsafe static String[] TestGetRadioList_unsafe()
{
// define our buffer using stackalloc
uint numRadios = 10;
uint radioSize = (uint)(sizeof(RADIO_INFO2_unsafe));
uint bufferSize = radioSize * numRadios;
RADIO_INFO2_unsafe* radiosBuffer
= stackalloc RADIO_INFO2_unsafe[(int)numRadios];
// initialize the buffer (the DLL needs the bLength
// member to be initialized to the size of the struct
RADIO_INFO2_unsafe* radioPtr
= (RADIO_INFO2_unsafe*)radiosBuffer;
for (uint i = 0; i < numRadios; i++, radioPtr++ )
{
(*radioPtr).bLength = radioSize;
}
// make the DLL call
uint radioInfoSize = radioSize;
uint numRadiosFromCall
= GetRadioList_unsafe(radiosBuffer,
bufferSize,
&radioInfoSize);
// Parse the output
// This is just a bit of code that digests the radio info
// returned from the DLL, packages it up in a String[],
// and returns it to the caller for display to the user.
// Of course, since I specified byte arrays in my struct,
// the RadioInfoToString_unsafe() method called below has
// to convert those byte[]s to strings. No big deal, but
// a little extra work.
String[] radioStrings
= new String[numRadiosFromCall < numRadios ?
(int)numRadiosFromCall :
(int)numRadios];
for (int i = 0; i < numRadiosFromCall && i < numRadios; i+
+)
{
radioStrings
= RadioInfoToString_unsafe(radiosBuffer);
}
return radioStrings;
}

Thanks again, and I hope this helps others.
Todd Pafford
Todd,
C style arrays don't have any information about the length of the
array,
so the marshaling layer doesn't know how much data to marshal back to the
calling code. If you are marshaling data one way, then you don't have to
worry about this, since arrays in .NET have length information attached
to
them, so the marshaling layer can figure out how much data to send to
unmanaged code.
Automation doesn't have this problem since it uses SAFEARRAYs which
have
the length contained in them.
In order to get around this, you will need to allocate a block of
unmanaged memory for your structures, marshal the structures to that
block,
make the call, passing the block to the function (you would have to
change
your declaration for this), and then reverse the process when the call
returns.
If you are using unsafe code, you can do this differently, as you
could
declare the array on the stack, or fix the pointer to the array of
structures (if you can't declare it on the stack) and then pass that.
However, you would have to change your structure declaration to embedded
byte arrays, as the strings would not be marshalled (you would be passing
a
pointer).
--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Thanks for the quick response, Nicholas. Indeed, the MarshalAs()
directive fixes the string sizing and typing issue. This is a great
help.
Oddly, when I pass an array of ten of these structs as refs to the
DLL, I get back an array of length 1 (I've only got one radio so this
is ok), but that one struct contains only the initialization values I
assigned all the elements in my original array. It's as if the array
was passed by reference and so could be modified, but each element of
the array cannot be modified. Does this make sense according to the
marshalling rules? Certainly, the problem could be in the DLL in
which case I can take it up with the manufacturers, but could this
behavior be a result of a problem with my definitions?
Thanks,
Todd
On Jan 14, 2:06 pm, "Nicholas Paldino [.NET/C# MVP]"
Todd,
You can just tell the marshalling layer to embed the string into
the
struct by value:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet =
CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szSerNum;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=9)]
public string szProdName;
public UInt64 MinFreq;
public UInt64 MaxFreq;
public UInt32 Features;
};
If you are using unsafe code, then you could use the fixed keyword
as
well to fix the character array to a certain size, but what's above
should
work just fine.
--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Hi all,
I'm taking my first steps into C# <--> C++ DLL Interop and
unfortunately I've run into (what seems to be) a very complicated
case
as my first task. Perhaps someone here can help me.
I need to pass an array of RADIO_INFO2 structures to be filled by a
function in the DLL. This is how the structure is defined in the
C++
example that comes with the DLL:
#pragma pack(1) // set byte packing
typedef struct {
unsigned __int32 bLength;
char szSerNum[9];
char szProdName[9];
unsigned __int64 MinFreq;
unsigned __int64 MaxFreq;
struct {
unsigned __int32 ExtRef:1;
unsigned __int32 FMWEnabled:1;
unsigned __int32 Reserved:30;
} Features;
} RADIO_INFO2;
#pragma pack() // set back the default packing
As you can see, there are a few obvious difficulties. First, the
nested Features struct. I think I've solved this by just defining
Features as a UInt32 and I'll handle the bit masking manually if I
need to. The second are the two char arrays. The DLL wants two 9
byte buffers to fill with character data while the marshalling code
wants to pass references. This brings me to the third problem, the
size of the structure. In C++ it's 42 bytes long: 32bits + 9bytes+
9bytes + 64bits + 64bits + 32bits = 42bytes. However, due to the
reference vs. value array problem the Marshalling code is coming up
with 32bytes: 32bits + 32bit (ref) + 32bit (ref) + 64bits + 64bits+
32bits = 32 bytes. The following is my best shot at describing this
structure in C#.
[StructLayout(LayoutKind.Sequential, Pack = 1,
CharSet = CharSet.Ansi)]
public struct RADIO_INFO2
{
public UInt32 bLength;
public Byte[] szSerNum;
public Byte[] szProdName;
public UInt64 MinFreq;

...

read more »
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top