Search code examples
javascriptfirefox-addonjsctypes

How to get value of a UInt64, toString only way?


I have this: "ctypes.UInt64("7")"

It is returned by this:

var chars = SendMessage(hToolbar, TB_GETBUTTONTEXTW, local_tbb.idCommand, ctypes.voidptr_t(0));

so

console.log('chars=', chars, chars.toString(), uneval(chars));

gives

'chars=' 'UInt64 {  }' "7" 'ctypes.UInt64("7")'

So I can get the value by going chars.toString(), but I have to run a parseInt on that, is there anyway to read it like a property? Like chars.UInt64?


Solution

  • The problem with 64-bit integers in js-ctypes is that Javascript lacks a compatible type. All Javascript numbers are IEEE double precision floating point numbers (double), and those can represent 53-bit integers at most. So you shouldn't even be trying to parse the int yourself, unless you know for a fact that the result would fit into a double. E.g. You cannot know this for pointers.

    E.g. consider the following:

    // 6 * 8-bit = 48 bit; 48 < 53, so this is OK
    ((parseInt("0xffffffffffff", 16) + 2) == parseInt("0xffffffffffff", 16)) == false
    // However, 7 * 8-bit = 56 bit; 56 < 53, so this is not OK
    ((parseInt("0xffffffffffffff", 16) + 2) == parseInt("0xffffffffffffff", 16)) == true
    // Oops, that compared equal, because a double precision floating point
    // cannot actual hold the parseInt result, which is still well below 64-bit!
    

    Lets deal with 64-bit integers in JS properly...

    If you just want to comparisons, use UInt64.compare()/Int64.compare(), e.g.

    // number == another number
    ctypes.UInt64.compare(ctypes.UInt64("7"), ctypes.UInt64("7")) == 0
    // number != another number
    ctypes.UInt64.compare(ctypes.UInt64("7"), ctypes.UInt64("6")) != 0
    // number > another number
    ctypes.UInt64.compare(ctypes.UInt64("7"), ctypes.UInt64("6")) > 0
    // number < another number
    ctypes.UInt64.compare(ctypes.UInt64("7"), ctypes.UInt64("8")) < 0
    

    If you need the result, but are not sure it is a 32-bit unsigned integer, you can detect if you're dealing with 32 bit unsigned integers that are just packed into Uint64:

    ctypes.UInt64.compare(ctypes.UInt64("7"), ctypes.UInt64("0xffffffff")) < 0
    

    And the analog for 32-bit signed integers in Int64, but you need to compare minimum and maximum:

    ctypes.Int64.compare(ctypes.Int64("7"), ctypes.Int64("2147483647")) < 0 &&
    ctypes.Int64.compare(ctypes.Int64("7"), ctypes.Int64("-2147483648")) > 0
    

    So, once you know or detected that something will fit into a JS double, it is safe to call parseInt on it.

    var number = ...;
    if (ctypes.UInt64.compare(number, ctypes.UInt64("0xffffffff")) > 0) {
      throw Error("Whoops, unexpectedly large value that our code would not handle correctly");
    }
    chars = parseInt(chars.toString(), 10); 
    

    (For the sake of completeness, there is also UInt64.hi()/Int64.hi() and UInt64.lo()/Int64.lo() to get the high and low 32-bits for real 64-bit integers and do 64-bit integer math yourself (e.g.), but beware of endianess).

    PS: The return value of SendMessage is intptr_t not uintptr_t, which is important here because SendMessage(hwnd, TB_GETBUTTONTEXT, ...) may return -1 on failure!

    So putting all this together (untested):

    var SendMessage = user32.declare(
        'SendMessageW',
        ctypes.winapi_abi,
        ctypes.intptr_t,
        ctypes.voidptr_t, // HWND
        ctypes.uint32_t, // MSG
        ctypes.uintptr_t, // WPARAM
        ctypes.intptr_t // LPARAM
    );
    // ...
    
    var chars = SendMessage(hToolbar, TB_GETBUTTONTEXTW, local_tbb.idCommand, ctypes.voidptr_t(0));
    if (ctypes.Int64.compare(chars, ctypes.Int64("0")) < 0) {
      throw new Error("TB_GETBUTTONTEXT returned a failure (negative value)");
    }
    if (ctypes.Int64.comare(chars, ctypes.Int64("32768")) > 0) {
      throw new Error("TB_GETBUTTONTEXT returned unreasonably large number > 32KiB");
    }
    chars = parseInt(chars.toString());