ecFlow学习笔记:使用SWIG创建golang客户端

目录

警告:依照本文方法实现的ecflow客户端有严重的内存泄漏问题,请不要在长时间运行的程序中使用。

之前一篇博文中介绍如何使用ecFlow的C++ API创建客户端(《ecFlow学习笔记04:使用C++ API》)。 本文介绍如何基于C++的客户端构建golang客户端。

golang调用c代码

golang提供CGO工具调用C代码,相关信息请参考官方文档《cgo》。

虽然cgo可以使用C++编译器编译C++代码,但必须使用C语言对C++代码进行包装才能在go代码中调用。所以对于使用c++接口的ecflow客户端来说,直接使用cgo非常繁琐。

好在,有其他工具可以帮助我们在golang中调用c++代码。

SWIG就属于这样的工具。SWIG是一款实现跨语言调用C/C++代码的工具。

下面介绍如何使用SWIG创建ecFlow的golang客户端。

简化的ecFlow c++ 客户端

ecflow-cpp-client实现了获取节点状态组成状态树的功能,在golang客户端中,只保留获取节点状态的部分,大幅减少需要的c++代码。

简化后的c++代码头文件如下所示:

#pragma once
#include <string>
#include <vector>

namespace EcflowUtil {

// 状态条目,包括节点路径和节点状态
struct NodeStatusRecord {
    std::string path_;
    std::string status_;
};

class EcflowClientWrapperPrivate;

class EcflowClientWrapper {
public:
    EcflowClientWrapper() = delete;

    // 使用ip地址和端口号创建client
    EcflowClientWrapper(const std::string &host, const std::string &port);

    ~EcflowClientWrapper();

    // 设置连接ecFlow服务器的超时时间,以秒为单位
    void setConnectTimeout(int time_out);

    // 从ecFlow服务器同步系统状态
    int sync();

    // 返回状态条目列表
    std::vector<NodeStatusRecord> statusRecords() {
        return status_records_;
    }

    std::string errorMessage();

private:
    std::string host_;
    std::string port_;

    EcflowClientWrapperPrivate* p_;
    std::vector<NodeStatusRecord> status_records_;
};

} // namespace EcflowUtil

使用ip地址和端口号创建EcflowClientWrapper,调用sync方法获取系统状态,最后调用statusRecords方法获取状态记录列表。

SWIG描述文件

SWIG使用特定的语法创建描述文件,用于描述C/C++的接口。

下面代码为上面的头文件创建描述文件。

%module ecflow_client
%{
#include "ecflow_util.h"
%}

%include "std_vector.i"
%include "std_string.i"

%include "ecflow_util.h"

namespace std {
  %template(NodeStatusRecordVector) vector<EcflowUtil::NodeStatusRecord>;
};

因为头文件中使用了std::vectorstd::string,所以需要引入SWIG为标准库vector和string开发的描述文件。 同时需要显示指定使用vector的模板类vector<EcflowUtil::NodeStatusRecord>

生成接口文件

使用swig命令生成接口文件。

swig -go -cgo -c++ -intgosize 64 ecflow_util.i

swig会生成用于封装c++接口的ecflow_util_wrap.cxx文件和go接口文件ecflow_client.go

封装c++接口

创建状态条目类型,与EcflowUtil::NodeStatusRecord对应。

type StatusRecord struct {
	Path   string `json:"path"`
	Status string `json:"status"`
}

创建EcflowClient封装EcflowClientWrapper接口。

type EcflowClient struct {
	ServerHost    string
	ServerPort    string
	CollectedTime time.Time
	wrapper       EcflowClientWrapper
}

go语言没有构造函数,创建一个函数用于构造EcflowClient对象。

func CreateEcflowClient(host string, port string) *EcflowClient {
	client := &EcflowClient{
		ServerHost: host,
		ServerPort: port,
	}
	client.wrapper = NewEcflowClientWrapper(client.ServerHost, client.ServerPort)
	return client
}

封装EcflowClientWrapper的成员函数。

func (c *EcflowClient) SetConnectTimeout(timeout int) {
	c.wrapper.SetConnectTimeout(timeout)
}

func (c *EcflowClient) Sync() int {
	if c.wrapper == nil {
		c.wrapper = NewEcflowClientWrapper(c.ServerHost, c.ServerPort)
	}
	errorCode := c.wrapper.Sync()
	c.CollectedTime = time.Now()
	return errorCode
}

func (c *EcflowClient) StatusRecords() []StatusRecord {
	var returnRecords []StatusRecord
	records := c.wrapper.StatusRecords()
	count := int(records.Size())
	for i := 0; i < count; i++ {
		record := records.Get(i)
		returnRecords = append(returnRecords, StatusRecord{
			Path:   record.GetPath_(),
			Status: record.GetStatus_(),
		})
	}
	return returnRecords
}

func (c *EcflowClient) Close() {
	DeleteEcflowClientWrapper(c.wrapper)
	c.wrapper = nil
}

示例

下面的代码展示如何使用EcflowClient获取并打印ecflow服务所有节点的路径和状态。

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/perillaroc/ecflow-client-go"
	"log"
	"os"
)

func main() {
	// 创建命令行参数:ip地址,端口号,输出方式
	ecflowHost := flag.String("host", "localhost", "ecflow server host")
	ecflowPort := flag.String("port", "3141", "ecflow server port")
	outputFile := flag.String("output", "", "output file, default is os.Stdout")

	flag.Parse()

	// 创建EcflowClient
	client := ecflow_client.CreateEcflowClient(*ecflowHost, *ecflowPort)
	defer client.Close()

	// 如果设定outputFile,则输出到文件,否则输出到标准输出
	var err error
	target := os.Stdout
	if *outputFile != "" {
		target, err = os.Create(*outputFile)
		if err != nil {
			panic(err)
		}
	}

	// 同步ecflow状态
	ret := client.Sync()
	if ret != 0 {
		log.Fatal("sync has error")
	}

	// 获取状态列表并打印
	records := client.StatusRecords()
	for _, record := range records {
		b, err := json.Marshal(record)
		if err != nil {
			continue
		}
		fmt.Fprintf(target, "%s\n", b)
	}
	fmt.Fprintf(target, "%d nodes\n", len(records))
}

参考

完整代码请参考:

perillaroc/ecflow-client-go