Fabric1.4:Go 链码开发与编写

1 链码结构

1.1 链码接口

链码启动必须通过调用 shim 包中的 Start 函数,传递一个类型为 Chaincode 的参数,该参数是一个接口类型,有两个重要的函数 Init 与 Invoke 。

type Chaincode interface{
    Init(stub ChaincodeStubInterface) peer.Response
    Invoke(stub ChaincodeStubInterface) peer.Response
}
  • Init:在链码实例化或升级时被调用, 完成初始化数据的工作
  • Invoke:更新或查询帐本数据状态时被调用, 需要在此方法中实现响应调用或查询的业务逻辑

实际开发中, 开发人员可以自行定义一个结构体,重写 Chaincode 接口的两个方法,并将两个方法指定为自定义结构体的成员方法。

1.2 链码结构

package main

// 引入必要的包
import(
    "fmt"
    "github.com/hyperledger/fabric/core/chaincode/shim"
    pb "github.com/hyperledger/fabric/protos/peer"
)

// 声明一个结构体
type SimpleChaincode struct {

}

// 为结构体添加Init方法
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response{
  // 在该方法中实现链码初始化或升级时的处理逻辑
  // 编写时可灵活使用stub中的API
}

// 为结构体添加Invoke方法
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response{
  // 在该方法中实现链码运行中被调用或查询时的处理逻辑
  // 编写时可灵活使用stub中的API
}

// 主函数,需要调用shim.Start( )方法
func main() {
  err := shim.Start(new(SimpleChaincode))
  if err != nil {
     fmt.Printf("Error starting Simple chaincode: %s", err)
  }
}
  • shim: 用来访问/操作数据状态、事务上下文和调用其他链代码的 API, 链码通过 shim.ChaincodeStub 提供的方法来读取和修改账本的状态
  • peer: 提供了链码执行后的响应信息的 API,peer.Response 封装了响应信息

2 链码相关的 API

shim 包提供了如下几种类型的接口:

  • 参数解析 API:调用链码时需要给被调用的目标函数/方法传递参数,该 API 提供解析这些参数的方法
  • 账本状态数据操作 API:该 API 提供了对账本数据状态进行操作的方法,包括对状态数据的查询及事务处理等
  • 交易信息获取 API:获取提交的交易信息的相关 API
  • 事件处理 API:与事件处理相关的 API
  • 对 PrivateData 操作的 API: Hyperledger Fabric 在 1.2.0 版本中新增的对私有数据操作的相关 API

2.1 参数解析 API

// 返回调用链码时在交易提案中指定提供的被调用函数及参数列表
GetArgs() [][]byte

// 返回调用链码时在交易提案中指定提供的参数列表
GetArgsSlice() ([]byte, error)

// 返回调用链码时在交易提案中指定提供的被调用的函数名称及其参数列表
GetFunctionAndParameters() (function string, params []string)

// 返回调用链码时指定提供的参数列表
GetStringArgs() []string

一般使用 GetFunctionAndParameters() 及 GetStringArgs() 两个获取被调用函数及参数列表 。

2.2 账本数据状态操作 API

// 根据指定的 Key 查询相应的数据状态
GetState(key string) ([]byte, error)

// 根据指定的 key,将对应的 value 保存在分类账本中
PutState(key string, value []byte) error

// 根据指定的 key 将对应的数据状态删除
DelState(key string) error

// 根据指定的开始及结束 key,查询范围内的所有数据状态。注意:结束 key 对应的数据状态不包含在返回的结果集中
GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)

// 根据指定的 key 查询所有的历史记录信息
GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error)

// 创建一个复合键:objectType~attributes[0]~attributes[1]~...
CreateCompositeKey(objectType string, attributes []string) (string, error)

// 将指定的复合键进行分割
SplitCompositeKey(compositeKey string) (string, []string, error)

// 部分复合键的查询,将 objectType 和 keys 组成复合键,返回的是键前缀为该复合键的结果集
GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error)

// 对(支持富查询功能的)状态数据库进行富查询,目前支持富查询的只有 CouchDB
GetQueryResult(query string) (StateQueryIteratorInterface, error)

注意: 通过 put 写入的数据状态不能立刻 get 到,因为 put 只是链码执行的模拟交易(防止重复提交攻击),并不会真正将状态保存到账本中,必须经过 Orderer 达成共识之后,将数据状态保存在区块中,然后保存在各 peer 节点的账本中。

2.3 交易信息相关 API

// 返回交易提案中指定的交易 ID
GetTxID() string

// 返回交易提案中指定的 Channel ID
GetChannelID() string

// 返回交易创建的时间戳,这个时间戳是peer 接收到交易的具体时间
GetTxTimestamp() (*timestamp.Timestamp, error)

// 返回交易的绑定信息。如果一些临时信息,以避免重复性攻击
GetBinding() ([]byte, error)

// 返回与交易提案相关的签名身份信息
GetSignedProposal() (*pb.SignedProposal, error)

// 返回该交易提交者的身份信息(用户证书)
GetCreator() ([]byte, error)

// 返回交易中不会被写至账本中的一些临时信息
GetTransient() (map[string][]byte, error)

2.4 事件处理 API

// 设置事件,包括事件名称及内容,设置的是通知 Client 的事件
SetEvent(name string, payload []byte) error

2.5 对 PrivateData 操作的 API

// 根据指定的 key,从指定的私有数据集中查询对应的私有数据
GetPrivateData(collection, key string) ([]byte, error)

// 将指定的 key 与 value 保存到私有数据集中
PutPrivateData(collection string, key string, value []byte) error

// 根据指定的 key 从私有数据集中删除相应的数据
DelPrivateData(collection, key string) error

// 根据指定的开始与结束 key 查询范围(不包含结束key)内的私有数据
GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error)

// 根据给定的部分组合键的集合,查询给定的私有状态
GetPrivateDataByPartialCompositeKey(collection, objectType string, keys []string) (StateQueryIteratorInterface, error)

// 根据指定的查询字符串执行富查询 (只支持支持富查询的 CouchDB)
GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error)

3 链码开发

3.1 账户转账

package main

import (
    "fmt"
    "strconv"

    "github.com/hyperledger/fabric/core/chaincode/shim"
    pb "github.com/hyperledger/fabric/protos/peer"
)

type SimpleChaincode struct {
}

// 初始化数据状态,实例化/升级链码时被自动调用
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
    // println 函数的输出信息会出现在链码容器的日志中
    fmt.Println("ex02 Init")

    // 获取用户传递给调用链码的所需参数
    args := stub.GetStringArgs()

    var A, B string    // 两个账户
    var Aval, Bval int // 两个账户的余额
    var err error

    // 检查合法性, 检查参数数量是否为 4 个, 如果不是, 则返回错误信息
    if len(args) != 4 {
        return shim.Error("Incorrect number of arguments. Expecting 4")
    }

    A = args[0]  // 账户 A 用户名
    Aval, err = strconv.Atoi(args[1])  // 账户 A 余额
    if err != nil {
        return shim.Error("Expecting integer value for asset holding")
    }

    B = args[2]  // 账户 B 用户名
    Bval, err = strconv.Atoi(args[3])  // 账户 B 余额
    if err != nil {
        return shim.Error("Expecting integer value for asset holding")
    }

    fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

    // 将账户 A 的状态写入账本中
    err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
    if err != nil {
        return shim.Error(err.Error())
    }

    // 将账户 B 的状态写入账本中
    err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
    if err != nil {
        return shim.Error(err.Error())
    }

    // 一切成功,返回 nil(shim.Success)
    return shim.Success(nil)
}

// 对账本数据进行操作时被自动调用(query, invoke)
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    fmt.Println("ex02 Invoke")
    // 获取用户传递给调用链码的函数名称及参数
    function, args := stub.GetFunctionAndParameters()

    // 对获取到的函数名称进行判断
    if function == "invoke" {
        // 调用 invoke 函数实现转账操作
        return t.invoke(stub, args)
    } else if function == "delete" {
        // 调用 delete 函数实现账户注销
        return t.delete(stub, args)
    } else if function == "query" {
        // 调用 query 实现账户查询操作
        return t.query(stub, args)
    }
    // 传递的函数名出错,返回 shim.Error()
    return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"")
}

// 账户间转钱
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    var A, B string    // 账户 A 和 B
    var Aval, Bval int // 账户余额
    var X int          // 转账金额
    var err error

    if len(args) != 3 {
        return shim.Error("Incorrect number of arguments. Expecting 3")
    }

    A = args[0]       // 账户 A 用户名
    B = args[1]       // 账户 B 用户名

    // 从账本中获取 A 的余额
    Avalbytes, err := stub.GetState(A)
    if err != nil {
        return shim.Error("Failed to get state")
    }
    if Avalbytes == nil {
        return shim.Error("Entity not found")
    }
    Aval, _ = strconv.Atoi(string(Avalbytes))

    // 从账本中获取 B 的余额
    Bvalbytes, err := stub.GetState(B)
    if err != nil {
        return shim.Error("Failed to get state")
    }
    if Bvalbytes == nil {
        return shim.Error("Entity not found")
    }
    Bval, _ = strconv.Atoi(string(Bvalbytes))

    // X 为 转账金额
    X, err = strconv.Atoi(args[2])
    if err != nil {
        return shim.Error("Invalid transaction amount, expecting a integer value")
    }
    // 转账
    Aval = Aval - X
    Bval = Bval + X
    fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

    // 更新转账后账本中 A 余额
    err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
    if err != nil {
        return shim.Error(err.Error())
    }

    // 更新转账后账本中 B 余额
    err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
    if err != nil {
        return shim.Error(err.Error())
    }

    return shim.Success(nil)
}

// 账户注销
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting 1")
    }

    A := args[0]   // 账户用户名

    // 从账本中删除该账户状态
    err := stub.DelState(A)
    if err != nil {
        return shim.Error("Failed to delete state")
    }

    return shim.Success(nil)
}

// 账户查询
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    var A string
    var err error

    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting name of the person to query")
    }

    A = args[0]   // 账户用户名

    // 从账本中获取该账户余额
    Avalbytes, err := stub.GetState(A)
    if err != nil {
        jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
        return shim.Error(jsonResp)
    }

    if Avalbytes == nil {
        jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"
        return shim.Error(jsonResp)
    }

    jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
    fmt.Printf("Query Response:%s\n", jsonResp)
    // 返回转账金额
    return shim.Success(Avalbytes)
}

func main() {
    err := shim.Start(new(SimpleChaincode))
    if err != nil {
        fmt.Printf("Error starting Simple chaincode: %s", err)
    }
}

该链码位于 ./fabric-samples/chaincode/chaincode_example02,我们启动 dev 网络对其进行测试:

$ cd ./fabric-samples/chaincode-docker-devmode/
$ docker-compose -f docker-compose-simple.yaml up -d

进入链码容器,对链码进行编译:

$ docker exec -it chaincode bash
# cd chaincode_example02/go/
# go build
# CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=test:0 ./go

打开一个新的终端,进入 cli 容器,安装并示例化链码:

$ docker exec -it cli bash
# peer chaincode install -p chaincodedev/chaincode/chaincode_example02/go -n test -v 0
# peer chaincode instantiate -n test -v 0 -c '{"Args":["init","a", "100", "b","200"]}' -C myc

查询账户 a 的余额,返回结果为 100:

# peer chaincode query -n test -c '{"Args":["query","a"]}' -C myc

从账户 a 转账 10 给 b:

# peer chaincode invoke -n test -c '{"Args":["invoke","a","b","10"]}' -C myc

再次查询账户 b 的余额,返回结果为 90:

# peer chaincode query -n test -c '{"Args":["query","a"]}' -C myc

可以在 chaincode 容器中查看到运行的日志:

ex02 Init
Aval = 100, Bval = 200
ex02 Invoke
Query Response:{"Name":"a","Amount":"100"}
ex02 Invoke
Aval = 90, Bval = 210
ex02 Invoke
Query Response:{"Name":"a","Amount":"90"}

关闭网络:

$ docker-compose -f docker-compose-simple.yaml down

3.2 汽车信息记录

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "strconv"

    "github.com/hyperledger/fabric/core/chaincode/shim"
    sc "github.com/hyperledger/fabric/protos/peer"
)

type SmartContract struct {
}

type Car struct {
    Make   string `json:"make"`   // 产商
    Model  string `json:"model"`  // 型号
    Colour string `json:"colour"` // 颜色
    Owner  string `json:"owner"`  // 拥有者
}

// 在链码初始化过程中调用 Init 来数据,此处不做任何操作
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
    return shim.Success(nil)
}

// query 和 invoke 时被自动调用
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {

    // 解析用户调用链码传递的函数名及参数
    function, args := APIstub.GetFunctionAndParameters()

    // 调用不同的函数
    if function == "queryCar" {
        return s.queryCar(APIstub, args)
    } else if function == "initLedger" {
        return s.initLedger(APIstub)
    } else if function == "createCar" {
        return s.createCar(APIstub, args)
    } else if function == "queryAllCars" {
        return s.queryAllCars(APIstub)
    } else if function == "changeCarOwner" {
        return s.changeCarOwner(APIstub, args)
    }

    return shim.Error("Invalid Smart Contract function name.")
}

// 初始化账本数据
func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
    cars := []Car{
        Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
        Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
        Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
        Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
        Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
        Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
        Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
        Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
        Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
        Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
    }

    i := 0
    for i < len(cars) {
        fmt.Println("i is ", i)
        carAsBytes, _ := json.Marshal(cars[i])
        // key 为编号 CARi,value 为 Car 结构体的 json 串
        APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
        fmt.Println("Added", cars[i])
        i = i + 1
    }

    return shim.Success(nil)
}

// 根据编号查询汽车
func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting 1")
    }

    carAsBytes, _ := APIstub.GetState(args[0])
    return shim.Success(carAsBytes)
}

// 创建一辆新的汽车数据
func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

    if len(args) != 5 {
        return shim.Error("Incorrect number of arguments. Expecting 5")
    }

    var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]}

    carAsBytes, _ := json.Marshal(car)
    APIstub.PutState(args[0], carAsBytes)

    return shim.Success(nil)
}

// 查询全部的汽车
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {

    // 查询 startKey(包括)到 endKey(不包括)间的值
    startKey := "CAR0"
    endKey := "CAR999"

    resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
    if err != nil {
        return shim.Error(err.Error())
    }
    defer resultsIterator.Close()  // 延迟关闭迭代器

    // 将查询结果以 json 字符串的形式写入 buffer
    var buffer bytes.Buffer
    buffer.WriteString("[")

    bArrayMemberAlreadyWritten := false
    for resultsIterator.HasNext() {
        queryResponse, err := resultsIterator.Next()
        if err != nil {
            return shim.Error(err.Error())
        }

        if bArrayMemberAlreadyWritten == true {
            buffer.WriteString(",")
        }
        buffer.WriteString("{\"Key\":")
        buffer.WriteString("\"")
        buffer.WriteString(queryResponse.Key)
        buffer.WriteString("\"")

        buffer.WriteString(", \"Record\":")
        // Record is a JSON object, so we write as-is
        buffer.WriteString(string(queryResponse.Value))
        buffer.WriteString("}")
        bArrayMemberAlreadyWritten = true
    }
    buffer.WriteString("]")

    fmt.Printf("- queryAllCars:\n%s\n", buffer.String())

    return shim.Success(buffer.Bytes())
}

// 根据汽车编号改变车的拥有者
func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

    if len(args) != 2 {
        return shim.Error("Incorrect number of arguments. Expecting 2")
    }

    carAsBytes, _ := APIstub.GetState(args[0])
    car := Car{}

    json.Unmarshal(carAsBytes, &car)
    car.Owner = args[1]   // 更改汽车拥有者

    carAsBytes, _ = json.Marshal(car)
    APIstub.PutState(args[0], carAsBytes)  // 更新账本

    return shim.Success(nil)
}

func main() {
    err := shim.Start(new(SmartContract))
    if err != nil {
        fmt.Printf("Error creating new Smart Contract: %s", err)
    }
}

该链码位于 ./fabric-samples/chaincode/fabcar,我们启动 dev 网络对其进行测试:

$ cd ./fabric-samples/chaincode-docker-devmode/
$ docker-compose -f docker-compose-simple.yaml up -d

进入链码容器,对链码进行编译:

$ docker exec -it chaincode bash
# cd fabcar/go/
# go build
# CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=test:0 ./go

打开一个新的终端,进入 cli 容器,安装并示例化链码:

$ docker exec -it cli bash
# peer chaincode install -p chaincodedev/chaincode/fabcar/go -n test -v 0
# peer chaincode instantiate -n test -v 0 -c '{"Args":[]}' -C myc

初始化账本数据:

# peer chaincode invoke -n test -c '{"Args":["initLedger"]}' -C myc

查询账本全部汽车的信息:

# peer chaincode query -n test -c '{"Args":["queryAllCars"]}' -C myc
[{"Key":"CAR0", "Record":{"make":"Toyota","model":"Prius","colour":"blue","owner":"Tomoko"}},{"Key":"CAR1", "Record":{"make":"Ford","model":"Mustang","colour":"red","owner":"Brad"}},{"Key":"CAR2", "Record":{"make":"Hyundai","model":"Tucson","colour":"green","owner":"Jin Soo"}},{"Key":"CAR3", "Record":{"make":"Volkswagen","model":"Passat","colour":"yellow","owner":"Max"}},{"Key":"CAR4", "Record":{"make":"Tesla","model":"S","colour":"black","owner":"Adriana"}},{"Key":"CAR5", "Record":{"make":"Peugeot","model":"205","colour":"purple","owner":"Michel"}},{"Key":"CAR6", "Record":{"make":"Chery","model":"S22L","colour":"white","owner":"Aarav"}},{"Key":"CAR7", "Record":{"make":"Fiat","model":"Punto","colour":"violet","owner":"Pari"}},{"Key":"CAR8", "Record":{"make":"Tata","model":"Nano","colour":"indigo","owner":"Valeria"}},{"Key":"CAR9", "Record":{"make":"Holden","model":"Barina","colour":"brown","owner":"Shotaro"}}]

创建一个新的汽车信息写入账本:

# peer chaincode invoke -n test -c '{"Args":["createCar","CAR10","Toyota","Prius","blue","233"]}' -C myc

查询编号为 CAR10 的汽车信息:

# peer chaincode query -n test -c '{"Args":["queryCar","CAR10"]}' -C myc
{"make":"Toyota","model":"Prius","colour":"blue","owner":"233"}

改变编号为 CAR10 的汽车的拥有者:

# peer chaincode invoke -n test -c '{"Args":["changeCarOwner","CAR10","hehe"]}' -C myc

再次查询编号为 CAR10 的汽车信息:

# peer chaincode query -n test -c '{"Args":["queryCar","CAR10"]}' -C myc
{"make":"Toyota","model":"Prius","colour":"blue","owner":"hehe"}

关闭网络:

$ docker-compose -f docker-compose-simple.yaml down

参考

  1. 《Hyperledger Fabric 菜鸟进行攻略》

原文地址:https://www.cnblogs.com/zongmin/p/11874792.html

时间: 2024-10-06 01:19:43

Fabric1.4:Go 链码开发与编写的相关文章

HyperLeger Fabric开发(七)——HyperLeger Fabric链码开发

HyperLeger Fabric开发(七)--HyperLeger Fabric链码开发 一.链码开发模式 1.链码开发模式简介 Fabric的链码开发调试比较繁琐.在不使用链码开发模式的情况下,链码不能在本地测试,必须部署到docker,install和instantiate后,Peer节点会在新的容器中启动链码.但只能通过docker logs查看链码日志,通过打印日志的方式进行链码调试.如果对链码进行了修改,需要重新开始上述流程.为了简化Fabric链码开发的调试过程,Fabric引入了

HyperLeger Fabric开发(八)——HyperLeger Fabric链码开发测试

HyperLeger Fabric开发(八)--HyperLeger Fabric链码开发测试 一.链码实例 SACC项目链码实例如下: package main import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer" ) // SimpleAsset implements a si

Fabric1.4:链码管理与测试

1 链码介绍 智能合约在 Hyperledger Fabric 中称为链码(chaincode),是提供分布式账本的状态处理逻辑.链码被部署在fabric 的网络节点中,能够独立运行在具有安全特性的受保护的 Docker 容器中,以 gRPC 协议与相应的 peer 节点进行通信,以操作分布式账本中的数据. 一般链码分为两种:系统链码和用户链码. 1.1 系统链码 负责 Fabric 节点自身的处理逻辑,包括系统配置.背书.校验等工作,在 Peer 节点启动时会自动完成注册和部署.系统链码分为以

【lushengduan】01、搭建安卓App开发环境 编写程序HelloWorld

一.搭建开发环境 1.JDK环境变量 JDK下载 链接:http://pan.baidu.com/s/1gen1o9l 密码:8fko 打开计算机-属性-高级系统设置-环境变量,新建JAVA_HOME系统环境变量 C:\Program Files\Java\jdk1.7.0_80 新建CLASSPATH系统环境变量 .;%JAVA_HOME%lib;%JAVA_HOME%lib\tools.jar; 在系统环境变量Path后面追加 ;%JAVA_HOME%/bin;C:\Program File

基于JZ2440开发板编写bootloader总结(一)

凡走过必留下痕迹,学点什么都会有用的. 本系列博文总结了自己在学习嵌入式Linux编程过程中的收获,若有错误,恳请指正,谢谢! --参考教材韦东山系列教材 bootloader 是一个用于启动linux内核的C程序,为了达到最终启动内核的目的需要完成以下几个步骤: step1:硬件相关初始化,为启动内核准备硬件平台: step2:将内核从NAND FLASH读取到SDRAM: step3:设置需要传递给内核的启动参数: step4:跳转到SDRAM,运行内核: 下面将详细讲述各个步骤细节: 第一

项目管理(十二)- 敏捷开发之编写故事

在本章我们将关注故事编写,为了更好的构造故事,我们关注六个特性,一个好的故事应该具有如下6个方面的特点 故事的6个特征 1.独立的 避免故事之间的相互依赖,在对故事排列优先级时,或者使用故事做计划时,故事间的相互依赖会导致一些问题 2.可讨论的 故事是可讨论的,他们不是签署好的合同或者软件中必须实现的需求,敏捷故事是功能的简短描述,细节将在客户团队和开发团队中讨论中产生,故事是提醒客户团队和开发团队以后要进行关于需求的对话,它并不是具体的需求本身,因而它不需要包含具体的细节.这些细节可以在后期例

全志A33 lichee 搭建Qt App开发环境编写helloworld

开发平台 芯灵思SinlinxA33开发板 淘宝店铺: [https://sinlinx.taobao.com/]() 嵌入式linux 开发板交流 QQ:641395230 Step 1 在虚拟机(CentOS7)上安装Qt Creator 将qt-creator-opensource-linux-x86_64-3.5.1.run 拷贝到虚拟机中,双击安装,全部点下一步即可. Step 2 Qt Creater 设置 打开Qt Creator (Applications->Programmin

越狱开发:用iosOpenDev配置越狱开发环境 编写第一个hello world-b

上网搜索越狱环境搭建,查到iosOpenDev软件,是个很方便的编写插件,而且可以直接用Xcode创建的工具,便开始着手学习如何去安装 集合了网上的方法后,我安装的时候发现: 1.作者最后更新的时间是四个月前,而且并未适配Xcode 6之后的版本,所以在当前OS X 10.11.1 和Xcode 7 下造成了很多问题 尤其是大量的安装失败却得不到解答方法,很是让我懊恼 2.同时需要适配的安装工具也已经严重过期,比如theos,ldid等工具 所以根据自己的摸索,附上一篇如何安装iosOpenDe

迅为4412开发板编写简单应用调用驱动

本文转自迅为4412精英版群: 本期实验比较简单,就是写一个简单的应用程序调用前面写的驱动. 硬件工具 1)iTOP4412 开发板 2)U 盘或者 TF 卡 3)PC 机 4)串口 9.1.1.2软件工具 1)虚拟机 Vmware 2)Ubuntu12.04.2 3)超级终端(串口助手) 4)实验配套源码文件夹“invoke_hello” 视频资源 本节配套视频为“视频 09-编写简单应用调用驱动” 学习目标 本章需要学习以下内容: 学会调用设备节点 实验操作 本期实验很简单,在前面 Linu