FreeCAD源码分析:FreeCADMain模块

 

FreeCAD源码分析:FreeCADCmd\\FreeCADMain\\FreeCADPy模块

济南友泉软件有限公司

FreeCAD提供了控制台、GUI、Python等三种运行模式,分别对应FreeCADCmd、FreeCADMain、FreeCADPy。由于这三者的代码结构比较相似,但FreeCADMain相对复杂一些,因此,本文着重就FreeCADMain展开分析。了解清楚FreeCADMain模块,就可以轻松掌握FreeCADMainCmd、FreeCADMainPy模块的原理。

注:FreeCADMain也提供了控制台模式运行。

一、模块功能概述

FreeCADMain主要用于根据配置参数(默认参数、用户参数)启动软件FreeCAD,具体来说,主要功能包括:

  • 配置参数

生成默认配置参数,解析用户输入参数,订制FreeCAD的启动特性。

  • dmp文件

软件意外崩溃时,输出堆栈信息到crash.dmp文件(默认位置%APPDATA%\\FreeCAD)。

  • 日志重定向

将标准输出、标准错误、标准日志等重定向到日志文件。

  • 启动软件

根据配置参数启动GUI版本或者Console版本的FreeCAD。

二、处理流程

通过分析MainGui.cpp以研究FreeCADMain的主要功能的实现流程。

   2.1 移除PYTHONHOME环境变量

PYTHONHOME环境变量指向Python的安装目录,通常不需要用户自己设置该变量。为了防止多个版本的Python对FreeCAD的影响,在main函数进入之后,首先移除了PYTHONHOME环境变量。

#if defined (FC_OS_LINUX) || defined(FC_OS_BSD)
    // Make sure to setup the Qt locale system before setting LANG and LC_ALL to C.
    // which is needed to use the system locale settings.
    (void)QLocale::system();

#if QT_VERSION < 0x050000
    // http://www.freecadweb.org/tracker/view.php?id=399
    // Because of setting LANG=C the Qt automagic to use the correct encoding
    // for file names is broken. This is a workaround to force the use of UTF-8 encoding
    QFile::setEncodingFunction(myEncoderFunc);

    QFile::setDecodingFunction(myDecoderFunc);

#endif
    // See https://forum.freecadweb.org/viewtopic.php?f=18&t=20600
    // See Gui::Application::runApplication()
    putenv("LC_NUMERIC=C");
    putenv("PYTHONPATH=");

#elif defined(FC_OS_MACOSX)
    (void)QLocale::system();
    putenv("PYTHONPATH=");

#else
    _putenv("PYTHONPATH=");                                                                        

    // https://forum.freecadweb.org/viewtopic.php?f=4&t=18288                                   
    // https://forum.freecadweb.org/viewtopic.php?f=3&t=20515                                     
    const char* fc_py_home = getenv("FC_PYTHONHOME");                                            

    if (fc_py_home)                                                                             
        _putenv_s("PYTHONHOME", fc_py_home);                                                    

    else                                                                                        
        _putenv("PYTHONHOME=");                                                                  
#endif

   2.2 配置默认软件参数

静态成员函数Application::Config返回静态对象Application::mConfig对象,

static std::map<std::string,std::string> mConfig;

mConfig以字符串的形式保存了不同参数对应的参数值。通过设置mConfig就可以指定FreeCAD的启动特性。
   

    // Name and Version of the Application
    App::Application::Config()["ExeName"] = "FreeCAD";
    App::Application::Config()["ExeVendor"] = "FreeCAD";
    App::Application::Config()["AppDataSkipVendor"] = "true";
    App::Application::Config()["MaintainerUrl"] = "http://www.freecadweb.org/wiki/Main_Page";


    // set the banner (for logging and console)
    App::Application::Config()["CopyrightInfo"] = sBanner;
    App::Application::Config()["AppIcon"] = "freecad";
    App::Application::Config()["SplashScreen"] = "freecadsplash";
    App::Application::Config()["StartWorkbench"] = "StartWorkbench";

    //App::Application::Config()["HiddenDockWindow"] = "Property editor";
    App::Application::Config()["SplashAlignment" ] = "Bottom|Left";
    App::Application::Config()["SplashTextColor" ] = "#ffffff"; // white
    App::Application::Config()["SplashInfoColor" ] = "#c8c8c8"; // light grey

   2.3 启动模式

FreeCAD具有GUI、Console等运行两种模式。通过设置“Console”/“RunMode”可以指定FreeCAD的运行模式。

名称

取值范围

默认值

Console

0 : 不启动控制台

1 : 启动控制台

0

RunMode

Gui :  界面模式

Internal :

Gui

// Init phase ===========================================================
// sets the default run mode for FC, starts with gui if not overridden in InitConfig...
  App::Application::Config()["RunMode"] = "Gui";
  App::Application::Config()["Console"] = "0";

如果“Console”为“1”,则FreeCAD将启动控制台。

如果“RunMode”为“Gui”或者“Internal”,将会调用FreeCADGui模块以界面模式启动FreeCAD;否则将会调用FreeCADMainCmd以控制台模式启动FreeCAD。

        // if console option is set then run in cmd mode
        if (App::Application::Config()["Console"] == "1")
            App::Application::runApplication();
        if (App::Application::Config()["RunMode"] == "Gui" ||
            App::Application::Config()["RunMode"] == "Internal")
            Gui::Application::runApplication();
        else
            App::Application::runApplication();

  2.4 导出崩溃信息

通过调用dbghelp.dll中MiniDumpWriteDump函数,修改软件运行时堆栈信息导出到mConfig[“UserAppData”]\\creash.dmp文件中。

        // create a dump file when the application crashes
        std::string dmpfile = App::Application::getUserAppDataDir();
        dmpfile += "crash.dmp";
        InitMiniDumpWriter(dmpfile);

  2.5 重定向日志输出

Base::RedirectStdOutput、Base::RedirectStdLog、RedirectStdError均继承自std::streambuf。Base::RedirectStdOutput、Base::RedirectStdLog将输出、日志等重定向到了Base::Console的Log函数;RedirectStdError则将错误重定向到Base::Console的Error函数。

通过调用std:: basic_ios::rdbuf函数设置std::cout、std::clog、std::cerr等流对象的缓冲区可以分别完成标准输出、日志、错误等输出的重定向。

   Base::RedirectStdOutput stdcout;
   Base::RedirectStdLog    stdclog;
   Base::RedirectStdError  stdcerr;
   std::streambuf* oldcout = std::cout.rdbuf(&stdcout);
   std::streambuf* oldclog = std::clog.rdbuf(&stdclog);
   std::streambuf* oldcerr = std::cerr.rdbuf(&stdcerr);

FreeCAD退出之前,需要恢复标准输出、日志、错误等流对象缓冲区。

    std::cout.rdbuf(oldcout);
    std::clog.rdbuf(oldclog);
    std::cerr.rdbuf(oldcerr);

三、控制台应用程序

App::Application类用于FreeCAD主程序的抽象。实现配置参数管理、文档管理等功能。

   3.1 初始化

应用程序类调用init成员函数完成应用程序的初始化工作,init函数的原型为:

static void init(int argc, char ** argv);

参数说明:

argc: argv数组中的长度,由主函数main传入

argv: 参数列表,由main函数传入

根据传入的参数,调用initConfig()函数初始化配置参数,并将默认配置参数存放到mConfig中,

static std::map<std::string,std::string> mConfig;

注:mConfig主要配置参数参见附录A

   3.2 参数管理

App::Application使用mConfig保存基本的软件信息,包括程序名称、用户参数目录、系统参数目录等。

std::map<std::string,std::string> Application::mConfig

App::Application使用一系列ParameterManager类型对象来管理软件参数,

std::map<std::string,ParameterManager *> mpcPramManager;

可以通过

ParameterManager不仅可以加载、导出DOM格式的参数文件,而且提供多种接口以方便的获取整形、浮点型、布尔类型的参数值。

mpcPramManager中有两个比较重要的参数管理器,即_pcSysParamMngr与_pcUserParamMngr,

ParameterManager *App::Application::_pcSysParamMngr;
ParameterManager *App::Application::_pcUserParamMngr;

在Application::LoadParameters()中,_pcUserParamMngr 会加载mConfig[“UserParameter”] 指定的文件(mConfig[“UserAppData”]\\user.cfg)文件来完成对;_pcSysParamMngr会加载mConfig[“SystemParameter”](默认是mConfig[“UserAppData”]\\system.cfg)

   3.3 文档管理

基于信号-槽机制,App::Application提供了创建、打开、激活、关闭文档等一系列文档操作函数。  

    App::Document* newDocument(const char * Name=0l, const char * UserName=0l);

    /// Closes the document \\a name and removes it from the application.
    bool closeDocument(const char* name);

    /// find a unique document name
    std::string getUniqueDocumentName(const char *Name) const;

    /// Open an existing document from a file
    App::Document* openDocument(const char * FileName=0l);

    /// Retrieve the active document
    App::Document* getActiveDocument(void) const;

    /// Retrieve a named document
    App::Document* getDocument(const char *Name) const;

    /// gets the (internal) name of the document
    const char * getDocumentName(const App::Document* ) const;

    /// get a list of all documents in the application
    std::vector<App::Document*> getDocuments() const;

    /// Set the active document
    void setActiveDocument(App::Document* pDoc);
    void setActiveDocument(const char *Name);

    /// close all documents (without saving)
    void closeAllDocuments(void);

四、重定向输出类

FreeCADBase模块提供了RedirectStdOutput、RedirectStdError、RedirectStdLog等三个类用于日志输出重定向。

class BaseExport RedirectStdOutput : public std::streambuf
{
public:
    RedirectStdOutput();

protected:
    int overflow(int c = EOF);
    int sync();

private:
    std::string buffer;
};

class BaseExport RedirectStdError : public std::streambuf
{
public:
    RedirectStdError();

protected:
    int overflow(int c = EOF);
    int sync();

private:
    std::string buffer;
};

class BaseExport RedirectStdLog : public std::streambuf
{
public:
    RedirectStdLog();

protected:
    int overflow(int c = EOF);
    int sync();

private:
    std::string buffer;
};

可以看到,这三个类的实现比较相似。这三个类都继承自std::streambuf类。

using streambuf = basic_streambuf<char, char_traits<char>>;

streambuf实际上是std::basic_streambuf模板类char类型的实例化之后得到的一个类。

当有新的字符插入时,就会调用overflow(int c)函数,参数c就是要插入的字符。sync()函数用于清空缓冲区中的内容。

  4.1 RedirectStdOutput

由于RedirectStdOutput、RedirectStdError、RedirectStdLog等三个类的实现方式比较相似,这里仅对RedirectStdOutput进行分析。

在RedirectStdOutput的构造函数中,设置了缓冲区的大小,

RedirectStdOutput::RedirectStdOutput() 
{
    buffer.reserve(80);
}

当有新的字符需要插入的时候,overflow函数将字符放到内部字符串的最后面,同事返回当前字符串的长度。

int RedirectStdOutput::overflow(int c)
{
    if (c != EOF)
        buffer.push_back((char)c);
    return c;
}

调用sync函数,将字符串当前的内容通过Base::Console().Log函数输出。

int RedirectStdOutput::sync()
{
    // Print as log as this might be verbose
    if (!buffer.empty()) {
        Base::Console().Log("%s", buffer.c_str());
        buffer.clear();
    }
    return 0;
}

  4.2 ConsoleSingleton

ConsoleSigleton用于信息、警告、错误等输出。程序运行时,只有一个ConsoleSigleton对象。可以通过Base::Console()获取该唯一的控制台输出对象。

  #include <Base/Console.h>
  Base::Console().Log("Stage: %d",i);

ConsoleSigleton提供了多种工作模式,可以通SetConsoleMode函数进行设置,

    /// Change mode
    void SetConsoleMode(ConsoleMode m);

五、界面应用程序类

略(Gui::Application与App::Application比较相似),参照FreeCADApp源码分析。

六、FreeCADMainCmd模块

FreeCADMainCmd构建生成FreeCADCmd(_d).exe程序,实际上是直接调用FreeCADApp模块完成FreeCAD启动。

七、FreeCADMainPy模块

FreeCADMainPy构建生成FreeCAD(_d).pyd动态链接库,用于在Python环境中导入FreeCAD模块。MainPy.cpp文件中,定义了Python FreeCAD模块的入口函数,

PyMOD_INIT_FUNC(FreeCAD)
{
    // Init phase ===========================================================
    App::Application::Config()["ExeName"] = "FreeCAD";
    App::Application::Config()["ExeVendor"] = "FreeCAD";
    App::Application::Config()["AppDataSkipVendor"] = "true";

    int    argc=1;
    char** argv;
    argv = (char**)malloc(sizeof(char*)* (argc+1));

#if defined(FC_OS_WIN32)
    argv[0] = (char*)malloc(MAX_PATH);
    strncpy(argv[0],App::Application::Config()["AppHomePath"].c_str(),MAX_PATH);
    argv[0][MAX_PATH-1] = '\\0'; // ensure null termination
#elif defined(FC_OS_CYGWIN)
    HMODULE hModule = GetModuleHandle("FreeCAD.dll");
    char szFileName [MAX_PATH];
    GetModuleFileNameA(hModule, szFileName, MAX_PATH-1);
    argv[0] = (char*)malloc(MAX_PATH);
    strncpy(argv[0],szFileName,MAX_PATH);
    argv[0][MAX_PATH-1] = '\\0'; // ensure null termination
#elif defined(FC_OS_LINUX) || defined(FC_OS_BSD)
    putenv("LANG=C");
    putenv("LC_ALL=C");
    // get whole path of the library
    Dl_info info;
#if PY_MAJOR_VERSION >= 3
    int ret = dladdr((void*)PyInit_FreeCAD, &info);
#else
    int ret = dladdr((void*)initFreeCAD, &info);
#endif
    if ((ret == 0) || (!info.dli_fname)) {
        free(argv);
        PyErr_SetString(PyExc_ImportError, "Cannot get path of the FreeCAD module!");
#if PY_MAJOR_VERSION >= 3
        return 0;
#else
        return;
#endif
    }

    argv[0] = (char*)malloc(PATH_MAX);
    strncpy(argv[0], info.dli_fname,PATH_MAX);
    argv[0][PATH_MAX-1] = '\\0'; // ensure null termination
    // this is a workaround to avoid a crash in libuuid.so
#elif defined(FC_OS_MACOSX)

    // The MacOS approach uses the Python sys.path list to find the path
    // to FreeCAD.so - this should be OS-agnostic, except these two
    // strings, and the call to access().
    const static char libName[] = "/FreeCAD.so";
    const static char upDir[] = "/../";

    char *buf = NULL;

    PyObject *pySysPath = PySys_GetObject("path");
    if ( PyList_Check(pySysPath) ) {
        int i;
        // pySysPath should be a *PyList of strings - iterate through it
        // backwards since the FreeCAD path was likely appended just before
        // we were imported.
        for (i = PyList_Size(pySysPath) - 1; i >= 0 ; --i) {
            const char *basePath;
            PyObject *pyPath = PyList_GetItem(pySysPath, i);
            long sz = 0;

#if PY_MAJOR_VERSION >= 3
            if ( PyUnicode_Check(pyPath) ) {
                // Python 3 string
                basePath = PyUnicode_AsUTF8AndSize(pyPath, &sz);
            }
#else
            if ( PyString_Check(pyPath) ) {
                // Python 2 string type
                PyString_AsStringAndSize(pyPath, &basePath, &sz);
            }
            else if ( PyUnicode_Check(pyPath) ) {
                // Python 2 unicode type - explicitly use UTF-8 codec
                PyObject *fromUnicode = PyUnicode_AsUTF8String(pyPath);
                PyString_AsStringAndSize(fromUnicode, &basePath, &sz);
                Py_XDECREF(fromUnicode);
            }
#endif // #if/else PY_MAJOR_VERSION >= 3
            else {
                continue;
            }

            if (sz + sizeof(libName) > PATH_MAX) {
                continue;
            }

            // buf gets assigned to argv[0], which is free'd at the end
            buf = (char *)malloc(sz + sizeof(libName));
            if (buf == NULL) {
                break;
            }

            strcpy(buf, basePath);

            // append libName to buf
            strcat(buf, libName);
            if (access(buf, R_OK | X_OK) == 0) {

                // The FreeCAD "home" path is one level up from
                // libName, so replace libName with upDir.
                strcpy(buf + sz, upDir);
                buf[sz + sizeof(upDir)] = '\\0';
                break;
            }
        } // end for (i = PyList_Size(pySysPath) - 1; i >= 0 ; --i) {
    } // end if ( PyList_Check(pySysPath) ) {

    if (buf == NULL) {
        PyErr_SetString(PyExc_ImportError, "Cannot get path of the FreeCAD module!");
#if PY_MAJOR_VERSION >= 3
        return 0;
#else
        return;
#endif
    }

    argv[0] = buf;
#else
# error "Implement: Retrieve the path of the module for your platform."
#endif
    argv[argc] = 0;

    try {
        // Inits the Application
        App::Application::init(argc,argv);
    }
    catch (const Base::Exception& e) {
        std::string appName = App::Application::Config()["ExeName"];
        std::stringstream msg;
        msg << "While initializing " << appName << " the following exception occurred: '"
            << e.what() << "'\\n\\n";
        msg << "\\nPlease contact the application's support team for more information.\\n\\n";
        printf("Initialization of %s failed:\\n%s", appName.c_str(), msg.str().c_str());
    }

    free(argv[0]);
    free(argv);

#if PY_MAJOR_VERSION >= 3
    //PyObject* module = _PyImport_FindBuiltin("FreeCAD");
    PyObject* modules = PyImport_GetModuleDict();
    PyObject* module = PyDict_GetItemString(modules, "FreeCAD");
    if (!module) {
        PyErr_SetString(PyExc_ImportError, "Failed to load FreeCAD module!");
    }
    return module;
#endif
}

附录A: FreeCAD配置参数

名称

默认值

描述

APPDATA

操作系统内置环境变量,例如

C:\\Users\\Administrator\\AppData

 

AppDataSkipVendor

true

 

ExeVendor

FreeCAD

 

ExeName

FreeCAD

 

UserAppData

$( APPDATA)\\FreeCAD

 

UserParameter

$(UserAppData)\\user.cfg

 

SystemParameter

$(UserAppData)\\system.cfg

 

参考资料

  1. FreeCAD Developer hub
 
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片