0
votes

I'm struggling with a C# program to read binary records from a database. The records were created with Borland Delphi. Here's an example:

// Delphi record definition
  tBowler_Rec = Record
  public
    gender          : tGender;
    bowler_num      : byte;
    name            : tString32;
    initials        : String[3];
    ...
// Corresponding C# definition (unmanaged code)
    [StructLayout(LayoutKind.Sequential, Pack=4)]
    public unsafe struct tBowler_Rec
    {
        public tGender gender;
        public byte bowler_num;
        public fixed byte name[32];
        public fixed byte initials[3];
        ...

I'm actually able to read this binary struct out of a SQL Server database and see the data in the Visual Studio debugger. Yay! I'm able to access fields like "gender" and "bowler_num" with no problem. Yay!

Q: How do I turn "name" into a C# string?

An example name is "ASHTON". It looks like this in memory:

\0x6ASHTON\0x0\0x0...

Here's how I'm trying to access it:


[StructLayout(LayoutKind.Sequential, Pack=4)]
public unsafe struct tBowler_Rec
{
    public tGender gender;
    public byte bowler_num;
    public fixed byte name[32];
    ...
    public string Name
    {
        get
        {
            StringBuilder sb = new StringBuilder();
            int ilen = name[0];
            for (int i = 1; i <= ilen; i++)
                sb.Append(name[i]);
            return sb.ToString();
        }
    }

I'm getting this error:

Error: You cannot use fixed size buffers contained in unfixed expressions. Try using the fixed statement.

Help?????

Thank you in advance!

4
Why are you using unsafe and fixed? I don't see why you would need to do that. It looks like a pretty routine P/Invoke to me.David Heffernan
TString32 is a custom type in Delphi, what is it's declaration? I am guessing String[32].Robert Love
Are you really sure you want to send string[N] instances between Delphi and C#?David Heffernan
\0x6ASHTON\0x0\0x0 clearly is good ol' Turbo Pascal string where length byte prepends up to 255 character bytes.Premature Optimization

4 Answers

2
votes

Try

Encoding.ASCII.GetString(name, 1, name[0]);
2
votes

As I'm not very familiar with Delphi so I can't give you a straight answer on the tString32 field. It seems to be UnmanagedType.AnsiBStr.

If this is the case, I would go for something like this:

[StructLayout(LayoutKind.Sequential, Pack=4)]
public struct tBowler_Rec
{
    public tGender gender;
    public byte bowler_num;
    [MarshalAs(UnmanagedType.AnsiBStr)]
    public string name;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public char[] initials;

Look also how I'm doing the initials marshaling. If tString is not an AnsiBStr, this would be a better way to marshal the characters from name.

I also would like to show that I've removed the fixed and unsafe instructions from the struct declaration as this is not necessary for what you are trying to do.

1
votes

The string stored in the original format is not "null terminated" ( C style string).

The original format is 'char count Then Count chars => 0x6 = char count, A = 0 S = 1 H = 2 T = 3 O = 4 N = 5.

You try to read Chars until a null char is encountered. But there are no null char, it's not a null terminated string. You have to set a custom data caster for this or to convert the database.

0
votes

I found the answer here: Fixed size buffer cannot be directly used from "this" object

Solution:

    [StructLayout(LayoutKind.Sequential, Pack=4)]
    public unsafe struct tBowler_Rec
    {
        public tGender gender;
        public byte bowler_num;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]
        public byte[] name;
        ...
        public string Name
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                int ilen = name[0];
                for (int i = 1; i <= ilen; i++)
                    sb.Append(name[i]);
                return sb.ToString();
            }
        }

Vladimir was absolutely on the right track: the fundamental problem was that I needed to treat this Delphi array as a value type, not a C# (reference type) array. The solution is "MarshalAs(UnmanagedType.ByValArray)"/

Thank you, all!