Установите значение FourCC в C++

Я хочу установить значение FourCC в C++, то есть 4-байтовое целое число без знака.

Я полагаю, что очевидным способом является #define, например

#define FOURCC(a,b,c,d) ( (uint32) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a)) )

а потом:

uint32 id( FOURCC('b','l','a','h') );

Какой самый элегантный способ сделать это?

12 ответов

Вы можете сделать это постоянной времени компиляции, используя:

template <int a, int b, int c, int d>
struct FourCC
{
    static const unsigned int value = (((((d << 8) | c) << 8) | b) << 8) | a;
};

unsigned int id(FourCC<'a', 'b', 'c', 'd'>::value);

Приложив немного дополнительных усилий, вы можете проверить во время компиляции, что каждое переданное число находится в диапазоне от 0 до 255.

uint32_t FourCC = *((uint32_t*)"blah");

Почему не это?

РЕДАКТИРОВАТЬ: int -> uint32_t.

И нет, это не бросает символ ** в uint32_t. Он преобразует (char *) в (uint32_t *), а затем разыменовывает (uint32_t *). В этом нет никакого порядка байтов, так как он назначает uint32_t для uint32_t. Единственными недостатками являются выравнивание, и я не указал явно 32-битный тип.

С помощью C++11 constexpr вы можете написать что-то вроде:

constexpr uint32_t fourcc( char const p[5] )
{
    return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}

И затем используйте это как:

fourcc( "blah" );

плюсы:

  • Более читабельным,
  • если строковый аргумент известен во время компиляции, то функция оценивается во время компиляции (без накладных расходов во время выполнения).
  • не зависит от порядкового номера (то есть первый символ аргумента всегда будет в старшем байте fourcc).

минусы:

  • Требуется компилятор C++11 (или новее).

Или сделать то же самое с помощью встроенной функции

inline uint32_t FOURCC(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
{
     return ( (uint32) (((d)<<24) | (uint32_t(c)<<16) | (uint32_t(b)<<8) | uint32_t(a)) )
} 

и избежать головной боли макроса, но в противном случае ваш подход выглядит хорошо для меня.

Если я не ошибаюсь, вы можете просто использовать многозначные символьные константы для этого права?

unsigned int fourCC = 'blah';

Это совершенно верно для спецификации ANSI/ISO, хотя некоторые компиляторы будут немного жаловаться. Вот как типы ресурсов раньше обрабатывались в более старых API Macintosh.

Если постоянная времени компиляции не требуется, возможно, самая

unsigned int FourCCStr(const char (&tag)[5])
{
    return (((((tag[3] << 8 ) | tag[2]) << 8) | tag[1]) << 8) | tag[0];
}

#define FOURCC(tag) FourCCStr(#tag)

unsigned int id(FOURCC(blah));

Это принимает только теги из четырех символов, как требуется.

Предполагая Windows (так как FOURCC является концепцией Windows), Win API уже предоставляет mmioStringToFOURCC и mmioFOURCC.

Я не вижу ничего плохого в вашем алгоритме. Но для чего-то подобного я бы просто написал функцию вместо макроса. Макросы содержат множество скрытых функций / проблем, которые могут со временем вас укусить.

uint FourCC(char a, char b, char c, char d) { 
  return ( (uint32) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a)) );
}

Вместо #define я бы, вероятно, поместил практически такой же код и использовал бы встроенный компилятор.

В настоящее время есть лучшее решение с использованием constexpr и С++17 (может быть, раньше, не уверен). Я не уверен, что это полностью кроссплатформенная платформа, но она работает в Visual Studio и XCode.

Во-первых, вам нужна функция-оболочка для преобразования функций в значения времени компиляции:

      template <class TYPE, TYPE VALUE> constexpr TYPE CompileTimeValue() { return VALUE; }

Затем вам нужна функция constexpr для преобразования короткой строки в целое число:

      template <class UINT, UInt32 IS_LITTLE_ENDIAN> constexpr UINT MakeCCFromNullTerminatedString(const char * string)
{
    UINT cc = 0;

    UINT shift = 1;

    if (IS_LITTLE_ENDIAN)
    {
        shift = (sizeof(UINT) == 8) ? (0xFFFFFFFFFFFFFFull + 1) : 0xFFFFFF + 1;
    }

    while (UINT c = *string++)
    {
        c *= shift;

        cc |= c;

        if (IS_LITTLE_ENDIAN)
        {
            shift /= 256;
        }
        else
        {
            shift *= 256;
        }
    }

    return cc;
}

Затем оберните макросы, чтобы иметь как 4-байтовые, так и 8-байтовые символьные константы, с вариантами младшего и старшего порядка байтов (если хотите)...

      #define ID32(x) CompileTimeValue<UInt32,MakeCCFromNullTerminatedString<UInt32,0>(x)>() 
#define ID64(x) CompileTimeValue<UInt64,MakeCCFromNullTerminatedString<UInt64,0>(x)>()
#define CC32(x) CompileTimeValue<UInt32,MakeCCFromNullTerminatedString<UInt32,1>(x)>()
#define CC64(x) CompileTimeValue<UInt64,MakeCCFromNullTerminatedString<UInt64,1>(x)>()

Несколько тестов для проверки..

      ASSERT(CC32("test") == 'test');

UInt32 v = CC32("fun");

UInt32 test;

switch (v)
{
case CC32("fun"):
    test = 1;
    break;

case CC32("with"):
    test = 2;
    break;

case CC32("4ccs"):
    test = 3;
    break;
}

Проверка превышения границ не выполняется, хотя, возможно, ее можно добавить с помощью утверждений времени компиляции.

Как насчет:

#if BYTE_ORDER == BIG_ENDIAN
#define FOURCC(c0,c1,c2,c3) ((uint32) ((((uint32)((uint8)(c0)))<<24) +(((uint32)((uint8)(c1)))<<16)+ (((uint32)((uint8)(c2)))<<8) + ((((uint32)((uint8)(c3)))))) 
#else
#if BYTE_ORDER == LITTLE_ENDIAN
#define FOURCC(c3,c2,c1,c0) ((uint32) ((((uint32)((uint8)(c0)))<<24) +(((uint32)((uint8)(c1)))<<16)+ (((uint32)((uint8)(c2)))<<8) + ((((uint32)((uint8)(c3)))))) 
#else
#error BYTE_ORDER not defined
#endif
#endif
uint32 fcc(char * a)
{   
    if( strlen(a) != 4)
        return 0;       //Unknown or unspecified format

    return 
    (
            (uint32) 
            ( 
                ((*(a+3))<<24) |
                ((*(a+2))<<16) |
                ((*(a+1))<<8) | 
                (*a)
            )       
    );
}
Другие вопросы по тегам