0

我使用灵活的数组成员来定义 USB-String 描述符:

// defined
typedef struct {
    uint8_t  bLength;
    uint8_t  bDescriptorType;
    uint16_t bString[];
} ATTR_PACKED USB_StringDescriptor;

// declared
#define STR_PRODUCT  L"My Cool Product"
const USB_StringDescriptor __flash manufacturer_string = {
    sizeof(STR_PRODUCT)+2,
    USB_DTYPE_String,       // 0x03
    STR_PRODUCT
};

这很有效,并且允许在其他地方定义实际的字符串常量,以便在项目之间轻松更改。但是由于编译器的限制,我不能再使用灵活的数组成员。我想避免硬编码一个字符数组,我基本上只需要在一个宽字符数组的前面添加两个字节。是否有任何巧妙的方法来声明一个数组或一个预处理器宏来实现这一点?

4

3 回答 3

3

如果不以可移植的方式使用动态内存分配,我认为无法实现具有灵活大小的结构。

但是,以下两种具有固定大小结构的方法之一也可能适用于您:

  1. 定义结构,使得它bstring是一个固定大小的数组;如果您的程序中只有一个STR_PRODUCT,则可以使用它的大小。如果您有更多结构实例,请使用您希望允许的最大大小定义数组。
  2. bstring用指针定义结构;您仍然可以使用指向字符串文字的指针对其进行初始化。请注意,一个对象将不会包含连续内存块中的所有信息。如果您需要包含所有信息的连续记忆,则此方法 2 将不起作用。

请参阅以下演示这两种方法的程序:

#define STR_PRODUCT_1   L"My Cool Product"
#define STR_PRODUCT_2   L"Another Cool Product"
#define MAX_PRODUCT_STR  "012345678901234567890"

// Approach 1:
typedef struct {
    uint8_t  bLength;
    uint8_t  bDescriptorType;
    wchar_t  bString[sizeof(MAX_PRODUCT_STR)];
} USB_StringDescriptor;

const USB_StringDescriptor manufacturer_string = {
    sizeof(STR_PRODUCT_1)+2,
    0x03,
    STR_PRODUCT_1
};

const USB_StringDescriptor manufacturer_string2 = {
    sizeof(STR_PRODUCT_2)+2,
    0x03,
    STR_PRODUCT_2
};

// Approach 2:
typedef struct {
    uint8_t  bLength;
    uint8_t  bDescriptorType;
    wchar_t  *bString;
} USB_StringDescriptor_V2;

const USB_StringDescriptor_V2 manufacturer_string_v2 = {
    sizeof(STR_PRODUCT_1)+2,
    0x03,
    STR_PRODUCT_1
};

const USB_StringDescriptor_V2 manufacturer_string_v2_2 = {
    sizeof(STR_PRODUCT_2)+2,
    0x03,
    STR_PRODUCT_2
};

int main() {
    wprintf(L"1: %ls\n",manufacturer_string.bString);
    wprintf(L"2: %ls\n",manufacturer_string2.bString);

    wprintf(L"1 ptr: %ls\n",manufacturer_string_v2.bString);
    wprintf(L"2 ptr: %ls\n",manufacturer_string_v2_2.bString);
}
于 2017-08-08T19:57:43.277 回答
2

除了Stephen Lechner 的回答,您可以使用外部程序生成字符串描述符(和其他类似程序),输出 C 源文件以包含在实现中。仅当您有很多常量需要按摩时,这才有意义。

uint16_t请注意,您可以使用USB 字符串描述符数组,而不是结构。(您只需要知道架构上的字节顺序,因此字节长度字节也是内存中的第一个字节:它是大端架构的高字节,小端架构的低字节 -字节序架构。)

例如,考虑以下 awk 脚本constants.awk

#!/usr/bin/awk -f
BEGIN {
    # 0 = little endian, least significant byte first
    # 1 = big endian, most significant byte first
    BYTEORDER = 1

    RS = "[\t\v\f ]*(\r\n|\n\r|\r|\n)[\t\v\f ]*"
    FS = "[\t\v\f ]+"

    split("", codepoint)
    for (i = 1; i < 128; i++)
        codepoint[sprintf("%c", i)] = i
    # Note: Could add unicode code points U+00A0 to U+FFFF
    #       to codepoint[] array.

    printf "#ifndef   CONSTANTS_H\n"
    printf "#define   CONSTANTS_H\n"
    printf "\n"
    printf "/* Do not edit this file; edit constants.in instead.\n"
    printf "   This file is automatically generated by constants.awk.\n"
    printf "*/\n\n"
}

$1 == "usb_string_descriptor" && NF >= 3 {
    name = $2
    value = $0
    sub(/^[^"]*"/, "", value)   # Remove everything before first "
    sub(/"[^"]*$/, "", value)   # Remove everything after last "
    valuelen = length(value)
    type = 3

    printf "#define  %s_size  %d\n", name, 2*valuelen + 2
    printf "#define  %s_type  %d\n", name, type
    printf "#define  %s_len   %d\n", name, valuelen
    printf "static const uint16_t %s[%d] = {\n", name, valuelen + 1
    printf "    /* \"%s\" */\n", value
    if (BYTEORDER == 1)
        printf "    0x%02x%02x, /* length = %d bytes, type = 0x%02x */", 2*valuelen + 2, type, 2*valuelen + 2, type
    else
        printf "    0x%02x%02x, /* length = %d bytes, type = 0x%02x */", type, 2*valuelen + 2, 2*valuelen + 2, type
    for (i = 1; i <= valuelen; i++) {
        if ((i % 8) == 1)
            printf "\n    "
        printf "0x%04x, ", codepoint[substr(value, i, 1)]
    }
    printf "\n};\n\n"
}

END {
    printf "#endif /* CONSTANTS_H */\n"
}

假设您有一个文件,例如constants.in,描述了上述脚本应定义的一些常量:

usb_string_descriptor manufacturer_string_1 "My Cool Product"
usb_string_descriptor manufacturer_string_2 "My Other Cool Product"

让您的构建机器运行,例如awk -f constants.awk constants.in > constants.h。如果你使用 Makefiles,那么

AWK := awk

constants.h: constants.in
    $(AWK) -f constants.h constants.in > constants.h

应该做的伎俩。(它甚至会导致constant.h重新生成头文件,如果您编辑constants.in

在实际的 C 源文件实现中,您只需#include "constants.h".

以上将输出

#ifndef   CONSTANTS_H
#define   CONSTANTS_H

/* Do not edit this file; edit constants.in instead.
   This file is automatically generated by constants.awk.
*/

#define  manufacturer_string_1_size  32
#define  manufacturer_string_1_type  3
#define  manufacturer_string_1_len   15
static const uint16_t manufacturer_string_1[16] = {
    /* "My Cool Product" */
    0x2003, /* length = 32 bytes, type = 0x03 */
    0x004d, 0x0079, 0x0020, 0x0043, 0x006f, 0x006f, 0x006c, 0x0020, 
    0x0050, 0x0072, 0x006f, 0x0064, 0x0075, 0x0063, 0x0074, 
};

#define  manufacturer_string_2_size  44
#define  manufacturer_string_2_type  3
#define  manufacturer_string_2_len   21
static const uint16_t manufacturer_string_2[22] = {
    /* "My Other Cool Product" */
    0x2c03, /* length = 44 bytes, type = 0x03 */
    0x004d, 0x0079, 0x0020, 0x004f, 0x0074, 0x0068, 0x0065, 0x0072, 
    0x0020, 0x0043, 0x006f, 0x006f, 0x006c, 0x0020, 0x0050, 0x0072, 
    0x006f, 0x0064, 0x0075, 0x0063, 0x0074, 
};

#endif /* CONSTANTS_H */

(注意:我似乎记得 USB 字符串描述符不需要终止 NUL 字符(0)。我可能记错了。)

我使用了 awk,因为它适用于各种风格的所有平台——不仅是 GNU awk,还有 nawk、mawk等等。你会想要使用一种脚本语言来生成易于维护的脚本;上述版本旨在可扩展到 USB 字符串描述符以外的其他类型。

我怀疑 Python 脚本从长远来看可能更容易维护,因为它比 awk(或 Perl,另一种用于这种预处理的典型脚本语言)更受欢迎。另外,Python 具有ord()您可以使用的内置函数,并允许您在 USB 字符串描述符中使用非 ASCII Unicode 字符。

于 2017-08-08T20:46:12.410 回答
1

在我看来,如果您的编译器不支持这种类型的初始化,最好的方法是使用宏(我不是长多行宏的粉丝,但如果没有其他方法)

#define STR_PRODUCT  L"My Cool Product"

#define USB_desc(name, bL, bD, bS) struct { \
    uint8_t  bLength; \
    uint8_t  bDescriptorType;  \
    wchar_t  bString[]; \
} name = {bL, bD, bS}

USB_desc(manufacturer_string, sizeof(STR_PRODUCT) + 2, 3, STR_PRODUCT);
于 2017-08-08T20:26:07.890 回答