I need to use C library for working with NetCDF file format in my C# project. To receive some portion of informtaion from NetCDF file, i need to use methods nc_open to open CDF file, nc_inq_att to get the length of array (this one works fine and gives no error) and then nc_get_att_text to get pointer to array of chars. Then I need to get this array of chars and transform them into string.

Original documentation on UniData web-site describes nc_get_att_text:

int nc_get_att_text 
(   
int ncid,
int varid,
const char* name,
char* value 
)   

Parameters ncid NetCDF file or group ID. varid Variable ID, or NC_GLOBAL for a global attribute. name Attribute name. value Pointer that will get array of attribute value(s).

I use code like this to import netcdf.dll:

using System;
using System.Runtime.InteropServices;

namespace test
{
    private class LoadData
    {
        private LoadData()
        { 
            OpenFileDialog File = new OpenFileDialog();
            int ncid;
            int errorid;
            string op_name;

            File.Filter = "File .cdf|*.cdf";
            if (File.ShowDialog() == DialogResult.OK)
            {
                //Open CDF file - this one works fine
                errorid = nc_open(File.FileName, CreateMode.NC_NOWRITE, out ncid);

                //inquiring the length of array - this one works fine and gives me correct value (checked with original NetCDF software). attlength.ToInt64() gives result 7.
                errorid = nc_inq_att(ncid, -1, "operator_name", out NcType atttype, out IntPtr attlength);

                //getting pointer works fine
                errorid = nc_get_att_text(ncid, -1, "operator_name", out IntPtr value);

                //sending pointer to array of char and array length to my method
                op_name = CharPointerToString(value, attlength.ToInt64());
            }
        }

        // This method should copy bytes to my bytes array and then make a string from them.
        private string CharPointerToString(IntPtr _pointer, long _length)
        {
            string result;
            char[] chars = new char[(int)_length];
            IntPtr pointer = _pointer;
            Marshal.Copy(_pointer, chars, 0, (int)_length); // here I get Violation "Attempted to read or write protected memory. This is often an indication that other memory is corrupt"
            return result = new string(chars);
        }

        [DllImport("netcdf.dll")]
        public static extern int nc_open(string _FileName, CreateMode _CreateMode, out int ncid);

        [DllImport("netcdf.dll")]
        public static extern int nc_inq_att(int _ncid, int _varid, string _AttributeName, out NcType _type, out IntPtr _AttributeLength);

        [DllImport("netcdf.dll")]
        public static extern int nc_get_att_text(int _ncid, int _varid, string _AttributeName, out IntPtr _value);
    }
}

Currently I have no idea, how to handle this and what causes the problem.

I have found that in C char can by only one byte due to ASCII encoding, whether char in C# is 4 bytes. I have replaced code in my function with this one to ready bytes instead of chars and to transform bytes to string using ASCII encoding, but this gave the same error.

 private string CharPointerToString(IntPtr _pointer, long _length)
    {
       string result;
       byte[] bytes = new byte[(int)_length];
       Marshal.Copy(_pointer, bytes, 0, (int)_length);
       result = Encoding.ASCII.GetString(bytes);
       return result;
    }

Original library is build for x64 systems, I also build my project to x64. I also tried to build for Any CPU, no difference.

1

There are 1 best solutions below

1
DingleFlop On

It appears (from doing a bit of research) that there was an erroneous assumption that nc_get_att_text() handles memory for you, allocating the space for the result on its own.

To the best of my knowledge, this is not the case. I am not speaking to the rest of the implementation, but from the documentation it seems that you are meant to allocate a buffer, and then YOU pass the pointer to that function, rather than it passing one to you.

To implement this solution, you change your P/Invoke definition to accept rather than emit the pointer:

[DllImport("netcdf.dll")]
public static extern int nc_get_att_text(int _ncid, int _varid, string _AttributeName, IntPtr _value);

And then allocate the memory, before passing it in:

IntPtr buffer = Marshal.AllocHGlobal(attLength + 1);// One more byte to hold trailing null
try
{
    int errorid = nc_get_att_text(ncid, -1, "operator_name", buffer);
    string op_name = Marshal.PtrToStringAnsi(buffer, attLength);
}
finally
{
    Marshal.FreeHGlobal(buffer);
}