项目地址:​​https://github.com/theArcticOcean/Gifer​

QT log 重定向问题。

描述:在QML中的log print成功输出所有信息,CPP中的打印不能显示文件名,行号,函数名

看了帮助文档中的例子
QtMessageHandler qInstallMessageHandler(QtMessageHandler handler)
安装我们自己的handler后可以重定向

#include <qapplication.h>
#include <stdio.h>
#include <stdlib.h>

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtInfoMsg:
fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtWarningMsg:
fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtCriticalMsg:
fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtFatalMsg:
fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
abort();
}
}

int main(int argc, char **argv)
{
qInstallMessageHandler(myMessageOutput);
QApplication app(argc, argv);
...
return app.exec();
}

我使用在自己的项目中,源代码如下

...
int main()
{
//...
qInstallMessageHandler( LogBase );
//...
return 0;
}

结果log 重定向,QML成功打印所有信息,CPP中的打印不能显示文件名,行号,函数名

[Debug ((null), 0, (null))] LogInit finished.
[Warning (qrc:/main.qml, 72, (null))] qrc:/main.qml:72:9: QML Button: Cannot anchor to an item that isn't a parent or sibling. [Debug (qrc:/main.qml, 30, onPressed)] onPressed: (613.9609375, 507.06640625)

在查看源码后,发现QMessageLogContext有两种构造方式。

class QMessageLogContext
{
Q_DISABLE_COPY(QMessageLogContext)
public:
Q_DECL_CONSTEXPR QMessageLogContext()
: version(2), line(0), file(Q_NULLPTR), function(Q_NULLPTR), category(Q_NULLPTR) {}
Q_DECL_CONSTEXPR QMessageLogContext(const char *fileName, int lineNumber, const char *functionName, const char *categoryName)
: version(2), line(lineNumber), file(fileName), function(functionName), category(categoryName)

在我们的工程中,CPP类下的QMessageLogContext构造使用了第一种方式,QML下的的QMessageLogContext构造使用了第二种方式。我想,这和应用程序的启动相关,本工程使用QQmlApplicationEngine启动。
我们可以通过修改message handler函数来保证文件名,行号,函数名的输出:

const QMessageLogContext &context = QMessageLogContext( __FILE__, __LINE__, __FUNCTION__, NULL ),
const QString &msg = QString("")
)
{

Qt Quick设置icon

在pro文件中加上:

macx{
message("compile for mac os x")
ICON = Images/logo.icns
}

重新生成Makeifle,再make。

双击mac程序,程序在移动文件时提示没有权限。

双击app启动程序,我一开始猜想是Finder进程启动app进程。使用pstree可以查看相关的进程派生关系。可以使用brew安装pstree。
查询Finder相关的分支:

➜ Images git:(master) ✗ ps aux |grep Finder
weiyang 284 0.0 0.5 4751940 45756 ?? R 14Jul18 9:12.45 /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
weiyang 27974 0.0 0.0 4267752 576 s002 R+ 9:07AM 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn Finder

➜ Images git:(master) ✗ pstree -p 284
-+= 00001 root /sbin/launchd
\--= 00284 weiyang /System/Library/CoreServices/Finder.app/Contents/MacOS/Find

用类似的方法,查询双击Gifer.app后的进程派生关系。

➜ Images git:(master) ✗ ps aux |grep Gifer
weiyang 28107 0.2 0.6 4474736 46468 ?? S 9:08AM 0:01.01 /Users/weiyang/pro/Gifer/Gifer.app/Contents/MacOS/Gifer
weiyang 28129 0.0 0.0 4267752 532 s002 R+ 9:09AM 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn Gifer

➜ Images git:(master) ✗ pstree -p 28107
-+= 00001 root /sbin/launchd
\--= 28107 weiyang /Users/weiyang/pro/Gifer/Gifer.app/Contents/MacOS/Gifer

原来,是launchd启动app的。
那么命令行启动的进程派生又是怎么样子的呢?

➜ Images git:(master) ✗ ps aux |grep Gifer
weiyang 28395 0.0 0.0 4267752 640 s002 R+ 9:16AM 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn Gifer
weiyang 28342 0.0 0.6 4476844 46860 s001 S+ 9:15AM 0:00.70 ./Gifer
➜ Images git:(master) ✗ pstree -p 28342
-+= 00001 root /sbin/launchd
\-+= 17185 weiyang /Applications/iTerm.app/Contents/MacOS/iTerm2
\-+= 17249 weiyang /Applications/iTerm.app/Contents/MacOS/iTerm2 --server lo
\-+= 17250 root login -fp weiyang
\-+= 17251 weiyang -zsh
\--= 28342 weiyang ./Gifer

命令行启动的进程是由终端进程启动的。

在源码中,我使用了​​QDir::current().absolutePath()​​​定位程序的路径,但他返回的是​​/​​。进一步查看该函数的解释

[static] QDir QDir::current()
Returns the application’s current directory.
The directory is constructed using the absolute path of the current directory

再看看QCoreApplication::applicationDirPath()的解释

[static] QString QCoreApplication::applicationDirPath()
Returns the directory that contains the application executable.
For example, if you have installed Qt in the C:\Qt directory, and you run the regexp example, this function will return “C:/Qt/examples/tools/regexp”.

在程序启动起来后QDir QDir::current()返回的application’s current directory是不确定的。
如果是双击app启动,返回值是​​​/​​​;如果是命令行启动,返回值是可执行文件的路径,也就和​​QCoreApplication::applicationDirPath()​​​一样了。
后者是我们需要的函数。

使用system运行命令的问题

假设我使用system运行这样一条命令:
​​​ffmpeg -threads 1 -y -r 70 -i /Users/weiyang/pro/Gifer/Gifer.app/Contents/MacOS/Pictures/out%d.png -final_delay 300 /Users/weiyang/pro/Gifer/Gifer.app/Contents/MacOS/Pictures/output.gif​​​
再这样判断:

;
if( -1 == ret || 127 == ret )
{
tmp = cmdStr + strerror(errno);
qWarning() << tmp;
QMessageBox::information( this,
tr("Combine Gif Failed"),
tmp,
QMessageBox::Ok
);
return false;

判断依据来自于mac上的帮助文档

RETURN VALUES
The system() function returns the exit status of the shell as returned by
waitpid(2), or -1 if an error occurred when invoking fork(2) or
waitpid(2). A return value of 127 means the execution of the shell
failed.


system的工作大致是这样的:
父进程fork子进程
父进程等待子进程
在子进程中执行字符串所表述的命令
返回执行结果。

我将源码改成:

system( cmdStr.toStdString().c_str() );
// create sub
qCritical("system error!");
}
else
{
qDebug("ret is not -1, exit ret value = [0x%x]", ret); //0x7f00

// call shell script and finish successfully.
if (WIFEXITED(ret)) // WIFEXITED is a macro
{
if (0 == WEXITSTATUS(ret)) // return value of shell script run.
{
qDebug("run shell script successfully.");
}
// 0x7f = 127
else
{
qCritical("run shell script fail, script exit code: %d, reason: %s", WEXITSTATUS(ret), strerror(errno));
}
}
else
{
qCritical("exit ret = [%d]", WEXITSTATUS(ret));
}
}

当我在shell终端中命令行方式启动后,没有问题。但如果是双击Gifer.app就会是这样的结果:

[Debug (windowgrabber.cpp, 159, bool WindowGrabber::combineImages())] ret is not -1, exit ret value = [0x7f00]
[Critical (windowgrabber.cpp, 171, bool WindowGrabber::combineImages())] run shell script fail, script exit code: 127, reason: No such file or

一样的命令,结果却是不同的。
我找到了ffmpeg的具体位置,然后在system的命令中也给出ffmpeg的绝对路径,问题就不存在了。也就是说,在双击启动app的方式下,系统找不到ffmpeg。
在双击启动app的方式下,PATH的值是

/usr/bin:/bin:/usr/sbin:/sbin

此结果在代码中添加​​system("echo $PATH > ~/code/txt");​​​可以检测出来。
这和我在​​​~/.bashrc​​​下的设置是不一样的。
新的PATH值刚好对应

/usr/include/paths.h
67:#define _PATH_STDPATH "/usr/bin:/bin:/usr/sbin:/sbin"

/usr/libexec/security-checksystem
12:export PATH=/usr/bin:/bin:/usr/sbin:/sbin

/usr/local/Homebrew/bin/brew
71: PATH="/usr/bin:/bin:/usr/sbin:/sbin"

system的源码为:

int system(const char * cmdstring)
{
pid_t pid;
int status;
if( cmdstring == NULL )
{
return 1;
}
if( (pid = fork()) < 0 )
{
status = -1;
}
else if( pid == 0 )
{
// execl create new process to replace old process
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
// if above statement execute successfully, the following function would not work.
_exit(127);
}
else
{
while( waitpid(pid, &status, 0) < 0 )
{
if( errno != EINTR )
{
status = -1;
break;
}
}
}
return

execlp搜索目录默认值是paths.h中的​​_PATH_DEFPATH​​​。也就是前面提到过的​​/usr/bin:/bin:/usr/sbin:/sbin​

For execlp() and
execvp(), search path is the path specified in the environment by
PATH variable. If this variable is not specified, the default path
is set according to the ​​​_PATH_DEFPATH​​​ definition in ​​<paths.h>​​​, which is
set to “/usr/bin:/bin”. For execvP(), the search path is specified as
an argument to the function. In addition, certain errors are treated
specially.

故,在​​/usr/bin/​​下建立一个软连接即可。

➜ ~ sudo ln -s

怎样使得dmg安装包自带Application文件夹

之前打包都是使用QT的打包功能,macdeployqt或windeployqt,但是生成的dmg有一个缺点,他没有Application文件夹图标生成。每次都需要拖动到Finder中的Application。
我在​​​https://stackoverflow.com/questions/8680132/creating-nice-dmg-installer-for-mac-os-x​​ 看到了一位大神Linus Unnebäck 的回答,他的方案简单易懂。

brew install node
npm install

创建spec.json

{
"title": "Gifer Installer",
"icon": "Images/logo.icns",
"background": "Images/back.png",
"icon-size": 80,
"contents": [
{ "x": 100, "y": 120, "type": "file", "path": "Gifer.app"},
{ "x": 400, "y": 120, "type": "link", "path": "/Applications"}
],
"window": {
"size": { "width": 500, "height": 300}
}
}

生成dmg:
​​​appdmg spec.json Gifer.dmg​​​
效果: