Read / write large integers or floats using a simple byte protocol in C

Disclaimer: The author of the question has an average knowledge of Erlang and a basic (but increasing) knowledge of C.

I use the function read()to read the bytes that my program port.cretrieves from the Erlang port example in the Compatibility Tutorial Guide (this is also described in chapter 12 of Erlang Programming).

But I tend to think that the question is not related to Erlang at all, because the wrong values ​​that I get (for example, 231 instead of 999) come from C.

The problem is that this protocol does not work with parameters that are greater than 255 (otherwise it works well). I suppose this has something to do with the type byteand implementation read_exact(), but I don't know how to fix it or make it possible to pass values ​​in it float.

I read half the K & R book to understand this code, but I'm still stuck.

Here is the code:

Actual C functions:

/* complex.c */

int foo(int x) {
  return x+1;
}

int bar(int y) {
  return y*2;
}

Port C:

/ * port.c * /

typedef unsigned char byte;

int main() {
  int fn, arg, res;
  byte buf[100];

  while (read_cmd(buf) > 0) {
    fn = buf[0];
    arg = buf[1];

    if (fn == 1) {
      res = foo(arg);
    } else if (fn == 2) {
      res = bar(arg);
    }

    buf[0] = res;
    write_cmd(buf, 1);
  }
}

Buffer management:

/* erl_comm.c */

typedef unsigned char byte;

read_cmd(byte *buf)
{
  int len;

  if (read_exact(buf, 2) != 2)
    return(-1);
  len = (buf[0] << 8) | buf[1];
  return read_exact(buf, len);
}

write_cmd(byte *buf, int len)
{
  byte li;

  li = (len >> 8) & 0xff;
  write_exact(&li, 1);

  li = len & 0xff;
  write_exact(&li, 1);

  return write_exact(buf, len);
}

read_exact(byte *buf, int len)
{
  int i, got=0;

  do {
    if ((i = read(0, buf+got, len-got)) <= 0)
      return(i);
    got += i;
  } while (got<len);

  return(len);
}

write_exact(byte *buf, int len)
{
  int i, wrote = 0;

  do {
    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
      return (i);
    wrote += i;
  } while (wrote<len);

  return (len);
}

Erlang Port:

-module(complex1).
-export([start/1, stop/0, init/1]).
-export([foo/1, bar/1]).

start(ExtPrg) ->
    spawn(?MODULE, init, [ExtPrg]).
stop() ->
    complex ! stop.

foo(X) ->
    call_port({foo, X}).
bar(Y) ->
    call_port({bar, Y}).

call_port(Msg) ->
    complex ! {call, self(), Msg},
    receive
    {complex, Result} ->
        Result
    end.

init(ExtPrg) ->
    register(complex, self()),
    process_flag(trap_exit, true),
    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    loop(Port).

loop(Port) ->
    receive
    {call, Caller, Msg} ->
        Port ! {self(), {command, encode(Msg)}},
        receive
        {Port, {data, Data}} ->
            Caller ! {complex, decode(Data)}
        end,
        loop(Port);
    stop ->
        Port ! {self(), close},
        receive
        {Port, closed} ->
            exit(normal)
        end;
    {'EXIT', Port, Reason} ->
        exit(port_terminated)
    end.

encode({foo, X}) -> [1, X];
encode({bar, Y}) -> [2, Y].

decode([Int]) -> Int.

I made a stupid attempt to change

typedef unsigned char byte;

to

typedef int byte;

but it didn’t work.

There are actually two problems:

  • if we call a port with a parameter that is greater than 255 (for example, foo(256)from the Erlang port), execution ends with read () inside read_cmd () with i = 0;
  • , 255, 255 (, int foo(int x) { return x+1000; }, , ;

, : , ?

+3
3

, - , -128 127 () 0 255 ( ); . http://www.erlang.org/documentation/doc-5.6/pdf/tutorial.pdf Erlang Erlang C.

+2

byte read_cmd.

...
  int len;

  if (read_exact(buf, 2) != 2)
    return(-1);
  len = (buf[0] << 8) | buf[1];
...

byte buf int (- byte). this, a double 8 , . - (, , , ..):

double marshall_eight(byte *buff) {
  int i;
  double tmp, res;
  for(i=0; i<8;i++) {
    tmp = buff[i] << 8*i;
    res += tmp;
  }
  return res;
}
+2

, Erlang , .

, ...

while (read_cmd(buf) > 0) { 
 fn = buf[0];
    arg = buf[1];

:

while (arg=read_cmd(buf) > 0) {
 fn = buf[0];

ints, .

.

------------ ----------------- , c:

 #include <stdio.h>
 #include <math.h>

 /*
  * With certain compilers  __attribute__((transparent_union)) 
  * can be used to ease handling of an unknown type.
  */
 union  u_unionalue
 {
     char                char_union;
     unsigned char       u_char_union;
     short               s_int_union;
     unsigned short      us_int_union;
     int                 int_union;
     unsigned int        u_int_union;
     long                l_int_union;
     unsigned long       ul_int_union;
     long long           ll_int_union;
     unsigned long long  ull_int_union;
     float               float_union;
     double              double_union;
     long double         l_double_union;
 };

 enum e_type
 {
     char_enum    ,
     u_char_enum  ,
     s_int_enum   ,
     us_int_enum  ,
     int_enum     ,
     u_int_enum   ,
     l_int_enum   ,
     ul_int_enum  ,
     ll_int_enum  ,
     ull_int_enum ,
     float_enum   ,
     double_enum  ,
     l_double_enum,
     last_type
 };

 struct s_type
 {
     int  type;
     char *name;
     union u_unionalue value;
     int   size;
     char *stringFormat;

 } as_typeList[]=
 /**
  * This is a quick example of how convoluted type handling can be in C. The 
  * non portable  __attribute__((transparent_union)) can be useful if the 
  * complier supports it. This helps to 
  * reduce the amount of casting, but these are the convoluted tricks that 
  * occur behind the scenes. C++ has to handle this in the compiler as well
  * as a result .. sometimes what you get is not what you expect.
  */
 {
     { char_enum    ,  "char"              ,  {.char_union=(1 << (-1 + sizeof( char ) * 8  ))}, sizeof( char ),"%+d" },
     { u_char_enum  ,  "unsigned char"     ,  {.u_char_union=-1} , sizeof( unsigned char ) ,"%+d" },
     { s_int_enum   ,  "short"             ,  {.s_int_union=((short)1 << (-1 + sizeof( short ) * 8))}  , sizeof( short ),"%+d" },
     { us_int_enum  ,  "unsigned short"    ,  {.us_int_union=-1}, sizeof( unsigned short ),"%+u"  },
     { int_enum     ,  "int"               ,  {.int_union = ((int)1<< (-1 + sizeof( int) * 8  ))}, sizeof( int), "%+i"  },
     { u_int_enum   ,  "unsigned int"      ,  {.u_int_union=-1}, sizeof( unsigned int ), "%+u"  },
     { l_int_enum   ,  "long"              ,  {.l_int_union=((long)1<< (-1 + sizeof( long) * 8  ))}, sizeof( long ), "%+li" },
     { ul_int_enum  ,  "unsigned long"     ,  {.ul_int_union=(long)-1}, sizeof( unsigned long ), "%+lu" },
     { ll_int_enum  ,  "long long"         ,  {.ll_int_union=(long long)-1 }, sizeof( long long ), "%+lli"},
     { ull_int_enum ,  "unsigned long long",  {.ull_int_union=((unsigned long long)1<< (-1 + sizeof( unsigned long long) * 8  ))}, sizeof( unsigned long long  ), "%+llu"},
     { float_enum   ,  "float"             ,  {.float_union=1e+37L}, sizeof( float ), "%+f"  },
     { double_enum  ,  "double"            ,  {.double_union=1e+37L}, sizeof( double ), "%+lf"  },
     { l_double_enum,  "long double"       ,  {.l_double_union=1e+37L}, sizeof( long double), "%+lle"}
 };


 /**
  * This is how your foo and bar functions should be organized this would 
  * allow handling for all your types. but the type is needed.
  */
 void sprintVal(struct s_type *typeVal, char*buf)
 {
     switch(typeVal->type)
     {
     case char_enum     :
         sprintf(buf, typeVal->stringFormat, typeVal->value.char_union);
         break;
     case u_char_enum   :
         sprintf(buf, typeVal->stringFormat, typeVal->value.u_char_union);
         break;
     case s_int_enum    :
         sprintf(buf, typeVal->stringFormat, typeVal->value.s_int_union);
         break;
     case us_int_enum   :
         sprintf(buf, typeVal->stringFormat, typeVal->value.us_int_union);
         break;
     case int_enum      :
         sprintf(buf, typeVal->stringFormat, typeVal->value.int_union);
         break;
     case u_int_enum    :
         sprintf(buf, typeVal->stringFormat, typeVal->value.u_int_union);
         break;
     case l_int_enum    :
         sprintf(buf, typeVal->stringFormat, typeVal->value.l_int_union);
         break;
     case ul_int_enum   :
         sprintf(buf, typeVal->stringFormat, typeVal->value.ul_int_union);
         break;
     case ll_int_enum   :
         sprintf(buf, typeVal->stringFormat, typeVal->value.ll_int_union);
         break;
     case ull_int_enum  :
         sprintf(buf, typeVal->stringFormat, typeVal->value.ull_int_union);
         break;
     case float_enum    :
         sprintf(buf, typeVal->stringFormat, typeVal->value.float_union);
         break;
     case double_enum   :
         sprintf(buf, typeVal->stringFormat, typeVal->value.double_union);
         break;
     case l_double_enum :
         sprintf(buf, typeVal->stringFormat, typeVal->value.l_double_union);
         break;
     }
 }


 void print_types()
 {
     int i=0;
     char buf[100];

     while(i < last_type )
     {
         sprintVal( &as_typeList[i], buf);
         printf( "Type: %-18s value=%-30s size=  %-dBytes \n", as_typeList[i].name, buf, as_typeList[i].size );
         i++;
     };
 }

 int main(int argc, char** argv)
 {

     print_types();
     return(0);
 }

The biggest problem in your problem is the messaging between erlang and c. Your current format is / CMD / VALUE / minimum should be / CMD / TYPE / VALUE /, although the common message header and the checksum footer are common. Types should be encouraged so that greater value can be returned. Knowing what you are going through is necessary.

Oh, if you pass the data as a string, you can change your type to some extent. It also prevents problems that occur when there is a difference between the ends of the pipe.

+1
source

All Articles