袁瑞 的个人资料袁瑞的共享空间照片日志留言簿更多 工具 帮助

日志


3月24日

在DHTML中响应ActiveX控件的事件

转载自www.diybl.com  作者:佚名

ActiveX控件可以用连接点创建事件,此事件可以在DHTML网页中被javascript函数处理,在js中响应ActiveX控件事件的方法如下:
1. 静态创建方法     
<script>
function  OnEvent1()
{
}
</script>
<OBJECT id="myControl"
                   codeBase="myControl.cab"              
                   classid="clsid:1ACA02DF-AF52-4371-89B9-B9245BD21831"
></OBJECT>
<script       language=javascript
                   FOR="myControl"
                   EVENT="event1()">   
                   OnEvent1();
</script>
2. 动态创建方法
function onBodyLoad()
{
        myControl = document.createElement("OBJECT");
        document.body.appendChild(myControl);
        myControl.codeBase = "myControl.cab";
        myControl.classid = "clsid:1ACA02DF-AF52-4371-89B9-B9245BD21831";
        myControl.attachEvent("event1", OnEvent1);
}
<body onload="onBodyLoad()">
...
3. 错误的动态创建方法
function onBodyLoad()
{
        myControl = new ActiveXObject(myControl.Test);
        myControl.attachEvent("event1", OnEvent1);
        ~~~~~~~~~~~~~~~~~~此时会报告“无此方法”,这是因为
用new ActiveXObject创建的对象并非一个DOM对象,因此不能
调用attachEvent方法。而用document.createElement("OBJECT")
创建的才是一个DOM对象。
}
<body onload="onBodyLoad()">

1月15日

动态创建的TIdTime控件无法获取准确时间的问题

在BCB2009内测试发现,使用代码动态创建的TIdTime获取到的服务器时间,总是比服务器的实际时间少两天。于是创建一个测试程序,将TIdTiime控件直接拖放到窗口中,测试发现获取到的时间是正确的。一切都是默认设置,代码中和界面中都只是设置了Host属性而已,到底区别在哪里呢?

我开始怀疑BaseDate属性,这个属性在拖放到界面上后会自动设置为一个默认值,如下图:1900/1/1,由于我使用的是英文操作系统,在其他系统上也许不是用/来分隔的。

image

查看TIdTime类的头文件,发现其属性BaseTime并没有默认值

__property System::TDateTime BaseDate = {read=FBaseDate, write=FBaseDate};

在IDE属性编辑器中的默认值也许是在窗口内创建控件时加入的,而通过代码动态创建时似乎未设置。

RFC868中要求的时间是从00:00 (midnight) 1 January 1900 GMT开始,而TDateTime是从12/30/1899 12:00开始计算,恰好是2天!

于是我在动态创建TIdTime后,再设置BaseDate,测试获取时间正常了。

这里需要提醒一下,BaseDate是TDateTime类型,如果要将字符串“1900-1-1”转换为TDateTime类型,也许会出现错误,原因是日期分隔符不一定是"-",日期格式也不一定是YYYY-MM-DD,需要设置日期格式和分隔符,再进行转换:

    DateSeparator = '-';
    ShortDateFormat = "yyyy-mm-dd";
    StrToDateTime("1900-1-1");

值得注意的是,似乎TIdTime控件在BCB6下的版本无此问题,BCB2009的TIdTime版本存在这个问题。

1月11日

bcb.exe.manifest 引发的“血案”

升级到英文Vista后,最近发现BCB6中一些使用了ImageList类的程序会启动失败报Failed to read ImageList data的错误,让人很是费解。还有的程序可以在Vista下正常运行,但是在Xp下启动却会出现这个错误。一怒之下只能怪罪于TImageList,不曾想却是一桩冤案。

在使用Xp系统时,我就为了让BCB6.0的IDE也具有XP界面风格,将bcb.exe.manifest拷贝到安装目录下,这样可以强制BCB使用XP界面风格,即启用COMCTL32.DLL版本6(Vista下应该是更高版本)支持。这样界面在IDE的设计期内使用的是新版本,但是程序运行时如果不设置启用新版本支持就出现TImageList运行时无法正常读取数据的问题。

为了反向验证,将BCB目录下的bcb.exe.manifest文件删除,再打开问题程序时,打开界面时同样开始报错了。

同样也可以解释Vista下运行正常的程序,返回到Xp下运行失败的问题。Vista下程序内支持了更高版本的界面特性,导致TImageList在XP下运行无法读取数据,程序仍然遇到了类似的问题。

1月8日

setlocale同mbstowcs函数的关系

程序中,如果要将ASCII码字符串转换为宽字符(Unicode),可以利用标准C的mbstowcs函数。

微软在MSDN中有示例,如下:

image

然而,这段代码在处理含有汉字的字符串时就会出现问题。比如将:

image 替换为image

查看运行结果就会发现,mbstowcs函数将汉字视作两个ASCII字符,这样一个汉字就变成了两个wchar_t。原因是mbstowcs需要我们明确的告诉他要转换的字符语言。这里需要使用setlocale函数。在网上发现不少人遇到这个问题,微软的MSDN也是,为什么这里就不说明一下呢?

只要在调用前,使用setlocale(LC_ALL, "chs")设置,结果就正常了。

我测试在英文Vista操作系统内,Visual Studio 2008下setlocale(LC_ALL, "chs")可以执行成功。

但是在Borland C++Builder 6、CodeGear RAD Studio 2009下执行都失败,BCB提供的帮助文件内也未找到,反复测试使用setlocale(LC_ALL, "Chinese (Simplified)_People's Republic of China")可以奏效(这么大一个长串,BCB对搞中文编码的程序员也够狠)。

更讽刺的是,在BCB内使用setlocale(LC_ALL, "jpn"),或"cht"都可以成功。唯独就不支持"chs",对BCB的做法彻底无语了。我相信在大多数unix或linux上也还是支持"chs"的。如果有时间,我再搞一个MinGW试试看。

另外如果程序运行在非中文操作系统内,使用setlocale修改运行时字符集环境,会影响当前应用程序的编码方式,因此使用前需要保留一下老的编码,使用后再恢复。

 

for 在linux上coding的兄弟们,locale别名表大概在 /usr/lib/X11/locale/locale.alias

11月29日

判断是否为数字型字符串

周六早晨睡醒,看见CSDN上有人求判断字符串是否为数字的代码,给出一个方法
 
bool __fastcall CheckIsNumber(AnsiString InputStr)
{
    for(int i=1;i<=InputStr.Length();i++)
    {
        if( InputStr[i]<'0' || InputStr[i]>'9')
            return false;
    }
    return true;
}
10月9日

BDS 2009 Update1 破解工具

今天BDS的Update1放出了,于是写了破解工具,目前放到纳米上了,有需要的朋友去下吧。
 
9月9日

C++Builder 2009 的一个Bug

编译ActiveX control时,按照原来在CBuilder中的做法,将控件的GUID改成一个固定的GUID值(因为不想每次升级控件后,注册表中出现一堆无用的GUID)

一切看起来都很正常,编译成功。注册ocx控件时,却出现了0x8002802b错误。意思应该是找不到元素。

我开始并没有怀疑是修改了GUID的问题。反复定位才发现不修改GUID时是好的。

可不修改GUID以后怎么玩啊!这个2009也太扯了吧!很多使用控件的地方可都是用GUID实例化的。

 

我反复尝试,最终发现原因。

在 ridl接口设计器上修改接口时,ridl文件内容都是同步修改的,但只有修改GUID时,貌似ridl文件是修改了,但是最终的rtl文件CBuilder似乎没同步。即使你选择全部保存,单独保存都没有用。汗!这样就出现编译出来的内容跟接口对不上。

解决办法是,修改了GUID后,必须在ridl文件的设计界面上点击刷新按键,问题才能解决。

 

看来C++Builder2009,在很多细节上还有问题。

9月5日

使用C++Builder2009编写ActiveX控件的Unicode问题

以前使用C++Builder6编写ActiveX控件, Event中的参数如果有字符串,编译总是有问题。

如果参数类型是AnsiString,创建ActiveX控件时,CBuiler根本就不会为你创建这个事件接口。

如果参数类型是char*,生成的ActiveX控件的参数类型则变成short,我是修改生成的代码,将参数手工变为LPSTR解决的问题。

 

而现在的2009,默认使用的是unicode,参数如果是UnicodeString,CBuilder创建的事件参数则是BSTR,不过我试验了很多次,编译出来的ActiveX控件在微软的测试容器中运行时,触发该事件都会引起内存访问错误。

另外一种方式,将参数声明为wchar_t*,生成的ActiveX控件的参数会变成short,这次改换成LPWSTR,手工修改CBuilder生成的实现类的cpp和h文件,修改TLB文件的h文件,将所有wchar_t类型替换成wchar_t*,同时将short也替换成wchar_t*,编译通过。

不过问题又出现了,在容器中运行,字符串会出现被截断的情况。

比如点燃事件中的字符串本来是"test",作为CBuilder控件进行测试是正常的,但编译成ActiveX控件到了容器里收到的却是"te"。

 

翻了翻2009的帮助文件,其中有这么一段:

WideString

WideString was previously used for multibyte character data. Its format is essentially the same as a Windows BSTR. WideString is still appropriate for use in COM applications. WideString is not reference counted, and so UnicodeString is more flexible and efficient in other types of applications.

New String Type: UnicodeString

The new default for the type string in RAD Studio is the UnicodeString type. This type can contain either Unicode or ANSI string data.  

For Delphi, Char and PChar types still "float" to WideChar and PWideChar, respectively.  

For C++, the _TCHAR maps to option controls the floating definition of _TCHAR, which can be either wchart_t or char.  

Format of UnicodeString Data Type  

CodePage 

Element Size 

Reference Count  

Length  

String Data (element sized)  

Null Term 

-12  

-10  

-8  

-4  

0  

Length * elementsize  

Both UnicodeString and AnsiString have this format, though UnicodeString prefers multibyte data and AnsiString prefers single byte data. 

 

总的来说,新的UnicodeString为了跟AnsiString兼容做了部分处理,而WideString是纯粹的多字节字符串。

 

我看看我的点燃函数:

    if(FOnTextEvent)

        FOnTextEvent(this, Edit->Text.w_str());

 

这里的Edit->Text是UnicodeString类型,w_ctr()返回类型虽然是wchar_t*但是我怀疑问题就是出在这里。

 

于是改成如下代码:

    if(FOnTextEvent)

        FOnTextEvent(this, WideString(Edit->Text).c_bstr());

 

重新测试,一切正常了。这里不禁要提出疑问,2009在转换ActiveX控件时究竟让UnicodeString做了什么工作?是否出现了问题?

8月4日

如何处理算数表达式

今天看CSDN上有人问如何计算算数表达式的结果,比如"(2+3)*6"

原帖见:http://topic.csdn.net/u/20080731/11/50de3417-53d4-4228-99e6-505f64f8edde.html?seed=1989390904

我的算法是利用后缀表达式进行计算。后缀表达式的好处是在O(N)时间内完成表达式计算。

比如 "(2+3)*6"  转换为 " 2 3 + 6 * "

如果不清楚什么是后缀表达式,我就无语了。

得到后续表达式后,就是一个顺序压入堆栈执行的问题了

比如先获取第一个2入堆栈 ,然后是3入堆栈,然后发现是运算符+,这时执行运算符+,将2和3弹出,相加后入堆栈,然后是6,6入堆栈,然后是*,执行*,将堆栈中的两个数弹出执行,得到最后结果。

 

下面代码是我用BCB写的中缀转后缀表达式代码。图省事用了AnsiString,有空改成String就通用了。

//---------------------------------------------------------------------------
bool __fastcall IsOpertator(char aChar)
{
    switch(aChar)
    {
        case '+':
        case '-':
        case '*':
        case '/':
        case '(':
        case ')':
            return true;
        default:
            return false;
    }
}

int __fastcall OperatorLevel(char Opt)
{
    switch(Opt)
    {
        case '*':
            return 2;
        case '/':
            return 2;
        case '(':
            return 99;
        case ')':
            return 0;
        case '+':
            return 1;
        case '-':
            return 1;
        default:
            return 0;
    }
}

AnsiString __fastcall InfixToPostFix(AnsiString inputString)
{
    vector<char> OperatorStack;
    AnsiString PostFixString;

    //将表达式改为后缀表达式
    AnsiString tmpNumber;
    for(int i=0;i<inputString.Length();i++)
    {
        char tmpChar = inputString.c_str()[i];
        if(IsOpertator(tmpChar)) // 如果是操作符或括号
        {
            if(tmpNumber.Length()>0) // 如果还有正在处理的数字,数字读取完整,入后缀队列。
                PostFixString += tmpNumber+" ";
            tmpNumber = "";

            if(tmpChar == ')') // 如果是闭括号,需要一直弹出到开括号
            {
                while(!OperatorStack.empty())
                {
                    char thePopOpt = OperatorStack.back();

                    if(thePopOpt == '(')
                    {
                        OperatorStack.pop_back();
                        break;
                    }
                    else
                    {
                        PostFixString += AnsiString(thePopOpt)+" ";
                        OperatorStack.pop_back();
                    }
                }
                continue; // continue for;
            }

            // 同操作符堆栈栈顶元素比较,如果优先级高,就入栈,否则直到弹出所有元素
            while(!OperatorStack.empty())
            {
                char thePopOpt = OperatorStack.back();

                if(OperatorLevel(thePopOpt)>OperatorLevel(tmpChar))
                {
                    if(thePopOpt=='(')
                    {
                        OperatorStack.push_back(tmpChar);
                        tmpChar = '\0';
                        break;
                    }
                    else
                    {
                        OperatorStack.pop_back();
                        PostFixString += AnsiString(thePopOpt)+" ";
                    }
                }
                else if(OperatorLevel(thePopOpt)==OperatorLevel(tmpChar))
                {
                    OperatorStack.pop_back();
                    PostFixString += AnsiString(thePopOpt)+" ";

                    OperatorStack.push_back(tmpChar);
                    tmpChar = '\0';
                    break;
                }
                else
                {
                    OperatorStack.push_back(tmpChar);
                    tmpChar = '\0';
                    break; // break while
                }
            } // end while

            if(tmpChar!='\0')
                OperatorStack.push_back(tmpChar);
        }
        else    // 如果是数字
        {
            tmpNumber += AnsiString(tmpChar);

            if(i == inputString.Length()-1)
            {
                PostFixString += AnsiString(tmpNumber)+" ";
                break;
            }
        }
    }
    //----end for

    while(!OperatorStack.empty())
    {
        PostFixString += AnsiString(OperatorStack.back())+" ";
        OperatorStack.pop_back();
    }

    return PostFixString;
}

7月16日

研究散列(Hashing)算法的体会

散列,虽然只支持二叉查找树所允许的一部分操作,但是散列具有以常数平均时间执行插入、删除和查找的特点。

散列无法有效支持元素间排序的操作,因此像findMax、findMin以及在线性时间内按顺序打印所有元素的操作都无法支持。

为了解决散列冲突问题,普遍存在两种方式,

  1. 分离链接法(separate chaining),其做法是将散列到同一个位置的元素以一个链表的方式保存。通常产生冲突的元素被插入到链表的最前面,这样不仅方便,而且由于最后插入的元素最有可能不久再使用,这样在查找起来效率会增加。分离链接实现简单,但是双向链表会占用额外的内存,冲突严重时,查找效率也会严重下降。
  2. 探测散列表(probing hash tables),其做法是发生冲突时,尝试使用其他的单元,知道找到空闲的单元为止。因为要确保所有的元素都能插入到表中,所需要的表要比分离链接法的表大,其装填因子应该低于λ=0.5。

探测散列表又有三种解决冲突的方法,一种是线性探测,即逐个探测f(i)=i。优点是算法简单,缺点是存在"一次聚集"问题(Primary Clustering)。另一种是平方探测,即f(i)=i2。对于平方探测,表的大小是素数,那么当表至少有一半是空的时候,总能插入一个新的元素。虽然平方探测排除了一次聚集,但是散列到同一位置上的那些元素将探测相同的备选单元,即"二次聚集"(Secondary Clustering)。最后一种是双散列(double hashing),一种流行的选择是f(i)=i*hash2(x)。这个公式表明需要用到第二个散列函数用来解决冲突。诸如hash2(x)=R-(x mod R),R是小于TableSize的素数这样的函数将起到良好的作用,它可以尽可能做到在第一次散列到同样单元的元素,第二次散列到不同的其他元素上,即使会发生最坏的情况,但是非常少。

 

研究散列算法让我联系到另外一个应用,就是产生不重复的随机数,例如卡管理系统。都需要产生不规则的随机数作为卡号或密码,记得遇到一个类似的系统是通过产生可以重复的随机数(20位左右),利用数据库唯一索引检查,避免出现重复。不过运行时间长后,由于数据量越来越大,执行效率低的可怕。现在想起来,其实就是一个解决插入冲突的问题,分段的hashing算法就是一个解决办法不是。

11月14日

函数调用的区别

 

左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明)。

1
_stdcallPascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。


2
C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。


_cdecl
CC++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。


3
__fastcall调用约定是“人”如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECXEDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。


_fastcall
方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。


4
thiscall仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。


5
naked call采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESIEDIEBXEBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。


关键字 __stdcall__cdecl__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting...\C/C++ \Code Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz/Gd/Gr。缺省状态为/Gd,即__cdecl


要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定,至于函数名修饰约定,可以通过其它方法模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏,它可以将出函数翻译成适当的调用约定,在WIN32中,它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs



VC++
对函数的省缺声明是"__cedcl",将只能被C/C++调用
.

CB
在输出函数声明时使用4种修饰符号

//__cdecl
cb
的默认值,它会在输出函数名前加_,并保留此函数名不变,参数按照从右到左的顺序依次传递给栈,也可以写成_cdeclcdecl形式。

//__fastcall
她修饰的函数的参数将尽可能地使用寄存器来处理,其函数名前加@,参数按照从左到右的顺序压栈;

//__pascal
它说明的函数名使用Pascal格式的命名约定。这时函数名全部大写。参数按照从左到右的顺序压栈;

//__stdcall
使用标准约定的函数名。函数名不会改变。使用__stdcall修饰时。参数按照由右到左的顺序压栈,也可以是_stdcall