在实习中使用 micro 框架,但是挺多不懂的,看了部分源码搞懂了一些,还是有一些比较复杂没搞懂。

第一部分:初始化 service 并修改端口

main.go

// waitgroup is a handler wrapper which adds a handler to a sync.WaitGroup
func waitgroup(wg *util.WaitGroupWrapper) server.HandlerWrapper {
return func(h server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
wg.Add(1)
defer wg.Done()
return h(ctx, req, rsp)
}
}
} func main() {
// ... var wg util.WaitGroupWrapper
var service micro.Service // 这里在下面给出解释
service = micro.NewService(
// 这里返回了四个函数闭包,同时也是Option类型,NewService的参数是可变长度的Option数组
micro.Name("xxx"),
micro.WrapHandler(waitgroup(&wg)),
micro.RegisterTTL(30*time.Second),
micro.RegisterInterval(10*time.Second),
) // 先对 service 初始化
service.Init()
// 因为在 go run *.go 的时候回传入参数 --server_address=":8880" 这个时候可以这样将传入的参数取出,之后给webservice使用
addr := service.Server().Options().Address
// 将这个的端口赋为默认地址,即:0
service.Server().Init(server.Address(server.DefaultAddress)) // ...
}

go-micro.go

type Service interface {
Init(...Option)
Options() Options
Client() client.Client
Server() server.Server
Run() error
String() string
} type Option func(*Options) // NewService creates and returns a new Service based on the packages within.
func NewService(opts ...Option) Service {
return newService(opts...)
}

这里定义了一个 function type ,一开始对这个不太熟悉,看了 这篇文章 之后懂了点。定义这个 Option 之后的初始化操作就是将 Option 当做参数,传入到 NewXXX 中,在这些 NewXXX 方法又会遍历传入的 []Option 数组,然后调用这个 Option 在里面套一个 Init() 方法或者直接对变量进行赋值来完成对参数 *Options 的部分变量的初始化。

service.go

func newService(opts ...Option) Service {
// newOptions 的操作在下面的 options.go 文件中
options := newOptions(opts...) // 包装client,添加一些信息
options.Client = &clientWrapper{
options.Client,
metadata.Metadata{
HeaderPrefix + "From-Service": options.Server.Options().Name,
},
} return &service{
opts: options,
}
} type service struct {
opts Options once sync.Once
} func (s *service) Init(opts ...Option) {
// process options
for _, o := range opts {
o(&s.opts)
} // 能保证once只执行一次,无论你是否更换once.Do(xx)这里的方法,这个sync.Once块只会执行一次。
s.once.Do(func() {
// save user action
action := s.opts.Cmd.App().Action
// 下面注释给出 cmd.App().Action 的初始化
// func newCmd(opts ...Option) Cmd {
// options := Options{
// Broker: &broker.DefaultBroker,
// Client: &client.DefaultClient,
// Registry: &registry.DefaultRegistry,
// Server: &server.DefaultServer,
// Selector: &selector.DefaultSelector,
// Transport: &transport.DefaultTransport, // Brokers: DefaultBrokers,
// Clients: DefaultClients,
// Registries: DefaultRegistries,
// Selectors: DefaultSelectors,
// Servers: DefaultServers,
// Transports: DefaultTransports,
// } // ... // cmd := new(cmd)
// cmd.opts = options
// cmd.app = cli.NewApp()
// ...
// cmd.app.Action = func(c *cli.Context) {} 这里
// ... // return cmd // set service action
s.opts.Cmd.App().Action = func(c *cli.Context) {
// set register interval
if i := time.Duration(c.GlobalInt("register_interval")); i > 0 {
s.opts.RegisterInterval = i * time.Second
} // user action
action(c)
} // Initialise the command flags, overriding new service
_ = s.opts.Cmd.Init(
cmd.Broker(&s.opts.Broker),
cmd.Registry(&s.opts.Registry),
cmd.Transport(&s.opts.Transport),
cmd.Client(&s.opts.Client),
cmd.Server(&s.opts.Server),
)
})
}

options.go

func newOptions(opts ...Option) Options {
opt := Options {
Broker: broker.DefaultBroker,
Cmd: cmd.DefaultCmd,
Client: client.DefaultClient,
Server: server.DefaultServer,
Registry: registry.DefaultRegistry,
Transport: transport.DefaultTransport,
Context: context.Background(),
} // 第一个值是下标,第二个值(o)是相当于value,这里是一个Option类型的函数。主要就是对 opt 赋值初始化
for _, o := range opts {
o(&opt)
} return opt
} // 下面四个函数的返回一个闭包,然后在 newOptions 的时候调用完成初始化操作
// Name of the service
func Name(n string) Option {
return func(o *Options) {
o.Server.Init(server.Name(n))
}
} // RegisterTTL specifies the TTL to use when registering the service
func RegisterTTL(t time.Duration) Option {
return func(o *Options) {
o.Server.Init(server.RegisterTTL(t))
}
} // RegisterInterval specifies the interval on which to re-register
func RegisterInterval(t time.Duration) Option {
return func(o *Options) {
o.RegisterInterval = t
}
} // WrapHandler adds a handler Wrapper to a list of options passed into the server
func WrapHandler(w ...server.HandlerWrapper) Option {
return func(o *Options) {
var wrappers []server.Option for _, wrap := range w {
wrappers = append(wrappers, server.WrapHandler(wrap))
} // Init once
// 根据这些option初始化server
o.Server.Init(wrappers...)
}
}

第二部分:订阅Topic

main.go

func subEvent(ctx context.Context, stats *proto.Event) error {
golog.Logf("Received event %+v\n", stats)
return nil
} func main() {
// ...
// subEvent是自定义函数
micro.RegisterSubscriber("TOPIC", service.Server(), subEvent)
// ...
}

go-micro.go

func RegisterSubscriber(topic string, s server.Server, h interface{}, opts ...server.SubscriberOption) error {
// 调用 server.go 的函数
return s.Subscribe(s.NewSubscriber(topic, h, opts...))
}

server.go

var (
DefaultServer Server = newRpcServer()
) func NewSubscriber(topic string, h interface{}, opts ...SubscriberOption) Subscriber {
return DefaultServer.NewSubscriber(topic, h, opts...)
} func Subscribe(s Subscriber) error {
// 这里的 DefaultServer 是 RpcServer,调用下面 rpc_server.go 的函数
return DefaultServer.Subscribe(s)
}

rpc_server.go

// 调用 subscriber.go 中的函数
func (s *rpcServer) NewSubscriber(topic string, sb interface{}, opts ...SubscriberOption) Subscriber {
return newSubscriber(topic, sb, opts...)
}

subscriber.go

type handler struct {
method reflect.Value
reqType reflect.Type
ctxType reflect.Type
} type subscriber struct {
topic string
rcvr reflect.Value
typ reflect.Type
subscriber interface{}
handlers []*handler
endpoints []*registry.Endpoint
opts SubscriberOptions
} func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
// 利用传进来的 SubcriberOption 初始化,这个和第一部分的类似,略过
var options SubscriberOptions
for _, o := range opts {
o(&options)
} var endpoints []*registry.Endpoint
var handlers []*handler // 利用反射去对传进来的 interface{} 操作,我在前面最开始传的是 subEvent 函数
if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
// 因为我传入的是 Func 类型,所以会进入这里
h := &handler{
// 获取方法
method: reflect.ValueOf(sub),
} // 判断参数个数,并分别给对应位置的类型赋值
switch typ.NumIn() {
case 1:
h.reqType = typ.In(0)
case 2:
h.ctxType = typ.In(0)
h.reqType = typ.In(1)
} handlers = append(handlers, h) endpoints = append(endpoints, &registry.Endpoint{
Name: "Func",
Request: extractSubValue(typ),
Metadata: map[string]string{
"topic": topic,
"subscriber": "true",
},
})
} else {
hdlr := reflect.ValueOf(sub)
name := reflect.Indirect(hdlr).Type().Name() for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
h := &handler{
method: method.Func,
} switch method.Type.NumIn() {
case 2:
h.reqType = method.Type.In(1)
case 3:
h.ctxType = method.Type.In(1)
h.reqType = method.Type.In(2)
} handlers = append(handlers, h) endpoints = append(endpoints, &registry.Endpoint{
Name: name + "." + method.Name,
Request: extractSubValue(method.Type),
Metadata: map[string]string{
"topic": topic,
"subscriber": "true",
},
})
}
} return &subscriber{
rcvr: reflect.ValueOf(sub),
typ: reflect.TypeOf(sub),
topic: topic,
subscriber: sub,
handlers: handlers,
endpoints: endpoints,
opts: options,
}
}

rpc_server.go

func (s *rpcServer) Subscribe(sb Subscriber) error {
sub, ok := sb.(*subscriber)
if !ok {
return fmt.Errorf("invalid subscriber: expected *subscriber")
}
if len(sub.handlers) == 0 {
return fmt.Errorf("invalid subscriber: no handler functions")
} // 验证是否合法
if err := validateSubscriber(sb); err != nil {
return err
} s.Lock()
defer s.Unlock()
_, ok = s.subscribers[sub]
if ok {
return fmt.Errorf("subscriber %v already exists", s)
}
// 置为nil,nil也是值
s.subscribers[sub] = nil
return nil
}

第三部分:运行 service

main.go

// 启动订阅服务
if err := service.Run(); err != nil {
log.Fatal(err)
}

调用 service.goRun() 方法。

service.go

func (s *service) Run() error {
if err := s.Start(); err != nil {
return err
} // start reg loop
ex := make(chan bool) // 看下面
go s.run(ex) ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) select {
// wait on kill signal
case <-ch:
// wait on context cancel
case <-s.opts.Context.Done():
} // exit reg loop
close(ex) return s.Stop()
} func (s *service) Start() error {
// 遍历在开始前的需要执行的函数并执行
for _, fn := range s.opts.BeforeStart {
if err := fn(); err != nil {
return err
}
} // 调用 rpc_server 的 Start()
if err := s.opts.Server.Start(); err != nil {
return err
} // 调用 rpc_server 的 Register()
if err := s.opts.Server.Register(); err != nil {
return err
} // 遍历在开始后的需要执行的函数并执行
for _, fn := range s.opts.AfterStart {
if err := fn(); err != nil {
return err
}
} return nil
} func (s *service) run(exit chan bool) {
if s.opts.RegisterInterval <= time.Duration(0) {
return
} t := time.NewTicker(s.opts.RegisterInterval) for {
select {
case <-t.C:
err := s.opts.Server.Register()
if err != nil {
log.Log("service run Server.Register error: ", err)
}
case <-exit:
t.Stop()
return
}
}
} func (s *service) Stop() error {
var gerr error for _, fn := range s.opts.BeforeStop {
if err := fn(); err != nil {
gerr = err
}
} // 取消注册
if err := s.opts.Server.Deregister(); err != nil {
return err
} // 停止
if err := s.opts.Server.Stop(); err != nil {
return err
} for _, fn := range s.opts.AfterStop {
if err := fn(); err != nil {
gerr = err
}
} return gerr
}

Start() 方法会调用 server.goStart() 方法

server.go

func (s *rpcServer) Start() error {
registerDebugHandler(s)
config := s.Options() // 返回一个监听该地址的 listener
ts, err := config.Transport.Listen(config.Address)
if err != nil {
return err
} log.Logf("Listening on %s", ts.Addr())
s.Lock()
s.opts.Address = ts.Addr()
s.Unlock() // 启动一条协程接收信息,内部调用了 net 包的 Accept,看下面的 accept()
go ts.Accept(s.accept) go func() {
// wait for exit
ch := <-s.exit // wait for requests to finish
if wait(s.opts.Context) {
s.wg.Wait()
} // close transport listener
ch <- ts.Close() // disconnect the broker
config.Broker.Disconnect()
}() // TODO: subscribe to cruft
return config.Broker.Connect()
} func (s *rpcServer) accept(sock transport.Socket) {
defer func() {
// close socket
sock.Close() if r := recover(); r != nil {
log.Log("panic recovered: ", r)
log.Log(string(debug.Stack()))
}
}() for {
var msg transport.Message // 接收信息存放到msg中
if err := sock.Recv(&msg); err != nil {
return
} // we use this Timeout header to set a server deadline
to := msg.Header["Timeout"]
// we use this Content-Type header to identify the codec needed
ct := msg.Header["Content-Type"] // 将原来的 Content-Type 转换为可以被 rpc 使用的 Type
cf, err := s.newCodec(ct)
// TODO: needs better error handling
if err != nil {
sock.Send(&transport.Message{
Header: map[string]string{
"Content-Type": "text/plain",
},
Body: []byte(err.Error()),
})
return
} // 返回一个新的 codec
codec := newRpcPlusCodec(&msg, sock, cf) // strip our headers
hdr := make(map[string]string)
for k, v := range msg.Header {
hdr[k] = v
}
delete(hdr, "Content-Type")
delete(hdr, "Timeout") ctx := metadata.NewContext(context.Background(), hdr) // set the timeout if we have it
if len(to) > 0 {
if n, err := strconv.ParseUint(to, 10, 64); err == nil {
ctx, _ = context.WithTimeout(ctx, time.Duration(n))
}
} // add to wait group
s.wg.Add(1)
defer s.wg.Done() // TODO: needs better error handling
// 里面包括了许多方法:
// server.readRequest() // 从codec中读取请求
// server.sendResponse() // 最后会将response的信息通过socket发送
// service.call()
if err := s.rpc.serveRequest(ctx, codec, ct); err != nil {
log.Logf("Unexpected error serving request, closing socket: %v", err)
return
}
}
} func (s *rpcServer) Register() error {
// parse address for host, port
config := s.Options()
var advt, host string
var port int // check the advertise address first
// if it exists then use it, otherwise
// use the address
if len(config.Advertise) > 0 {
advt = config.Advertise
} else {
advt = config.Address
} parts := strings.Split(advt, ":")
if len(parts) > 1 {
host = strings.Join(parts[:len(parts)-1], ":")
port, _ = strconv.Atoi(parts[len(parts)-1])
} else {
host = parts[0]
} addr, err := addr.Extract(host)
if err != nil {
return err
} // register service
node := &registry.Node{
Id: config.Name + "-" + config.Id,
Address: addr,
Port: port,
Metadata: config.Metadata,
} node.Metadata["transport"] = config.Transport.String()
node.Metadata["broker"] = config.Broker.String()
node.Metadata["server"] = s.String()
node.Metadata["registry"] = config.Registry.String() s.RLock()
// Maps are ordered randomly, sort the keys for consistency
// 生成 handlerList
var handlerList []string
for n, e := range s.handlers {
// Only advertise non internal handlers
if !e.Options().Internal {
handlerList = append(handlerList, n)
}
}
sort.Strings(handlerList) // 生成 subscriberList
var subscriberList []*subscriber
for e := range s.subscribers {
// Only advertise non internal subscribers
if !e.Options().Internal {
subscriberList = append(subscriberList, e)
}
}
sort.Slice(subscriberList, func(i, j int) bool {
return subscriberList[i].topic > subscriberList[j].topic
}) var endpoints []*registry.Endpoint
for _, n := range handlerList {
endpoints = append(endpoints, s.handlers[n].Endpoints()...)
}
for _, e := range subscriberList {
endpoints = append(endpoints, e.Endpoints()...)
}
s.RUnlock() service := &registry.Service{
Name: config.Name,
Version: config.Version,
Nodes: []*registry.Node{node},
Endpoints: endpoints,
} s.Lock()
registered := s.registered
s.Unlock() if !registered {
log.Logf("Registering node: %s", node.Id)
} // create registry options
rOpts := []registry.RegisterOption{registry.RegisterTTL(config.RegisterTTL)} if err := config.Registry.Register(service, rOpts...); err != nil {
return err
} // already registered? don't need to register subscribers
if registered {
return nil
} s.Lock()
defer s.Unlock() s.registered = true for sb, _ := range s.subscribers {
handler := s.createSubHandler(sb, s.opts)
var opts []broker.SubscribeOption
if queue := sb.Options().Queue; len(queue) > 0 {
opts = append(opts, broker.Queue(queue))
}
// 订阅
sub, err := config.Broker.Subscribe(sb.Topic(), handler, opts...)
if err != nil {
return err
}
// 放入之前设为 nil 的 map 里面
s.subscribers[sb] = []broker.Subscriber{sub}
} return nil
}

第四部分:初始化WebService

main.go

// 对主页服务
// 自定义的Handler函数 serveHome
func serveHome(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL)
if r.URL.Path != "/" {
http.Error(w, "Not found", http.StatusNotFound)
return
}
log.Println(r.URL.Path[1:])
http.ServeFile(w, r, "./view/login.html")
} func main() {
// ...
// 初始化操作和前面类似,略过
webService := web.NewService(
web.Name("xxx"),
web.RegisterTTL(30*time.Second),
web.RegisterInterval(10*time.Second),
web.Address(addr),
web.Registry(service.Options().Registry),
)
// 部署静态资源,看下面
// http.FileServer()返回一个Handler,将 "" 目录(即当前目录)下的资源部署
webService.Handle("/view/", http.FileServer(http.Dir(""))) // 主页
// 自定义的Handler函数 serveHome
webService.HandleFunc("/", serveHome) if err := webService.Run(); err != nil {
log.Fatal(err)
}
// ...
}

service.go

func (s *service) Handle(pattern string, handler http.Handler) {
var seen bool
for _, ep := range s.srv.Endpoints {
if ep.Name == pattern {
seen = true
break
}
}
if !seen {
s.srv.Endpoints = append(s.srv.Endpoints, &registry.Endpoint{
Name: pattern,
})
}
// 底层调用http包的方法
s.mux.Handle(pattern, handler)
} func (s *service) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
var seen bool
for _, ep := range s.srv.Endpoints {
if ep.Name == pattern {
seen = true
break
}
}
if !seen {
s.srv.Endpoints = append(s.srv.Endpoints, &registry.Endpoint{
Name: pattern,
})
} s.mux.HandleFunc(pattern, handler)
}

net/http/server.go

type Handler interface {
ServeHTTP(ResponseWriter, *Request)
} // HandlerFunc 实现了 ServeHTTP 方法,因此实现了Handler接口
type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
} // HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
} func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock() if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
} if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{h: handler, pattern: pattern} if pattern[0] != '/' {
mux.hosts = true
}
}

附无用Demo

sync.Once

package main

import (
"fmt"
"sync"
) func main() {
var once sync.Once
for i := 0; i < 10; i++ {
once.Do(func() {
fmt.Println("once :", i)
})
}
} 输出:
once : 0

type function

package main

import (
"fmt"
) type Fun func(s string) func A(s string) {
fmt.Println("a : ", s)
} func B(s string) {
fmt.Println("b : ", s)
} func C(s string) {
fmt.Println("c : ", s)
} func D(s string) {
fmt.Println("d : ", s)
} func main() {
f := []Fun{A, B, C, D}
for k, v := range f {
fmt.Println(k)
v("233")
}
} 输出:
0
a : 233
1
b : 233
2
c : 233
3
d : 233

Golang之mirco框架部分浅析的更多相关文章

  1. 【GoLang】golang 微服务框架 go-kit

    golang-Microservice Go kit - A toolkit for microservices kubernetes go-kit_百度搜索 Peter Bourgon谈使用Go和& ...

  2. Golang 网络爬虫框架gocolly/colly 四

    Golang 网络爬虫框架gocolly/colly 四 爬虫靠演技,表演得越像浏览器,抓取数据越容易,这是我多年爬虫经验的感悟.回顾下个人的爬虫经历,共分三个阶段:第一阶段,09年左右开始接触爬虫, ...

  3. Golang 网络爬虫框架gocolly/colly 三

    Golang 网络爬虫框架gocolly/colly 三 熟悉了<Golang 网络爬虫框架gocolly/colly一>和<Golang 网络爬虫框架gocolly/colly二& ...

  4. Golang 网络爬虫框架gocolly/colly 二 jQuery selector

    Golang 网络爬虫框架gocolly/colly 二 jQuery selector colly框架依赖goquery库,goquery将jQuery的语法和特性引入到了go语言中.如果要灵活自如 ...

  5. Golang 网络爬虫框架gocolly/colly 一

    Golang 网络爬虫框架gocolly/colly 一 gocolly是用go实现的网络爬虫框架,目前在github上具有3400+星,名列go版爬虫程序榜首.gocolly快速优雅,在单核上每秒可 ...

  6. Golang 网络爬虫框架gocolly/colly 五 获取动态数据

    Golang 网络爬虫框架gocolly/colly 五 获取动态数据 gcocolly+goquery可以非常好地抓取HTML页面中的数据,但碰到页面是由Javascript动态生成时,用goque ...

  7. [Golang] GoConvey测试框架使用指南

    GoConvey 是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多 Web 界面特性. GoConvey 网站 : http://smartystreet ...

  8. 流式处理框架storm浅析(下篇)

    本文来自网易云社区 作者:汪建伟 举个栗子 1 实现的目标 设计一个系统,来实现对一个文本里面的单词出现的频率进行统计. 2 设计Topology结构: 这是一个简单的例子,topology也非常简单 ...

  9. golang的beego框架开发时出现的问题纪录

    golang的beego框架开发时出现的问题纪录1.数据库并发时问题:[ORM]2017/02/20 23:44:05 -[Queries/default] - [FAIL / db.Query / ...

随机推荐

  1. Entity framework 配置文件,实现类,测试类

    配置文件信息App.config: 数据库IP地址为192.168.2.186 ,数据库名为 Eleven-Six , 用户名 123456,密码654321 <?xml version=&qu ...

  2. TCPClient组件和TCPServer组件的主要方法和属性

    IdTCPClient属性1 : IOHandler 如果有相应的输入/输出操作,那么IOHandler相对应的组件或接口将提供一个虚拟/抽象的输入/输出接口给相应的网络连接2 : Intercept ...

  3. WPF TreeView HierarchicalDataTemplate

    原文 WPF TreeView HierarchicalDataTemplate HierarchicalDataTemplate 的DataType是本层的绑定,而ItemsSource是绑定下层的 ...

  4. wpf-MVVM界面自适应:界面自适应及字体自适应

    原文:wpf-MVVM界面自适应:界面自适应及字体自适应 1,界面自适应 界面先划分Region,每个填充Region的View不设置Width属性,即可按照Region划分的比例,自适应屏幕分辨率 ...

  5. Win8Metro(C#)数字图像处理--2.6图像对比度调整

    原文:Win8Metro(C#)数字图像处理--2.6图像对比度调整  2.6图像对比度调整函数 [函数名称] 图像对比度调整函数ContrastAdjustProcess(WriteableBi ...

  6. Windows10 【系统周期表】【系统下载表】【大型软件表】

    系统周期表 商用名称 商用英文名 代号 版本 系统版本 上市日期 服务周期 备注 Windows 10 无 Threshold 1 1507 10.0.10240.17443 2015.07.29 2 ...

  7. 微信小程序把玩(二十七)audio组件

    原文:微信小程序把玩(二十七)audio组件 音频播放已经封装的很好!只需配合属性设置即可! (method和data配合使用) 主要属性: wxml <audio action="{ ...

  8. hadoop(三)

    hadoop(三) 1.对MapReduce的认识   MapReduce是运行在yarn上面的一个分布式运算框架,它是用来解决海量的分布式运算的.对于MapReduce来说,我们可以把它分成两部分来 ...

  9. 【Qt】无边框窗体中带有ActiveX组件时的一个BUG

    无意中发现的一个BUG,Qt5.1.1正式版首先创建一个GUI工程,拖入一个QAxWidget控件(为了使ActiveX生效,需要在.pro文件中加入CONFIG += qaxcontainer)接着 ...

  10. 配置QtCreator+CDB远程调试环境(用到了符号表) good

    相关环境信息:开发机Win7 x64.远程机器WinXP.调试器是CDB.Qt版本5.2.1 一.部署远程机器环境 我这里用的是虚拟机(Windows XP),根据你要调试的程序选择安装不同架构的Wi ...