lhf 的窝

OpenCv MPR.DLL WNetRestoreConnectionA

最近在搞opencv。有个问题连续搞了两次,本来已经搞好了,过了个国庆,回来全忘了,结果又搞了大半天。最后终于发现之前已经搞过一次了。现在记下来,以免再搞第3次。

是这样的。我的工程(vs2005,MFC)使用了cv110.dll,cxcore110.dll 两个opencv的库。在公司的机器上运行正常,拷到家里的机器就不能初始化。运行就报错误"应用程序正常初始化(0xc0150002)失败..."。

 使用depends.exe查看,发现工程的exe使用了MFC80U.dll,它又使用了SHLWAPI.dll,后者使用了MPR.dll,该dll有个输出函数WNetRestoreConnectionA无法定位,导致程序初始化失败.

这里MPR.dll是个延迟加载的dll,按理来说,只要不真实调用该函数就没有问题.但现在的情况是我的工程代码中肯定没有调用MRP.dll中的任何输出函数,但程序却挂了.看来比较奇怪。网上搜了一把,发现有说静态链接MFC的。好像有点道理,本来没有使用这个函数,如果静态链接
的话,就应该绕过了MFC80U.dll,因此也就没有MPR.dll。结果一试,不行。反复折腾了好几个小时,最后都想重装系统了(我家里的xp+sp2,公司的xp+sp3)。也想过下载个mpr.dll。最后终于想起来,好像以前搞过类似的问题(唉,就在几天前,都忘了)。

原来不是MFC80U.dll的事,是opencv的dll。CXCORE110.DLL使用了ADVAPI32.DLL(主要是注册表相关函数)。调用关系依次是:

SECUR32.DLL-->NETAPI32.DLL-->DNSAPI.DLL-->IPHLPAPI.DLL-->

MPRAPI.DLL-->SETUPAPI.DLL-->SHLWAPI.DLL-->MPR.DLL

那剩下的事就好办了。opencv是开源的嘛,有源代码的。只要找到相关代码,注释掉就好了。代码在 ...cxcore\src\cxswitcher.cpp中。把函数icvInitProcessorInfo注释掉就好了。当然如果你需要cpu信息方面的功能,就没这么省事了。我就简单的这么处理了。编译后,替换原来的两个dll,运行后,ok。

 这里其实还有几个问题没有搞清楚,等哪天有时间了再研究。现在先这么解决一下,能用就行。

FFMpeg相关

1、ffmpeg.exe的使用

ffmeg.exe功能强大,可用于视频格式转换,截图,或捕获视频音频等。这里只说明截图的方式,做个记录,以备将来参考。

截图的主要参数是:

    -i  :指定输入文件(视频文件),可以是相对路径或绝对路径。

   -y  :覆盖输出文件,即如果有同名文件的话,则直接覆盖。

  -f    :输出格式,一般采用 image2。

 -ss: 表示相对于文件开始处的时间偏移值, 单位是秒,也支持  hh:mm:ss的格式。

-s    : 指定输出图片的尺寸。 比如: -s 120*90

-vframes: 表示截图的数  。

上面各参数之间的顺序不重要,但一般好像都把 -i 作为第一个参数

 

看一个例子:ffmpeg -i  aa.mpg  -f image2 -ss 5 -s 120*90 -vframes 1 test.jpg 

含义是: 对当前目录下的aa.mpg截图,从第5秒开始截图,图片尺寸是 120*90,只截第一个图,保存为 test.jpg。

 

如果要输出多个图,则可以在图片名称中加入%d的参数,输出时,%d会被序号代替(支持扩展的%02d等格式)。比如:

ffmpeg -i aa.mpg tt%02d.jpg              输出aa.mpg中的每一帧图像,依次保存为 tt01.jpg,tt02.jpg等等。

ffmpeg -i aa.mpg tt%02d.jpg -vframes 5   输出aa.mpg的前5帧图像,依次保存为 tt01.jpg,tt02.jpg等等。

ffmpeg -i aa.mpg -ss 2 tt%02d.jpg -vframes 5  输出从第2秒开始的连续5帧图,依次为 tt01.jpg,tt02.jpg等。

 

 

表达式求值的递归算法

表达式求值,一般采用逆波兰算法,即转化为后缀表达式再进行计算。这当然很好。可是这个算法逻辑上不那么直观,如果是笔试或机考的话,除非你最近恰好看过该算法,或者你就是研究这些算法的,一般估计很难在限定的时间内理清头绪并写出代码。

如果直接采用中缀表达式求值,就更复杂了。如果不是准备充分,相信很难在笔试/机考的情况下完成代码。本人早几天去笔试就碰到这个题目,两小时,机考.当时看到这个题目,想了一个小时,也没想起逆波兰算法。在学校学的东西基本都忘的差不多了。最后只好直接对中缀表达式求值。结果当然是没搞定。还好该职位并不是很有吸引力的,不然就亏大了。

 事后想想,这个问题应该用递归的方法解决。这才是逻辑简单的,适合应试的算法。不废话,先贴代码。

(下面的代码支持 加减乘除,乘方,括号。没有进行错误检查。) 

#include "stdafx.h"

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>

//去括号,当整个表达式被一个括号包围时,可直接去掉.
void DelBracket(char *expre)// 比如: (3+4* (2-1)+5 )
{
        if(expre[0]=='(')
        {
                int  level = 0;
                char *pos;

                for(pos=expre;*pos;++pos)
                {
                        if(*pos=='(')      ++level;
                        else if(*pos==')') --level;

                        if(level==0)//找到匹配的括号.
                                break;
                }
       
                if(*(pos+1)==0)//下一个字符是结尾
                {
                        *pos = 0;
                        memmove(expre,expre+1,pos-expre);
                }
        }
}

bool IsNum(const char *str,double & v)
{
        char *end;
        double temp = strtod(str,&end);
       
        if(end==str+strlen(str))
        {
                v = temp;
                return true;
        }
       
        return false;
}

int FindOp(const char *expre)//找出最后一个优先级最低的运算符.
{
        const char *pos;
        int  level = 0;//当前位置的括号层数
       
        int  pos1 = -1; //最后一个 + - 出现的位置.
        int  pos2 = -1; //最后一个 * / 出现的位置.
        int  pos3 = -1; //最后一个 ^   出现的位置.
       
        for(pos=expre; *pos; ++pos)
        {
                if(*pos=='(')      ++level;
                else if(*pos==')') --level;
               
                if(level==0)//不在括号中.
                {
                        if(*pos=='+'||*pos=='-')
                        {
                                pos1 = pos - expre;
                        }
                        else if(*pos=='*'||*pos=='/')
                        {
                                pos2 = pos - expre;
                        }
                        else if(*pos=='^')
                        {
                                pos3 = pos - expre;
                        }
                }
        }
       
        int op_pos = -1;

        if(pos1!=-1)      op_pos = pos1;
        else if(pos2!=-1) op_pos = pos2;
        else if(pos3!=-1) op_pos = pos3;
        else {} //找不到运算符.

        return op_pos;
}

//把表达式拆成 a op b 的形式,并计算其值.
double DivExpression(const char *expre)
{
        double result = 999999999999;

        char buf[300];
        strcpy(buf,expre);

        DelBracket(buf);//去括号,当整个表达式被一个括号包围时,可直接去掉.

        int pos = FindOp(buf);//找到最右边的最低优先级的运算符.

        if(pos!=-1)//能拆成 a op b 的形式
        {
                char a[300];
                char b[300];

                strncpy(a,buf,pos);
                a[pos] = 0;

                char op = buf[pos];

                ++pos;
                strcpy(b,buf+pos);

                double a_v,b_v;

                if(strlen(a)==0) a_v = 0.0; //a是空字符串.
                else if(!IsNum(a,a_v))
                         a_v = DivExpression(a);

                if(!IsNum(b,b_v))
                        b_v =DivExpression(b)

                switch(op)
                {
                case  '+':  result = a_v+b_v;       break;
                case  '-':  result = a_v-b_v;       break;
                case  '*':  result = a_v*b_v;       break;
                case  '/':  result = a_v/b_v;       break;
                case  '^':  result = pow(a_v,b_v)break;
                default:    assert(0);              break;
                }
        }
    else//找不到二元操作符.可能是一元/三元操作符表达式.
        {
                IsNum(buf,result);//这里只支持表达式就是一个数.
        }

        return result;
}

int main(int argc, char* argv[])
{
        typedef struct
        {
                char * expre;
                double result;
        }Item;

        Item test[] = {
                {"1",       1},
                {"1+2",     3},
                {"1-2+4",   3},
                {"1+2-4"-1},
                {"1+2*4",   9},
                {"1+4/2",   3},
                {"3*2-4/1", 2},
                {"3^2-4*2", 1},
                {"5-(3+1)", 1},
                {"5-(3+1)/2", 3},
                {"5^2*3-(4+12/(3-1))/2", 70},
                {"3^3*((6+3)-5)",108},
                {"-(3+8)", -11},
                {"-11+(-3)",-14},
                {"-(-5)*3",15},
                {"(-5)^2",25},
                {"-5^2",-25},
                {"-5^(-2)",-0.04},
                {"5*(3+1)/2^2", 5},
        };

        for(int i=0;i<sizeof(test)/sizeof(Item);++i)
        {
                double re = DivExpression(test[i].expre);
                double diff = re-test[i].result;

                printf("%s = %.2f \t\t\t\t\t",test[i].expre,test[i].result);

                if(diff<0.0001 && diff>-0.0001)
                {
                        printf("ok\n");
                }
                else
                {
                        printf("error: %.2f\n",re);
                }
        }

        return 0;
}

 

 

 基本思路是这样的:一个表达式,可视为 a op b的形式,其中op是运算符。如果a,b都是操作数,就可以直接求值了。如果a,b是表达式,则还需再分解为 a op b 的形式,直到分解为全部是数为止。

 这其中有几点要注意的:

1、分解的时候,不能拆括号。即不能把一个括号的内容分解到运算符的两边。

2、只能先从低优先级运算符开始。比如:2*3+4/5,只能从+号开始分解。

3、同一运算符,从最右边的开始分解。比如:2-3+4,只能从 + 号开始分解。

4、如果一个表达式整个被括号包围,该括号可去掉。

5、对于(-5)这样的表达式,有一个小技巧,看成是 (0-5),即如果 a为空的话,则对于的值视为0。这就避免了还要考虑一元运算符。

上面的原则,1,2,3都好理解,都对应到运算规则:a,先算括号内的。 b,先乘方,再乘除,最后加减。c,同优先级,从左到右。

这3条规则保证拆分后的运算顺序和原来的是一样的。

第4条规则,是程序上的需要。因为不能拆开括号,如果一个表达式整个是一个括号,就没办法往下拆分了。所以必须去括号。

规则5是为了统一为二元运算符,这样实现起来简单。

上面的代码没有考虑非法的表达式。如果要考虑,就复杂了。包括括号不匹配,非法字符,多个运算符串联,除0错误等等。

 

 

 

 

FreeType的光栅化功能

FreeType是一个字体格式开源工程。其中不但支持各种字体格式的解析,还提供了一个独立的光栅化引擎。

---------------------------下面的文字解释了什么是光栅化--------------------------------------------------------------------

现在流行的字体格式如TureType,Opentype等,其中描述的字形信息(就是字符的笔划信息)都是矢量的。

其中字符的每一个笔划都是由多条曲线或直线(直线可视为一次曲线)包围而形成的。一次曲线需要两个点来确定。

二次需要三个点,三次就需要四个点。字体内部就保存了这些点的坐标。显示字体的时候,因为显示器和打印机

都是点阵设备,所以必须将字符转化为用点阵来描述。这个过程就是光栅化。说白了,就是矢量描述转化为点阵描述。

不过字体的光栅化与与其它光栅化(如图形学中的Bresenham,DDA算法等)不同的是,它关心的是区域的点阵化,

即判断每个点属于那个区域。

----------------------------------------------------------------------------------------------------------------------------------------------

FreeType2.3.7实际提供了两个光栅化引擎。ft_grays_raster和ft_standard_raster。

分别位于:src\smooth\ftgrays.c 和 src\raster\ftraster.c 。二者的用法完全相同。FreeType自己用的是前者。

要想在FreeType之外独立使用两个引擎,我们分两个的步骤:

1、编译问题。把相关的文件加入工程,设置_STANDALONE_等宏。可能要修改代码,比如注释掉一些代码行等。

将int64_t 改为 __int64(for vc),等等。这些事,对于一个编程熟手来说,应该10分钟搞定了。我就不多啰嗦。

2、代码调用。主要的函数调用有三个: 

    ft_grays_raster.raster_new(memory,&raster);
    ft_grays_raster.raster_reset(raster,pool_base,pool_size);
    ft_grays_raster.raster_render(raster,&param);

   这三个调用中,头两个很easy。参数很简单: 

void*       memory = NULL;
FT_Raster   raster;

int    pool_size = 10240;
BYTE*  pool_base = new BYTE[pool_size];

ft_grays_raster.raster_new(memory,&raster);
ft_grays_raster.raster_reset(raster,pool_base,pool_size);

  第三个调用有点麻烦,先看一下参数param的如何设置: 

        FT_Raster_Params  param;

        param.target      = &bmp;
        param.source      = &outline;
        param.flags       = FT_RASTER_FLAG_AA;
        param.gray_spans  = NULL;
        param.black_spans = NULL;
        param.bit_test    = NULL;
        param.bit_set     = NULL;
        param.user        = NULL;

其中bmp和outline的定义如下: 

      FT_Bitmap bmp;   

      FT_Outline outline;

 这两个参数非常重要,一个是输出(位图格式),一个是输入(矢量格式)。

先看一下FT_Bitmap的格式,它描述了一个位图,其中的buffer是位图的位数据,是4字节对齐的,从上往下的数据(你必须先了解windows位图的格式才可以理解上面的话)。因此bmp应这样设置:

        FT_Bitmap bmp;

        bmp.width        = 16;//输出位图的size.
        bmp.rows         = 16;
        bmp.pitch        = 16;//每行需要的字节数,4字节对齐的.
        bmp.buffer       = new BYTE[bmp.rows*bmp.pitch];
        bmp.num_grays    = 256
        bmp.pixel_mode   = FT_PIXEL_MODE_GRAY;
        bmp.palette_mode = 0;
        bmp.palette      = NULL;

        memset(bmp.buffer,0,bmp.rows*bmp.pitch);

      FT_Outline的格式就比较复杂些。它描述了一系列的轮廓线。其n_contours和n_points成员是容易理解的,分别表示轮廓的数目和点的数目。轮廓指围成一个闭合区域的边界。比如字符”一“是一条轮廓,字符‘二’是两条轮廓。字符”十“是一条轮廓。对更复杂的字符,就不一定能直接看出有多少个轮廓,因为轮廓之间会有重叠。点的数目,是指末端点和首端点不重合情况下的数目。比如,对一个矩形,应该是4个端点,而不是5个。FreeType内部会自动将末端点连到首段点。

其它几个成员的含义如下: 

outline.points     = new FT_Vector[n_points];//点的坐标
outline.tags       = new char [n_points];    //每个点的类型.
outline.contours   = new short [n_contours]; //每条轮廓线占用的点数.
outline.flags      = FT_OUTLINE_OWNER|FT_OUTLINE_HIGH_PRECISION;

points,tags,contours描述了每条轮廓的具体数据。三者必须严格对应上。这三个数组的具体格式也就是本篇文章的意义所在了。

points数组,tags数组 一般可以从字体文件数据中得到。但是其中的单位可能会有不同。这个跟字体的格式有关。不行就多试几个比例值。只要看到任何一点输出(比如一个字的其中一个笔画),就离成功不远了,在附近多试几个比例值。很快就可以让字符和位图的大小能符合了。

contours数组描述了一个轮廓使用的点数。比如:

contours[0]如果为8,则表示points数组中,元素0-8,都是第一条轮廓线的。

然后point数组中,9-contours[1]都是第二条轮廓线的。

依次类推,第n条轮廓线的点是: contours[n-2]+1 到 contours[n-1] (其中n>=2)

很明显,最后一个轮廓线对应的 contours[x]元素,其值必定等于 n_points-1。因为points数组最后一个元素就是 points[n_points-1]。

如果不满足此条件,ft_grays_raster直接跳出,认为数据错误。

 

 

 

 

 

几点心得

1.在多媒体定时器的响应函数中不能使用DestroyWindow函数。

因为多媒体定时器实际是内部开了一个线程,在该线程中调用定时器的回调函数。

而MSDN告诉我们:

A thread cannot use DestroyWindow to destroy a window created by a different thread.

所有就有上面的结论了。那么,如果确实要调用怎么办呢? 比如用多媒体定时器来控制一个窗口的生存时间,到了一定的秒数就关闭窗口。

应该用sendmessage。

 

BOOL CScreenWnd::CreateTimer(UINT uDelay)
{
        TIMECAPS tc;
       
        if(::timeGetDevCaps(&tc,sizeof(TIMECAPS)) == TIMERR_NOERROR)//查询设备能力。
        {
                m_timerRes = min(max(tc.wPeriodMin,1),tc.wPeriodMax);
               
                if(::timeBeginPeriod(m_timerRes)==TIMERR_NOERROR)//设定定时器精度。
                {
                        MMRESULT result= ::timeSetEvent(uDelay,m_timerRes,TimerProc,(DWORD)this, TIME_PERIODIC);
                       
                        if (result != NULL)//设置成功,定时器开始起作用。
                        {
                                m_timerId = (UINT)result;
                                return TRUE;
                        }
                }
        }

        return FALSE;            
}

下面是响应函数:

 

void CALLBACK CScreenWnd::TimerProc(UINT id,UINT msg,DWORD dwUser,DWORD dw1,DWORD dw2)
{
        ::SendMessage(m_pThis->m_hWnd,WM_MY_TimeTick,0,0);
       
//      if(m_pThis->m_LiveTime==0)
//      {
                //注意,这里不能直接调DestroyWindow,因为不在同一个线程中.
//      }
}

注意:WM_MY_TimeTick是一个自定义的消息。可以在响应该消息是调用DestroyWindow。SendMessage函数为什么可以成功呢,因为该函数没有要求在同一个线程中(实际上连同一个进程都没要求,可以安全的发送消息到另一个进程的窗口,很多情况下会失败是因为地址空间的关系)。它之所以不要求,是因为该函数自己会处理线程切换等事务。

 2、服务的类型和子进程的类型

 如果服务注册为非交互式的,然后在其中产生子进程。那么子进程可以使用窗口吗(即是否能是一个GUI程序)?答案是非也。

这个问题大概根窗口站(window station)相关吧。窗口站包含了桌面,桌面包含了窗口。windows将可见的窗口站命名为WinSta0,其余的窗口站都是不可见的。

除非另行指定,windows子系统将所有运行在本地系统账户下的服务与一个非可见窗口站:Service-0x0-3e7$关联起来。所有非交互式服务都共享此窗口站。

凡是指定了某个账户(非本地系统账户)的服务,都运行在一个独立的非可见窗口站中。

总结:如果是非交互式的服务,则其子进程也在同一个窗口站(该窗口站不可见)运行,因此子进程的窗口也是不可见的。

以上内容,详见《深入解析windows操作系统第4版》p221。

 3、如何屏蔽Ctrl+alt+del

简单的说,hook winlogon.exe进程中的SAS 窗口的窗口过程。在其中拦截该组合键即可。主要步骤是:

1. 打开winlogon.exe进程。这个又分为:    a.取进程id.    b.提升权限。 c.打开进程.

 2.写该进程的内存。 写入一些数据,为加载DLL做准备。

 3.创建远程线程,让winlogon加载一个dll。

4.DLL的主函数中,替换winlogon的SAS窗口过程。注意,该窗口在桌面Winlogon下,必须先打开该桌面,才能枚举窗口。

注:2,3,4也可以完全不使用dll,直接把代码写入winlogon进程,这就需要几个占位函数以便计算各函数的代码长度。

或者,使用DLL,但只是加入代码,并不在dll主函数中做hook窗口的工作。而是创建远程线程,执行该dll中的代码。

 相关的代码网上很容易找,不过自己做的话还是会碰到一些细节问题。只要基本概念有了,找到代码然后修改是很容易的。

自定义的打包文件

实现了一个自定义的打包格式,可以将一个目录下的所有文件打包成一个文件,并保持完整的目录结构。另外还支持分文件类型压缩。

阅读全文

虚拟表格显示控件

一个虚拟的大表格数据显示控件(MFC)

阅读全文

让vector析构时不释放内存

通过指定allocator的办法控制vector的内存释放。

阅读全文

MFC通用控件的初始化

介绍了MFC中通用控件初始化过程

阅读全文

慎用7za.exe的-r参数

7za.exe的一个bug.在遍历目录的时候初始目录搞错了一个层次.

阅读全文