1998-09-21 03:22:07 +02:00
|
|
|
/* GNU Objective C Runtime archiving
|
|
|
|
Copyright (C) 1993, 1995, 1996, 1997 Free Software Foundation, Inc.
|
|
|
|
Contributed by Kresten Krab Thorup
|
|
|
|
|
|
|
|
This file is part of GNU CC.
|
|
|
|
|
|
|
|
GNU CC is free software; you can redistribute it and/or modify it under the
|
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; either version 2, or (at your option) any later version.
|
|
|
|
|
|
|
|
GNU CC is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
|
|
details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
|
|
GNU CC; see the file COPYING. If not, write to the Free Software
|
|
|
|
Foundation, 59 Temple Place - Suite 330,
|
|
|
|
Boston, MA 02111-1307, USA. */
|
|
|
|
|
|
|
|
/* As a special exception, if you link this library with files compiled with
|
|
|
|
GCC to produce an executable, this does not cause the resulting executable
|
|
|
|
to be covered by the GNU General Public License. This exception does not
|
|
|
|
however invalidate any other reasons why the executable file might be
|
|
|
|
covered by the GNU General Public License. */
|
|
|
|
|
1998-10-01 23:35:22 +02:00
|
|
|
#include "tconfig.h"
|
1998-09-21 03:22:07 +02:00
|
|
|
#include "runtime.h"
|
|
|
|
#include "typedstream.h"
|
|
|
|
#include "encoding.h"
|
|
|
|
|
|
|
|
#ifdef HAVE_STDLIB_H
|
|
|
|
#include <stdlib.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
extern int fflush(FILE*);
|
|
|
|
|
|
|
|
#define ROUND(V, A) \
|
|
|
|
({ typeof(V) __v=(V); typeof(A) __a=(A); \
|
|
|
|
__a*((__v+__a-1)/__a); })
|
|
|
|
|
|
|
|
#define PTR2LONG(P) (((char*)(P))-(char*)0)
|
|
|
|
#define LONG2PTR(L) (((char*)0)+(L))
|
|
|
|
|
|
|
|
/* Declare some functions... */
|
|
|
|
|
|
|
|
static int
|
|
|
|
objc_read_class (struct objc_typed_stream* stream, Class* class);
|
|
|
|
|
|
|
|
int objc_sizeof_type(const char* type);
|
|
|
|
|
|
|
|
static int
|
|
|
|
objc_write_use_common (struct objc_typed_stream* stream, unsigned long key);
|
|
|
|
|
|
|
|
static int
|
|
|
|
objc_write_register_common (struct objc_typed_stream* stream,
|
|
|
|
unsigned long key);
|
|
|
|
|
|
|
|
static int
|
|
|
|
objc_write_class (struct objc_typed_stream* stream,
|
|
|
|
struct objc_class* class);
|
|
|
|
|
|
|
|
const char* objc_skip_type (const char* type);
|
|
|
|
|
|
|
|
static void __objc_finish_write_root_object(struct objc_typed_stream*);
|
|
|
|
static void __objc_finish_read_root_object(struct objc_typed_stream*);
|
|
|
|
|
|
|
|
static __inline__ int
|
|
|
|
__objc_code_unsigned_char (unsigned char* buf, unsigned char val)
|
|
|
|
{
|
|
|
|
if ((val&_B_VALUE) == val)
|
|
|
|
{
|
|
|
|
buf[0] = val|_B_SINT;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
buf[0] = _B_NINT|0x01;
|
|
|
|
buf[1] = val;
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_unsigned_char (struct objc_typed_stream* stream,
|
|
|
|
unsigned char value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof (unsigned char)+1];
|
|
|
|
int len = __objc_code_unsigned_char (buf, value);
|
|
|
|
return (*stream->write)(stream->physical, buf, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static __inline__ int
|
1999-03-27 00:44:04 +01:00
|
|
|
__objc_code_char (unsigned char* buf, signed char val)
|
1998-09-21 03:22:07 +02:00
|
|
|
{
|
|
|
|
if (val >= 0)
|
|
|
|
return __objc_code_unsigned_char (buf, val);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
buf[0] = _B_NINT|_B_SIGN|0x01;
|
|
|
|
buf[1] = -val;
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
1999-03-27 00:44:04 +01:00
|
|
|
objc_write_char (struct objc_typed_stream* stream, signed char value)
|
1998-09-21 03:22:07 +02:00
|
|
|
{
|
|
|
|
unsigned char buf[sizeof (char)+1];
|
|
|
|
int len = __objc_code_char (buf, value);
|
|
|
|
return (*stream->write)(stream->physical, buf, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static __inline__ int
|
|
|
|
__objc_code_unsigned_short (unsigned char* buf, unsigned short val)
|
|
|
|
{
|
|
|
|
if ((val&_B_VALUE) == val)
|
|
|
|
{
|
|
|
|
buf[0] = val|_B_SINT;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int c, b;
|
|
|
|
|
|
|
|
buf[0] = _B_NINT;
|
|
|
|
|
|
|
|
for (c= sizeof(short); c != 0; c -= 1)
|
|
|
|
if (((val>>(8*(c-1)))%0x100) != 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
buf[0] |= c;
|
|
|
|
|
|
|
|
for (b = 1; c != 0; c--, b++)
|
|
|
|
{
|
|
|
|
buf[b] = (val >> (8*(c-1)))%0x100;
|
|
|
|
}
|
|
|
|
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_unsigned_short (struct objc_typed_stream* stream,
|
|
|
|
unsigned short value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof (unsigned short)+1];
|
|
|
|
int len = __objc_code_unsigned_short (buf, value);
|
|
|
|
return (*stream->write)(stream->physical, buf, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static __inline__ int
|
|
|
|
__objc_code_short (unsigned char* buf, short val)
|
|
|
|
{
|
|
|
|
int sign = (val < 0);
|
|
|
|
int size = __objc_code_unsigned_short (buf, sign ? -val : val);
|
|
|
|
if (sign)
|
|
|
|
buf[0] |= _B_SIGN;
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_short (struct objc_typed_stream* stream, short value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof (short)+1];
|
|
|
|
int len = __objc_code_short (buf, value);
|
|
|
|
return (*stream->write)(stream->physical, buf, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static __inline__ int
|
|
|
|
__objc_code_unsigned_int (unsigned char* buf, unsigned int val)
|
|
|
|
{
|
|
|
|
if ((val&_B_VALUE) == val)
|
|
|
|
{
|
|
|
|
buf[0] = val|_B_SINT;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int c, b;
|
|
|
|
|
|
|
|
buf[0] = _B_NINT;
|
|
|
|
|
|
|
|
for (c= sizeof(int); c != 0; c -= 1)
|
|
|
|
if (((val>>(8*(c-1)))%0x100) != 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
buf[0] |= c;
|
|
|
|
|
|
|
|
for (b = 1; c != 0; c--, b++)
|
|
|
|
{
|
|
|
|
buf[b] = (val >> (8*(c-1)))%0x100;
|
|
|
|
}
|
|
|
|
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_unsigned_int (struct objc_typed_stream* stream, unsigned int value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(unsigned int)+1];
|
|
|
|
int len = __objc_code_unsigned_int (buf, value);
|
|
|
|
return (*stream->write)(stream->physical, buf, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static __inline__ int
|
|
|
|
__objc_code_int (unsigned char* buf, int val)
|
|
|
|
{
|
|
|
|
int sign = (val < 0);
|
|
|
|
int size = __objc_code_unsigned_int (buf, sign ? -val : val);
|
|
|
|
if (sign)
|
|
|
|
buf[0] |= _B_SIGN;
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_int (struct objc_typed_stream* stream, int value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(int)+1];
|
|
|
|
int len = __objc_code_int (buf, value);
|
|
|
|
return (*stream->write)(stream->physical, buf, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static __inline__ int
|
|
|
|
__objc_code_unsigned_long (unsigned char* buf, unsigned long val)
|
|
|
|
{
|
|
|
|
if ((val&_B_VALUE) == val)
|
|
|
|
{
|
|
|
|
buf[0] = val|_B_SINT;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int c, b;
|
|
|
|
|
|
|
|
buf[0] = _B_NINT;
|
|
|
|
|
|
|
|
for (c= sizeof(long); c != 0; c -= 1)
|
|
|
|
if (((val>>(8*(c-1)))%0x100) != 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
buf[0] |= c;
|
|
|
|
|
|
|
|
for (b = 1; c != 0; c--, b++)
|
|
|
|
{
|
|
|
|
buf[b] = (val >> (8*(c-1)))%0x100;
|
|
|
|
}
|
|
|
|
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_unsigned_long (struct objc_typed_stream* stream,
|
|
|
|
unsigned long value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(unsigned long)+1];
|
|
|
|
int len = __objc_code_unsigned_long (buf, value);
|
|
|
|
return (*stream->write)(stream->physical, buf, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static __inline__ int
|
|
|
|
__objc_code_long (unsigned char* buf, long val)
|
|
|
|
{
|
|
|
|
int sign = (val < 0);
|
|
|
|
int size = __objc_code_unsigned_long (buf, sign ? -val : val);
|
|
|
|
if (sign)
|
|
|
|
buf[0] |= _B_SIGN;
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_long (struct objc_typed_stream* stream, long value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(long)+1];
|
|
|
|
int len = __objc_code_long (buf, value);
|
|
|
|
return (*stream->write)(stream->physical, buf, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_string (struct objc_typed_stream* stream,
|
|
|
|
const unsigned char* string, unsigned int nbytes)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(unsigned int)+1];
|
|
|
|
int len = __objc_code_unsigned_int (buf, nbytes);
|
|
|
|
|
|
|
|
if ((buf[0]&_B_CODE) == _B_SINT)
|
|
|
|
buf[0] = (buf[0]&_B_VALUE)|_B_SSTR;
|
|
|
|
|
|
|
|
else /* _B_NINT */
|
|
|
|
buf[0] = (buf[0]&_B_VALUE)|_B_NSTR;
|
|
|
|
|
|
|
|
if ((*stream->write)(stream->physical, buf, len) != 0)
|
|
|
|
return (*stream->write)(stream->physical, string, nbytes);
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_string_atomic (struct objc_typed_stream* stream,
|
|
|
|
unsigned char* string, unsigned int nbytes)
|
|
|
|
{
|
|
|
|
unsigned long key;
|
|
|
|
if ((key = PTR2LONG(hash_value_for_key (stream->stream_table, string))))
|
|
|
|
return objc_write_use_common (stream, key);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int length;
|
|
|
|
hash_add (&stream->stream_table, LONG2PTR(key=PTR2LONG(string)), string);
|
|
|
|
if ((length = objc_write_register_common (stream, key)))
|
|
|
|
return objc_write_string (stream, string, nbytes);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
objc_write_register_common (struct objc_typed_stream* stream,
|
|
|
|
unsigned long key)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof (unsigned long)+2];
|
|
|
|
int len = __objc_code_unsigned_long (buf+1, key);
|
|
|
|
if (len == 1)
|
|
|
|
{
|
|
|
|
buf[0] = _B_RCOMM|0x01;
|
|
|
|
buf[1] &= _B_VALUE;
|
|
|
|
return (*stream->write)(stream->physical, buf, len+1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
buf[1] = (buf[1]&_B_VALUE)|_B_RCOMM;
|
|
|
|
return (*stream->write)(stream->physical, buf+1, len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
objc_write_use_common (struct objc_typed_stream* stream, unsigned long key)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof (unsigned long)+2];
|
|
|
|
int len = __objc_code_unsigned_long (buf+1, key);
|
|
|
|
if (len == 1)
|
|
|
|
{
|
|
|
|
buf[0] = _B_UCOMM|0x01;
|
|
|
|
buf[1] &= _B_VALUE;
|
|
|
|
return (*stream->write)(stream->physical, buf, 2);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
buf[1] = (buf[1]&_B_VALUE)|_B_UCOMM;
|
|
|
|
return (*stream->write)(stream->physical, buf+1, len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static __inline__ int
|
|
|
|
__objc_write_extension (struct objc_typed_stream* stream, unsigned char code)
|
|
|
|
{
|
|
|
|
if (code <= _B_VALUE)
|
|
|
|
{
|
|
|
|
unsigned char buf = code|_B_EXT;
|
|
|
|
return (*stream->write)(stream->physical, &buf, 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_OPCODE,
|
|
|
|
"__objc_write_extension: bad opcode %c\n", code);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
__objc_write_object (struct objc_typed_stream* stream, id object)
|
|
|
|
{
|
|
|
|
unsigned char buf = '\0';
|
|
|
|
SEL write_sel = sel_get_any_uid ("write:");
|
|
|
|
if (object)
|
|
|
|
{
|
|
|
|
__objc_write_extension (stream, _BX_OBJECT);
|
|
|
|
objc_write_class (stream, object->class_pointer);
|
|
|
|
(*objc_msg_lookup(object, write_sel))(object, write_sel, stream);
|
|
|
|
return (*stream->write)(stream->physical, &buf, 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return objc_write_use_common(stream, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_object_reference (struct objc_typed_stream* stream, id object)
|
|
|
|
{
|
|
|
|
unsigned long key;
|
|
|
|
if ((key = PTR2LONG(hash_value_for_key (stream->object_table, object))))
|
|
|
|
return objc_write_use_common (stream, key);
|
|
|
|
|
|
|
|
__objc_write_extension (stream, _BX_OBJREF);
|
|
|
|
return objc_write_unsigned_long (stream, PTR2LONG (object));
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_root_object (struct objc_typed_stream* stream, id object)
|
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
if (stream->writing_root_p)
|
|
|
|
objc_error (nil, OBJC_ERR_RECURSE_ROOT,
|
|
|
|
"objc_write_root_object called recursively");
|
|
|
|
else
|
|
|
|
{
|
|
|
|
stream->writing_root_p = 1;
|
|
|
|
__objc_write_extension (stream, _BX_OBJROOT);
|
|
|
|
if((len = objc_write_object (stream, object)))
|
|
|
|
__objc_finish_write_root_object(stream);
|
|
|
|
stream->writing_root_p = 0;
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_object (struct objc_typed_stream* stream, id object)
|
|
|
|
{
|
|
|
|
unsigned long key;
|
|
|
|
if ((key = PTR2LONG(hash_value_for_key (stream->object_table, object))))
|
|
|
|
return objc_write_use_common (stream, key);
|
|
|
|
|
|
|
|
else if (object == nil)
|
|
|
|
return objc_write_use_common(stream, 0);
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int length;
|
|
|
|
hash_add (&stream->object_table, LONG2PTR(key=PTR2LONG(object)), object);
|
|
|
|
if ((length = objc_write_register_common (stream, key)))
|
|
|
|
return __objc_write_object (stream, object);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
__objc_write_class (struct objc_typed_stream* stream, struct objc_class* class)
|
|
|
|
{
|
|
|
|
__objc_write_extension (stream, _BX_CLASS);
|
|
|
|
objc_write_string_atomic(stream, (char*)class->name,
|
|
|
|
strlen((char*)class->name));
|
|
|
|
return objc_write_unsigned_long (stream, class->version);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
objc_write_class (struct objc_typed_stream* stream,
|
|
|
|
struct objc_class* class)
|
|
|
|
{
|
|
|
|
unsigned long key;
|
|
|
|
if ((key = PTR2LONG(hash_value_for_key (stream->stream_table, class))))
|
|
|
|
return objc_write_use_common (stream, key);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int length;
|
|
|
|
hash_add (&stream->stream_table, LONG2PTR(key=PTR2LONG(class)), class);
|
|
|
|
if ((length = objc_write_register_common (stream, key)))
|
|
|
|
return __objc_write_class (stream, class);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
__objc_write_selector (struct objc_typed_stream* stream, SEL selector)
|
|
|
|
{
|
|
|
|
const char* sel_name;
|
|
|
|
__objc_write_extension (stream, _BX_SEL);
|
|
|
|
/* to handle NULL selectors */
|
|
|
|
if ((SEL)0 == selector)
|
|
|
|
return objc_write_string (stream, "", 0);
|
|
|
|
sel_name = sel_get_name (selector);
|
|
|
|
return objc_write_string (stream, sel_name, strlen ((char*)sel_name));
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_selector (struct objc_typed_stream* stream, SEL selector)
|
|
|
|
{
|
|
|
|
const char* sel_name;
|
|
|
|
unsigned long key;
|
|
|
|
|
|
|
|
/* to handle NULL selectors */
|
|
|
|
if ((SEL)0 == selector)
|
|
|
|
return __objc_write_selector (stream, selector);
|
|
|
|
|
|
|
|
sel_name = sel_get_name (selector);
|
|
|
|
if ((key = PTR2LONG(hash_value_for_key (stream->stream_table, sel_name))))
|
|
|
|
return objc_write_use_common (stream, key);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int length;
|
|
|
|
hash_add (&stream->stream_table,
|
|
|
|
LONG2PTR(key=PTR2LONG(sel_name)), (char*)sel_name);
|
|
|
|
if ((length = objc_write_register_common (stream, key)))
|
|
|
|
return __objc_write_selector (stream, selector);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Read operations
|
|
|
|
*/
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
objc_read_char (struct objc_typed_stream* stream, char* val)
|
|
|
|
{
|
|
|
|
unsigned char buf;
|
|
|
|
int len;
|
|
|
|
len = (*stream->read)(stream->physical, &buf, 1);
|
|
|
|
if (len != 0)
|
|
|
|
{
|
|
|
|
if ((buf & _B_CODE) == _B_SINT)
|
|
|
|
(*val) = (buf & _B_VALUE);
|
|
|
|
|
|
|
|
else if ((buf & _B_NUMBER) == 1)
|
|
|
|
{
|
|
|
|
len = (*stream->read)(stream->physical, val, 1);
|
|
|
|
if (buf&_B_SIGN)
|
|
|
|
(*val) = -1*(*val);
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA,
|
|
|
|
"expected 8bit signed int, got %dbit int",
|
|
|
|
(int)(buf&_B_NUMBER)*8);
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
objc_read_unsigned_char (struct objc_typed_stream* stream, unsigned char* val)
|
|
|
|
{
|
|
|
|
unsigned char buf;
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, &buf, 1)))
|
|
|
|
{
|
|
|
|
if ((buf & _B_CODE) == _B_SINT)
|
|
|
|
(*val) = (buf & _B_VALUE);
|
|
|
|
|
|
|
|
else if ((buf & _B_NUMBER) == 1)
|
|
|
|
len = (*stream->read)(stream->physical, val, 1);
|
|
|
|
|
|
|
|
else
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA,
|
|
|
|
"expected 8bit unsigned int, got %dbit int",
|
|
|
|
(int)(buf&_B_NUMBER)*8);
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
objc_read_short (struct objc_typed_stream* stream, short* value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(short)+1];
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, buf, 1)))
|
|
|
|
{
|
|
|
|
if ((buf[0] & _B_CODE) == _B_SINT)
|
|
|
|
(*value) = (buf[0] & _B_VALUE);
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int pos = 1;
|
|
|
|
int nbytes = buf[0] & _B_NUMBER;
|
|
|
|
if (nbytes > sizeof (short))
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA,
|
|
|
|
"expected short, got bigger (%dbits)", nbytes*8);
|
|
|
|
len = (*stream->read)(stream->physical, buf+1, nbytes);
|
|
|
|
(*value) = 0;
|
|
|
|
while (pos <= nbytes)
|
|
|
|
(*value) = ((*value)*0x100) + buf[pos++];
|
|
|
|
if (buf[0] & _B_SIGN)
|
|
|
|
(*value) = -(*value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
objc_read_unsigned_short (struct objc_typed_stream* stream,
|
|
|
|
unsigned short* value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(unsigned short)+1];
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, buf, 1)))
|
|
|
|
{
|
|
|
|
if ((buf[0] & _B_CODE) == _B_SINT)
|
|
|
|
(*value) = (buf[0] & _B_VALUE);
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int pos = 1;
|
|
|
|
int nbytes = buf[0] & _B_NUMBER;
|
|
|
|
if (nbytes > sizeof (short))
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA,
|
|
|
|
"expected short, got int or bigger");
|
|
|
|
len = (*stream->read)(stream->physical, buf+1, nbytes);
|
|
|
|
(*value) = 0;
|
|
|
|
while (pos <= nbytes)
|
|
|
|
(*value) = ((*value)*0x100) + buf[pos++];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
objc_read_int (struct objc_typed_stream* stream, int* value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(int)+1];
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, buf, 1)))
|
|
|
|
{
|
|
|
|
if ((buf[0] & _B_CODE) == _B_SINT)
|
|
|
|
(*value) = (buf[0] & _B_VALUE);
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int pos = 1;
|
|
|
|
int nbytes = buf[0] & _B_NUMBER;
|
|
|
|
if (nbytes > sizeof (int))
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA, "expected int, got bigger");
|
|
|
|
len = (*stream->read)(stream->physical, buf+1, nbytes);
|
|
|
|
(*value) = 0;
|
|
|
|
while (pos <= nbytes)
|
|
|
|
(*value) = ((*value)*0x100) + buf[pos++];
|
|
|
|
if (buf[0] & _B_SIGN)
|
|
|
|
(*value) = -(*value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
objc_read_long (struct objc_typed_stream* stream, long* value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(long)+1];
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, buf, 1)))
|
|
|
|
{
|
|
|
|
if ((buf[0] & _B_CODE) == _B_SINT)
|
|
|
|
(*value) = (buf[0] & _B_VALUE);
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int pos = 1;
|
|
|
|
int nbytes = buf[0] & _B_NUMBER;
|
|
|
|
if (nbytes > sizeof (long))
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA, "expected long, got bigger");
|
|
|
|
len = (*stream->read)(stream->physical, buf+1, nbytes);
|
|
|
|
(*value) = 0;
|
|
|
|
while (pos <= nbytes)
|
|
|
|
(*value) = ((*value)*0x100) + buf[pos++];
|
|
|
|
if (buf[0] & _B_SIGN)
|
|
|
|
(*value) = -(*value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
__objc_read_nbyte_uint (struct objc_typed_stream* stream,
|
|
|
|
unsigned int nbytes, unsigned int* val)
|
|
|
|
{
|
|
|
|
int len, pos = 0;
|
|
|
|
unsigned char buf[sizeof(unsigned int)+1];
|
|
|
|
|
|
|
|
if (nbytes > sizeof (int))
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA, "expected int, got bigger");
|
|
|
|
|
|
|
|
len = (*stream->read)(stream->physical, buf, nbytes);
|
|
|
|
(*val) = 0;
|
|
|
|
while (pos < nbytes)
|
|
|
|
(*val) = ((*val)*0x100) + buf[pos++];
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
objc_read_unsigned_int (struct objc_typed_stream* stream,
|
|
|
|
unsigned int* value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(unsigned int)+1];
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, buf, 1)))
|
|
|
|
{
|
|
|
|
if ((buf[0] & _B_CODE) == _B_SINT)
|
|
|
|
(*value) = (buf[0] & _B_VALUE);
|
|
|
|
|
|
|
|
else
|
|
|
|
len = __objc_read_nbyte_uint (stream, (buf[0] & _B_VALUE), value);
|
|
|
|
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
__objc_read_nbyte_ulong (struct objc_typed_stream* stream,
|
|
|
|
unsigned int nbytes, unsigned long* val)
|
|
|
|
{
|
|
|
|
int len, pos = 0;
|
|
|
|
unsigned char buf[sizeof(unsigned long)+1];
|
|
|
|
|
|
|
|
if (nbytes > sizeof (long))
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA, "expected long, got bigger");
|
|
|
|
|
|
|
|
len = (*stream->read)(stream->physical, buf, nbytes);
|
|
|
|
(*val) = 0;
|
|
|
|
while (pos < nbytes)
|
|
|
|
(*val) = ((*val)*0x100) + buf[pos++];
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
objc_read_unsigned_long (struct objc_typed_stream* stream,
|
|
|
|
unsigned long* value)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(unsigned long)+1];
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, buf, 1)))
|
|
|
|
{
|
|
|
|
if ((buf[0] & _B_CODE) == _B_SINT)
|
|
|
|
(*value) = (buf[0] & _B_VALUE);
|
|
|
|
|
|
|
|
else
|
|
|
|
len = __objc_read_nbyte_ulong (stream, (buf[0] & _B_VALUE), value);
|
|
|
|
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
__inline__ int
|
|
|
|
objc_read_string (struct objc_typed_stream* stream,
|
|
|
|
char** string)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof(unsigned int)+1];
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, buf, 1)))
|
|
|
|
{
|
|
|
|
unsigned long key = 0;
|
|
|
|
|
|
|
|
if ((buf[0]&_B_CODE) == _B_RCOMM) /* register following */
|
|
|
|
{
|
|
|
|
len = __objc_read_nbyte_ulong(stream, (buf[0] & _B_VALUE), &key);
|
|
|
|
len = (*stream->read)(stream->physical, buf, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (buf[0]&_B_CODE) {
|
|
|
|
case _B_SSTR:
|
|
|
|
{
|
|
|
|
int length = buf[0]&_B_VALUE;
|
|
|
|
(*string) = (char*)objc_malloc(length+1);
|
|
|
|
if (key)
|
|
|
|
hash_add (&stream->stream_table, LONG2PTR(key), *string);
|
|
|
|
len = (*stream->read)(stream->physical, *string, length);
|
|
|
|
(*string)[length] = '\0';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _B_UCOMM:
|
|
|
|
{
|
|
|
|
char *tmp;
|
|
|
|
len = __objc_read_nbyte_ulong(stream, (buf[0] & _B_VALUE), &key);
|
|
|
|
tmp = hash_value_for_key (stream->stream_table, LONG2PTR (key));
|
|
|
|
*string = objc_malloc (strlen(tmp) + 1);
|
|
|
|
strcpy (*string, tmp);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _B_NSTR:
|
|
|
|
{
|
|
|
|
unsigned int nbytes = buf[0]&_B_VALUE;
|
|
|
|
len = __objc_read_nbyte_uint(stream, nbytes, &nbytes);
|
|
|
|
if (len) {
|
|
|
|
(*string) = (char*)objc_malloc(nbytes+1);
|
|
|
|
if (key)
|
|
|
|
hash_add (&stream->stream_table, LONG2PTR(key), *string);
|
|
|
|
len = (*stream->read)(stream->physical, *string, nbytes);
|
|
|
|
(*string)[nbytes] = '\0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA,
|
|
|
|
"expected string, got opcode %c\n", (buf[0]&_B_CODE));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_read_object (struct objc_typed_stream* stream, id* object)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof (unsigned int)];
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, buf, 1)))
|
|
|
|
{
|
|
|
|
SEL read_sel = sel_get_any_uid ("read:");
|
|
|
|
unsigned long key = 0;
|
|
|
|
|
|
|
|
if ((buf[0]&_B_CODE) == _B_RCOMM) /* register common */
|
|
|
|
{
|
|
|
|
len = __objc_read_nbyte_ulong(stream, (buf[0] & _B_VALUE), &key);
|
|
|
|
len = (*stream->read)(stream->physical, buf, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buf[0] == (_B_EXT | _BX_OBJECT))
|
|
|
|
{
|
|
|
|
Class class;
|
|
|
|
|
|
|
|
/* get class */
|
|
|
|
len = objc_read_class (stream, &class);
|
|
|
|
|
|
|
|
/* create instance */
|
|
|
|
(*object) = class_create_instance(class);
|
|
|
|
|
|
|
|
/* register? */
|
|
|
|
if (key)
|
|
|
|
hash_add (&stream->object_table, LONG2PTR(key), *object);
|
|
|
|
|
|
|
|
/* send -read: */
|
|
|
|
if (__objc_responds_to (*object, read_sel))
|
|
|
|
(*get_imp(class, read_sel))(*object, read_sel, stream);
|
|
|
|
|
|
|
|
/* check null-byte */
|
|
|
|
len = (*stream->read)(stream->physical, buf, 1);
|
|
|
|
if (buf[0] != '\0')
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA,
|
|
|
|
"expected null-byte, got opcode %c", buf[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
else if ((buf[0]&_B_CODE) == _B_UCOMM)
|
|
|
|
{
|
|
|
|
if (key)
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_KEY, "cannot register use upcode...");
|
|
|
|
len = __objc_read_nbyte_ulong(stream, (buf[0] & _B_VALUE), &key);
|
|
|
|
(*object) = hash_value_for_key (stream->object_table, LONG2PTR(key));
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (buf[0] == (_B_EXT | _BX_OBJREF)) /* a forward reference */
|
|
|
|
{
|
|
|
|
struct objc_list* other;
|
|
|
|
len = objc_read_unsigned_long (stream, &key);
|
|
|
|
other = (struct objc_list*)hash_value_for_key (stream->object_refs,
|
|
|
|
LONG2PTR(key));
|
|
|
|
hash_add (&stream->object_refs, LONG2PTR(key),
|
|
|
|
(void*)list_cons(object, other));
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (buf[0] == (_B_EXT | _BX_OBJROOT)) /* a root object */
|
|
|
|
{
|
|
|
|
if (key)
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_KEY,
|
|
|
|
"cannot register root object...");
|
|
|
|
len = objc_read_object (stream, object);
|
|
|
|
__objc_finish_read_root_object (stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA,
|
|
|
|
"expected object, got opcode %c", buf[0]);
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
objc_read_class (struct objc_typed_stream* stream, Class* class)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof (unsigned int)];
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, buf, 1)))
|
|
|
|
{
|
|
|
|
unsigned long key = 0;
|
|
|
|
|
|
|
|
if ((buf[0]&_B_CODE) == _B_RCOMM) /* register following */
|
|
|
|
{
|
|
|
|
len = __objc_read_nbyte_ulong(stream, (buf[0] & _B_VALUE), &key);
|
|
|
|
len = (*stream->read)(stream->physical, buf, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buf[0] == (_B_EXT | _BX_CLASS))
|
|
|
|
{
|
|
|
|
char* class_name;
|
|
|
|
unsigned long version;
|
|
|
|
|
|
|
|
/* get class */
|
|
|
|
len = objc_read_string (stream, &class_name);
|
|
|
|
(*class) = objc_get_class(class_name);
|
|
|
|
objc_free(class_name);
|
|
|
|
|
|
|
|
/* register */
|
|
|
|
if (key)
|
|
|
|
hash_add (&stream->stream_table, LONG2PTR(key), *class);
|
|
|
|
|
|
|
|
objc_read_unsigned_long(stream, &version);
|
|
|
|
hash_add (&stream->class_table, (*class)->name, (void*)version);
|
|
|
|
}
|
|
|
|
|
|
|
|
else if ((buf[0]&_B_CODE) == _B_UCOMM)
|
|
|
|
{
|
|
|
|
if (key)
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_KEY, "cannot register use upcode...");
|
|
|
|
len = __objc_read_nbyte_ulong(stream, (buf[0] & _B_VALUE), &key);
|
|
|
|
(*class) = hash_value_for_key (stream->stream_table, LONG2PTR(key));
|
|
|
|
if (!*class)
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_CLASS,
|
|
|
|
"cannot find class for key %lu", key);
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA,
|
|
|
|
"expected class, got opcode %c", buf[0]);
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_read_selector (struct objc_typed_stream* stream, SEL* selector)
|
|
|
|
{
|
|
|
|
unsigned char buf[sizeof (unsigned int)];
|
|
|
|
int len;
|
|
|
|
if ((len = (*stream->read)(stream->physical, buf, 1)))
|
|
|
|
{
|
|
|
|
unsigned long key = 0;
|
|
|
|
|
|
|
|
if ((buf[0]&_B_CODE) == _B_RCOMM) /* register following */
|
|
|
|
{
|
|
|
|
len = __objc_read_nbyte_ulong(stream, (buf[0] & _B_VALUE), &key);
|
|
|
|
len = (*stream->read)(stream->physical, buf, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buf[0] == (_B_EXT|_BX_SEL)) /* selector! */
|
|
|
|
{
|
|
|
|
char* selector_name;
|
|
|
|
|
|
|
|
/* get selector */
|
|
|
|
len = objc_read_string (stream, &selector_name);
|
|
|
|
/* To handle NULL selectors */
|
|
|
|
if (0 == strlen(selector_name))
|
|
|
|
{
|
|
|
|
(*selector) = (SEL)0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
(*selector) = sel_get_any_uid(selector_name);
|
|
|
|
objc_free(selector_name);
|
|
|
|
|
|
|
|
/* register */
|
|
|
|
if (key)
|
|
|
|
hash_add (&stream->stream_table, LONG2PTR(key), (void*)*selector);
|
|
|
|
}
|
|
|
|
|
|
|
|
else if ((buf[0]&_B_CODE) == _B_UCOMM)
|
|
|
|
{
|
|
|
|
if (key)
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_KEY, "cannot register use upcode...");
|
|
|
|
len = __objc_read_nbyte_ulong(stream, (buf[0] & _B_VALUE), &key);
|
|
|
|
(*selector) = hash_value_for_key (stream->stream_table,
|
|
|
|
LONG2PTR(key));
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_DATA,
|
|
|
|
"expected selector, got opcode %c", buf[0]);
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** USER LEVEL FUNCTIONS
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Write one object, encoded in TYPE and pointed to by DATA to the
|
|
|
|
** typed stream STREAM.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_type(TypedStream* stream, const char* type, const void* data)
|
|
|
|
{
|
|
|
|
switch(*type) {
|
|
|
|
case _C_ID:
|
|
|
|
return objc_write_object (stream, *(id*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CLASS:
|
|
|
|
return objc_write_class (stream, *(Class*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_SEL:
|
|
|
|
return objc_write_selector (stream, *(SEL*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CHR:
|
1999-03-27 00:44:04 +01:00
|
|
|
return objc_write_char(stream, *(signed char*)data);
|
1998-09-21 03:22:07 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_UCHR:
|
|
|
|
return objc_write_unsigned_char(stream, *(unsigned char*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_SHT:
|
|
|
|
return objc_write_short(stream, *(short*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_USHT:
|
|
|
|
return objc_write_unsigned_short(stream, *(unsigned short*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_INT:
|
|
|
|
return objc_write_int(stream, *(int*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_UINT:
|
|
|
|
return objc_write_unsigned_int(stream, *(unsigned int*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_LNG:
|
|
|
|
return objc_write_long(stream, *(long*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_ULNG:
|
|
|
|
return objc_write_unsigned_long(stream, *(unsigned long*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CHARPTR:
|
|
|
|
return objc_write_string (stream, *(char**)data, strlen(*(char**)data));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_ATOM:
|
|
|
|
return objc_write_string_atomic (stream, *(char**)data,
|
|
|
|
strlen(*(char**)data));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_ARY_B:
|
|
|
|
{
|
|
|
|
int len = atoi(type+1);
|
|
|
|
while (isdigit(*++type))
|
|
|
|
;
|
|
|
|
return objc_write_array (stream, type, len, data);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_STRUCT_B:
|
|
|
|
{
|
|
|
|
int acc_size = 0;
|
|
|
|
int align;
|
|
|
|
while (*type != _C_STRUCT_E && *type++ != '=')
|
|
|
|
; /* skip "<name>=" */
|
|
|
|
while (*type != _C_STRUCT_E)
|
|
|
|
{
|
|
|
|
align = objc_alignof_type (type); /* padd to alignment */
|
|
|
|
acc_size += ROUND (acc_size, align);
|
|
|
|
objc_write_type (stream, type, ((char*)data)+acc_size);
|
|
|
|
acc_size += objc_sizeof_type (type); /* add component size */
|
|
|
|
type = objc_skip_typespec (type); /* skip component */
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_TYPE,
|
|
|
|
"objc_write_type: cannot parse typespec: %s\n", type);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Read one object, encoded in TYPE and pointed to by DATA to the
|
|
|
|
** typed stream STREAM. DATA specifies the address of the types to
|
|
|
|
** read. Expected type is checked against the type actually present
|
|
|
|
** on the stream.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_read_type(TypedStream* stream, const char* type, void* data)
|
|
|
|
{
|
|
|
|
char c;
|
|
|
|
switch(c = *type) {
|
|
|
|
case _C_ID:
|
|
|
|
return objc_read_object (stream, (id*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CLASS:
|
|
|
|
return objc_read_class (stream, (Class*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_SEL:
|
|
|
|
return objc_read_selector (stream, (SEL*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CHR:
|
|
|
|
return objc_read_char (stream, (char*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_UCHR:
|
|
|
|
return objc_read_unsigned_char (stream, (unsigned char*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_SHT:
|
|
|
|
return objc_read_short (stream, (short*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_USHT:
|
|
|
|
return objc_read_unsigned_short (stream, (unsigned short*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_INT:
|
|
|
|
return objc_read_int (stream, (int*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_UINT:
|
|
|
|
return objc_read_unsigned_int (stream, (unsigned int*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_LNG:
|
|
|
|
return objc_read_long (stream, (long*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_ULNG:
|
|
|
|
return objc_read_unsigned_long (stream, (unsigned long*)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CHARPTR:
|
|
|
|
case _C_ATOM:
|
|
|
|
return objc_read_string (stream, (char**)data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_ARY_B:
|
|
|
|
{
|
|
|
|
int len = atoi(type+1);
|
|
|
|
while (isdigit(*++type))
|
|
|
|
;
|
|
|
|
return objc_read_array (stream, type, len, data);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_STRUCT_B:
|
|
|
|
{
|
|
|
|
int acc_size = 0;
|
|
|
|
int align;
|
|
|
|
while (*type != _C_STRUCT_E && *type++ != '=')
|
|
|
|
; /* skip "<name>=" */
|
|
|
|
while (*type != _C_STRUCT_E)
|
|
|
|
{
|
|
|
|
align = objc_alignof_type (type); /* padd to alignment */
|
|
|
|
acc_size += ROUND (acc_size, align);
|
|
|
|
objc_read_type (stream, type, ((char*)data)+acc_size);
|
|
|
|
acc_size += objc_sizeof_type (type); /* add component size */
|
|
|
|
type = objc_skip_typespec (type); /* skip component */
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_TYPE,
|
|
|
|
"objc_read_type: cannot parse typespec: %s\n", type);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Write the object specified by the template TYPE to STREAM. Last
|
|
|
|
** arguments specify addresses of values to be written. It might
|
|
|
|
** seem surprising to specify values by address, but this is extremely
|
|
|
|
** convenient for copy-paste with objc_read_types calls. A more
|
|
|
|
** down-to-the-earth cause for this passing of addresses is that values
|
|
|
|
** of arbitrary size is not well supported in ANSI C for functions with
|
|
|
|
** variable number of arguments.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_types (TypedStream* stream, const char* type, ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
const char *c;
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
va_start(args, type);
|
|
|
|
|
|
|
|
for (c = type; *c; c = objc_skip_typespec (c))
|
|
|
|
{
|
|
|
|
switch(*c) {
|
|
|
|
case _C_ID:
|
|
|
|
res = objc_write_object (stream, *va_arg (args, id*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CLASS:
|
|
|
|
res = objc_write_class (stream, *va_arg(args, Class*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_SEL:
|
|
|
|
res = objc_write_selector (stream, *va_arg(args, SEL*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CHR:
|
|
|
|
res = objc_write_char (stream, *va_arg (args, char*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_UCHR:
|
|
|
|
res = objc_write_unsigned_char (stream,
|
|
|
|
*va_arg (args, unsigned char*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_SHT:
|
|
|
|
res = objc_write_short (stream, *va_arg(args, short*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_USHT:
|
|
|
|
res = objc_write_unsigned_short (stream,
|
|
|
|
*va_arg(args, unsigned short*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_INT:
|
|
|
|
res = objc_write_int(stream, *va_arg(args, int*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_UINT:
|
|
|
|
res = objc_write_unsigned_int(stream, *va_arg(args, unsigned int*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_LNG:
|
|
|
|
res = objc_write_long(stream, *va_arg(args, long*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_ULNG:
|
|
|
|
res = objc_write_unsigned_long(stream, *va_arg(args, unsigned long*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CHARPTR:
|
|
|
|
{
|
|
|
|
char** str = va_arg(args, char**);
|
|
|
|
res = objc_write_string (stream, *str, strlen(*str));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_ATOM:
|
|
|
|
{
|
|
|
|
char** str = va_arg(args, char**);
|
|
|
|
res = objc_write_string_atomic (stream, *str, strlen(*str));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_ARY_B:
|
|
|
|
{
|
|
|
|
int len = atoi(c+1);
|
|
|
|
const char* t = c;
|
|
|
|
while (isdigit(*++t))
|
|
|
|
;
|
|
|
|
res = objc_write_array (stream, t, len, va_arg(args, void*));
|
|
|
|
t = objc_skip_typespec (t);
|
|
|
|
if (*t != _C_ARY_E)
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_TYPE, "expected `]', got: %s", t);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_TYPE,
|
|
|
|
"objc_write_types: cannot parse typespec: %s\n", type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Last arguments specify addresses of values to be read. Expected
|
|
|
|
** type is checked against the type actually present on the stream.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_read_types(TypedStream* stream, const char* type, ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
const char *c;
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
va_start(args, type);
|
|
|
|
|
|
|
|
for (c = type; *c; c = objc_skip_typespec(c))
|
|
|
|
{
|
|
|
|
switch(*c) {
|
|
|
|
case _C_ID:
|
|
|
|
res = objc_read_object(stream, va_arg(args, id*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CLASS:
|
|
|
|
res = objc_read_class(stream, va_arg(args, Class*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_SEL:
|
|
|
|
res = objc_read_selector(stream, va_arg(args, SEL*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CHR:
|
|
|
|
res = objc_read_char(stream, va_arg(args, char*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_UCHR:
|
|
|
|
res = objc_read_unsigned_char(stream, va_arg(args, unsigned char*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_SHT:
|
|
|
|
res = objc_read_short(stream, va_arg(args, short*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_USHT:
|
|
|
|
res = objc_read_unsigned_short(stream, va_arg(args, unsigned short*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_INT:
|
|
|
|
res = objc_read_int(stream, va_arg(args, int*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_UINT:
|
|
|
|
res = objc_read_unsigned_int(stream, va_arg(args, unsigned int*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_LNG:
|
|
|
|
res = objc_read_long(stream, va_arg(args, long*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_ULNG:
|
|
|
|
res = objc_read_unsigned_long(stream, va_arg(args, unsigned long*));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_CHARPTR:
|
|
|
|
case _C_ATOM:
|
|
|
|
{
|
|
|
|
char** str = va_arg(args, char**);
|
|
|
|
res = objc_read_string (stream, str);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _C_ARY_B:
|
|
|
|
{
|
|
|
|
int len = atoi(c+1);
|
|
|
|
const char* t = c;
|
|
|
|
while (isdigit(*++t))
|
|
|
|
;
|
|
|
|
res = objc_read_array (stream, t, len, va_arg(args, void*));
|
|
|
|
t = objc_skip_typespec (t);
|
|
|
|
if (*t != _C_ARY_E)
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_TYPE, "expected `]', got: %s", t);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
objc_error(nil, OBJC_ERR_BAD_TYPE,
|
|
|
|
"objc_read_types: cannot parse typespec: %s\n", type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Write an array of COUNT elements of TYPE from the memory address DATA.
|
|
|
|
** This is equivalent of objc_write_type (stream, "[N<type>]", data)
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_write_array (TypedStream* stream, const char* type,
|
|
|
|
int count, const void* data)
|
|
|
|
{
|
|
|
|
int off = objc_sizeof_type(type);
|
|
|
|
const char* where = data;
|
|
|
|
|
|
|
|
while (count-- > 0)
|
|
|
|
{
|
|
|
|
objc_write_type(stream, type, where);
|
|
|
|
where += off;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Read an array of COUNT elements of TYPE into the memory address
|
|
|
|
** DATA. The memory pointed to by data is supposed to be allocated
|
|
|
|
** by the callee. This is equivalent of
|
|
|
|
** objc_read_type (stream, "[N<type>]", data)
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
|
|
|
objc_read_array (TypedStream* stream, const char* type,
|
|
|
|
int count, void* data)
|
|
|
|
{
|
|
|
|
int off = objc_sizeof_type(type);
|
|
|
|
char* where = (char*)data;
|
|
|
|
|
|
|
|
while (count-- > 0)
|
|
|
|
{
|
|
|
|
objc_read_type(stream, type, where);
|
|
|
|
where += off;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
__objc_fread(FILE* file, char* data, int len)
|
|
|
|
{
|
|
|
|
return fread(data, len, 1, file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
__objc_fwrite(FILE* file, char* data, int len)
|
|
|
|
{
|
|
|
|
return fwrite(data, len, 1, file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
__objc_feof(FILE* file)
|
|
|
|
{
|
|
|
|
return feof(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
__objc_no_write(FILE* file, char* data, int len)
|
|
|
|
{
|
|
|
|
objc_error (nil, OBJC_ERR_NO_WRITE, "TypedStream not open for writing");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
__objc_no_read(FILE* file, char* data, int len)
|
|
|
|
{
|
|
|
|
objc_error (nil, OBJC_ERR_NO_READ, "TypedStream not open for reading");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
__objc_read_typed_stream_signature (TypedStream* stream)
|
|
|
|
{
|
|
|
|
char buffer[80];
|
|
|
|
int pos = 0;
|
|
|
|
do
|
|
|
|
(*stream->read)(stream->physical, buffer+pos, 1);
|
|
|
|
while (buffer[pos++] != '\0')
|
|
|
|
;
|
|
|
|
sscanf (buffer, "GNU TypedStream %d", &stream->version);
|
|
|
|
if (stream->version != OBJC_TYPED_STREAM_VERSION)
|
|
|
|
objc_error (nil, OBJC_ERR_STREAM_VERSION,
|
|
|
|
"cannot handle TypedStream version %d", stream->version);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
__objc_write_typed_stream_signature (TypedStream* stream)
|
|
|
|
{
|
|
|
|
char buffer[80];
|
|
|
|
sprintf(buffer, "GNU TypedStream %d", OBJC_TYPED_STREAM_VERSION);
|
|
|
|
stream->version = OBJC_TYPED_STREAM_VERSION;
|
|
|
|
(*stream->write)(stream->physical, buffer, strlen(buffer)+1);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __objc_finish_write_root_object(struct objc_typed_stream* stream)
|
|
|
|
{
|
|
|
|
hash_delete (stream->object_table);
|
|
|
|
stream->object_table = hash_new(64,
|
|
|
|
(hash_func_type)hash_ptr,
|
|
|
|
(compare_func_type)compare_ptrs);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __objc_finish_read_root_object(struct objc_typed_stream* stream)
|
|
|
|
{
|
|
|
|
node_ptr node;
|
|
|
|
SEL awake_sel = sel_get_any_uid ("awake");
|
|
|
|
cache_ptr free_list = hash_new (64,
|
|
|
|
(hash_func_type) hash_ptr,
|
|
|
|
(compare_func_type) compare_ptrs);
|
|
|
|
|
|
|
|
/* resolve object forward references */
|
|
|
|
for (node = hash_next (stream->object_refs, NULL); node;
|
|
|
|
node = hash_next (stream->object_refs, node))
|
|
|
|
{
|
|
|
|
struct objc_list* reflist = node->value;
|
|
|
|
const void* key = node->key;
|
|
|
|
id object = hash_value_for_key (stream->object_table, key);
|
|
|
|
while(reflist)
|
|
|
|
{
|
|
|
|
*((id*)reflist->head) = object;
|
|
|
|
if (hash_value_for_key (free_list,reflist) == NULL)
|
|
|
|
hash_add (&free_list,reflist,reflist);
|
|
|
|
|
|
|
|
reflist = reflist->tail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* apply __objc_free to all objects stored in free_list */
|
|
|
|
for (node = hash_next (free_list, NULL); node;
|
|
|
|
node = hash_next (free_list, node))
|
|
|
|
objc_free ((void *) node->key);
|
|
|
|
|
|
|
|
hash_delete (free_list);
|
|
|
|
|
|
|
|
/* empty object reference table */
|
|
|
|
hash_delete (stream->object_refs);
|
|
|
|
stream->object_refs = hash_new(8, (hash_func_type)hash_ptr,
|
|
|
|
(compare_func_type)compare_ptrs);
|
|
|
|
|
|
|
|
/* call -awake for all objects read */
|
|
|
|
if (awake_sel)
|
|
|
|
{
|
|
|
|
for (node = hash_next (stream->object_table, NULL); node;
|
|
|
|
node = hash_next (stream->object_table, node))
|
|
|
|
{
|
|
|
|
id object = node->value;
|
|
|
|
if (__objc_responds_to (object, awake_sel))
|
|
|
|
(*objc_msg_lookup(object, awake_sel))(object, awake_sel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* empty object table */
|
|
|
|
hash_delete (stream->object_table);
|
|
|
|
stream->object_table = hash_new(64,
|
|
|
|
(hash_func_type)hash_ptr,
|
|
|
|
(compare_func_type)compare_ptrs);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Open the stream PHYSICAL in MODE
|
|
|
|
*/
|
|
|
|
|
|
|
|
TypedStream*
|
|
|
|
objc_open_typed_stream (FILE* physical, int mode)
|
|
|
|
{
|
|
|
|
TypedStream* s = (TypedStream*)objc_malloc(sizeof(TypedStream));
|
|
|
|
|
|
|
|
s->mode = mode;
|
|
|
|
s->physical = physical;
|
|
|
|
s->stream_table = hash_new(64,
|
|
|
|
(hash_func_type)hash_ptr,
|
|
|
|
(compare_func_type)compare_ptrs);
|
|
|
|
s->object_table = hash_new(64,
|
|
|
|
(hash_func_type)hash_ptr,
|
|
|
|
(compare_func_type)compare_ptrs);
|
|
|
|
s->eof = (objc_typed_eof_func)__objc_feof;
|
|
|
|
s->flush = (objc_typed_flush_func)fflush;
|
|
|
|
s->writing_root_p = 0;
|
|
|
|
if (mode == OBJC_READONLY)
|
|
|
|
{
|
|
|
|
s->class_table = hash_new(8, (hash_func_type)hash_string,
|
|
|
|
(compare_func_type)compare_strings);
|
|
|
|
s->object_refs = hash_new(8, (hash_func_type)hash_ptr,
|
|
|
|
(compare_func_type)compare_ptrs);
|
|
|
|
s->read = (objc_typed_read_func)__objc_fread;
|
|
|
|
s->write = (objc_typed_write_func)__objc_no_write;
|
|
|
|
__objc_read_typed_stream_signature (s);
|
|
|
|
}
|
|
|
|
else if (mode == OBJC_WRITEONLY)
|
|
|
|
{
|
|
|
|
s->class_table = 0;
|
|
|
|
s->object_refs = 0;
|
|
|
|
s->read = (objc_typed_read_func)__objc_no_read;
|
|
|
|
s->write = (objc_typed_write_func)__objc_fwrite;
|
|
|
|
__objc_write_typed_stream_signature (s);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
objc_close_typed_stream (s);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
s->type = OBJC_FILE_STREAM;
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Open the file named by FILE_NAME in MODE
|
|
|
|
*/
|
|
|
|
|
|
|
|
TypedStream*
|
|
|
|
objc_open_typed_stream_for_file (const char* file_name, int mode)
|
|
|
|
{
|
|
|
|
FILE* file = NULL;
|
|
|
|
TypedStream* s;
|
|
|
|
|
|
|
|
if (mode == OBJC_READONLY)
|
|
|
|
file = fopen (file_name, "r");
|
|
|
|
else
|
|
|
|
file = fopen (file_name, "w");
|
|
|
|
|
|
|
|
if (file)
|
|
|
|
{
|
|
|
|
s = objc_open_typed_stream (file, mode);
|
|
|
|
if (s)
|
|
|
|
s->type |= OBJC_MANAGED_STREAM;
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
** Close STREAM freeing the structure it self. If it was opened with
|
|
|
|
** objc_open_typed_stream_for_file, the file will also be closed.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
objc_close_typed_stream (TypedStream* stream)
|
|
|
|
{
|
|
|
|
if (stream->mode == OBJC_READONLY)
|
|
|
|
{
|
|
|
|
__objc_finish_read_root_object (stream); /* Just in case... */
|
|
|
|
hash_delete (stream->class_table);
|
|
|
|
hash_delete (stream->object_refs);
|
|
|
|
}
|
|
|
|
|
|
|
|
hash_delete (stream->stream_table);
|
|
|
|
hash_delete (stream->object_table);
|
|
|
|
|
|
|
|
if (stream->type == (OBJC_MANAGED_STREAM | OBJC_FILE_STREAM))
|
|
|
|
fclose ((FILE*)stream->physical);
|
|
|
|
|
|
|
|
objc_free(stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
|
|
objc_end_of_typed_stream (TypedStream* stream)
|
|
|
|
{
|
|
|
|
return (*stream->eof)(stream->physical);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
objc_flush_typed_stream (TypedStream* stream)
|
|
|
|
{
|
|
|
|
(*stream->flush)(stream->physical);
|
|
|
|
}
|
|
|
|
|
|
|
|
long
|
|
|
|
objc_get_stream_class_version (TypedStream* stream, Class class)
|
|
|
|
{
|
|
|
|
if (stream->class_table)
|
|
|
|
return PTR2LONG(hash_value_for_key (stream->class_table, class->name));
|
|
|
|
else
|
|
|
|
return class_get_version (class);
|
|
|
|
}
|
|
|
|
|