Version 9 of Tcl_NewByteArrayObj

Updated 2008-10-31 18:43:17 by Duoas

Tcl_Obj * Tcl_NewByteArrayObj(CONST unsigned char *bytes, int length)

Tcl_Obj * Tcl_NewByteArrayObj(bytes, length)

Tcl_NewByteArrayObj will create a new object of byte-array type. Both of these procedures set the object's type to be byte-array and set the object's internal representation to a copy of the array of bytes given by bytes. Tcl_NewByteArrayObj returns a pointer to a newly allocated object with a reference count of zero.

http://www.tcl.tk/man/tcl8.5/TclLib/ByteArrObj.htm


HaO: To populate a byte array by a C extension and return it as the result object, the following code might be used:

Tcl_Obj *pObj = Tcl_NewObj();
unsigned char *pChar = Tcl_SetByteArrayLength(pObj, 3);

// Dummy population functionality
pChar[0] = '\1'; pChar[1] = '\xff'; pChar[2] = '\x80';
Tcl_InvalidateStringRep(pObj);
Tcl_SetObjResult(interp,pObj); 

Remarks:

  • Alexandre Ferrieux: Tcl_NewObj() may be replaced by Tcl_NewByteArrayObj( "", 1 ).
  • DGP: If the object is not newly created (for example a parameter object is modified, one should check the object to be shared:
if ( Tcl_IsShared(pObj) )
    pObj = Tcl_DuplicateObj(pObj);

DKF: I don't recommend using a length of 0 to Tcl_NewByteArrayObj; the behaviour of that depends on whether malloc(0) keels over (which is system dependent).

Duoas That seems me fairly obnoxious. Doesn't the Tcl_NewByteArrayObj() code know that it oughtn't bother to try allocating zero bytes, and just initialize an empty/nil internal representation to begin with? [edit2] For that matter, would it be reasonable to specify NULL for source and a non-zero length for an uninitialized block of bytes?

DKF: I merely report the current state of affairs.

Duoas :-D Perhaps I'll get the latest CVS and fix that sometime in the next couple of days.

Duoas 2008-10-31 Well, I've looked at the source (~/tcl/generic/tclBinary.c) and this is what I've found.

The documentation plainly states that 'length' must be greater than or equal to zero. Assuming no one ever passes a negative number, none of the functions will break. Zero is fine --malloc() will not get a zero size_t for a zero-length byte array, so it is malloc()-safe (remembering again not to give it negative values).

Frankly, I think I would insert a line above 327 that forces 'length' to be >= 0.

As for a NULL 'bytes' argument, if 'length' is zero, there is no problem. However, if 'length' is non-zero, then you will almost surely cause a segfault/access violation. Again, I would insert a little line above 330:331 just for that:

void
Tcl_SetByteArrayObj(
    Tcl_Obj *objPtr,		/* Object to initialize as a ByteArray. */
    const unsigned char *bytes,	/* The array of bytes to use as the new
				 * value. */
    int length)			/* Length of the array of bytes, which must be
				 * >= 0. */
{
    ByteArray *byteArrayPtr;

    if (Tcl_IsShared(objPtr)) {
	Tcl_Panic("%s called with shared object", "Tcl_SetByteArrayObj");
    }
    TclFreeIntRep(objPtr);
    Tcl_InvalidateStringRep(objPtr);

    length = (length < 0) ? 0 : length;
    byteArrayPtr = (ByteArray *) ckalloc(BYTEARRAY_SIZE(length));
    byteArrayPtr->used = length;
    byteArrayPtr->allocated = length;
    if (bytes && length) {
        memcpy(byteArrayPtr->bytes, bytes, (size_t) length);
    }

    objPtr->typePtr = &tclByteArrayType;
    SET_BYTEARRAY(objPtr, byteArrayPtr);
}

These two simple changes make the routine idiot-proof --er-- error-proof, and improve the functionality to allow creating a new ByteArray object with an uninitialized 'length' of bytes:

  Tcl_Obj *myByteArray = Tcl_NewByteArray( NULL, 1024 );
  unsigned char *my1024 = Tcl_GetByteArrayFromObj( myByteArray, NULL );

Finally, I would have Tcl_GetByteArrayFromObj() return NULL if the ByteArray is zero-length.

unsigned char *
Tcl_GetByteArrayFromObj(
    Tcl_Obj *objPtr,		/* The ByteArray object. */
    int *lengthPtr)		/* If non-NULL, filled with length of the
				 * array of bytes in the ByteArray object. */
{
    ByteArray *baPtr;

    if (objPtr->typePtr != &tclByteArrayType) {
	SetByteArrayFromAny(NULL, objPtr);
    }
    baPtr = GET_BYTEARRAY(objPtr);

    if (lengthPtr != NULL) {
	*lengthPtr = baPtr->used;
    }
    if (!baPtr->bytes) {
        return NULL;
    }
    return (unsigned char *) baPtr->bytes;
}

But this is not essential... assuming the user is smart enough to actually verify that there is data before reading/writing it.

If you wan't I'll actually submit a patch, but as this only requires adding six lines...