自制编程语言基于c语言实验记录之五:虚拟机

news2024/11/23 4:19:50

1.创建类与堆栈框架

1 )对象调用实例方法,也就是向一个对象发送消息 时,运行时系统会在对象所属类的方法集合中查找方法。
2 )类调用类方法,也就是向一个类发送消息时,运行时系统会在类的 meta-class 的 方法集合中查找方法。
在这里插入图片描述

1.1 buildCore


//编译核心模块
void buildCore(VM* vm) {

   //核心模块不需要名字,模块也允许名字为空
   ObjModule* coreModule = newObjModule(vm, NULL);

   //创建核心模块,录入到vm->allModules
   mapSet(vm, vm->allModules, CORE_MODULE, OBJ_TO_VALUE(coreModule));

   //创建object类并绑定方法
   vm->objectClass = defineClass(vm, coreModule, "object");
   PRIM_METHOD_BIND(vm->objectClass, "!", primObjectNot);
   PRIM_METHOD_BIND(vm->objectClass, "==(_)", primObjectEqual);
   PRIM_METHOD_BIND(vm->objectClass, "!=(_)", primObjectNotEqual);
   PRIM_METHOD_BIND(vm->objectClass, "is(_)", primObjectIs);
   PRIM_METHOD_BIND(vm->objectClass, "toString", primObjectToString);
   PRIM_METHOD_BIND(vm->objectClass, "type", primObjectType);

   //定义classOfClass类,它是所有meta类的meta类和基类
   vm->classOfClass = defineClass(vm, coreModule, "class");

   //objectClass是任何类的基类 
   bindSuperClass(vm, vm->classOfClass, vm->objectClass);

   PRIM_METHOD_BIND(vm->classOfClass, "name", primClassName);
   PRIM_METHOD_BIND(vm->classOfClass, "supertype", primClassSupertype);
   PRIM_METHOD_BIND(vm->classOfClass, "toString", primClassToString);

   //定义object类的元信息类objectMetaclass,它无须挂载到vm
   Class* objectMetaclass = defineClass(vm, coreModule, "objectMeta");
   
   //classOfClass类是所有meta类的meta类和基类
   bindSuperClass(vm, objectMetaclass, vm->classOfClass);

   //类型比较
   PRIM_METHOD_BIND(objectMetaclass, "same(_,_)", primObjectmetaSame);

   //绑定各自的meta类
   vm->objectClass->objHeader.class = objectMetaclass;
   objectMetaclass->objHeader.class = vm->classOfClass;
   vm->classOfClass->objHeader.class = vm->classOfClass; //元信息类回路,meta类终点
}


//定义类
static Class* defineClass(VM* vm, ObjModule* objModule, const char* name) {
   //1先创建类
   Class* class = newRawClass(vm, name, 0);

   //2把类做为普通变量在模块中定义
   defineModuleVar(vm, objModule, name, strlen(name), OBJ_TO_VALUE(class));
   return class;
}

暂时忽略PRIM_METHOD_BIND

  1. defineClass创建object类保存于vm->objectClass
  2. defineClass创建object的metaClass,无需挂载到vm
  3. defineClass创建class类保存于vm->classOfClass
  4. object类的meta类设置为object的metaClass,即objHeader.class指向object的metaClass
  5. objectMetaclass类的meta类设置为class类
  6. class类的meta类设置为class类

2.newClass

//创建一个类
Class* newClass(VM* vm, ObjString* className, uint32_t fieldNum, Class* superClass) {
   //10表示strlen(" metaClass"
   #define MAX_METACLASS_LEN MAX_ID_LEN + 10
   char newClassName[MAX_METACLASS_LEN] = {'\0'};
   #undef MAX_METACLASS_LEN

   memcpy(newClassName, className->value.start, className->value.length);
   memcpy(newClassName + className->value.length, " metaclass", 10);

   //先创建子类的meta类
   Class* metaclass = newRawClass(vm, newClassName, 0);
   metaclass->objHeader.class = vm->classOfClass;

   pushTmpRoot(vm, (ObjHeader*)metaclass);
   //绑定classOfClass为meta类的基类
   //所有类的meta类的基类都是classOfClass
   bindSuperClass(vm, metaclass, vm->classOfClass);
   
   //最后再创建类
   memcpy(newClassName, className->value.start, className->value.length);
   newClassName[className->value.length] = '\0';
   Class* class = newRawClass(vm, newClassName, fieldNum);
   pushTmpRoot(vm, (ObjHeader*)class);

   class->objHeader.class = metaclass;
   bindSuperClass(vm, class, superClass);

   popTmpRoot(vm);   // metaclass
   popTmpRoot(vm);   // class
   
   return class;
}

//新建一个裸类
Class* newRawClass(VM* vm, const char* name, uint32_t fieldNum) {
   Class* class = ALLOCATE(vm, Class); 

   //裸类没有元类
   initObjHeader(vm, &class->objHeader, OT_CLASS, NULL);
   class->name = newObjString(vm, name, strlen(name));
   class->fieldNum = fieldNum;
   class->superClass = NULL;   //默认没有基类

   pushTmpRoot(vm, (ObjHeader*)class);
   MethodBufferInit(&class->methods);
   popTmpRoot(vm);

   return class;
}

传入参数要创建的类名className、实例域数量fieldNum、superClass。

  1. 根据传入className的利用newRawClass创建了Class、metaClass。
  2. metaclass的meta类、基类都设置为class类
  3. class的meta类设置为metaclass,基类设置为superClass

3.ensureStack

//确保stack有效
void ensureStack(VM* vm, ObjThread* objThread, uint32_t neededSlots) {
   if (objThread->stackCapacity >= neededSlots) {
      return;
   }

   uint32_t newStackCapacity = ceilToPowerOf2(neededSlots);
   ASSERT(newStackCapacity > objThread->stackCapacity, "newStackCapacity error!");

   //记录原栈底以用于下面判断扩容后的栈是否是原地扩容
   Value* oldStackBottom = objThread->stack;

   uint32_t slotSize = sizeof(Value);
   objThread->stack = (Value*)memManager(vm, objThread->stack,
	 objThread->stackCapacity * slotSize, newStackCapacity * slotSize);
   objThread->stackCapacity = newStackCapacity;

   //为判断是否原地扩容
   long offset = objThread->stack - oldStackBottom;

   //说明os无法在原地满足内存需求, 重新分配了起始地址,下面要调整
   if (offset != 0) {
      //调整各堆栈框架的地址  
      uint32_t idx = 0;
      while (idx < objThread->usedFrameNum) {
	 objThread->frames[idx++].stackStart += offset; 
      }
      
      //调整"open upValue"
      ObjUpvalue* upvalue = objThread->openUpvalues;
      while (upvalue != NULL) {
	 upvalue->localVarPtr += offset;
	 upvalue = upvalue->next; 
      }
   
      //更新栈顶
      objThread->esp += offset;
   }
}

大运行时栈空间单位为Value。
根据传入参数neededSlots即需要的栈空间重新给objThread->stack申请动态内存,动态内存管理器memManager返回的内存地址如果变了,则给objThread->frames[idx++].stackStart和objThread->openUpvalues->localVarPtr重新加上改变的偏移offset。

4.createFrame

//为objClosure在objThread中创建运行时栈
inline static void createFrame(VM* vm, ObjThread* objThread,
      ObjClosure* objClosure, int argNum) {

   if (objThread->usedFrameNum + 1 > objThread->frameCapacity) { //扩容
      uint32_t newCapacity = objThread->frameCapacity * 2; 
      uint32_t frameSize = sizeof(Frame);
      objThread->frames = (Frame*)memManager(vm, objThread->frames,
	    frameSize * objThread->frameCapacity, frameSize * newCapacity);
      objThread->frameCapacity = newCapacity;
   }
   
   //栈大小等于栈顶-栈底
   uint32_t stackSlots = (uint32_t)(objThread->esp - objThread->stack);
   //总共需要的栈大小
   uint32_t neededSlots = stackSlots + objClosure->fn->maxStackSlotUsedNum;

   ensureStack(vm, objThread, neededSlots);

   //准备上cpu
   prepareFrame(objThread, objClosure, objThread->esp - argNum);
}

//为运行函数准备桢栈
void prepareFrame(ObjThread* objThread, ObjClosure* objClosure, Value* stackStart) {
   ASSERT(objThread->frameCapacity > objThread->usedFrameNum, "frame not enough!!");   
   //objThread->usedFrameNum是最新可用的frame
   Frame* frame = &(objThread->frames[objThread->usedFrameNum++]);

   //thread中的各个frame是共享thread的stack 
   //frame用frame->stackStart指向各自frame在thread->stack中的起始地址
   frame->stackStart = stackStart;
   frame->closure = objClosure;
   frame->ip = objClosure->fn->instrStream.datas;
}

函数闭包objClosure作为线程objThread的参数,让线程来运行这个函数。objClosure->fn->instrStream.datas中保存了指令流。想要执行函数的指令流,需要线程来提供运行时栈,objThread->stack为线程的大栈,线程给每个函数分配一个框架objThread->frames,框架中包含一个从大栈分配出去的小栈来作为该函数的运行时栈,frame->stackStart为该函数栈的起始地址。
对于createFrame,每次想要给一个函数闭包创建一个框架,需要扩容线程框架数和线程大运行时栈栈空间:

  1. 先查看线程原来的框架数是否够用,不够则扩大一倍
  2. 根据当前大运行时栈的总空间和本函数闭包需要的栈空间objClosure->fn->maxStackSlotUsedNum来调用ensureStack重新扩容大运行时栈空间
  3. 以上两项扩容完毕后,objThread->usedFrameNum是最新可用的frame,调用prepareFrame使该frame的ip指向函数闭包指令流objClosure->fn->instrStream.datas,该frame的运行时栈指向大运行时栈中对应位置。

5.创建与关闭upvalue

函数的局部变量存在函数的frame指向的小运行时栈中,函数内部是可以定义函数的,如果内部函数引用了外部函数的局部变量被称为open upvalue,那么该内部函数对应的线程会保存upvalue队列(objThread->openUpvalues),其中每个upvalue的upvalue->localVarPtr指向外部函数运行时栈中的局部变量。
如果外部函数的生命周期结束了,内部函数还需要继续引用外部函数的局部变量,这时的局部变量被称为closed upvalue。在关闭本函数的upvalue的时候,会把upvalue被保存在upvalue->closedUpvalue,而upvalue->localVarPtr不再指向本函数运行时栈,而是指向upvalue->closedUpvalue

5.1 closeUpvalue、createOpenUpvalue


//关闭在栈中slot为lastSlot及之上的upvalue
static void closeUpvalue(ObjThread* objThread, Value* lastSlot) {
   ObjUpvalue* upvalue = objThread->openUpvalues;
   while (upvalue != NULL && upvalue->localVarPtr >= lastSlot) {
      //localVarPtr改指向本结构中的closedUpvalue
      upvalue->closedUpvalue = *(upvalue->localVarPtr);
      upvalue->localVarPtr = &(upvalue->closedUpvalue);

      upvalue = upvalue->next;
   }
   objThread->openUpvalues = upvalue;
}

//创建线程已打开的upvalue链表,并将localVarPtr所属的upvalue以降序插入到该链表
static ObjUpvalue* createOpenUpvalue(VM* vm, ObjThread* objThread, Value* localVarPtr) {
   //如果openUpvalues链表为空就创建
   if (objThread->openUpvalues == NULL) {
      objThread->openUpvalues = newObjUpvalue(vm, localVarPtr);
      return objThread->openUpvalues;
   }

   //下面以upvalue.localVarPtr降序组织openUpvalues
   ObjUpvalue* preUpvalue = NULL;
   ObjUpvalue* upvalue = objThread->openUpvalues;

   //后面的代码保证了openUpvalues按照降顺组织,
   //下面向堆栈的底部遍历,直到找到合适的插入位置
   while (upvalue != NULL && upvalue->localVarPtr > localVarPtr) {
      preUpvalue = upvalue; 
      upvalue = upvalue->next;
   }

   //如果之前已经插入了该upvalue则返回
   if (upvalue != NULL && upvalue->localVarPtr == localVarPtr) {
      return upvalue;
   }

   //openUpvalues中未找到该upvalue,
   //现在就创建新upvalue,按照降序插入到链表
   ObjUpvalue* newUpvalue = newObjUpvalue(vm, localVarPtr);

   //保证了openUpvalues首结点upvalue->localVarPtr的值是最高的
   if (preUpvalue == NULL) {
      //说明上面while的循环体未执行,新结点(形参localVarPtr)的值大于等于链表首结点
      //因此使链表结点指向它所在的新upvalue结点
      objThread->openUpvalues = newUpvalue; 
   } else {
      //preUpvalue已处于正确的位置
      preUpvalue->next = newUpvalue;
   }
   newUpvalue->next = upvalue;

   return newUpvalue;//返回该结点
}

createOpenUpvalue就是选择排序算法。从大到小排序
作用是在传入参数线程objThread的upvalue链表objThread->openUpvalues中,将传入的value创建为upvalue,并按从大到小顺序插入该链表,最后返回创建的upvalue结点

6.运行虚拟机

6.1 runFile

//执行脚本文件
static void runFile(const char* path) {
   const char* lastSlash = strrchr(path, '/');
   if (lastSlash != NULL) {
      char* root = (char*)malloc(lastSlash - path + 2);
      memcpy(root, path, lastSlash - path + 1);
      root[lastSlash - path + 1] = '\0';
      rootDir = root;
   }

   VM* vm = newVM();
   const char* sourceCode = readFile(path);
   executeModule(vm, OBJ_TO_VALUE(newObjString(vm, path, strlen(path))), sourceCode);
   freeVM(vm);
}

6.2 executeModule

//执行模块
VMResult executeModule(VM* vm, Value moduleName, const char* moduleCode) {
   ObjThread* objThread = loadModule(vm, moduleName, moduleCode);
   return executeInstruction(vm, objThread);
}

6.3 loadModule、getModule

//载入模块moduleName并编译
static ObjThread* loadModule(VM* vm, Value moduleName, const char* moduleCode) {
   //确保模块已经载入到 vm->allModules
   //先查看是否已经导入了该模块,避免重新导入
   ObjModule* module = getModule(vm, moduleName);

   //若该模块未加载先将其载入,并继承核心模块中的变量
   if (module == NULL) {
      //创建模块并添加到vm->allModules
      ObjString* modName = VALUE_TO_OBJSTR(moduleName);
      ASSERT(modName->value.start[modName->value.length] == '\0', "string.value.start is not terminated!");

      module = newObjModule(vm, modName->value.start);

      pushTmpRoot(vm, (ObjHeader*)module);
      mapSet(vm, vm->allModules, moduleName, OBJ_TO_VALUE(module));
      popTmpRoot(vm);
      
      //继承核心模块中的变量
      ObjModule* coreModule = getModule(vm, CORE_MODULE);
      uint32_t idx = 0;
      while (idx < coreModule->moduleVarName.count) {
	 defineModuleVar(vm, module,
	       coreModule->moduleVarName.datas[idx].str,
	       strlen(coreModule->moduleVarName.datas[idx].str),
	       coreModule->moduleVarValue.datas[idx]);
	 idx++; 
      }
   }

   ObjFn* fn = compileModule(vm, module, moduleCode);
   pushTmpRoot(vm, (ObjHeader*)fn);
   ObjClosure* objClosure = newObjClosure(vm, fn);
   pushTmpRoot(vm, (ObjHeader*)objClosure);
   ObjThread* moduleThread = newObjThread(vm, objClosure);
   popTmpRoot(vm); // objClosure
   popTmpRoot(vm); // fn

   return moduleThread;  
}

//从modules中获取名为moduleName的模块
static ObjModule* getModule(VM* vm, Value moduleName) {
   Value value = mapGet(vm->allModules, moduleName);
   if (value.type == VT_UNDEFINED) {
      return NULL;
   }
   return (ObjModule*)(value.objHeader);
}

//新建线程
ObjThread* newObjThread(VM* vm, ObjClosure* objClosure) {
   ASSERT(objClosure != NULL, "objClosure is NULL!");

   Frame* frames = ALLOCATE_ARRAY(vm, Frame, INITIAL_FRAME_NUM);

   //加1是为接收者的slot
   uint32_t stackCapacity = ceilToPowerOf2(objClosure->fn->maxStackSlotUsedNum + 1);  
   Value* newStack = ALLOCATE_ARRAY(vm, Value, stackCapacity); 

   ObjThread* objThread = ALLOCATE(vm, ObjThread);
   initObjHeader(vm, &objThread->objHeader, OT_THREAD, vm->threadClass);

   objThread->frames = frames;
   objThread->frameCapacity = INITIAL_FRAME_NUM;
   objThread->stack = newStack;
   objThread->stackCapacity = stackCapacity;

   resetThread(objThread, objClosure);
   return objThread;
}
//重置thread
void resetThread(ObjThread* objThread, ObjClosure* objClosure) {
   objThread->esp = objThread->stack;  
   objThread->openUpvalues = NULL;
   objThread->caller = NULL;
   objThread->errorObj = VT_TO_VALUE(VT_NULL);
   objThread->usedFrameNum = 0;

   ASSERT(objClosure != NULL, "objClosure is NULL in function resetThread");
   prepareFrame(objThread, objClosure, objThread->stack);
}
  1. 调用loadModule有两种情况,一种是runfile运行脚本文件,该脚本文件是一个模块,所以需要加载该模块,也就是继续编译该模块里的程序;第二种是识别到关键字import,从而调用编译模块。
  2. 编译完模块后得到模块的指令流fn,申请新函数闭包objClosure保存fn,再申请新线程newObjThread保存该函数闭包objClosure。
  3. 新线程的初始框架数为默认框架数,同时上述的闭包是该新线程的第一个函数闭包,所以直接prepareFrame。待调用executeInstruction中的LOAD_CUR_FRAME后就可直接执行该函数闭包的指令流。

6.4 compileModule

//编译模块
ObjFn* compileModule(VM* vm, ObjModule* objModule, const char* moduleCode) {
   //各源码模块文件需要单独的parser
   Parser parser;
   parser.parent = vm->curParser;
   vm->curParser = &parser;

   if (objModule->name == NULL) {
      // 核心模块是core.script.inc
      initParser(vm, &parser, "core.script.inc", moduleCode, objModule);
   } else {
      initParser(vm, &parser, 
	    (const char*)objModule->name->value.start, moduleCode, objModule);
   }

   CompileUnit moduleCU;
   initCompileUnit(&parser, &moduleCU, NULL, false);

   //记录现在模块变量的数量,后面检查预定义模块变量时可减少遍历
   uint32_t moduleVarNumBefor = objModule->moduleVarValue.count;

   //初始的parser->curToken.type为TOKEN_UNKNOWN,下面使其指向第一个合法的token
   getNextToken(&parser);

   //编译模块
   while (!matchToken(&parser, TOKEN_EOF)) {
      compileProgram(&moduleCU);
   }

   //模块编译完成,生成return null返回,避免执行下面endCompileUnit中添加的OPCODE_END
   writeOpCode(&moduleCU, OPCODE_PUSH_NULL);  
   writeOpCode(&moduleCU, OPCODE_RETURN);  
   
   //检查在函数id中用行号声明的模块变量是否在引用之后有定义
   uint32_t idx = moduleVarNumBefor;
   while (idx < objModule->moduleVarValue.count) {
      //为简单起见,依然是遇到第一个错后就报错退出,后面的不再检查
      if (VALUE_IS_NUM(objModule->moduleVarValue.datas[idx])) {
	 char* str = objModule->moduleVarName.datas[idx].str;
	 ASSERT(str[objModule->moduleVarName.datas[idx].length] == '\0',
	       "module var name is not closed!");
	 uint32_t lineNo = VALUE_TO_NUM(objModule->moduleVarValue.datas[idx]);
	 COMPILE_ERROR(&parser, "line:%d, variable \'%s\' not defined!", lineNo, str);
      } 
      idx++;
   }

   //模块编译完成,当前编译单元置空
   vm->curParser->curCompileUnit = NULL;
   vm->curParser = vm->curParser->parent;
#if DEBUG
   return endCompileUnit(&moduleCU, "(script)", 8); 
#else
   return endCompileUnit(&moduleCU);
#endif
}

6.5 compileProgram

//编译程序
static void compileProgram(CompileUnit* cu) {
   if (matchToken(cu->curParser, TOKEN_CLASS)) {
      compileClassDefinition(cu);
   } else if (matchToken(cu->curParser, TOKEN_FUN)) {
      compileFunctionDefinition(cu);
   } else if (matchToken(cu->curParser, TOKEN_VAR)) {
      compileVarDefinition(cu, cu->curParser->preToken.type == TOKEN_STATIC); 
   } else if (matchToken(cu->curParser, TOKEN_IMPORT)) {
      compileImport(cu);
   } else {
      compileStatment(cu);
   }
}

6.6 executeInstruction

//执行指令
VMResult executeInstruction(VM* vm, register ObjThread* curThread) {
   vm->curThread = curThread;   
   register Frame* curFrame;
   register Value* stackStart;
   register uint8_t* ip;
   register ObjFn* fn;
   OpCode opCode;

   //定义操作运行时栈的宏
   //esp是栈中下一个可写入数据的slot
   #define PUSH(value)  (*curThread->esp++ = value)   //压栈
   #define POP()        (*(--curThread->esp))   //出栈
   #define DROP()       (curThread->esp--)	
   #define PEEK()       (*(curThread->esp - 1))	// 获得栈顶的数据
   #define PEEK2()      (*(curThread->esp - 2))	// 获得次栈顶的数据
   
   //下面是读取指令流:objfn.instrStream.datas
   #define READ_BYTE()  (*ip++)   //从指令流中读取一字节
   //读取指令流中的2字节
   #define READ_SHORT() (ip += 2, (uint16_t)((ip[-2] << 8) | ip[-1]))

   //当前指令单元执行的进度就是在指令流中的指针,即ip,将其保存起来
   #define STORE_CUR_FRAME() curFrame->ip = ip   // 备份ip以能回到当前

   //加载最新的frame
   #define LOAD_CUR_FRAME()        \
      /* frames是数组,索引从0起,故usedFrameNum-1 */   \
      curFrame = &curThread->frames[curThread->usedFrameNum - 1]; \
      stackStart = curFrame->stackStart; \
      ip = curFrame->ip; \
      fn = curFrame->closure->fn;

   #define DECODE loopStart: \
      opCode = READ_BYTE();\
      switch (opCode)

   #define CASE(shortOpCode) case OPCODE_##shortOpCode
   #define LOOP() goto loopStart

   LOAD_CUR_FRAME();
   DECODE {
      //若OPCODE依赖于指令环境(栈和指令流),会在各OPCODE下说明

      CASE(LOAD_LOCAL_VAR):
	  ......
	  ......
	  LOOP();
	  CASE(xxx)
	  ...
	}
}

方法调用会切换框架执行框架对应的函数闭包,采用的就是

case MT_SCRIPT:
	       STORE_CUR_FRAME();
	       createFrame(vm, curThread, (ObjClosure*)method->obj, argNum);
	       LOAD_CUR_FRAME();   //加载最新的frame
	       break;

显然,在curThread-frames中,新框架就是老框架的下一个。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1187455.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Sysmon 日志监控

系统监视器 &#xff08;Sysmon&#xff09; 是一个 Windows 日志记录加载项&#xff0c;它提供精细的日志记录功能并捕获默认情况下通常不记录的安全事件。它提供有关进程创建、网络连接、文件系统更改等的信息。分析 Sysmon 日志对于发现恶意活动和安全威胁至关重要。 在不断…

浅谈智能变电站自动化系统的应用与产品选型

安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;现如今&#xff0c;智能变电站发展已经成为了电力系统发展过程中的内容&#xff0c;如何提高智能变电站的运行效率也成为电力系统发展的一个重要目标&#xff0c;为了能够更好地促进电力系统安全稳定运行&#xff0c;…

单片机程序无法下载?

原因一&#xff1a;电源问题 电源可能是导致STM32微控制器无法下载程序的一个常见原因。确保电源稳定对于正常运行和下载程序至关重要。以下是一些电源问题&#xff1a; 1. 电源电压不足&#xff1a;如果STM32微控制器没有足够的电压供应&#xff0c;它可能无法正常工作或下载程…

【AICFD案例教程】电子机箱风冷散热分析

AICFD是由天洑软件自主研发的通用智能热流体仿真软件&#xff0c;用于高效解决能源动力、船舶海洋、电子设备和车辆运载等领域复杂的流动和传热问题。软件涵盖了从建模、仿真到结果处理完整仿真分析流程&#xff0c;帮助工业企业建立设计、仿真和优化相结合的一体化流程&#x…

关于脑机接口实现的思考——再谈埃隆马斯克的脑机接口新动态

今天看到了埃隆马斯克的Neuralink的新动态&#xff0c;要招募志愿者进行试验&#xff0c;因此基于此前的文章&#xff0c;这里做一个新的探讨&#xff0c;第一篇文章在下面的链接&#xff0c;大家可以先过去看一下&#xff1a; 关于脑机接口该如何实现的考虑 埃隆马斯克的Neura…

评估APP网页小程序代码UI开发H5估价师怎么评估开发精确研发价格?

作为一名应用程序开发评估师&#xff0c;可能涉及到的主要任务是为特定的应用程序提供估算开发成本和所需时间预测。为了为一个应用程序更准确地评估价格&#xff0c;须遵循以下几个步骤&#xff1a; 问: 如何让一个App更好、更精确地评估出价格&#xff1f; 答: 以下是一个可…

MOSFET和IGBT栅极驱动器TLP250H(D4-TP1,F)电路的基本原理

TLP250H&#xff0c;TLP250H(D4-TP1,F)是SOP8封装中的光电耦合器&#xff0c;由GaA组成ℓ作为红外发光二极管&#xff08;LED&#xff09;光学耦合到集成的高增益、高速光电探测器IC芯片。它在高达125℃的温度下提供有保证的性能和规格. TLP250H具有内部法拉第屏蔽&#xff0c;…

浏览器插件实现国税网自动登录,以及解决浏览器记住密码会自动填充表单无法修改的问题

公司的做账系统&#xff0c;用户在系统里设置保存了国税网的账号密码以后&#xff0c;下次点击进入国税网&#xff0c;能够直接进入国税系统。 之前的解决方案是pupptteer模拟登录一遍拿到cookie等登录凭证后&#xff0c;保存到数据库&#xff0c;然后插件请求接口拿到cookie&…

柴油发电机负载测试的方法

柴油发电机负载测试是检查发电机组性能的重要环节&#xff0c;通过模拟实际工作负载&#xff0c;检测发电机组在不同负载下的运行情况&#xff0c;以确保其安全可靠地为用电设备提供电力。以下是柴油发电机负载测试的方法&#xff1a; 1. 准备工作&#xff1a;在进行负载测试之…

黔院长 | 黄帝内经:脏气法时论!

自然界有四季&#xff0c;四季更替影响万物的轮回。人体脏腑功能和气机运行与时间变化也有显著的影响。古人结合五脏之气&#xff0c;取四时五行的生克规律作为救治疾病的法则&#xff0c;对至今中医养生有重要参考作用&#xff01; 五行&#xff0c;即木、火、土、金、水&…

C#Web文件上传的几种方式

1.第一种上传方式,基本通用于.net所有的框架 [HttpPost][Route("Common/uploadFile1")]public string uploads(){HttpContextBase context (HttpContextBase)Request.Properties["MS_HttpContext"];//获取传统contextHttpRequestBase request context.Re…

springbootMysql文华学院青年志愿者服务预约系统97973-计算机毕业设计项目选题推荐(附源码)

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 文华学院青年志愿者服务预约系统&#xff0c;主要的模块包括管理员&#xff1a;后台首页、轮播图、通知公告管理、资源管理&#xff08;新闻资…

人工智能-卷积神经网络(LeNet)

为了能够应用softmax回归和多层感知机&#xff0c;我们首先将每个大小为\(28\times28\)的图像展平为一个784维的固定长度的一维向量&#xff0c;然后用全连接层对其进行处理。 而现在&#xff0c;我们已经掌握了卷积层的处理方法&#xff0c;我们可以在图像中保留空间结构。 同…

数据分析:小红书新兴场景洞察,捕捉消费新势力

导语 飞盘、骑行&#xff0c;露营…如今&#xff0c;户外运动日渐被人们所喜爱。近年来&#xff0c;继飞盘和骑行后&#xff0c;一项新潮的户外运动越来越受到年轻人的欢迎&#xff0c;路亚钓鱼&#xff0c;越来越多年轻人在入坑钓鱼。 图 | 小红书 图 | 小红书 什么是“路亚…

11.(vue3.x+vite)组件间通信方式之ref与$parent、$children

前端技术社区总目录(订阅之前请先查看该博客) 示例效果 注: (1)ref 加在标签(div等)上,是拿到dom 对象 (2)ref加上组件上,拿到的是组件的引用 (3)让父组件获取子组件的数据或者方法需要通过defineExpose对外暴露,另外让父组件获取子组件的数据或者方法需要通过d…

查看监控提示码流已加密,请切换至本地配置页面设置密钥后重启预览

环境&#xff1a; 硬盘录像机DS-8632N-I16 谷歌浏览器 问题描述&#xff1a; 查看监控提示码流已加密,请切换至本地配置页面设置密钥后重启预览 解决方案&#xff1a; 1.进入系统-安全管理-安全服务&#xff0c;关闭启用码流加密&#xff0c;保存 2.进入网络-高级配置-平…

AI:73-结合语法知识的神经机器翻译研究

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

Docker - 安装

Docker安装 Docker的基本组成 镜像&#xff08;image&#xff09;: ​ Docker镜像就好比是一个模板&#xff0c;可以通过这个模板来创建容器服务&#xff0c;tomcat镜像 -> run -> tomcat01容器&#xff08;提供服务器&#xff09;&#xff0c;通过这个镜像可以创建多个…

【VUE+ elementUI 实现动态表头渲染】

VUE elementUI 实现动态表头渲染 1、定义 columns&#xff08;表头数据&#xff09; 和 dataList&#xff08;表格数据&#xff09; data() {return {loading: false,dataList: [{ name: 张三, sex: 男, age: 18 },{ name: 林琳, sex: 女, age: 20 },{ name: 王五, sex: 男, …

安卓开发实例:高德地图

想要在app里面显示高德地图&#xff0c;遇到了很多问题&#xff0c;开始想显示百度地图的&#xff0c;个人感觉不喜欢百度地图&#xff0c;跟高德地图有缘&#xff0c;所以就弄个高德地图。 当然你可以直接看开发文档啊&#xff0c;慢走不送&#xff0c;谢谢。 https://lbs.ama…