skynet_main.c浅析

1.作为skynet的启动文件,主要完成了一些初始化和读取并存取配置文件内容的工作.
在这里只将代码读取配置文件的部分抽取出来,就算没有skynet环境,这些代码也是可以运行的,了解以后再对照源码进行分析,希望能对理解skynet带来一些帮助

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

#include <signal.h>
#include <assert.h>
#include "env.h"

struct config
{
 int thread;
 int harbor;
 const char *deamon;
 const char *module_path;
 const char *bootstrap;
 const char *logger;
 const char *logservice; 
};

void popn(lua_State *L, int n);

static int
optint(const char *key, int opt)
{
 const char *str = env_getenv(key);
 if (str == NULL)
 {
  char tmp[20];
  sprintf(tmp, "%d", opt);
  env_setenv(key, tmp);
  return opt;
 }
 return strtol(str, NULL, 10);
}

static const char *
optstring(const char *key, const char *opt)
{
 const char *str = env_getenv(key);
 if (str == NULL)
 {
  if (opt)
  {
   env_setenv(key, opt);
   opt = env_getenv(key);
  }
  return opt;
 }
 return str;
}

static const char * load_config = "\
 local config_name = ...\
 local f = assert(io.open(config_name))\
 local code = assert(f:read \‘*a\‘)\
 print(\"code is \", code)\
 local function getenv(name) return assert(os.getenv(name), \‘os.getenv() failed: \‘ .. name) end\
 code = string.gsub(code, \‘%$([%w_%d]+)\‘, getenv)\
 f:close()\
 print(\"code after replace is \", code)\
 local result = {}\
 assert(load(code,\‘=(load)\‘,\‘t\‘,result))()\
 return result\
";

static void
_init_env(lua_State *L)
{
 lua_pushnil(L);
 while(lua_next(L, -2) != 0)
 {
  int keyt = lua_type(L, -2);
  if (keyt != LUA_TSTRING)
  {
   fprintf(stderr, "Invalid, config table\n");
   exit(1);
  }

const char *key = lua_tostring(L, -2);
  if (lua_type(L, -1) == LUA_TBOOLEAN)
  {
   int b = lua_toboolean(L, -1);
   env_setenv(key, b ? "true" : "false");
  }
  else
  {
   const char *value = lua_tostring(L, -1);
   if (value == NULL)
   {
    fprintf(stderr, "Invalud config table key = %s\n", key);
    exit(1);
   }
   env_setenv(key, value);
  }
  lua_pop(L, 1);
 } 
 lua_pop(L, 1);
}

void popn(lua_State *L, int n)
{
 lua_pop(L, -(n) - 1);
}

int
sigign()
{
 struct sigaction sa;
 sa.sa_handler = SIG_IGN;
 sigaction(SIGPIPE, &sa, 0);
 return 0;
}

void
init_conf(struct config *conf)
{
 conf->thread = optint("thread", 8);
 conf->module_path = optstring("cpath", "./cservice/?.so");
 conf->harbor = optint("harbor", 1);
 conf->bootstrap = optstring("bootstrap", "snlua bootstrap");
 conf->deamon = optstring("deamon", NULL);
 conf->deamon = optstring("logger", NULL);
 conf->logservice = optstring("logservice", "logger");
}

void test_env()
{
 printf("thread: %s\n", env_getenv("thread"));
 printf("harbor: %s\n", env_getenv("harbor"));
}

int 
main(int argc, char *argv[])

 const char *config_file = NULL;
 if (argc > 1)
 {
  config_file = argv[1];
 }
 else
 {
  fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n"
   "usage: skynet configfilename\n");
 }
 
 sigign();
 env_env_init();
  
 struct config conf;
 struct lua_State *L = luaL_newstate();
 luaL_openlibs(L);
 int err = luaL_loadstring(L, load_config);
 assert(err == LUA_OK);
 lua_pushstring(L, config_file);

err = lua_pcall(L, 1, 1, 0);
 if (err)
 {
  fprintf(stderr, "%s,\n", lua_tostring(L, -1));
  lua_close(L);
  return 1;
 }
 _init_env(L);

init_conf(&conf);

test_env();

lua_close(L);

return 0;
}

 首先看main函数:

const char *config_file = NULL;

if (argc > 1)
{

config_file = argv[1];

}
else
{

fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n"
"usage: skynet configfilename\n");

}

这句话是读取配置文件路径:正如运行skynet的命令行为:./skynet ./examples/config.我将config拿出来放到和可执行文件用级,运行方式为: ./test ./config

于是config_file = argv[1]便保存了配置文件名.

下面两句:
sigign();
env_env_init();

设置信号处理函数,调用env_env_init()初始化保存配置文件的环境,该函数在env.c中,对应的是skynet_env.c(这里内容是一样的),代码如下:

env.c:

#include "env.h"
#include "spinlock.h"
#include "lauxlib.h"
#include "lua.h"
#include <stdlib.h>
#include <assert.h>
#include <error.h>
#include <string.h>

struct env
{
 struct spinlock lock;
 lua_State *L;
};

static struct env *E = NULL;

const char*
env_getenv(const char *key)
{
 SPIN_LOCK(E);
 lua_State *L = E->L;
 lua_getglobal(L, key);
 const char *result = lua_tostring(L, -1);
 //nil may return if there is no such a key-pair.
 lua_pop(L, 1);
 SPIN_UNLOCK(E);

return result;
}

void
env_setenv(const char *key, const char *value)
{
 SPIN_LOCK(E);
 lua_State *L = E->L;
 lua_getglobal(L, key);
 //after getglobal , if there is a value crosponds to the key , then it will be pushed onto stack, otherwise nil is pushed
 assert(lua_isnil(L, -1));
 lua_pop(L, 1); //pop nil from the stack
 lua_pushstring(L, value);
 lua_setglobal(L, key); //after setglobal the value on the stack will be poped.

SPIN_UNLOCK(E);
}

void
env_env_init()
{
 E = (struct env*)malloc(sizeof(*E));
 SPIN_INIT(E);
 E->L = luaL_newstate();
}

由此可见env.c创建了静态变量 static struct env *E 来保存配置。

继续main函数:

struct lua_State *L = luaL_newstate();

luaL_openlibs(L);

int err = luaL_loadstring(L, load_config);

assert(err == LUA_OK);

lua_pushstring(L, config_file);
err = lua_pcall(L, 1, 1, 0);

if (err)
{

fprintf(stderr, "%s,\n", lua_tostring(L, -1));
lua_close(L);

return 1;

}

_init_env(L);

这几句话便完成了读取配置文件并保存到env.c中的E的lua虚拟机里.

int err = luaL_loadstring(L, load_config);

这句话将load_config作为一个chunk加载到打开的L的函数堆栈里,我们看一下load_config:

static const char * load_config = "\
 local config_name = ...\
 local f = assert(io.open(config_name))\
 local code = assert(f:read \‘*a\‘)\
 print(\"code is \", code)\
 local function getenv(name) return assert(os.getenv(name), \‘os.getenv() failed: \‘ .. name) end\
 code = string.gsub(code, \‘%$([%w_%d]+)\‘, getenv)\
 f:close()\
 print(\"code after replace is \", code)\
 local result = {}\
 assert(load(code,\‘=(load)\‘,\‘t\‘,result))()\
 return result\
";

发现load_config其实是一段lua代码字符串,load到函数栈上以后,便可以执行,

但是根据 local config_name = ...\ 看出,需要传入一个参数,也就是配置文件的路径
那么如何传呢?继续看main函数
lua_pushstring(L, config_file);
这句话便将config_file这个字符串放到了栈上供

int err = luaL_loadstring(L, load_config);
产生的chunk调用.

于是执行
err = lua_pcall(L, 1, 1, 0);
if (err)
{
fprintf(stderr, "%s,\n", lua_tostring(L, -1));
lua_close(L);
return 1;
}

这句话便是开始执行lua函数栈上的chunk,参数为刚才push进去的config_file,那么chunk做了哪些事情呢?

local f = assert(io.open(config_name))\

local code = assert(f:read \‘*a\‘)\

首先打开配置文件,并读取所有内容,接下来定义了一个函数:

local function getenv(name) return assert(os.getenv(name), \‘os.getenv() failed: \‘ .. name) end\

这个函数执行返回 os.getenv(name), 也就是name为环境变量的名称,这里得到其值例如: print(os.getenv("HOME")),便会输出$(HOME)对应的值,

接下来:code = string.gsub(code, \‘%$([%w_%d]+)\‘, getenv)\

可以看到用string.gsub来讲code(配置文件内容)中的$(...)环境变量用getenv中得到的来替换

.但是skynet中得config
没有类似的环境变量,所以得到的code没有变化.

接下来

f:close()\

local result = {}

assert(load(code,\‘=(load)\‘,\‘t\‘,result))()\

return result

关闭配置文件,将code load到result表中,然后返回result表,

我们注意到我们调用lua_pcall(L, 1,1,0),因此,result表此时在栈顶的位置.
到此为止,配置文件的内容已经存放在result表中,并且这个表在lua调用栈的栈顶

,接下来便是从栈上的表中读取表中的内容,然后存放到env中:
紧接着调用

_init_env(L);

static void
_init_env(lua_State *L)
{
 lua_pushnil(L);
 while(lua_next(L, -2) != 0)
 {
  int keyt = lua_type(L, -2);
  if (keyt != LUA_TSTRING)
  {
   fprintf(stderr, "Invalid, config table\n");
   exit(1);
  }

const char *key = lua_tostring(L, -2);
  if (lua_type(L, -1) == LUA_TBOOLEAN)
  {
   int b = lua_toboolean(L, -1);
   env_setenv(key, b ? "true" : "false");
  }
  else
  {
   const char *value = lua_tostring(L, -1);
   if (value == NULL)
   {
    fprintf(stderr, "Invalud config table key = %s\n", key);
    exit(1);
   }
   env_setenv(key, value);
  }
  lua_pop(L, 1);
 } 
 lua_pop(L, 1);
}

这个函数是一个典型的读取table中以字符串作为键的table的内容,尤其是不知道table中的具体内容是什么的时候.

这里我们只知道是以键值对形式,且键是字符串,table为非数组。

上边我们知道此时lua函数栈的栈顶为存放了配置文件内容的table,那么这个函数就是挨个遍历table内容,并调用env_env_set()来存放。

首先lua_next(L, -2)先将栈顶元素弹出然后将table中的一个键值对放到栈上,键在-2位置上,值在-1位置上。因为lua_next(L, -2)先弹栈顶元素,因此在调用之前先pushnil,放进一个nil在栈顶,调用lua_next() nil 被弹出
然后table中的第一个键值对依次放到栈上,然后获得键值,调用 env_setenv()来存放内容到E。循环读取table的值,直到读完跳出循环。至此,config文件中的内容全部存放到env中的全局变量E中的虚拟机中,并可在其他地方调用来获得配置文件内容.
最后:
init_conf(&conf);
test_env();
lua_close(L);
其实就是仿照skynet_main.c来测试,test_env()可以替换成skynet_start(&conf)来继续启动skynet,这里简单的用来测试。
到此为止,读取config配置文件的工作就做完了,并保存到了env.c中的局部静态变量里。供其它地方使用配置。

基础有限,研究skynet时间有限,难免讲述不清或出现错误,望指正。
最后贴出全部测试文件代码:

----------------------------------------------------------------------------------------------------------

start.c

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

#include <signal.h>
#include <assert.h>
#include "env.h"

struct config
{
 int thread;
 int harbor;
 const char *deamon;
 const char *module_path;
 const char *bootstrap;
 const char *logger;
 const char *logservice; 
};

void popn(lua_State *L, int n);

static int
optint(const char *key, int opt)
{
 const char *str = env_getenv(key);
 if (str == NULL)
 {
  char tmp[20];
  sprintf(tmp, "%d", opt);
  env_setenv(key, tmp);
  return opt;
 }
 return strtol(str, NULL, 10);
}

static const char *
optstring(const char *key, const char *opt)
{
 const char *str = env_getenv(key);
 if (str == NULL)
 {
  if (opt)
  {
   env_setenv(key, opt);
   opt = env_getenv(key);
  }
  return opt;
 }
 return str;
}

static const char * load_config = "\
 local config_name = ...\
 local f = assert(io.open(config_name))\
 local code = assert(f:read \‘*a\‘)\
 print(\"code is \", code)\
 local function getenv(name) return assert(os.getenv(name), \‘os.getenv() failed: \‘ .. name) end\
 code = string.gsub(code, \‘%$([%w_%d]+)\‘, getenv)\
 f:close()\
 print(\"code after replace is \", code)\
 local result = {}\
 assert(load(code,\‘=(load)\‘,\‘t\‘,result))()\
 return result\
";

static void
_init_env(lua_State *L)
{
 lua_pushnil(L);
 while(lua_next(L, -2) != 0)
 {
  int keyt = lua_type(L, -2);
  if (keyt != LUA_TSTRING)
  {
   fprintf(stderr, "Invalid, config table\n");
   exit(1);
  }

const char *key = lua_tostring(L, -2);
  if (lua_type(L, -1) == LUA_TBOOLEAN)
  {
   int b = lua_toboolean(L, -1);
   env_setenv(key, b ? "true" : "false");
  }
  else
  {
   const char *value = lua_tostring(L, -1);
   if (value == NULL)
   {
    fprintf(stderr, "Invalud config table key = %s\n", key);
    exit(1);
   }
   env_setenv(key, value);
  }
  lua_pop(L, 1);
 } 
 lua_pop(L, 1);
}

void popn(lua_State *L, int n)
{
 lua_pop(L, -(n) - 1);
}

int
sigign()
{
 struct sigaction sa;
 sa.sa_handler = SIG_IGN;
 sigaction(SIGPIPE, &sa, 0);
 return 0;
}

void
init_conf(struct config *conf)
{
 conf->thread = optint("thread", 8);
 conf->module_path = optstring("cpath", "./cservice/?.so");
 conf->harbor = optint("harbor", 1);
 conf->bootstrap = optstring("bootstrap", "snlua bootstrap");
 conf->deamon = optstring("deamon", NULL);
 conf->deamon = optstring("logger", NULL);
 conf->logservice = optstring("logservice", "logger");
}

void test_env()
{
 printf("thread: %s\n", env_getenv("thread"));
 printf("harbor: %s\n", env_getenv("harbor"));
}

int 
main(int argc, char *argv[])

 const char *config_file = NULL;
 if (argc > 1)
 {
  config_file = argv[1];
 }
 else
 {
  fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n"
   "usage: skynet configfilename\n");
 }
 
 sigign();
 env_env_init();
  
 struct config conf;
 struct lua_State *L = luaL_newstate();
 luaL_openlibs(L);
 int err = luaL_loadstring(L, load_config);
 assert(err == LUA_OK);
 lua_pushstring(L, config_file);

err = lua_pcall(L, 1, 1, 0);
 if (err)
 {
  fprintf(stderr, "%s,\n", lua_tostring(L, -1));
  lua_close(L);
  return 1;
 }
 _init_env(L);

init_conf(&conf);

test_env();

lua_close(L);

return 0;
}

 env.c

#include "env.h"
#include "spinlock.h"
#include "lauxlib.h"
#include "lua.h"
#include <stdlib.h>
#include <assert.h>
#include <error.h>
#include <string.h>

struct env
{
 struct spinlock lock;
 lua_State *L;
};

static struct env *E = NULL;

const char*
env_getenv(const char *key)
{
 SPIN_LOCK(E);
 lua_State *L = E->L;
 lua_getglobal(L, key);
 const char *result = lua_tostring(L, -1);
 //nil may return if there is no such a key-pair.
 lua_pop(L, 1);
 SPIN_UNLOCK(E);

return result;
}

void
env_setenv(const char *key, const char *value)
{
 SPIN_LOCK(E);
 lua_State *L = E->L;
 lua_getglobal(L, key);
 //after getglobal , if there is a value crosponds to the key , then it will be pushed onto stack, otherwise nil is pushed
 assert(lua_isnil(L, -1));
 lua_pop(L, 1); //pop nil from the stack
 lua_pushstring(L, value);
 lua_setglobal(L, key); //after setglobal the value on the stack will be poped.

SPIN_UNLOCK(E);
}

void
env_env_init()
{
 E = (struct env*)malloc(sizeof(*E));
 SPIN_INIT(E);
 E->L = luaL_newstate();
}

env.h

#ifndef __ENV_H_
#define __ENV_H_

const char* env_getenv(const char*key);
void env_setenv(const char *key, const char *value);
void env_env_init();

#endif

 

spinlock.h

#ifndef SKYNET_SPINLOCK_H
#define SKYNET_SPINLOCK_H

#define SPIN_INIT(q) spinlock_init(&(q)->lock);
#define SPIN_LOCK(q) spinlock_lock(&(q)->lock);
#define SPIN_UNLOCK(q) spinlock_unlock(&(q)->lock);
#define SPIN_DESTROY(q) spinlock_destroy(&(q)->lock);

#ifndef USE_PTHREAD_LOCK

struct spinlock {
 int lock;
};

static inline void
spinlock_init(struct spinlock *lock) {
 lock->lock = 0;
}

static inline void
spinlock_lock(struct spinlock *lock) {
 while (__sync_lock_test_and_set(&lock->lock,1)) {}
}

static inline int
spinlock_trylock(struct spinlock *lock) {
 return __sync_lock_test_and_set(&lock->lock,1) == 0;
}

static inline void
spinlock_unlock(struct spinlock *lock) {
 __sync_lock_release(&lock->lock);
}

static inline void
spinlock_destroy(struct spinlock *lock) {
 (void) lock;
}

#else

#include <pthread.h>

// we use mutex instead of spinlock for some reason
// you can also replace to pthread_spinlock

struct spinlock {
 pthread_mutex_t lock;
};

static inline void
spinlock_init(struct spinlock *lock) {
 pthread_mutex_init(&lock->lock, NULL);
}

static inline void
spinlock_lock(struct spinlock *lock) {
 pthread_mutex_lock(&lock->lock);
}

static inline int
spinlock_trylock(struct spinlock *lock) {
 return pthread_mutex_trylock(&lock->lock) == 0;
}

static inline void
spinlock_unlock(struct spinlock *lock) {
 pthread_mutex_unlock(&lock->lock);
}

static inline void
spinlock_destroy(struct spinlock *lock) {
 pthread_mutex_destroy(&lock->lock);
}

#endif

#endife

config

root        = "./"
thread      = 8
--logger      = nil
logger      = "userlog"
logservice  = "snlua"
--logservice  = "catlogger"
logpath     = "./../../log/cat/"
harbor      = 1
address     = "127.0.0.1:2401"
master      = "127.0.0.1:2013"
start       = "main" -- main script
bootstrap   = "snlua bootstrap" -- The service for bootstrap
standalone  = "0.0.0.0:2013"
luaservice  = "./../../service/logind/?.lua;./../../service/db/?.lua;./../../service/web/?.lua;./../../service/cat/?.lua;./../../service/?.lua;./service/?.lua"
lualoader   = "lualib/loader.lua"
preload     = "./../../lualib/preload.lua" -- run preload.lua before every lua service run
snax        = root.."../logind/?.lua;"..root.."test/?.lua"
-- snax_interface_g = "snax_g"
cpath       = "./../../cservice/?.so;./cservice/?.so"
-- daemon = "./skynet.pid"

makefile:

CC= gcc
CFLAGS= -g -O2 -Wall
LUA_INC= /usr/include/lua5.2

all: main

main : start.c env.c env.h spinlock.h
 $(CC) $(CFLAGS) -o [email protected] $^ -I$(LUA_INC) -llua5.2 -lpthread -lm

clean :
 rm -f main

时间: 2024-10-15 13:05:54

skynet_main.c浅析的相关文章

Python之encode与decode浅析

 Python之encode与decode浅析 在 python 源代码文件中,如果你有用到非ASCII字符,则需要在文件头部进行字符编码的声明,声明如下: # code: UTF-8 因为python 只检查 #.coding 和编码字符串,为了美观等原因可以如下写法: #-*-coding:utf-8-*- 常见编码介绍: GB2312编码:适用于汉字处理.汉字通信等系统之间的信息交换. GBK编码:是汉字编码标准之一,是在 GB2312-80 标准基础上的内码扩展规范,使用了双字节编码.

浅析PHP的开源产品二次开发的基本要求

浅析PHP的开源产品二次开发的基本要求 第一, 基本要求:HTML(必须要非常熟悉),PHP(能看懂代码,能写一些小系统,如:留言板,小型CMS),Mysql(至少会一种数据库),Javascript(能看懂,能改现成的一些代码),Div+Css(能进行界面的调整,明白CSS是怎么使用的) 第二, 熟悉开源产品的使用,比如 Dedecms,你要知道怎么登录,怎么新建栏目,怎么添加文章,模板标签的使用方法,模型的概念和使用方法等等一些功能 第三, 要熟悉这个开源产品的数据库结构,还要理解里面核心文

word-break|overflow-wrap|word-wrap——CSS英文断句浅析

---恢复内容开始--- word-break|overflow-wrap|word-wrap--CSS英文断句浅析 一 问题引入 今天在再次学习 overflow 属性的时候,查看效果时,看到如下结果,内容在 div 中国换行了,可是两个 P 元素的内容并没有换行,搜索一番没有找到系统的答案,截图到群里请教大神,才知道是英文断句的问题,但是还是不太明白.之前没有遇到这种情况,为了彻底搞清楚,英文断句,又开始学习英文断句到底是怎么回事. 二 换行 每种语言里都有换行,就中文而言,我们最小语言单位

浅析vanish

浅析 VANISH --一种cache 第一部分:理解vanish的准备工作 1.对CDN的小剖析 CDN  content  delivery  network  内容分发(推送)网络,是在现有的Internet中增加一层新的网络架构,将网络内容发布到最接近用户的网络边缘(边缘服务器),使用户最近取得所需内容,解决网络拥挤状态,提高用户访问网站的速度. CDN网络架构主要有两部分组成,中心和边缘两部分,中心指CDN网管中心和DNS重定向解析中心,负责全局负载均衡.边缘主要指异地节点,CDN分发

健康,home? [java的内存浅析]

健康,home? [java的内存浅析] 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 乐观上上,how can other kno u,u r yourself!I must be strong and carry on. -泥沙砖瓦浆木匠 一.闲谈下 201407月记着那时候身体垮了下来,呵呵.想说,对自己的说,也是对大家的负责吧.那时候胸疼胸闷,然后几乎累垮了,我还坚持了一星期,那一星期真的迷迷糊糊.完全不能

Mysql查询优化器浅析

--Mysql查询优化器浅析 -----------------------------2014/06/11 1 定义 Mysql查询优化器的工作是为查询语句选择合适的执行路径.查询优化器的代码一般是经常变动的,这和存储引擎不太一样.因此,需要理解最新版本的查询优化器是如何组织的,请参考相应的源代码.整体而言,优化器有很多相同性,对mysql一个版本的优化器做到整体掌握,理解起mysql新版本以及其他数据库的优化器都是类似的. 优化器会对查询语句进行转化,转化等价的查询语句.举个例子,优化器会将

Volley框架源码浅析(一)

尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天开始,我打算为大家呈现关于Volley框架的源码分析的文章,Volley框架是Google在2013年发布的,主要用于实现频繁而且粒度比较细小的Http请求,在此之前Android中进行Http请求通常是使用HttpUrlConnection和HttpClient进行,但是使用起来非常麻烦,而且效率比较地下,我想谷歌正式基于此种原因发布了Volley框架,其实出了Voll

C语言中文件打开模式(r/w/a/r+/w+/a+/rb/wb/ab/rb+/wb+/ab+)浅析

C语言文件打开模式浅析 在C语言的文件操作语法中,打开文件文件有以下12种模式,如下图: 打开模式  只可以读   只可以写  读写兼备 文本模式 r w a r+ w+ a+ 二进制模式 rb wb ab  rb+ (r+b)   wb+ (w+b)   ab+ (a+b)  其中,二进制模式与文本模式操作相似,只不过是以二进制流的形式读写而已,下面以文本模式为例分析: 1."r" 模式: 1.1 打开文件进行“只读”操作,即只能从文件读取内容. 1.2 若欲操作的文件不存在,则打开

浅析STM32之usbh_def.H

[温故而知新]类似文章浅析USB HID ReportDesc (HID报告描述符) 现在将en.stm32cubef1\STM32Cube_FW_F1_V1.4.0\Middlewares\ST\STM32_USB_Host_Library\Core\Inc\usbh_def.H /** ****************************************************************************** * @file usbh_def.h * @aut