免责声明:问题的作者对Erlang有一般的了解,对C有基本的(但不断增加的(知识。
我正在使用 read()
函数来读取我的 port.c
程序从互操作性教程用户指南中提供的 Erlang 端口示例获得的字节("Erlang 编程"一书的第 12 章也有描述(。
但我倾向于认为这个问题根本不与 Erlang 相关,因为我得到的错误值(例如 231 而不是 999(来自 C 端。
问题是该协议不适用于超过 255 的参数(否则效果很好(。我想它与byte
类型和read_exact()
实现有关,但我不知道如何修复它或使其可以在其中传递float
值。
为了理解这段代码,我已经读了一半的K&R书,但我仍然卡住了。
这是代码:
实际的 C 函数:
/* complex.c */
int foo(int x) {
return x+1;
}
int bar(int y) {
return y*2;
}
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);
}
}
缓冲区管理:
/* 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.
我做了一个愚蠢的改变尝试
typedef unsigned char byte;
自
typedef int byte;
但它没有用。
实际上,有两个问题:
- 如果我们使用大于 255 的参数调用端口(例如
foo(256)
来自 Erlang 端口(,执行将在 read_cmd(( 内的 read(( 处终止,i = 0; - 如果我们使用小于 255 的参数调用端口,但函数的结果大于 255(比如
int foo(int x) { return x+1000; }
,那么程序不会终止,但我们得到了一些意想不到的值到我们的 Erlang 端口;
所以,问题是:我应该怎么做才能使协议适用于更大的数字甚至浮点数?
您可能遇到的任何计算机上的字节只能保存 -128 到 127(有符号(或 0 到 255(无符号(之间的值;任何其他内容都需要在两者之间封送数据。 http://www.erlang.org/documentation/doc-5.6/pdf/tutorial.pdf 是官方的 Erlang 教程,用于在 Erlang 和其他语言之间封送数据,并包含 C 语言的示例。
read_cmd
中已经存在将byte
编组为较大结构的示例。
...
int len;
if (read_exact(buf, 2) != 2)
return(-1);
len = (buf[0] << 8) | buf[1];
...
这将前两个byte
从buf
中取出,并将它们视为一个int
(一个四byte
结构(。据此,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;
}
假设从 Erlang 转换为字节数组而不是字符数组。
代码有点松散,最简单的答案...
while (read_cmd(buf) > 0) {
fn = buf[0];
arg = buf[1];
应该是:
while (arg=read_cmd(buf) > 0) {
fn = buf[0];
您的代码将只处理字节和整数,它不能处理浮点数和双精度数。
写入 cmds 中还有其他错误。
------------编辑-----------------以下是您要在 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);
}
问题中最大的问题是 erlang 和 c 之间的消息传递。您当前的格式是/CMD/VALUE/,这至少应该是/CMD/TYPE/VALUE/,尽管邮件头和校验和页脚很常见。应升级类型,以便可以返回较大的结果值。因此,需要知道您正在传递的内容。
哦,如果将数据作为字符串传递,则可以在一定程度上适当地调整类型大小。如果管道末端之间存在差异,它还可以防止字节序问题。