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 encodingQFile::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="); #endif2.2 配置默认软件参数
静态成员函数Application::Config返回静态对象Application::mConfig对象,
static std::map<std::string,std::string> mConfig;mConfig以字符串的形式保存了不同参数对应的参数值。通过设置mConfig就可以指定FreeCAD的启动特性。
2.3 启动模式
FreeCAD具有GUI、Console等运行两种模式。通过设置“Console”/“RunMode”可以指定FreeCAD的运行模式。
| 名称 | 取值范围 | 默认值 |
| Console | 0 : 不启动控制台 1 : 启动控制台 | 0 |
| RunMode | Gui : 界面模式 Internal : | Gui |
如果“Console”为“1”,则FreeCAD将启动控制台。
如果“RunMode”为“Gui”或者“Internal”,将会调用FreeCADGui模块以界面模式启动FreeCAD;否则将会调用FreeCADMainCmd以控制台模式启动FreeCAD。
// if console option is set then run in cmd modeif (App::Application::Config()["Console"] == "1")App::Application::runApplication();if (App::Application::Config()["RunMode"] == "Gui" ||App::Application::Config()["RunMode"] == "Internal")Gui::Application::runApplication();elseApp::Application::runApplication();2.4 导出崩溃信息
通过调用dbghelp.dll中MiniDumpWriteDump函数,修改软件运行时堆栈信息导出到mConfig["UserAppData"]\creash.dmp文件中。
// create a dump file when the application crashesstd::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::mConfigApp::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 namestd::string getUniqueDocumentName(const char *Name) const;/// Open an existing document from a fileApp::Document* openDocument(const char * FileName=0l);/// Retrieve the active documentApp::Document* getActiveDocument(void) const;/// Retrieve a named documentApp::Document* getDocument(const char *Name) const;/// gets the (internal) name of the documentconst char * getDocumentName(const App::Document* ) const;/// get a list of all documents in the applicationstd::vector<App::Document*> getDocuments() const;/// Set the active documentvoid 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 verboseif (!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 modevoid 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 libraryDl_info info; #if PY_MAJOR_VERSION >= 3int ret = dladdr((void*)PyInit_FreeCAD, &info); #elseint ret = dladdr((void*)initFreeCAD, &info); #endifif ((ret == 0) || (!info.dli_fname)) {free(argv);PyErr_SetString(PyExc_ImportError, "Cannot get path of the FreeCAD module!"); #if PY_MAJOR_VERSION >= 3return 0; #elsereturn; #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 >= 3if ( PyUnicode_Check(pyPath) ) {// Python 3 stringbasePath = PyUnicode_AsUTF8AndSize(pyPath, &sz);} #elseif ( PyString_Check(pyPath) ) {// Python 2 string typePyString_AsStringAndSize(pyPath, &basePath, &sz);}else if ( PyUnicode_Check(pyPath) ) {// Python 2 unicode type - explicitly use UTF-8 codecPyObject *fromUnicode = PyUnicode_AsUTF8String(pyPath);PyString_AsStringAndSize(fromUnicode, &basePath, &sz);Py_XDECREF(fromUnicode);} #endif // #if/else PY_MAJOR_VERSION >= 3else {continue;}if (sz + sizeof(libName) > PATH_MAX) {continue;}// buf gets assigned to argv[0], which is free'd at the endbuf = (char *)malloc(sz + sizeof(libName));if (buf == NULL) {break;}strcpy(buf, basePath);// append libName to bufstrcat(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 >= 3return 0; #elsereturn; #endif}argv[0] = buf; #else # error "Implement: Retrieve the path of the module for your platform." #endifargv[argc] = 0;try {// Inits the ApplicationApp::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 |
|
参考资料
总结
以上是生活随笔为你收集整理的FreeCAD源码分析:FreeCADMain模块的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 运行在Istio之上的Apache Ka
- 下一篇: modbus rtu与计算机通讯,Mod