Forward Declarations

本文探讨了在C++中对于标准库类型如ostream和string进行前向声明时遇到的问题,并解释了为什么这种做法会导致错误。此外,文章还讨论了如何正确地为这些类型做前向声明的方法。

Forward declarations are a great way to eliminate needless compile-time dependencies. But here's an example of a forward-declaration snare... how would you avoid it?

Problem

JG Question

1. Forward declarations are very useful tools. In this case, they don't work as the programmer expected. Why are the marked lines errors?

    // file f.h
    #ifndef XXX_F_H_
    #define XXX_F_H_

    class ostream;  // error
    class string;   // error

    string f( const ostream& );

    #endif
Guru Question

2. Without including any other files, can you write the correct forward declarations for ostream and string above?

Solution

1. Forward declarations are very useful tools. In this case, they don't work as the programmer expected. Why are the marked lines errors?

    // file f.h
    #ifndef XXX_F_H_
    #define XXX_F_H_

    class ostream;  // error
    class string;   // error

    string f( const ostream& );

    #endif

Alas, you cannot forward-declare ostream and string this way because they are not classes... both are typedefs of templates.

(True, you used to be able to forward-declare ostream and string this way, but that was many years ago and is no longer possible in Standard C++.)

2. Without including any other files, can you write the correct forward declarations for ostream and string above?

Unfortunately, the answer is that there is no standard and portable way to do this. The standard says:

It is undefined for a C++ program to add declarations or definitions to namespace std or namespaces within namespace std unless otherwise specified.

Among other things, this allows vendors to provide implementations of the standard library that have more template parameters for library templates than the standard requires (suitably defaulted, of course, to remain compatible).

The best you can do (which is not a solution to the problem "without including any other files") is this:

    #include <iosfwd>
    #include <string>

The iosfwd header does contain bona fide forward declarations. The string header does not. This is all that you can do and still be portable. Fortunately, forward-declaring string and ostream isn't too much of an issue in practice since they're generally small and widely-used. The same is true for most standard headers. However, beware the pitfalls, and don't be tempted to start forward-declaring templates -- or anything else -- that belongs to namespace std... that's reserved for the compiler and library writers, and them alone.

 
/* Full ESP32 program - extended from user's base. Features: - WS2812B LED strip (12 leds) - TTP229 16-key reading - Buzzer (LEDC) for tone output - SPI OLED (128x64) driver (no ssd1306) - Mode keys: physical TP4..TP7 as function keys - Mode4: LED-key interactive (latched while pressed, fade on release) - Mode5: LED gradient rotating - Mode6: Play blessing tune loop with LED feedback - Mode7: mini-game (7 regions) on OLED */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "driver/gpio.h" #include "driver/ledc.h" #include "driver/spi_master.h" #include "esp_log.h" #include "esp_rom_sys.h" #include "led_strip.h" #include <math.h> #include "esp_random.h" #define TAG "TTP229_16KEY_EXT" /// Hardware pins (adjust to your wiring) #define WS2812B_PIN 2 #define NUM_LEDS 12 #define TTP229_SCL 27 #define TTP229_SDO 26 #define BUZZER_PIN 25 // OLED SPI pins (example, change if different) #define OLED_SPI_HOST HSPI_HOST #define OLED_MISO_PIN -1 #define OLED_MOSI_PIN 17 #define OLED_SCLK_PIN 4 #define OLED_DC_PIN 18 #define OLED_CS_PIN 19 #define OLED_RST_PIN 5 #define OLED_WIDTH 128 #define OLED_HEIGHT 64 #define OLED_BUF_SIZE ((OLED_WIDTH * OLED_HEIGHT) / 8) #define M_PI 3.14159265358979323846 #pragma GCC diagnostic ignored "-Wunused-const-variable" static const char *TAG2 = "OLED"; led_strip_handle_t led_strip; /// Rainbow preset for LED colors (RGB) const uint8_t rainbow_colors[NUM_LEDS][3] = { {255,0,48}, {255,89,0}, {255,183,0}, {255,233,0}, {136,255,0}, {0,255,136}, {0,204,255}, {0,64,255}, {89,0,255}, {183,0,255}, {255,0,191}, {255,0,98} }; // 16-key note frequencies (C4~F5) static const int note_freqs[16] = { 262,294,330,349,370,392,415,440,466,494,523,554,587,622,659,698 }; // mapping logical index i -> TP number (as in your original) static const uint8_t key_map[16] = {3,2,1,0,15,14,13,12,8,9,10,11,4,5,6,7}; // forward declarations static void ws2812b_init(void); static void ttp229_init(void); static uint16_t ttp229_read_keys(void); static void buzzer_init(void); static void buzzer_play_tone(int freq); static void oled_init(void); static void oled_refresh(void); static void oled_clear(void); static void oled_draw_bitmap(int x, int y, int w, int h, const uint8_t *bm); static void oled_draw_rect(int x,int y,int w,int h); static void oled_fill_rect(int x,int y,int w,int h); static void oled_draw_text_centered(const char *txt); static void oled_draw_music_bars(uint16_t keys); static void start_blessing_task(void); static void stop_blessing_task(void); static void blessing_led_feedback(int step); // Global state typedef enum { MODE_NORMAL = 0, MODE_LED_KEY_INTERACT, // TP4 MODE_LED_GRADIENT_CYCLE, // TP5 MODE_BLESSING_PLAY, // TP6 MODE_GAME // TP7 } app_mode_t; static volatile app_mode_t g_mode = MODE_NORMAL; // Variables for LED-key interactivity static uint8_t led_brightness[NUM_LEDS]; // 0-255 brightness static uint8_t led_active[NUM_LEDS]; // 1=latched on, 0=idle static SemaphoreHandle_t led_mutex = NULL; // Gradient cycle control static volatile bool gradient_run = false; static int gradient_pos = 0; // Blessing task handle static TaskHandle_t blessing_task_handle = NULL; static volatile bool blessing_running = false; // Game state static bool game_running = false; static uint8_t game_regions[7]; // 1=block present, 0=empty static TickType_t game_next_spawn; // OLED framebuffer static uint8_t oled_buf[OLED_BUF_SIZE]; static spi_device_handle_t spi_oled; // Simple bitmaps (8x8 or larger) for emoticons/music glyphs // Emoticon 1: (^_^) - 16x8 bitmap (example) static const uint8_t bm_face1[] = { // 16x8 (each byte is 8 vertical pixels column-major if we render simply) 0x00,0x08,0x04,0x02,0x04,0x08,0x40,0x40, 0x40,0x40,0x08,0x04,0x02,0x04,0x08,0x00 }; // Emoticon 2: (o_o) static const uint8_t bm_face2[] = { 0x00,0x0E,0x0A,0x0A,0x0E,0x40,0x20,0x10, 0x20,0x40,0x0E,0x0A,0x0A,0x0E,0x00,0x00 }; // Music symbol (simple 12x12) static const uint8_t bm_music[] = { 0x10,0x10,0x10,0x18,0x18,0x1C,0x1C,0x0E,0x06,0x06,0x00,0x00, 0x1F,0x1F,0x0F,0x0F,0x07,0x07,0x03,0x03,0x01,0x01,0x00,0x00 }; // Small filled block 12x12 for game tile static const uint8_t bm_block12[12*2] = { // we'll draw block by filling rectangle, not use bitmap }; // Blessing tune array (freq, duration_ms) - shortened example uses your full data typedef struct { int freq; int dur; } tone_t; static const tone_t blessing[] = { // I will include the start of your provided data; you can extend as necessary. {494,600},{600,600},{440,600},{440,600},{392,600},{392,600},{370,600},{392,600},{392,600},{494,600},{440,600}, // (NOTE: in your provided sheet you had many entries — paste the entire array here in your final code) // For brevity in this example, repeat a few to demonstrate: {440,600},{392,600},{392,600},{370,600},{392,600},{392,600}, {0,600},{0,600},{0,600} // zeros mean rest }; static const int blessing_len = sizeof(blessing)/sizeof(blessing[0]); /*** --- Implementation --- ***/ static void ws2812b_init(void){ led_strip_config_t strip_config = { .strip_gpio_num = WS2812B_PIN, .max_leds = NUM_LEDS, }; led_strip_rmt_config_t rmt_config = { .resolution_hz = 10 * 1000 * 1000, }; ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config,&rmt_config,&led_strip)); // Initialize to rainbow for (int i = 0 ; i < NUM_LEDS ; i++){ led_strip_set_pixel(led_strip, i, rainbow_colors[i][0], rainbow_colors[i][1], rainbow_colors[i][2]); } led_strip_refresh(led_strip); if (!led_mutex) led_mutex = xSemaphoreCreateMutex(); // initialize brightness/active arrays for (int i=0;i<NUM_LEDS;i++){ led_brightness[i]=255; led_active[i]=0; } } static void ttp229_init(void) { gpio_config_t io_conf = {0}; io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pin_bit_mask = 1ULL << TTP229_SCL; gpio_config(&io_conf); io_conf.mode = GPIO_MODE_INPUT; io_conf.pin_bit_mask = 1ULL << TTP229_SDO; gpio_config(&io_conf); gpio_set_level(TTP229_SCL, 1); vTaskDelay(pdMS_TO_TICKS(10)); } static uint16_t ttp229_read_keys(void) { uint16_t data = 0; gpio_set_level(TTP229_SCL, 1); esp_rom_delay_us(2); for (int i = 0; i < 16; i++) { gpio_set_level(TTP229_SCL, 0); esp_rom_delay_us(2); int bit = gpio_get_level(TTP229_SDO); if (bit == 0) data |= (1 << i); gpio_set_level(TTP229_SCL, 1); esp_rom_delay_us(2); } return data; } static void buzzer_init(void) { ledc_timer_config_t timer_conf = { .duty_resolution = LEDC_TIMER_10_BIT, .freq_hz = 1000, .speed_mode = LEDC_LOW_SPEED_MODE, .timer_num = LEDC_TIMER_0 }; ledc_timer_config(&timer_conf); ledc_channel_config_t channel_conf = { .channel = LEDC_CHANNEL_0, .duty = 0, .gpio_num = BUZZER_PIN, .speed_mode = LEDC_LOW_SPEED_MODE, .timer_sel = LEDC_TIMER_0 }; ledc_channel_config(&channel_conf); } static void buzzer_play_tone(int freq) { if (freq <= 0) { ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0); return; } ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, freq); ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 512); ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); } /*** Simple SPI OLED (128x64) - basic frame buffer + commands ***/ static void oled_send_cmd(const uint8_t *data, size_t len) { spi_transaction_t t = { .length = len * 8, .tx_buffer = data, .flags = 0 }; // DC low for command gpio_set_level(OLED_DC_PIN, 0); ESP_ERROR_CHECK(spi_device_polling_transmit(spi_oled, &t)); } static void oled_send_data(const uint8_t *data, size_t len) { spi_transaction_t t = { .length = len * 8, .tx_buffer = data, .flags = 0 }; gpio_set_level(OLED_DC_PIN, 1); ESP_ERROR_CHECK(spi_device_polling_transmit(spi_oled, &t)); } static void oled_init(void) { gpio_config_t io_conf = {0}; // DC, CS, RST as outputs io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pin_bit_mask = (1ULL<<OLED_DC_PIN) | (1ULL<<OLED_CS_PIN) | (1ULL<<OLED_RST_PIN); gpio_config(&io_conf); // SPI bus init spi_bus_config_t buscfg = { .miso_io_num = OLED_MISO_PIN, .mosi_io_num = OLED_MOSI_PIN, .sclk_io_num = OLED_SCLK_PIN, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = OLED_BUF_SIZE + 8 }; ESP_ERROR_CHECK(spi_bus_initialize(OLED_SPI_HOST, &buscfg, 1)); spi_device_interface_config_t devcfg = { .clock_speed_hz = 8*1000*1000, .mode = 0, .spics_io_num = OLED_CS_PIN, .queue_size = 1, }; ESP_ERROR_CHECK(spi_bus_add_device(OLED_SPI_HOST, &devcfg, &spi_oled)); // Reset sequence gpio_set_level(OLED_RST_PIN, 0); vTaskDelay(pdMS_TO_TICKS(10)); gpio_set_level(OLED_RST_PIN, 1); vTaskDelay(pdMS_TO_TICKS(10)); // Basic init sequence (compatible with many 128x64 controllers) const uint8_t init_cmds[] = { 0xAE, // display off 0xD5, 0x80, // set display clock 0xA8, 0x3F, // multiplex 1/64 0xD3, 0x00, // display offset 0x40, // start line 0x8D, 0x14, // charge pump on 0x20, 0x00, // memory addressing mode horizontal 0xA1, // segment remap 0xC8, // COM scan dec 0xDA, 0x12, // COM pins 0x81, 0xCF, // contrast 0xD9, 0xF1, // precharge 0xDB, 0x40, // vcom detect 0xA4, // resume RAM 0xA6, // normal display 0xAF // display on }; oled_send_cmd(init_cmds, sizeof(init_cmds)); memset(oled_buf, 0, OLED_BUF_SIZE); oled_refresh(); ESP_LOGI(TAG2,"OLED initialized"); } static void oled_refresh(void) { // set column/page addresses const uint8_t setcol[] = {0x21, 0, OLED_WIDTH-1}; const uint8_t setpage[] = {0x22, 0, (OLED_HEIGHT/8)-1}; oled_send_cmd(setcol, sizeof(setcol)); oled_send_cmd(setpage, sizeof(setpage)); oled_send_data(oled_buf, OLED_BUF_SIZE); } static void oled_clear(void) { memset(oled_buf, 0, OLED_BUF_SIZE); oled_refresh(); } // draw rectangle border (byte addressing vertical pages) static void oled_draw_rect(int x,int y,int w,int h) { // naive draw pixel onto buffer for (int yy=y; yy<y+h; yy++) { for (int xx=x; xx<x+w; xx++) { if (xx<0 || xx>=OLED_WIDTH || yy<0 || yy>=OLED_HEIGHT) continue; int page = yy / 8; int idx = page * OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy & 7)); } } } // fill rect static void oled_fill_rect(int x,int y,int w,int h) { oled_draw_rect(x,y,w,h); } static void oled_draw_bitmap(int x, int y, int w, int h, const uint8_t *bm) { // simple bitmap: each byte is a column of 8 vertical pixels (LSB top) int bytes_per_col = (h + 7)/8; for (int col=0; col<w; col++) { for (int b=0;b<bytes_per_col;b++) { int src_idx = col*bytes_per_col + b; uint8_t v = bm[src_idx]; for (int bit=0;bit<8;bit++) { int yy = y + b*8 + bit; int xx = x + col; if (yy>=y+h) break; if (xx<0 || xx>=OLED_WIDTH || yy<0 || yy>=OLED_HEIGHT) continue; int page = yy/8; int dst_idx = page*OLED_WIDTH + xx; if (v & (1<<bit)) oled_buf[dst_idx] |= (1<<(yy&7)); } } } } // center simple string using very crude 6x8 font hard-coded for limited characters static const uint8_t font6x8_A[] = { // only include bytes for a few characters used in emoticons and small labels: // We'll support: space, '(', ')', '^', '_', 'o', '-', '<', '3', '>', '!', 'M', 'u', 's', 'i', 'c' // For brevity we won't include a complete font; we'll implement text using small bitmaps manually. }; static void oled_draw_text_centered(const char *txt) { // crude: show at middle as ascii using available characters; we'll just draw as pixel blocks for each char int len = strlen(txt); int char_w = 6; int total_w = len * (char_w+1); int start_x = (OLED_WIDTH - total_w)/2; int y = OLED_HEIGHT/2 - 4; // Simple: draw each char as filled rectangle representing presence (not real glyphs) for demo for (int i=0;i<len;i++) { int x = start_x + i*(char_w+1); if (txt[i] == ' ') continue; // draw small shape to represent char oled_fill_rect(x,y,4,6); } } /*** Mode helpers ***/ static void set_led_rgb(int idx, uint8_t r, uint8_t g, uint8_t b, uint8_t brightness) { // apply brightness scaling 0-255 uint32_t scale = brightness; uint8_t r2 = (r * scale)/255; uint8_t g2 = (g * scale)/255; uint8_t b2 = (b * scale)/255; led_strip_set_pixel(led_strip, idx, r2, g2, b2); } static void led_update_from_state(void) { xSemaphoreTake(led_mutex, portMAX_DELAY); for (int i=0;i<NUM_LEDS;i++) { uint8_t b = led_brightness[i]; if (b==0) { led_strip_set_pixel(led_strip,i,0,0,0); } else { set_led_rgb(i, rainbow_colors[i][0], rainbow_colors[i][1], rainbow_colors[i][2], b); } } led_strip_refresh(led_strip); xSemaphoreGive(led_mutex); } // background LED fade task static void led_fade_task(void *arg) { while (1) { bool changed = false; xSemaphoreTake(led_mutex, portMAX_DELAY); if (g_mode == MODE_LED_KEY_INTERACT) { for (int i=0;i<NUM_LEDS;i++) { if (!led_active[i] && led_brightness[i] > 0) { // fade down slowly uint8_t old = led_brightness[i]; int nb = old - 5; if (nb < 0) nb = 0; led_brightness[i] = nb; changed = true; } } } else if (g_mode == MODE_LED_GRADIENT_CYCLE && gradient_run) { // gradient animation: rotate brightness pattern gradient_pos++; for (int i=0;i<NUM_LEDS;i++) { int pos = (i + gradient_pos) % NUM_LEDS; // wave brightness int b = 80 + (int)(175.0 * (1.0 + sinf((pos*2.0*M_PI)/NUM_LEDS))/2.0); led_brightness[i] = (uint8_t)b; } changed = true; } else { // In normal mode ensure all leds full for (int i=0;i<NUM_LEDS;i++) { if (led_brightness[i] != 255) { led_brightness[i]=255; changed = true; } } } xSemaphoreGive(led_mutex); if (changed) led_update_from_state(); vTaskDelay(pdMS_TO_TICKS(40)); } } /*** Blessing player task (plays in loop while blessing_running) ***/ static void blessing_task(void *arg) { ESP_LOGI(TAG,"Blessing task started"); while (blessing_running) { for (int i=0;i<blessing_len && blessing_running;i++){ int f = blessing[i].freq; int d = blessing[i].dur; if (f>0) { buzzer_play_tone(f); // LED feedback: light one LED corresponding to index (wrap) blessing_led_feedback(i); } else { buzzer_play_tone(0); } int step = d/10; for (int t=0;t<d && blessing_running;t+=10) vTaskDelay(pdMS_TO_TICKS(10)); // small gap buzzer_play_tone(0); vTaskDelay(pdMS_TO_TICKS(30)); } // loop } buzzer_play_tone(0); ESP_LOGI(TAG,"Blessing task ending"); vTaskDelete(NULL); } static void start_blessing_task(void) { if (blessing_running) return; blessing_running = true; xTaskCreatePinnedToCore(blessing_task, "blessing", 4*1024, NULL, 5, &blessing_task_handle, tskNO_AFFINITY); } static void stop_blessing_task(void) { if (!blessing_running) return; blessing_running = false; // task will stop itself blessing_task_handle = NULL; } static void blessing_led_feedback(int step) { xSemaphoreTake(led_mutex, portMAX_DELAY); // simple: light LED at step % NUM_LEDS to full for short time int idx = step % NUM_LEDS; led_brightness[idx] = 255; led_update_from_state(); xSemaphoreGive(led_mutex); } /*** Game logic ***/ static void game_spawn_block(void) { // spawn random region among 0..6 that's empty int tries = 0; while (tries<10) { int r = esp_random() % 7; if (!game_regions[r]) { game_regions[r]=1; break; } tries++; } } static void game_draw(void) { // draw seven regions as columns on OLED oled_clear(); int region_w = OLED_WIDTH / 7; for (int i=0;i<7;i++){ int x = i*region_w + 4; int y = 12; int w = region_w - 8; int h = 40; if (game_regions[i]) { // filled for (int yy=y; yy<y+h; yy++) for (int xx=x;xx<x+w;xx++) { int page = yy/8; int idx = page*OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy&7)); } } else { // outline for (int xx=x;xx<x+w;xx++){ int yy=y; int page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7)); yy=y+h-1; page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7)); } for (int yy=y;yy<y+h;yy++){ int xx=x; int page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7)); xx=x+w-1; page = yy/8; oled_buf[page*OLED_WIDTH + xx] |= (1<<(yy&7)); } } } oled_refresh(); } /*** Main app ***/ void app_main(void) { // init components ESP_LOGI(TAG,"Starting extended piano app"); ws2812b_init(); ttp229_init(); buzzer_init(); oled_init(); // start led fade/animation task xTaskCreate(led_fade_task, "led_fade", 4096, NULL, 5, NULL); ESP_LOGI(TAG,"TTP229 16-key piano extended start!"); // previous key state for edge detection uint16_t prev_keys = 0; while (1) { uint16_t keys = ttp229_read_keys(); // active-low pressed -> bit=1 for pressed // FUNCTION KEYS - these are physical TP4..TP7 (bits 4..7) // detect rising edge (pressed now and not previously pressed) uint16_t newly = (keys & ~prev_keys); // TP4 (bit 4) -> toggle LED-key interaction mode if (newly & (1<<4)) { if (g_mode == MODE_LED_KEY_INTERACT) { g_mode = MODE_NORMAL; ESP_LOGI(TAG,"TP4: back to NORMAL"); oled_clear(); oled_draw_text_centered("^_^ (normal)"); oled_refresh(); } else { g_mode = MODE_LED_KEY_INTERACT; ESP_LOGI(TAG,"TP4: LED-KEY-INTERACT"); oled_clear(); oled_draw_bitmap(56, 28, 16, 8, bm_face1); oled_refresh(); } // small debounce vTaskDelay(pdMS_TO_TICKS(200)); } // TP5 (bit 5) -> toggle gradient cycle if (newly & (1<<5)) { if (g_mode == MODE_LED_GRADIENT_CYCLE) { g_mode = MODE_NORMAL; gradient_run = false; ESP_LOGI(TAG,"TP5: back to NORMAL"); oled_clear(); oled_draw_bitmap(56,28,16,8,bm_face1); oled_refresh(); } else { g_mode = MODE_LED_GRADIENT_CYCLE; gradient_run = true; ESP_LOGI(TAG,"TP5: LED GRADIENT CYCLE"); oled_clear(); oled_draw_bitmap(56,28,16,8,bm_face2); oled_refresh(); } vTaskDelay(pdMS_TO_TICKS(200)); } // TP6 (bit 6) -> toggle blessing play if (newly & (1<<6)) { if (g_mode == MODE_BLESSING_PLAY) { g_mode = MODE_NORMAL; stop_blessing_task(); ESP_LOGI(TAG,"TP6: STOP blessing"); oled_clear(); oled_draw_text_centered("Stopped"); oled_refresh(); } else { g_mode = MODE_BLESSING_PLAY; start_blessing_task(); ESP_LOGI(TAG,"TP6: START blessing"); oled_clear(); oled_draw_bitmap(56,20,12,12,bm_music); oled_refresh(); } vTaskDelay(pdMS_TO_TICKS(200)); } // TP7 (bit 7) -> toggle game if (newly & (1<<7)) { if (g_mode == MODE_GAME) { g_mode = MODE_NORMAL; game_running = false; ESP_LOGI(TAG,"TP7: STOP game"); oled_clear(); oled_draw_text_centered("Bye!"); oled_refresh(); } else { g_mode = MODE_GAME; game_running = true; memset(game_regions,0,sizeof(game_regions)); game_next_spawn = xTaskGetTickCount() + pdMS_TO_TICKS(800); ESP_LOGI(TAG,"TP7: START game"); oled_clear(); oled_draw_text_centered("Go!"); oled_refresh(); } vTaskDelay(pdMS_TO_TICKS(200)); } // If in game mode, manage spawns & draws if (g_mode == MODE_GAME && game_running) { if (xTaskGetTickCount() > game_next_spawn) { game_spawn_block(); game_next_spawn = xTaskGetTickCount() + pdMS_TO_TICKS(1000 + (esp_random()%2000)); game_draw(); } } // handle normal piano key behavior and modes requiring per-key handling if (keys) { // iterate logical keys (i = 0..15) bool played_any = false; for (int i=0;i<16;i++) { uint8_t tp = key_map[i]; if (keys & (1 << tp)) { // pressed // If function key pressed, we already handled on edge; just ignore as piano note if (tp>=4 && tp<=7) continue; int freq = note_freqs[i]; buzzer_play_tone(freq); played_any = true; // Mode specific behavior: if (g_mode == MODE_LED_KEY_INTERACT) { // map key i to LED idx (we'll map first 12 keys to leds) if (i < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_active[i] = 1; // latch on led_brightness[i] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); } } else if (g_mode == MODE_NORMAL) { // music visualization on OLED: draw bars based on which keys are pressed oled_draw_music_bars(keys); // show a friendly face small // do not block } else if (g_mode == MODE_BLESSING_PLAY) { // pressing keys can also trigger small sound/LED feedback // light a LED proportional int idx = i % NUM_LEDS; xSemaphoreTake(led_mutex, portMAX_DELAY); led_brightness[idx] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); } else if (g_mode == MODE_GAME) { // Map certain keys to game regions. // Game expects 7 regions -> map 7 left-most piano keys (i 0..6) if (i < 7) { if (game_regions[i]) { game_regions[i] = 0; // clear block // small success beep buzzer_play_tone(880); vTaskDelay(pdMS_TO_TICKS(80)); buzzer_play_tone(0); game_draw(); } else { // miss sound buzzer_play_tone(220); vTaskDelay(pdMS_TO_TICKS(60)); buzzer_play_tone(0); } } } // end mode cases // For LED gradient cycle, pressing keys can temporarily bump brightness if (g_mode == MODE_LED_GRADIENT_CYCLE) { if (i < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_brightness[i] = 255; xSemaphoreGive(led_mutex); led_update_from_state(); } } // simple small debounce for this key handling // (we don't use goto as before) } else { // key not pressed -> if in LED-key-interact mode, we should mark release for that key_map index // find i -> led index if (g_mode == MODE_LED_KEY_INTERACT) { if (i < NUM_LEDS) { // if it was active and now released, start fade by clearing active if (led_active[i]) { // check previous state had it pressed? simplest: clear active if not pressed // We can't know per-key previous here unless we track prev_keys; we'll use prev_keys below } } } } } // end for keys loop if (!played_any) { /* nothing else */ } // hold tone for small time to avoid chopping vTaskDelay(pdMS_TO_TICKS(40)); // stop tone unless key still pressed // We'll keep continuing while key status remains; main loop will handle } else { // no keys pressed -> silence buzzer buzzer_play_tone(0); } // handle key releases specifically to clear latched led_active when key released uint16_t releases = (~keys) & prev_keys; if (releases) { for (int i=0;i<16;i++) { uint8_t tp = key_map[i]; if (releases & (1<<tp)) { // if it was latched active for LED-key-interact, clear active -> fade begins if (g_mode == MODE_LED_KEY_INTERACT && i < NUM_LEDS) { xSemaphoreTake(led_mutex, portMAX_DELAY); led_active[i] = 0; // fade will reduce brightness in led_fade_task xSemaphoreGive(led_mutex); } } } } prev_keys = keys; vTaskDelay(pdMS_TO_TICKS(20)); } } /*** OLED small helper to show music bars visualization ***/ static void oled_draw_music_bars(uint16_t keys) { memset(oled_buf,0,OLED_BUF_SIZE); // We'll show up to 12 bars across horizontally mapped to 12 leds/keys int bars = NUM_LEDS; int w = OLED_WIDTH / bars; for (int i=0;i<bars;i++) { int x = i*w + 1; int base_y = OLED_HEIGHT - 2; // height depends on whether corresponding key is pressed // find corresponding logical key: reverse map -> find i's logical index if any int bar_h = 6; // simple rule: if any pressed among first 12 keys matching i -> tall bool pressed = false; for (int li=0; li<16; li++) { if (key_map[li] < 16 && li == i) { if (keys & (1 << key_map[li])) pressed = true; } } if (pressed) bar_h = OLED_HEIGHT/2; else bar_h = OLED_HEIGHT/6; // draw rectangle for (int yy = base_y - bar_h; yy <= base_y; yy++) { for (int xx = x; xx < x + w - 2; xx++) { if (xx<0 || xx>=OLED_WIDTH || yy<0 || yy>=OLED_HEIGHT) continue; int page = yy/8; int idx = page*OLED_WIDTH + xx; oled_buf[idx] |= (1 << (yy&7)); } } } oled_refresh(); } 这段代码出现了一些问题,出现了无用的参数,删去这些部分,并且tp6键播放blessing出现异常,蜂鸣器持续发生,找出问题所在,修改并重新整合代码
最新发布
10-17
关于控件的用法,你提供的代码大部分都是错误的,我把JvHidControllerClass.pas中函数及变量定义部分的内容给你: unit JvHidControllerClass; {$DEFINE DEFAULT_JVCL_INC} {$I jvcl.inc} {$I windowsonly.inc} interface uses {$IFDEF UNITVERSIONING} JclUnitVersioning, {$ENDIF UNITVERSIONING} Windows, Messages, Classes, SysUtils, JvComponentBase, DBT, SetupApi, Hid, JvTypes; const // a version string for the component cHidControllerClassVersion = '1.0.35'; // strings from the registry for CheckOutByClass cHidNoClass = 'HIDClass'; type // forward declarations TJvHidDeviceController = class; TJvHidDevice = class; // the Event function declarations TJvHidEnumerateEvent = function(HidDev: TJvHidDevice; const Idx: Integer): Boolean of object; TJvHidPlugEvent = procedure(HidDev: TJvHidDevice) of object; TJvHidUnplugEvent = TJvHidPlugEvent; TJvHidDataEvent = procedure(HidDev: TJvHidDevice; ReportID: Byte; const Data: Pointer; Size: Word) of object; TJvHidDataErrorEvent = procedure(HidDev: TJvHidDevice; Error: DWORD) of object; // check out test function TJvHidCheckCallback = function(HidDev: TJvHidDevice): Boolean; stdcall; // open overlapped read or write file handle TJvHidOpenExMode = (omhRead, omhWrite); // the physical descriptor TJvPhysicalDescriptor = array of WORD; // all USB relevant driver entries in the registry TJvHidPnPInfo = class(TObject) private FDeviceID: DWORD; FDevicePath: string; FCapabilities: DWORD; FClassDescr: string; FClassGUID: string; FCompatibleIDs: TStringList; FConfigFlags: DWORD; FDeviceDescr: string; FDriver: string; FFriendlyName: string; FHardwareID: TStringList; FLowerFilters: TStringList; FMfg: string; FUpperFilters: TStringList; FAddress: string; FBusNumber: DWORD; FBusType: string; FCharacteristics: string; FDevType: DWORD; FEnumeratorName: string; FExclusive: DWORD; FLegacyBusType: DWORD; FLocationInfo: string; FPhysDevObjName: string; FSecuritySDS: string; FService: string; FUINumber: DWORD; FUINumberFormat: string; function GetRegistryPropertyString(PnPHandle: HDEVINFO; const DevData: TSPDevInfoData; Prop: DWORD): string; function GetRegistryPropertyStringList(PnPHandle: HDEVINFO; const DevData: TSPDevInfoData; Prop: DWORD): TStringList; function GetRegistryPropertyDWord(PnPHandle: HDEVINFO; const DevData: TSPDevInfoData; Prop: DWORD): DWORD; function GetCompatibleIDs: TStrings; function GetHardwareID: TStrings; function GetLowerFilters: TStrings; function GetUpperFilters: TStrings; public property DeviceID: DWORD read FDeviceID; property DevicePath: string read FDevicePath; // registry values property Capabilities: DWORD read FCapabilities; property ClassDescr: string read FClassDescr; property ClassGUID: string read FClassGUID; property CompatibleIDs: TStrings read GetCompatibleIDs; property ConfigFlags: DWORD read FConfigFlags; property DeviceDescr: string read FDeviceDescr; property Driver: string read FDriver; property FriendlyName: string read FFriendlyName; property HardwareID: TStrings read GetHardwareID; property LowerFilters: TStrings read GetLowerFilters; property Mfg: string read FMfg; property UpperFilters: TStrings read GetUpperFilters; property Address: string read FAddress; property BusNumber: DWORD read FBusNumber; property BusType: string read FBusType; property Characteristics: string read FCharacteristics; property DevType: DWORD read FDevType; property EnumeratorName: string read FEnumeratorName; property Exclusive: DWORD read FExclusive; property LegacyBusType: DWORD read FLegacyBusType; property LocationInfo: string read FLocationInfo; property PhysDevObjName: string read FPhysDevObjName; property SecuritySDS: string read FSecuritySDS; property Service: string read FService; property UINumber: DWORD read FUINumber; property UINumberFormat: string read FUINumberFormat; constructor Create(APnPHandle: HDEVINFO; ADevData: TSPDevInfoData; ADevicePath: PChar); destructor Destroy; override; end; // a thread helper class to implement TJvHidDevice.OnData TJvHidDeviceReadThread = class(TJvCustomThread) private FErr: DWORD; procedure DoData; procedure DoDataError; constructor CtlCreate(const Dev: TJvHidDevice); protected procedure Execute; override; public Device: TJvHidDevice; NumBytesRead: Cardinal; Report: array of Byte; constructor Create(CreateSuspended: Boolean); end; // the representation of a HID device TJvHidDevice = class(TObject) private // internal control variables FMyController: TJvHidDeviceController; FIsPluggedIn: Boolean; FIsCheckedOut: Boolean; FIsEnumerated: Boolean; FHidFileHandle: THandle; FHidOverlappedRead: THandle; FHidOverlappedWrite: THandle; FOvlRead: TOverlapped; FOvlWrite: TOverlapped; // internal properties part FAttributes: THIDDAttributes; FPnPInfo: TJvHidPnPInfo; FVendorName: WideString; FProductName: WideString; FPhysicalDescriptor: TJvPhysicalDescriptor; FPreparsedData: PHIDPPreparsedData; FSerialNumber: WideString; FLanguageStrings: TStringList; FNumInputBuffers: Integer; FNumOverlappedBuffers: Integer; FThreadSleepTime: Integer; FLinkCollection: array of THIDPLinkCollectionNode; FMaxDataListLength: ULONG; FMaxUsageListLength: ULONG; FMaxButtonListLength: ULONG; FReportTypeParam: THIDPReportType; FUsagePageParam: TUsage; FLinkCollectionParam: WORD; FUsageParam: TUsage; FData: TJvHidDataEvent; FDataError: TJvHidDataErrorEvent; FUnplug: TJvHidUnplugEvent; FHasReadWriteAccess: Boolean; FDataThread: TJvHidDeviceReadThread; FTag: Integer; // tells if access to device is allowed function IsAccessible: Boolean; procedure GetMax; // internal property implementors function GetDeviceStringAnsi(Idx: Byte): string; function GetDeviceStringUnicode(Idx: Byte): WideString; function GetLinkCollectionNode(Idx: WORD): THIDPLinkCollectionNode; function GetConfiguration: THIDDConfiguration; function GetPreparsedData: PHIDPPreparsedData; function GetCaps: THIDPCaps; function GetVendorName: WideString; function GetProductName: WideString; function GetSerialNumber: WideString; function GetPhysicalDescriptor: TJvPhysicalDescriptor; function GetLanguageStrings: TStrings; function GetOverlappedReadResult: DWORD; function GetOverlappedWriteResult: DWORD; procedure SetConfiguration(const Config: THIDDConfiguration); procedure SetDataEvent(const DataEvent: TJvHidDataEvent); procedure SetNumInputBuffers(const Num: Integer); procedure SetNumOverlappedBuffers(const Num: Integer); procedure SetReportTypeParam(const ReportType: THIDPReportType); procedure SetThreadSleepTime(const SleepTime: Integer); procedure SetUsagePageParam(const UsagePage: TUsage); procedure StartThread; procedure StopThread; // Constructor is hidden! Only a TJvHidDeviceController can create a TJvHidDevice object. // APnPInfo becomes the property of this class, do not try to free it yourself, // even if this call raises an exception. // The destructor of this class will take care of the cleanup even when an exception // is raised (as specified by the Delphi language) constructor CtlCreate(const APnPInfo: TJvHidPnPInfo; const Controller: TJvHidDeviceController); protected // internal event implementor procedure DoUnplug; public // dummy constructor constructor Create; destructor Destroy; override; // methods function CancelIO(const Mode: TJvHidOpenExMode): Boolean; procedure CloseFile; procedure CloseFileEx(const Mode: TJvHidOpenExMode); function DeviceIoControl(IoControlCode: DWORD; InBuffer: Pointer; InSize: DWORD; OutBuffer: Pointer; OutSize: DWORD; var BytesReturned: DWORD): Boolean; function FlushQueue: Boolean; function GetButtonCaps(ButtonCaps: PHIDPButtonCaps; var Count: WORD): NTSTATUS; function GetButtons(UsageList: PUsage; var UsageLength: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function GetButtonsEx(UsageList: PUsageAndPage; var UsageLength: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function GetData(DataList: PHIDPData; var DataLength: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function GetFeature(var Report; const Size: Integer): Boolean; function GetScaledUsageValue(var UsageValue: Integer; var Report; ReportLength: ULONG): NTSTATUS; function GetSpecificButtonCaps(ButtonCaps: PHIDPButtonCaps; var Count: WORD): NTSTATUS; function GetSpecificValueCaps(ValueCaps: PHIDPValueCaps; var Count: WORD): NTSTATUS; function GetUsages(UsageList: PUsage; var UsageLength: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function GetUsagesEx(UsageList: PUsageAndPage; var UsageLength: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function GetUsageValue(var UsageValue: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function GetUsageValueArray(UsageValue: PAnsiChar; UsageValueByteLength: WORD; var Report; ReportLength: ULONG): NTSTATUS; function GetValueCaps(ValueCaps: PHIDPValueCaps; var Count: WORD): NTSTATUS; function OpenFile: Boolean; function OpenFileEx(Mode: TJvHidOpenExMode): Boolean; function SetButtons(UsageList: PUsage; var UsageLength: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function SetData(DataList: PHIDPData; var DataLength: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function SetFeature(var Report; const Size: Integer): Boolean; function SetScaledUsageValue(UsageValue: Integer; var Report; ReportLength: ULONG): NTSTATUS; function SetUsages(UsageList: PUsage; var UsageLength: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function SetUsageValue(UsageValue: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function SetUsageValueArray(UsageValue: PAnsiChar; UsageValueByteLength: WORD; var Report; ReportLength: ULONG): NTSTATUS; function UnsetButtons(UsageList: PUsage; var UsageLength: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function UnsetUsages(UsageList: PUsage; var UsageLength: ULONG; var Report; ReportLength: ULONG): NTSTATUS; function ReadFile(var Report; ToRead: DWORD; var BytesRead: DWORD): Boolean; function ReadFileEx(var Report; ToRead: DWORD; CallBack: TPROverlappedCompletionRoutine): Boolean; function WriteFile(var Report; ToWrite: DWORD; var BytesWritten: DWORD): Boolean; function WriteFileEx(var Report; ToWrite: DWORD; CallBack: TPROverlappedCompletionRoutine): Boolean; function CheckOut: Boolean; // Windows version dependent methods // added in Win 2000 function GetExtendedAttributes(ReportType: THIDPReportType; DataIndex: Word; Attributes: PHIDPExtendedAttributes; var LengthAttributes: ULONG): NTSTATUS; function InitializeReportForID(ReportType: THIDPReportType; ReportID: Byte; var Report; ReportLength: ULONG): NTSTATUS; // added in Win XP function GetInputReport(var Report; const Size: ULONG): Boolean; function SetOutputReport(var Report; const Size: ULONG): Boolean; // read only properties property Attributes: THIDDAttributes read FAttributes; property Caps: THIDPCaps read GetCaps; property HasReadWriteAccess: Boolean read FHasReadWriteAccess; property HidFileHandle: THandle read FHidFileHandle; property HidOverlappedRead: THandle read FHidOverlappedRead; property HidOverlappedWrite: THandle read FHidOverlappedWrite; property HidOverlappedReadResult: DWORD read GetOverlappedReadResult; property HidOverlappedWriteResult: DWORD read GetOverlappedWriteResult; property IsCheckedOut: Boolean read FIsCheckedOut; property IsPluggedIn: Boolean read FIsPluggedIn; property LanguageStrings: TStrings read GetLanguageStrings; property MaxButtonListLength: ULONG read FMaxButtonListLength; property MaxDataListLength: ULONG read FMaxDataListLength; property MaxUsageListLength: ULONG read FMaxUsageListLength; property PhysicalDescriptor: TJvPhysicalDescriptor read GetPhysicalDescriptor; property PnPInfo: TJvHidPnPInfo read FPnPInfo; property PreparsedData: PHIDPPreparsedData read GetPreparsedData; property ProductName: WideString read GetProductName; property SerialNumber: WideString read GetSerialNumber; property VendorName: WideString read GetVendorName; // read write properties property Configuration: THIDDConfiguration read GetConfiguration write SetConfiguration; property LinkCollectionParam: WORD read FLinkCollectionParam write FLinkCollectionParam; property NumInputBuffers: Integer read FNumInputBuffers write SetNumInputBuffers; property NumOverlappedBuffers: Integer read FNumOverlappedBuffers write SetNumOverlappedBuffers; property ReportTypeParam: THIDPReportType read FReportTypeParam write SetReportTypeParam; property Tag: Integer read FTag write FTag; property ThreadSleepTime: Integer read FThreadSleepTime write SetThreadSleepTime; property UsagePageParam: TUsage read FUsagePageParam write SetUsagePageParam; property UsageParam: TUsage read FUsageParam write FUsageParam; // indexed properties property DeviceStrings[Idx: Byte]: string read GetDeviceStringAnsi; property DeviceStringsUnicode[Idx: Byte]: WideString read GetDeviceStringUnicode; property LinkCollectionNodes[Idx: WORD]: THIDPLinkCollectionNode read GetLinkCollectionNode; // event properties property OnData: TJvHidDataEvent read FData write SetDataEvent; property OnDataError: TJvHidDataErrorEvent read FDataError write FDataError; property OnUnplug: TJvHidUnplugEvent read FUnplug write FUnplug; end; // controller class to manage all HID devices {$IFDEF RTL230_UP} [ComponentPlatformsAttribute(pidWin32 or pidWin64)] {$ENDIF RTL230_UP} TJvHidDeviceController = class(TJvComponent) private // internal properties part FHidGuid: TGUID; FArrivalEvent: TJvHidPlugEvent; FDeviceChangeEvent: TNotifyEvent; FEnumerateEvent: TJvHidEnumerateEvent; FDevDataEvent: TJvHidDataEvent; FDevDataErrorEvent: TJvHidDataErrorEvent; FDevUnplugEvent: TJvHidUnplugEvent; FRemovalEvent: TJvHidUnplugEvent; FDevThreadSleepTime: Integer; FVersion: string; FDummy: string; // internal list of all HID device objects FList: TList; // counters for the list FNumCheckedInDevices: Integer; FNumCheckedOutDevices: Integer; FNumUnpluggedDevices: Integer; // reentrancy FInDeviceChange: Boolean; FLParam: LPARAM; // window to catch WM_DEVICECHANGE FHWnd: HWND; // internal worker functions function CheckThisOut(var HidDev: TJvHidDevice; Idx: Integer; Check: Boolean): Boolean; procedure EventPipe(var Msg: TMessage); // internal event implementors procedure SetDeviceChangeEvent(const Notifier: TNotifyEvent); procedure SetEnumerate(const Enumerator: TJvHidEnumerateEvent); procedure SetDevThreadSleepTime(const DevTime: Integer); procedure SetDevData(const DataEvent: TJvHidDataEvent); procedure SetDevDataError(const DataErrorEvent: TJvHidDataErrorEvent); procedure SetDevUnplug(const Unplugger: TJvHidUnplugEvent); protected procedure DoArrival(HidDev: TJvHidDevice); procedure DoRemoval(HidDev: TJvHidDevice); procedure DoDeviceChange; function DoEnumerate(HidDev: TJvHidDevice; Idx: Integer): Boolean; public // normal constructor/destructor constructor Create(AOwner: TComponent); override; destructor Destroy; override; // methods to hand out HID device objects procedure CheckIn(var HidDev: TJvHidDevice); function CheckOut(var HidDev: TJvHidDevice): Boolean; function CheckOutByClass(var HidDev: TJvHidDevice; const ClassName: string): Boolean; function CheckOutByID(var HidDev: TJvHidDevice; const Vid, Pid: Integer): Boolean; function CheckOutByIndex(var HidDev: TJvHidDevice; const Idx: Integer): Boolean; function CheckOutByProductName(var HidDev: TJvHidDevice; const ProductName: WideString): Boolean; function CheckOutByVendorName(var HidDev: TJvHidDevice; const VendorName: WideString): Boolean; function CheckOutByCallback(var HidDev: TJvHidDevice; Check: TJvHidCheckCallback): Boolean; // methods to count HID device objects function CountByClass(const ClassName: string): Integer; function CountByID(const Vid, Pid: Integer): Integer; function CountByProductName(const ProductName: WideString): Integer; function CountByVendorName(const VendorName: WideString): Integer; function CountByCallback(Check: TJvHidCheckCallback): Integer; // iterate over the HID devices function Enumerate: Integer; class function HidVersion: string; // just to be complete the GUID property HidGuid: TGUID read FHidGuid; property NumCheckedInDevices: Integer read FNumCheckedInDevices; property NumCheckedOutDevices: Integer read FNumCheckedOutDevices; property NumUnpluggedDevices: Integer read FNumUnpluggedDevices; published property DevThreadSleepTime: Integer read FDevThreadSleepTime write SetDevThreadSleepTime default 100; property Version: string read FVersion write FDummy stored False; property OnArrival: TJvHidPlugEvent read FArrivalEvent write FArrivalEvent; // the iterator event property OnEnumerate: TJvHidEnumerateEvent read FEnumerateEvent write SetEnumerate; // the central event for HID device changes property OnDeviceChange: TNotifyEvent read FDeviceChangeEvent write SetDeviceChangeEvent; // these events are copied to TJvHidDevices on creation property OnDeviceData: TJvHidDataEvent read FDevDataEvent write SetDevData; property OnDeviceDataError: TJvHidDataErrorEvent read FDevDataErrorEvent write SetDevDataError; property OnDeviceUnplug: TJvHidUnplugEvent read FDevUnplugEvent write SetDevUnplug; property OnRemoval: TJvHidUnplugEvent read FRemovalEvent write FRemovalEvent; // to be callable at design time procedure DeviceChange; end; // helpers to check the HID function and method results function HidCheck(const RetVal: NTSTATUS): NTSTATUS; overload; function HidCheck(const RetVal: LongBool): LongBool; overload; function HidError(const RetVal: NTSTATUS): NTSTATUS; function HidErrorString(const RetVal: NTSTATUS): string; // to register the component in the palette procedure Register; {$IFDEF UNITVERSIONING} const UnitVersioning: TUnitVersionInfo = ( RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_45/run/JvHidControllerClass.pas $'; Revision: '$Revision: 13102 $'; Date: '$Date: 2011-09-07 07:46:34 +0200 (mer., 07 sept. 2011) $'; LogPath: 'JVCL\run' ); {$ENDIF UNITVERSIONING} implementation ====== 你仔细分析查看,用对控件!
09-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值