jvm造輪子

来源:https://www.cnblogs.com/zumengjie/archive/2022/06/21/16396338.html
-Advertisement-
Play Games

前言 當我們開始學習Python時,我們會養成一些不良編碼習慣,而更可怕的是我們連自己也不知道。 我們學習變成的過程中,大概有會這樣的經歷: 寫的代碼只能完成了一次工作,但後來再執行就會報錯或者失敗,令人感到懊惱, 或者偶然發現一個內置函數可以讓你的工作更輕鬆時,瞬間豁然開朗。 我們中的大多數人仍然 ...


博客內容來源於 劉欣老師的課程,劉欣老師的公眾號 碼農翻身 

博客內容來源於 Java虛擬機規範(JavaSE7)

博客內容的源碼 https://gitee.com/zumengjie/litejvm

閱讀此博客請配合源碼食用。

ClassLoader

ClassLoader就是根據類名去classpath路徑上找到Class文件然後解析Class文件形成Class類對象。

package com.datang.litejvm.loader;

import com.datang.litejvm.clz.Class;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.List;


/**
 * @author: 頂風少年
 * @Description: 類載入器
 * @date: 13:54 2022/6/10
 **/
public class ClassLoader {

    /**
     * @author: 頂風少年
     * @Description: 存放環境變數
     * @date: 21:58 2022/6/8
     **/
    private List<String> clzPaths = new ArrayList<String>();


    /**
     * @author: 頂風少年
     * @Description: 添加環境變數 classPath
     * @date: 21:53 2022/6/8
     **/
    public void addClassPath(String path) {
        if (this.clzPaths.contains(path)) {
            return;
        }
        this.clzPaths.add(path);
    }


    /**
     * @author: 頂風少年
     * @Description: 返回classPath, 多個中間使用 ; 拼接
     * @date: 21:53 2022/6/8
     **/
    public String getClassPath() {
        return StringUtils.join(this.clzPaths, ";");
    }

    /**
     * @author: 頂風少年
     * @Description: 將classpath和className拼接獲取類路徑
     * @date: 21:53 2022/6/8
     **/
    public byte[] readBinaryCode(String className) {
        className = className.replace('.', File.separatorChar) + ".class";
        for (String path : this.clzPaths) {
            String clzFileName = path + File.separatorChar + className;
            byte[] codes = loadClassFile(clzFileName);
            if (codes != null) {
                return codes;
            }
        }
        return null;
    }

    /**
     * @author: 頂風少年
     * @Description: 根據類路徑讀取class文件
     * @date: 21:54 2022/6/8
     **/
    private byte[] loadClassFile(String clzFileName) {
        File f = new File(clzFileName);
        try {
            return IOUtils.toByteArray(new FileInputStream(f));
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @author: 頂風少年
     * @Description: 解析class文件形成Class對象,在這裡是形成ClassFile對象
     * @date: 13:55 2022/6/10
     **/
    public Class loadClass(String className) {
        byte[] codes = this.readBinaryCode(className);
        ClassParser parser = new ClassParser();
        return parser.parse(codes);
    }
}
View Code

Class

首先我們對一個已經編譯完成的class文件進行解析,解析的過程就是讀取每一個位元組,然後瞭解其結構以及含義,將其解析為一個Class類。使用javap -v xxx.class命令可以查看該class的結構。

Class位元組碼文件的內容十分緊湊,首先是魔數,小版本號,大版本號,常量池,類訪問許可權,類,父類,父介面,欄位列表,方法列表。常量池占了大部分的位元組碼文件,其餘的部分很多都會引用常量池項。

解析Class文件就是讀取固定長度的位元組,魔數為4個位元組,小版本號2個位元組,大版本號兩個位元組。接下來的常量池稍微複雜,兩個位元組標記了常量池項的總個數,其中每一個常量池項都有對應的數據結構。需要先讀取1個位元組判斷它的結構,例如tag是1則它是一個CONSTANT_Utf8_info,這是一個最簡單的結構,2個位元組的長度表示後續字元串的長度,然後再讀取對應長度的位元組數。常量池項是可以引用常量池項的,例如第7項是一個CONSTANT_Class_info這個結構包含了2個位元組的index它指向常量池的第40項目,是Class對應的類名。解析常量池時需要根據tag判斷常量池項是什麼結構,然後再根據具體的結構讀取位元組。

接下來是讀取2個位元組類的訪問許可權,無論是類,欄位,方法都有訪問許可權,註意訪問許可權可能有多個,例如類可以是 <public> <final> 

接下來是2個位元組的類名,2個位元組的父類名,2個位元組的介面個數,每個介面名也是2個位元組。

之後是類成員變數也就是欄位,2個位元組的成員個數,每個欄位里包含2個位元組的訪問許可權,2個位元組的欄位名,2個位元組的欄位類型,兩個位元組的的屬性個數,這個屬性也是有多種結構的,方法里也有,放到方法里說。

位元組碼文件的最後一部分是方法,2個位元組的方法個數,每個方法包含2個位元組的訪問許可權,2個位元組的方法名,2個位元組的方法簽名(入參和返回值)。2個位元組的屬性個數,每個屬性中包含,2個位元組的屬性名稱。在方法中只有一個屬性就是Code。

jvm定義的屬性有以下結構,欄位的屬性也在其中。

         

首先是2個位元組的屬性名稱,根據屬性名判斷屬性。每個屬性都有4個位元組的屬性長度,它標記了接下來的屬性內容的位元組數。Code屬性中包含2個位元組的棧深度,2個位元組的局部變數表,4個位元組的位元組碼長度,位元組碼長度為N則表示有N個位元組的位元組碼指令,每個指令1個位元組。對於位元組碼指令需要展開說,每個位元組碼指令根據其含義的不同可能帶有不同的參數,例如bb含義為new對象,bb的後邊有2個位元組是它的參數,這兩個參數指向了常量池中的常量項是需要new的對象類名。

在位元組碼指令後是2個位元組的異常長度,異常的相關的內容,我本次沒有解析,只是手動跳過了異常。

Code屬性中還包含了2個其他屬性。LineNumberTable和LocalVariableTable其一是行號相關,其二是參數列表。

2個位元組區分屬性的名稱,4個位元組表示屬性內容的長度。LineNumberTable有2個位元組的行數,每一行中包含2個位元組的起始位,2個位元組的行號。

LocalVariableTable有2個位元組的參數個數,每個參數有2個位元組的起始位,2個位元組的長度,2個位元組的屬性名,2個位元組的下標。

以上是Class文件中可以解析出來的內容,當然根據Class的複雜程度,解析出來的內容不同,我這裡是最基本的Class屬性,將其解析完畢後,形成一個Class類。

package com.datang.litejvm.clz;


import com.datang.litejvm.constant.ConstantClassInfo;
import com.datang.litejvm.constant.ConstantPool;
import com.datang.litejvm.constant.ConstantUTF8Info;

import java.util.Iterator;
import java.util.List;

/**
 * @author: 頂風少年
 * @Description: Class類
 * @date: 13:55 2022/6/10
 **/
public class Class {


    //魔數
    private String magic;

    //小版本號
    private int minorVersion;

    //大版本號
    private int majorVersion;

    //常量池
    private ConstantPool pool;

    //訪問標記
    private ClassAccessFlag classAccessFlag;

    //
    private int thisClassIndex;

    //父類
    private int superClassIndex;

    private String superClassName;

    //介面引用
    private List<Integer> interfaceIndexList;

    //屬性列表
    private List<Field> fieldList;

    //方法
    private List<Method> methodList;

    //-------------------------------------------------------

    /**
     * @author: 頂風少年
     * @Description: 魔數
     * @date: 11:39 2022/6/10
     **/
    public String getMagic() {
        return magic;
    }

    /**
     * @author: 頂風少年
     * @Description: 魔數
     * @date: 11:39 2022/6/10
     **/
    public void setMagic(String magic) {
        this.magic = magic;
    }

    /**
     * @author: 頂風少年
     * @Description: 小版本號
     * @date: 11:28 2022/6/10
     **/
    public int getMinorVersion() {
        return minorVersion;
    }

    /**
     * @author: 頂風少年
     * @Description: 小版本號
     * @date: 11:28 2022/6/10
     **/
    public void setMinorVersion(int minorVersion) {
        this.minorVersion = minorVersion;
    }

    /**
     * @author: 頂風少年
     * @Description: 大版本號
     * @date: 11:28 2022/6/10
     **/
    public int getMajorVersion() {
        return majorVersion;
    }

    /**
     * @author: 頂風少年
     * @Description: 大版本號
     * @date: 11:28 2022/6/10
     **/
    public void setMajorVersion(int majorVersion) {
        this.majorVersion = majorVersion;
    }

    /**
     * @author: 頂風少年
     * @Description: 常量池
     * @date: 11:28 2022/6/10
     **/
    public ConstantPool getConstantPool() {
        return pool;
    }

    /**
     * @author: 頂風少年
     * @Description: 常量池
     * @date: 11:28 2022/6/10
     **/
    public void setConstPool(ConstantPool pool) {
        this.pool = pool;
    }

    /**
     * @author: 頂風少年
     * @Description: 訪問標記
     * @date: 17:18 2022/6/10
     **/
    public ClassAccessFlag getAccessFlag() {
        return classAccessFlag;
    }

    /**
     * @author: 頂風少年
     * @Description: 訪問標記
     * @date: 17:18 2022/6/10
     **/
    public void setAccessFlag(ClassAccessFlag classAccessFlag) {
        this.classAccessFlag = classAccessFlag;
    }

    /**
     * @author: 頂風少年
     * @Description: 類
     * @date: 17:18 2022/6/10
     **/
    public int getThisClassIndex() {
        return thisClassIndex;
    }

    public void setThisClassIndex(int thisClassIndex) {
        this.thisClassIndex = thisClassIndex;
    }

    /**
     * @author: 頂風少年
     * @Description: 父類
     * @date: 17:18 2022/6/10
     **/
    public int getSuperClassIndex() {
        return superClassIndex;
    }

    public void setSuperClassIndex(int superClassIndex) {
        this.superClassIndex = superClassIndex;
        ConstantClassInfo constantClassInfo = (ConstantClassInfo) pool.getConstantInfo(superClassIndex);
        this.superClassName = constantClassInfo.getClassName();
    }

    public String getSuperClassName() {
        return superClassName;
    }

    /**
     * @author: 頂風少年
     * @Description: 介面
     * @date: 17:18 2022/6/10
     **/
    public List<Integer> getInterfaceIndexList() {
        return interfaceIndexList;
    }

    public void setInterfaceIndexList(List<Integer> interfaceIndexList) {
        this.interfaceIndexList = interfaceIndexList;
    }

    /**
     * @author: 頂風少年
     * @Description: 屬性列表
     * @date: 11:22 2022/6/12
     **/
    public List<Field> getFieldList() {
        return fieldList;
    }

    /**
     * @author: 頂風少年
     * @Description: 屬性列表
     * @date: 11:22 2022/6/12
     **/
    public void setFieldList(List<Field> fieldList) {
        this.fieldList = fieldList;
    }

    /**
     * @author: 頂風少年
     * @Description: 方法
     * @date: 18:31 2022/6/12
     **/
    public List<Method> getMethodList() {
        return methodList;
    }

    /**
     * @author: 頂風少年
     * @Description: 方法
     * @date: 18:31 2022/6/12
     **/
    public void setMethodList(List<Method> methodList) {
        this.methodList = methodList;
    }


    /**
     * @author: 頂風少年
     * @Description: 根據方法名和方法簽名查詢方法
     * @date: 10:47 2022/6/16
     **/
    public Method getMethod(String methodName, String paramAndResultType) {
        Method rMethod = null;
        Iterator<Method> iter = methodList.iterator();
        while (iter.hasNext()) {
            Method method = iter.next();
            int nameIndex = method.getNameIndex();
            int descriptorIndex = method.getDescriptorIndex();

            ConstantUTF8Info nameInfo = (ConstantUTF8Info) pool.getConstantInfo(nameIndex);
            ConstantUTF8Info descriptorInfo = (ConstantUTF8Info) pool.getConstantInfo(descriptorIndex);

            if (nameInfo.getBytes().equals(methodName) && descriptorInfo.getBytes().equals(paramAndResultType)) {
                rMethod = method;
            }
        }
        return rMethod;
    }

    /**
     * @author: 頂風少年
     * @Description: 查詢main方法
     * @date: 10:36 2022/6/16
     **/
    public Method getMainMethod() {
        return getMethod("main", "([Ljava/lang/String;)V");
    }
}
View Code

MethodArea

方法區中有個Map它的key是class類名,value是Class對象。使用ClassLoader解析後的Class對象都存在這裡。

package com.datang.litejvm.engin;


import com.datang.litejvm.clz.Field;
import com.datang.litejvm.clz.Method;
import com.datang.litejvm.constant.ConstantFieldRefInfo;
import com.datang.litejvm.constant.ConstantMethodRefInfo;
import com.datang.litejvm.loader.ClassLoader;
import com.datang.litejvm.clz.Class;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: 頂風少年
 * @Description: 方法區
 * @date: 10:49 2022/6/16
 **/
public class MethodArea {
    
    public static final MethodArea instance = new MethodArea();
    
    /**
     * 註意:我們做了極大的簡化, ClassLoader 只有一個, 實際JVM中的ClassLoader,是一個雙親委托的模型
     */
    
    private ClassLoader classLoader = null;

    /**
     * @author: 頂風少年
     * @Description: 存放所有的Class
     * @date: 10:39 2022/6/16
     **/
    Map<String, Class> map = new HashMap<String, Class>();
    
    private MethodArea(){        
    }

    /**
     * @author: 頂風少年
     * @Description: 單例,獲取常量池
     * @date: 10:39 2022/6/16
     **/
    public static MethodArea getInstance(){
        return instance;
    }
    
    public void setClassFileLoader(ClassLoader clzLoader){
        this.classLoader = clzLoader;
    }

    /**
     * @author: 頂風少年
     * @Description: 獲取main方法
     * @date: 10:39 2022/6/16
     **/
    public Method getMainMethod(String className){
        Class clz = this.findClass(className);
        return clz.getMainMethod();
    }

    /**
     * @author: 頂風少年
     * @Description: 從指定class中 根據名稱獲取方法
     * @date: 22:23 2022/6/16
     **/
    public Method getMethod(ConstantMethodRefInfo constantMethodRefInfo){
        Class aClass = findClass(constantMethodRefInfo.getClassName());
        Method method = aClass.getMethod(constantMethodRefInfo.getMethodName(), constantMethodRefInfo.getParamAndResult());
        return method;
    }

    /**
     * @author: 頂風少年
     * @Description: 創建Class
     * @date: 10:38 2022/6/16
     **/
    public Class findClass(String className){
        if(map.get(className) != null){
            return map.get(className);
        }
        // 看來該class 文件還沒有load過
        Class aClass = this.classLoader.loadClass(className);
        map.put(className, aClass);
        return aClass;
    }
}
View Code

ExecutorEngine

執行引擎中主要有一個棧結構,棧中的每個元素是StackFrame棧幀。執行引擎執行第一步將main方法壓入棧中,然後就是執行棧幀。每個棧幀其實就是一個method當方法執行結束後返回ExecutionResult裡邊封裝了該方法是暫存運行其他method還是方法運行結束出棧。如果是運行其他的method則將跳轉方法壓入棧中,並且傳遞參數,如果是方法執行結束則將當前方法出棧。執行引擎就是在不斷的判斷棧內是否還有元素,如果棧為空則表示當前程式執行結束。

package com.datang.litejvm.engin;

import com.datang.litejvm.clz.Method;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * @author: 頂風少年
 * @Description: 執行引擎
 * @date: 15:19 2022/6/16
 **/
public class ExecutorEngine {

    /**
     * @author: 頂風少年
     * @Description: 棧
     * @date: 15:41 2022/6/16
     **/
    private Stack<StackFrame> stack = new Stack<StackFrame>();


    public void execute(Method mainMethod) {
        //創建main棧幀
        StackFrame stackFrame = StackFrame.create(mainMethod);
        //入棧
        stack.push(stackFrame);
        //接下來就是對棧做操作了,入棧,出棧
        while (!stack.isEmpty()) {
            //拿到棧頂棧幀
            StackFrame frame = stack.peek();
            //執行棧幀
            ExecutionResult result = frame.execute();
            //暫停,並運行新的棧幀.有函數調用了
            if (result.isPauseAndRunNewFrame()) {
                //下一個method
                Method nextMethod = result.getNextMethod();
                //形成新的棧幀
                StackFrame nextFrame = StackFrame.create(nextMethod);
                nextFrame.setCallerFrame(frame);
                setupFunctionCallParams(frame, nextFrame);
                //將新的棧幀也入棧
                stack.push(nextFrame);
            } else {
                //出棧
                stack.pop();
            }
        }
    }

    /**
     * @author: 頂風少年
     * @Description: 給下個調用方法設置參數
     * @date: 16:07 2022/6/16
     **/
    private void setupFunctionCallParams(StackFrame currentFrame, StackFrame nextFrame) {
        Method nextMethod = nextFrame.getMethod();
        //獲取參數列表
        List<String> parameterList = nextMethod.getParameterList();
        List<JavaObject> values = new ArrayList<>();
        //要添加 this
        int paramNum = parameterList.size() + 1;

        while (paramNum > 0) {
            values.add(currentFrame.getOperandStack().pop());
            paramNum--;
        }

        List<JavaObject> params = new ArrayList<>();
        for (int i = values.size() - 1; i >= 0; i--) {
            params.add(values.get(i));
        }
        //設置局部變數表
        nextFrame.setLocalVariableTable(params);
    }

}
View Code

StackFrame

每個方法入棧都會形成一個StackFrame,棧幀中包含兩個數據結構。局部變數表和操作數棧,局部變數表是用來存放方法的入參,方法內的計算結果,操作數棧則是真正運算的地方,我們經常說的 1 + 2 = 3 的操作其實是在操作數棧進行的,先將 1 和 2 壓入操作數棧,將 1 和 2 出棧進行運算最後得出的 3 將其再次壓入操作數棧。StackFrame執行時就是從method總取出Code屬性中的操作指令,一條一條的執行。每條指令執行結束後會對ExecutionResult進行設置。

package com.datang.litejvm.engin;

import com.datang.litejvm.clz.Method;
import com.datang.litejvm.cmd.ByteCodeCommand;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * @author: 頂風少年
 * @Description: 函數棧幀
 * @date: 15:41 2022/6/16
 **/
public class StackFrame {
    //局部變數表
    private List<JavaObject> localVariableTable = new ArrayList<JavaObject>();
    //操作數棧
    private Stack<JavaObject> operandStack = new Stack<JavaObject>();

    //位元組碼指令偏移量,指向下一個操作指令
    int index = 0;

    //當前方法
    private Method m = null;

    //上一個函數棧幀
    private StackFrame callerFrame = null;

    private StackFrame(Method m) {
        this.m = m;
    }

    //創建函數棧幀
    public static StackFrame create(Method m) {
        StackFrame frame = new StackFrame(m);
        return frame;
    }

    /**
     * @author: 頂風少年
     * @Description: 上一個函數棧幀
     * @date: 16:44 2022/6/16
     **/
    public StackFrame getCallerFrame() {
        return callerFrame;
    }

    public void setCallerFrame(StackFrame callerFrame) {
        this.callerFrame = callerFrame;
    }

    /**
     * @author: 頂風少年
     * @Description: 棧幀所屬方法
     * @date: 16:45 2022/6/16
     **/
    public Method getMethod() {
        return m;
    }

    /**
     * @author: 頂風少年
     * @Description: 設置局部變數表
     * @date: 16:40 2022/6/16
     **/
    public void setLocalVariableTable(List<JavaObject> values) {
        this.localVariableTable = values;
    }

    /**
     * @author: 頂風少年
     * @Description: 獲取局部變數表中的某個變數
     * @date: 10:22 2022/6/17
     **/
    public JavaObject getLocalVariableValue(int index) {
        return this.localVariableTable.get(index);
    }

    /**
     * @author: 頂風少年
     * @Description: 向局部變數表設置值
     * @date: 10:38 2022/6/17
     **/
    public void setLocalVariableValue(int index, JavaObject jo) {
        //問題: 為什麼要這麼做??
        if (this.localVariableTable.size() - 1 < index) {
            for (int i = this.localVariableTable.size(); i <= index; i++) {
                this.localVariableTable.add(null);
            }
        }
        this.localVariableTable.set(index, jo);
    }


    /**
     * @author: 頂風少年
     * @Description: 執行方法, 就是執行位元組碼指令
     * @date: 17:11 2022/6/16
     **/
    public ExecutionResult execute() {
        List<ByteCodeCommand> cmds = m.getCodeAttr().getCmds();
        while (index < cmds.size()) {
            //執行結果
            ExecutionResult result = new ExecutionResult();
            //下一條位元組碼指令
            ByteCodeCommand cmd = cmds.get(index);
            System.out.println(cmd.toString());
            //執行
            cmd.execute(this, result);
            //運行下一條
            if (result.isRunNextCmd()) {
                index++;
            } else if (result.isExitCurrentFrame()) {
                //退出當前棧幀,return 剩餘的棧幀不執行了
                return result;
            } else if (result.isPauseAndRunNewFrame()) {
                //暫停當前棧幀,執行新的函數棧幀
                index++;
                return result;
            } else if (result.isJump()) {
                //跳轉指令,跳轉到下一個位元組碼指令
                int offset = result.getNextCmdOffset();
                //設置下一個指令的偏移量
                this.index = getNextCommandIndex(offset);
            } else {
                index++;
            }
        }
        //如果迴圈走完了,說明沒有任何的跳轉,停止,表示當前StackFrame的指令全部執行完畢,可以退出了
        ExecutionResult result = new ExecutionResult();
        result.setNextAction(ExecutionResult.EXIT_CURRENT_FRAME);
        return result;
    }

    /**
     * @author: 頂風少年
     * @Description: 根據偏移量查詢位元組碼指令
     * @date: 17:48 2022/6/16
     **/
    
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 引言:今天閱讀前人源碼,看見一個提取文件名字的功能,用了string的函數折騰了一大堆,結果一查MsDoc,微軟原來早都提供了5個用於提取文件名稱的函數。 個人技術博客(文章整理+源碼): https://zobolblog.github.io/LearnWinAPI/ 最終效果: 1.CFileD ...
  • 點擊藍色“程式員黃小斜”關註我喲 加個“星標”,每天和你一起多進步一點點! 今天給大家分享一篇有意思的爽文,但也是根據多年之前一個真實報道改編而來的。 本文字數較多,建議先收藏,上下班路上、帶薪上廁所、渾水摸魚時再慢慢看~ 來源: https://www.zhihu.com/question/483 ...
  • Spring5——JdbcTemplate筆記 概念 template,翻譯過來是模板的意思,顧名思義,JdbcTemplate就是一個JDBC的模板,它對JDBC進行了封裝,可以很方便地實現對資料庫的CRUD(增、刪、改、查)的操作。 JdbcTemplate準備工作 引入相關的依賴 druid- ...
  • 給大家推薦八個非常實用的Python案例,希望大家看過後能夠有所收穫! 1、合併兩個字典 Python3.5之後,合併字典變得容易起來,我們可以通過**符號解壓字典,並將多個字典傳入{}中,實現合併。 def Merge(dict1,dict2): res = {**dict1,**dict2} r ...
  • 1、Collections sort(List list) 自然升序排序 reverse(List<?> list) 集合反轉 binarySearch(List<? extends Comparable<? super T>> list, T key) 二分查找(要求集合有序) addAll(Co ...
  • 前言 利用selenium在做自動化測試的時候,經常會用到數據來做批量測試,常用的方式有讀取txt文件,xml文件,csv文件以及excel文 件幾種。 使用 excel 來做數據管理時,需要利用 xlrd、xlwt 開源包來讀寫 excel。 1、安裝xlrd、xlwt pip install x ...
  • 大佬的理解-> Java多線程(三)--synchronized關鍵字詳情 大佬的理解-> Java多線程(三)--synchronized關鍵字續 1、問題引入 買票問題 1.1 通過繼承Thread買票 繼承Thread買票案例 /* 模擬網路購票,多線程資源共用問題,繼承Thread方式; 結 ...
  • 控制結構 順序 程式從上到下逐行地執行,中間沒有任何判斷和跳轉。 順序控制舉例和註意事項 Java中定義成員變數時採用合法的前向引用。如: public class Test{ ​ int num1 = 12; ​ int num2 = num1 + 2; } 錯誤形式: public class ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...