Visit My MSN Space
2006年3月24日 #
ATL3.0编写的组件在注册时,如果组件所在目录包含中文路径,不能在注册表生产正确的路径,从而产生虽然注册成功,却不能使用的问题,因为在注册表记录的DLL路径中文部分有乱码。这个问题很久以前就发现,一直没有解决。前段时间在网上搜索到解决方案,并转载在blog上,却一直没有实际试用。今天企图使用,却发现不成功,昏倒,试了几次都不行。看来网上的代码还是不能轻信。不知道文章的作者有没有实际调试成功,不过思路是没错的,因此自己改了下,调通了,这里记录一下。解决方案:修改STAREG.H文件,修改196行开始的AddChar 和 AddString函数,修改后的代码如下:
posted @ 2006-03-24 13:09 vibration 阅读(507) | 评论 (2) | 编辑
2005年11月16日 #
posted @ 2005-11-16 15:52 vibration 阅读(361) | 评论 (0) | 编辑
2005年11月4日 #
posted @ 2005-11-04 17:03 vibration 阅读(482) | 评论 (1) | 编辑
2005年9月20日 #
起因
上次完成了CAppBar的代码后,就一直想在标题栏的关闭按钮前面加一个按钮,来控制自动隐藏的设置。在标题栏上加按钮并不算特别麻烦,主要是控制WM_NCPAINT,把想要的效果画上去。当然,原理不复杂,要实现的好却不容易。上网搜了一下,有一些简单的例子用来讲述原理,没有找到比较好的实现,更谈不上可复用的代码了,那么只好自己实现了。
原理
首选必须明白原理,想在标题栏上创建CButton这样窗口按钮的努力是徒劳的。标题栏属于Non-Client区域,不能在其上创建子窗口,唯一的方法就是响应WM_NCPAINT消息,通过Window DC画上去,当然,还需要处理一些其他的鼠标消息以得到按钮的效果。这样,实现的思路就和CAppBar差不多了,再创建一个模板类,于是,CCaptionButton<T>就诞生了。 先看看效果
按钮图片
因为是画上去的按钮,我们需要准备一些按钮图片。一个按钮至少需要3个图片来表示3个状态:
我们通过一个ImageList来组织这些按钮图片。一幅象下面这样的图片资源可以产生一个按钮需要的ImageList,当然也可以通过其他方式实现。 当添加一个标题栏按钮时,我们需要这些按钮图片,参数的类型是HIMAGELIST
添加标题栏按钮
我们需要支持多个标题栏按钮,因此在CCaptionButton类中,通过一个vector来维护所有按钮的信息。具体实现不多说了,就讲怎么使用吧,添加按钮的方法就是:
int AddButton(UINT uID, int cx, int cy, HIMAGELIST himl, LPCTSTR lpszHint=NULL);
其中,uID是按钮的ID,当按钮被点击时,会有一个WM_COMMAND消息发送给窗口,uID为参数。因此,处理标题栏按钮点击的方法和处理普通按钮完全相同。cx和cy定义了按钮的宽度和高度,这个尺寸必须和按钮图片相符。himl则包含了3-5幅图片,表示按钮不同状态下的样子。最后,lpszHint是按钮的tooltip提示文字。
控制按钮位置
加入的按钮显示在标题栏的什么地方呢?自动控制按钮的位置似乎是一个可重用的类应该完成的任务。在类的实现中,我尝试自动计算标题栏上的空白区域,让我们添加的标题栏按钮靠窗口右边已有按钮排列。然而,我发现,由于存在许多不同的窗口样式,如是否有最小化按钮,是否有问号按钮,是否窄标题栏(即ToolWindow),自动计算很容易产生错误。不如让继承类来决定如何排放按钮位置。因此,提供了下面这个函数供继承类重载:
POINT GetButtonPos(int index);
CCaptionButton为这个函数提供了一个默认的实现,但是,这个实现比较简单,只有在窗口为ToolWindow,并且没有任何系统按钮的情况下才能正常显示。通过重载GetButtonPos函数,实际上我们得到了比自动计算强的多的功能,即我们可以在任意位置显示按钮,甚至可以控制按钮水平或者垂直排列,就象例子程序中看到的那样。
使用方法
总结一下使用方法
示例程序
示例程序中使用了5个标题按钮,并配合使用了CAppBar类,以实现桌面停靠的功能。5个标题按钮的排列使用了普通方式和垂直排列的方式,通过继承GetButtonPos函数实现。其中图钉按钮控制AppBar窗口的自动隐藏设置;其他4个按钮使窗口停靠到桌面的一边;并且把停靠到底部的按钮设置成Disabled,可以看到不同的效果
下载示例程序
posted @ 2005-09-20 10:45 vibration 阅读(1416) | 评论 (3) | 编辑
2005年9月14日 #
posted @ 2005-09-14 17:05 vibration 阅读(1803) | 评论 (6) | 编辑
2005年9月9日 #
posted @ 2005-09-09 15:04 vibration 阅读(1000) | 评论 (1) | 编辑
2005年9月7日 #
ATL组件注册的一个很糟糕的BUG,以至于需要通过修改ATL源码来解决。记录在此,方便下次重装机器后使用。
我曾用ATL写过一个COM组件(MBCS下编译),如果安装在中文路径下的话,注册就会失败. 为什么会失败? 打开ATL的源文件statreg.h,可以找到函数BOOL AddString(LPCOLESTR lpsz),他被组件的UpdateRegistry所调用,他又调用了BOOL AddChar(const TCHAR* pch).问题就出现在这两个函数中。请看: BOOL AddString(LPCOLESTR lpsz) { USES_CONVERSION; LPCTSTR lpszT = OLE2CT(lpsz); while (*lpszT) { AddChar(lpszT); lpszT++; // note! @1 } return TRUE; } BOOL AddChar(const TCHAR* pch) { if (nPos == nSize) // realloc @3 { nSize *= 2; p = (LPTSTR) CoTaskMemRealloc(p, nSize*sizeof(TCHAR)); } p[nPos++] = *pch;#ifndef _UNICODE if (IsDBCSLeadByte(*pch)) p[nPos++] = *(pch + 1); file://note! @2#endif return TRUE; } 当我们不是使用UNICODE时,如果遇到一个汉字的话,标注@2行识别整个汉字,存入缓冲区.但是pch变量仍然指向汉字的第一个字节,返回到@1行时,lpszT++后指向了该汉字的第二个字节!以后又把该汉字的第二字节当成一个独立的字符再次处理一遍.所以就产生了乱码.(致使组件注册的信息有一部分是错误的) 怎么解决? 由上面的分析,很容易得到解决的办法: BOOL AddString(LPCOLESTR lpsz) { USES_CONVERSION; LPCTSTR lpszT = OLE2CT(lpsz); while (*lpszT) { AddChar(lpszT); lpszT++; } return TRUE; }/******************************************************************* * This function cause some error in hanzi. * Modified by L.C. ,Nov 12th,2001 *******************************************************************//******************************************************************** BOOL AddChar(const TCHAR* pch) ********************************************************************/ BOOL AddChar(const TCHAR* &pch) file://we'll modify the pch value { if (nPos == nSize) // realloc { nSize *= 2; p = (LPTSTR) CoTaskMemRealloc(p, nSize*sizeof(TCHAR)); } p[nPos++] = *pch;#ifndef _UNICODE if (IsDBCSLeadByte(*pch))/******************************************************************* p[nPos++] = *(pch + 1); ********************************************************************/ p[nPos++] = *( ++ pch);#endif return TRUE; } 还有什么错误? 请观察@3行,如果阅读一下这个类的源代码(180行开始),很明显会有缓冲区溢出的危险:在非UNICODE情况下,nPos要加两次,而进入这段代码时有可能是nPos=nSize-1.如果是这样,恐怕程序的会有一些无法预测的行为(虽然几率很小:在 rgs文件中出现大段中文的可能性不多)。修改实际上比较容易,将if (nPos == nSize) 变为if (nPos == nSize-1)即可。(当然有很多别的方法) 结论 如果你的组件有可能出现在中文路径下的话(使用MBCS),建议你在编译时使用_ATL_STATIC_REGISTRY编译,并且在编译前修改ATL中相关的代码(或自己写注册函数)。否则现有的ATL.DLL会坏了你的好事. 修改现有的类库是很危险的事情。因为他们的调用关系太复杂了.不过,如果他里面有BUG,这也算是一个好方法.
posted @ 2005-09-07 17:11 vibration 阅读(609) | 评论 (0) | 编辑
2005年6月30日 #
posted @ 2005-06-30 14:27 vibration 阅读(3470) | 评论 (12) | 编辑
2005年6月2日 #
我们只能访问SomeClass的公共方法,而无法直接访问接口指针,这时就不能使用使用全局接口表的方法。如果需要在不同线程中调用SomeClass的方法,唯一的办法就是把所有的调用放在一个线程中。怎么做到这一点呢?用一个消息窗口来同步是一个简单的方法。首先定义一个窗口类,把所有对SomeClass的操作定义成窗口消息,如下:
在OnMethod1和OnMethod2中完成对m_someClass的方法调用,如果方法有参数,通过wParam和lParam传递。
然后,我们需要做的是在一个线程中创建这个窗口。并在需要调用SomeClass的方法时,通过向CThreadWnd窗口发送消息间接完成。例如:
posted @ 2005-06-02 10:16 vibration 阅读(1261) | 评论 (2) | 编辑
2005年5月30日 #
在多线程程序中使用COM对象真是件令人头疼的事情。当你能够访问一个接口指针的时候,并不意味着你可以调用接口上的方法。我从来没真正搞懂过所谓COM的几种线程模式。问题是,当我试图在一个线程里调用一个COM接口的方法,而这个COM接口是在另一个线程中创建时,从来没有成功过。更糟的是,连错误提示都没有。在多个线程中传递接口需要额外的工作,各种书籍上都介绍了所谓Marshal一个接口方法,不过我从来没有使用过。因为有更简单的方法,就是用全局接口表(GlobalInterfaceTable)。GlobalInterfaceTable允许你在任何地方访问任何线程中创建的COM接口。GlobalInterfaceTable本身是一个COM对象,它实现了IGlobalInterfaceTable接口。这个接口有三个方法,分别用来注册接口,取得接口和注销接口。下面的例子介绍了GlobalInterfaceTable的基本使用方法。首先当创建一个需要在其他线程使用的COM接口时,把它注册到GlobalInterfaceTable
注册时会返回一个Cookie,记住这个Cookie,并在任何线程需要使用前面接口时,通过这个Cookie获得接口。
最后,作为一个负责任的程序员,关闭之前一定要注销前面注册的接口。
posted @ 2005-05-30 10:13 vibration 阅读(1012) | 评论 (0) | 编辑
Powered by: 博客园 Copyright © vibration