ActionScriptで16進ダンプ表示を書いた(重いコードを軽くするByteArray)


Adobe AIRで使えるWinPcapのNative Extensionを作ったのですが、受信したデータを16進ダンプしたいと思い、関数を書いてみました。

(これは悪い例です)

    public static function hexdump(byteAry:ByteArray):String
    {
        if (byteAry == null)
        {
            return "<null>";
        }

        var newlineStr:String = "\r\n";
        var bytesPerLine:int = 16;
        var bytesLength:int = byteAry.length;
        var firstHexColumn:int =
              8                   // 8 characters for the address
            + 3;                  // 3 spaces
        var firstCharColumn:int = firstHexColumn
            + bytesPerLine * 3       // - 2 digit for the hexadecimal value and 1 space
            + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th
            + 2;                  // 2 spaces 
        var lineLength:int = firstCharColumn
            + bytesPerLine           // - characters to show the ascii value
            + 2; // Carriage return and line feed (should normally be 2)
        var expectedLines:int = (bytesLength + bytesPerLine - 1) / bytesPerLine;
        var retStr:String = "";
        var i:int;
        var j:int;
        var k:int;
        var line:String = "";
        var hexColumn:int = 0;
        var charColumn:int = 0;
        var byte:int = 0;

        for (i = 0; i < bytesLength; i += bytesPerLine)
        {
            line = "";
            line = hexChars((i >> 28) & 0xF);
            line += hexChars((i >> 24) & 0xF);
            line += hexChars((i >> 20) & 0xF);
            line += hexChars((i >> 16) & 0xF);
            line += hexChars((i >> 12) & 0xF);
            line += hexChars((i >> 8) & 0xF);
            line += hexChars((i >> 4) & 0xF);
            line += hexChars((i >> 0) & 0xF);

            hexColumn = firstHexColumn;
            charColumn = firstCharColumn;

            for (j = 0; j < bytesPerLine; j++)
            {
                if (j > 0 && (j & 7) == 0)
                {
                    hexColumn++;
                }
                if (i + j >= bytesLength)
                {
                    for (k = line.length; k < hexColumn; k++)
                    {
                        line += " ";
                    }
                    line += " ";
                    line += " ";
                }
                else
                {
                    byte = byteAry[i + j];
                    for (k = line.length; k < hexColumn; k++)
                    {
                        line += " ";
                    }
                    line += hexChars((byte >> 4) & 0xF);
                    line += hexChars(byte & 0xF);
                }
                hexColumn += 3;
            }
            for (j = 0; j < bytesPerLine; j++)
            {
                if (i + j >= bytesLength)
                {
                    for (k = line.length; k < charColumn; k++)
                    {
                        line += " ";
                    }
                    line += " ";
                }
                else
                {
                    byte = byteAry[i + j];
                    for (k = line.length; k < charColumn; k++)
                    {
                        line += " ";
                    }
                    line += (byte < 32 ? '·' : String.fromCharCode(byte));
                }
                charColumn++;
            }
            line += newlineStr;
            retStr += line;
        }
        return retStr;
    }

    private static function hexChars(pos:int):String
    {
        var hexChars:String = "0123456789ABCDEF";
        return hexChars.charAt(pos);
    }

これだとパケット到達の頻度に全然追いつかない。多分Stringの生成コストが高いんだろうと思い、ActionScriptで処理するのをやめ、Native ExtensionでCで書くことにしました。

(ActionScript Native Extension)

#define BYTEARRAYDUMP_FRE_FUNC(func) \
FREObject func(\
    FREContext ctx,\
    void *funcData,\
    uint32_t argc,\
    FREObject arg[]\
)
BYTEARRAYDUMP_FRE_FUNC(hexDump)
{
    FREResult res;
    FREObject freByteAryObj = NULL;
    int bytesLength = 0;
    FREByteArray freByteAry;
    uint8_t *bytes = NULL;
    const uint8_t *resNull = (const uint8_t *)"<null>";
    const uint8_t *resEmpty = (const uint8_t *)"";
    FREObject freResString;

    if (argc != 1)
    {
        return NULL;
    }
    freByteAryObj = arg[0];

    if (freByteAryObj == NULL)
    {
        res = FRENewObjectFromUTF8(strlen((const char *)resNull), resNull, &freResString);
        if (res != FRE_OK)
        {
            return NULL;
        }
        return freResString;
    }
    res = FREAcquireByteArray(freByteAryObj, &freByteAry);
    if (res != FRE_OK)
    {
        res = FREReleaseByteArray(freByteAryObj);
        if (res != FRE_OK)
        {
            return NULL;
        }
        return NULL;
    }
    bytesLength = freByteAry.length;
    bytes = freByteAry.bytes;
    if (bytesLength == 0)
    {
        res = FRENewObjectFromUTF8(strlen((const char *)resEmpty), resEmpty, &freResString);
        if (res != FRE_OK)
        {
            return NULL;
        }
        return freResString;
    }

    //printf("start dumping bytesLength = %d\r\n", bytesLength);

    char hexChars[17] = "0123456789ABCDEF";
    int bytesPerLine = 16;
    int firstHexColumn =
        8                   // 8 characters for the address
        + 3;                  // 3 spaces

    int firstCharColumn = firstHexColumn
        + bytesPerLine * 3       // - 2 digit for the hexadecimal value and 1 space
        + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th
        + 2;                  // 2 spaces 

    int lineLength = firstCharColumn
        + bytesPerLine           // - characters to show the ascii value
        + 2; // Carriage return and line feed (should normally be 2)

    char *line = new char[lineLength + 1]; // last +1 is for 0
    int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine;

    char *resStr = new char[expectedLines * lineLength + 1]; // last +1 is for 0
    char *curPtr = resStr;

    for (int i = 0; i < (lineLength - 2); i++)
    {
        line[i] = ' ';
    }
    line[lineLength - 2] = '\r';
    line[lineLength - 1] = '\n';
    line[lineLength] = 0;

    for (int i = 0; i < bytesLength; i += bytesPerLine)
    {
        line[0] = hexChars[(i >> 28) & 0xF];
        line[1] = hexChars[(i >> 24) & 0xF];
        line[2] = hexChars[(i >> 20) & 0xF];
        line[3] = hexChars[(i >> 16) & 0xF];
        line[4] = hexChars[(i >> 12) & 0xF];
        line[5] = hexChars[(i >> 8) & 0xF];
        line[6] = hexChars[(i >> 4) & 0xF];
        line[7] = hexChars[(i >> 0) & 0xF];

        int hexColumn = firstHexColumn;
        int charColumn = firstCharColumn;

        for (int j = 0; j < bytesPerLine; j++)
        {
            if (j > 0 && (j & 7) == 0) hexColumn++;
            if (i + j >= bytesLength)
            {
                line[hexColumn] = ' ';
                line[hexColumn + 1] = ' ';
                line[charColumn] = ' ';
            }
            else
            {
                uint8_t b = bytes[i + j];
                line[hexColumn] = hexChars[(b >> 4) & 0xF];
                line[hexColumn + 1] = hexChars[b & 0xF];
                line[charColumn] = (b < 32 ? '·' : (char)b);
            }
            hexColumn += 3;
            charColumn++;
        }
        // NOTE: size is lineLength instead of (lineLength + 1) because we don't need 0 termination char.
        memcpy(curPtr, line, lineLength);
        curPtr = (char *)curPtr + lineLength;
        *curPtr = 0;
    }
    //printf("end dumping\r\n");

    res = FREReleaseByteArray(freByteAryObj);
    if (res != FRE_OK)
    {
        delete[] line;
        delete[] resStr;
        return NULL;
    }
    res = FRENewObjectFromUTF8(strlen(resStr), (const uint8_t *)resStr, &freResString);
    if (res != FRE_OK)
    {
        delete[] line;
        delete[] resStr;
        return NULL;
    }
    delete[] line;
    delete[] resStr;
    //printf("OK\r\n");

    return freResString;
}

stringのような動的メモリ確保せず、char *line, char *resStrのメモリを最初に確保してそれを使いまわすようにしています。
これだと処理が間に合うようになりました。
では、ActionScriptで同じ様なことができないだろうか?
ByteArrayが使えそうだと思い実装してみました。

(最終形)

    public static function hexDump(byteAry:ByteArray):String
    {
        if (byteAry == null)
        {
            return "<null>";
        }

        var hexChars:String = "0123456789ABCDEF";
        var bytesPerLine:int = 16;
        var bytesLength:int = byteAry.length;
        var firstHexColumn:int =
              8                   // 8 characters for the address
            + 3;                  // 3 spaces
        var firstCharColumn:int = firstHexColumn
            + bytesPerLine * 3       // - 2 digit for the hexadecimal value and 1 space
            + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th
            + 2;                  // 2 spaces 
        var lineLength:int = firstCharColumn
            + bytesPerLine           // - characters to show the ascii value
            + 2; // Carriage return and line feed (should normally be 2)
        //var expectedLines:int = (bytesLength + bytesPerLine - 1) / bytesPerLine;
        var retStr:String = "";
        var i:int;
        var j:int;
        var line:ByteArray = new ByteArray();
        var hexColumn:int = 0;
        var charColumn:int = 0;
        var byte:int = 0;

        for (i = 0; i < lineLength - 2; i++)
        {
            line[i] = " ".charCodeAt(0);
        }
        //line[lineLength - 2] = "\r".charCodeAt(0);
        //line[lineLength - 1] = "\n".charCodeAt(0);
        //line[lineLength] = 0;

        for (i = 0; i < bytesLength; i += bytesPerLine)
        {
            line[0] = hexChars.charCodeAt((i >> 28) & 0xF);
            line[1] = hexChars.charCodeAt((i >> 24) & 0xF);
            line[2] = hexChars.charCodeAt((i >> 20) & 0xF);
            line[3] = hexChars.charCodeAt((i >> 16) & 0xF);
            line[4] = hexChars.charCodeAt((i >> 12) & 0xF);
            line[5] = hexChars.charCodeAt((i >> 8) & 0xF);
            line[6] = hexChars.charCodeAt((i >> 4) & 0xF);
            line[7] = hexChars.charCodeAt((i >> 0) & 0xF);

            hexColumn = firstHexColumn;
            charColumn = firstCharColumn;

            for (j = 0; j < bytesPerLine; j++)
            {
                if (j > 0 && (j & 7) == 0)
                {
                    hexColumn++;
                }
                if (i + j >= bytesLength)
                {
                    line[hexColumn] = " ".charCodeAt(0);
                    line[hexColumn + 1] = " ".charCodeAt(0);
                    line[charColumn] = " ".charCodeAt(0);
                }
                else
                {
                    byte = byteAry[i + j];
                    line[hexColumn] = hexChars.charCodeAt((byte >> 4) & 0xF);
                    line[hexColumn + 1] = hexChars.charCodeAt(byte & 0xF);
                    line[charColumn] = (byte < 32 ? '·' : byte);
                }
                hexColumn += 3;
                charColumn++;
            }
            line.position = 0;
            retStr += line.readUTFBytes(lineLength - 2) + "\r\n";
        }
        return retStr;
    }

本当はretStrもByteArrayにした方が速いでしょうが、retStrへの文字列追加はそんなに回数が多くないのでこれでもパケット到達に対して処理が遅れていくことはないようです。

元記事: https://ameblo.jp/ryujimiya/entry-12369881822.html