Search code examples
c#csocketsstructdouble

Problem sending structure containing double variables over network from C# to C


in a previous post I asked "how to send a Struct from C# (Win10) to C (Unix-like OS) via socket".

The suggested solution was Marshalling.

Now I have no problem sending Integers, Enums or Strings. Unfortunately I have problems sending or receiving (I don't know if I'm wrong the Send of C # or the Read of C) the Double and Long data.

C Structure declared in Unix-like OS (vxWorks):

enum TYPE_OP 
{
  START_INITIALIZE   = 0,
  STOP_INITIALIZE    = 1,
  IDENTIFY           = 2,        
  GET_VALUE          = 3,
  SET_VALUE          = 4,
  SET_RSE_MODE       = 5,
  GET_VALUE_DOUBLE   = 6,
  SET_VALUE_DOUBLE   = 7,
  SET_STRING         = 8,
  GET_VALUE_FLOAT    = 9,
  SET_VALUE_FLOAT    = 10,
  SET_TARGET         = 11,
  GET_TARGET         = 12,
  GET_TASK_STATUS    = 13
}; 

typedef struct 
{  
   long    addr;    //address of an element in GPIB chain (ex. 1, 13, etc.)
   long    value;   //number (ex. enumerative position)
   double  dvalue;  //a double number
} DESCR_DATA;


struct VAR_DATA   
{  
   int     len ;
   enum TYPE_OP type;   
   char         name[100]; //Name of the element in GPIB chain
   DESCR_DATA   data;
}; 

then after the "accept" in C:

bytes_read = read(client_sock, &vardata, sizeof(vardata));
printf("ENUM VALUE = %d\n", vardata.type);
printf("DOUBLE VALUE = %.2f\n", vardata.data.value);

but result is always 0.00

C# structure declared in Windows:

public enum TYPE_OP
{
    START_INITIALIZE = 0,
    STOP_INITIALIZE = 1,
    IDENTIFY = 2,
    GET_VALUE = 3,
    SET_VALUE = 4,
    SET_RSE_MODE = 5,
    GET_VALUE_DOUBLE = 6,
    SET_VALUE_DOUBLE = 7,
    SET_STRING = 8,
    GET_VALUE_FLOAT = 9,
    SET_VALUE_FLOAT = 10,
    SET_TARGET = 11,
    GET_TARGET = 12,
    GET_TASK_STATUS = 13
}

public struct DESCR_DATA
{
    public long addr;
    public long value;
    public double dvalue;
}

public struct VAR_DATA
{
    public int len;
    public TYPE_OP type;
    public string name;
    public DESCR_DATA data;
}
VAR_DATA vardata = new VAR_DATA();
    

Code in C# to send structure "vardata" to C server:

public  byte[] Struct2Bytes(CommonStructures.VAR_DATA vdata_struct)
    {
        byte[] b_arr = new byte[Marshal.SizeOf(vdata_struct)];
        IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(vdata_struct));
        Marshal.StructureToPtr(vdata_struct, ptr, true);
        Marshal.Copy(ptr, b_arr, 0, Marshal.SizeOf(vdata_struct));
        return b_arr;
    }

public void Identify(Socket Socket2Server, CommonStructures.VAR_DATA vardata, IPEndPoint remoteEP)
    {

        Socket2Server.Connect(remoteEP);
        Socket2Server.RemoteEndPoint.ToString();

        vardata.name = "Device Name"; // new char[100];
        vardata.len = 105;
        vardata.data.dvalue = 16.6;
        vardata.data.addr = 11;
        vardata.data.value = 666;
        vardata.type = CommonStructures.TYPE_OP.IDENTIFY;
        
        int bytesSent = Socket2Server.Send(Struct2Bytes(vardata));

        Socket2Server.Shutdown(SocketShutdown.Both);
        Socket2Server.Close();

    }

Solution

  • You have quite a few mistakes in your struct definitions:

    • long in C is 32-bit, which is int in C#
    • name is char[100], which is a fixed-size array of ANSI characters. So it should have ByValTStr applied to it, and VAR_DATA needs CharSet.Ansi
    public struct DESCR_DATA
    {
        public int addr;
        public int value;
        public double dvalue;
    }
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct VAR_DATA
    {
        public int len;
        public TYPE_OP type;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
        public string name;
        public DESCR_DATA data;
    }
    

    You don't need to marshal the struct yourself (your marshalling code is leaking HGlobals anyway). Instead just pass it as a ref parameter

     public void Identify(Socket Socket2Server, CommonStructures.VAR_DATA vardata, IPEndPoint remoteEP)
     {
    
         Socket2Server.Connect(remoteEP);
         Socket2Server.RemoteEndPoint.ToString();
    
         vardata.name = "Device Name";
         vardata.len = 105;
         vardata.data.dvalue = 16.6;
         vardata.data.addr = 11;
         vardata.data.value = 666;
         vardata.type = CommonStructures.TYPE_OP.IDENTIFY;
         
         int bytesSent = Socket2Server.Send(ref vardata);
    
         Socket2Server.Shutdown(SocketShutdown.Both);
         Socket2Server.Close();
    
     }
    

    If you really need to use a byte[] array, you must make sure to release the HGlobal in a finally

     public  byte[] Struct2Bytes(CommonStructures.VAR_DATA vdata_struct)
     {
         byte[] b_arr = new byte[Marshal.SizeOf(vdata_struct)];
         IntPtr ptr = IntPtr.Zero;
         try
         {
             ptr = Marshal.AllocHGlobal(Marshal.SizeOf(vdata_struct));
             Marshal.StructureToPtr(vdata_struct, ptr, true);
             Marshal.Copy(ptr, b_arr, 0, Marshal.SizeOf(vdata_struct));
         }
         finally
         {
             Marshal.FreeHGlobal(ptr);
         }
         return b_arr;
     }
    
    

    I must say, I don't understand why you are using C code here anyway. C# has a full complement of library classes for sockets.