2006年3月24日
#
ATL3.0编写的组件在注册时,如果组件所在目录包含中文路径,不能在注册表生产正确的路径,从而产生虽然注册成功,却不能使用的问题,因为在注册表记录的DLL路径中文部分有乱码。这个问题很久以前就发现,一直没有解决。前段时间在网上搜索到解决方案,并转载在blog上,却一直没有实际试用。今天企图使用,却发现不成功,昏倒,试了几次都不行。看来网上的代码还是不能轻信。不知道文章的作者有没有实际调试成功,不过思路是没错的,因此自己改了下,调通了,这里记录一下。
解决方案:修改STAREG.H文件,修改196行开始的AddChar 和 AddString函数,修改后的代码如下:
BOOL AddChar(const TCHAR* pch)
{
//if (nPos == nSize) // realloc
//fix register bug with chinese path
if (nPos == nSize - 1 )
{
nSize *= 2;
p = (LPTSTR) CoTaskMemRealloc(p, nSize*sizeof(TCHAR));
}
p[nPos++] = *pch;
#ifndef _UNICODE
if (IsDBCSLeadByte(*pch))
p[nPos++] = *(pch + 1);
#endif
return TRUE;
}
BOOL AddString(LPCOLESTR lpsz)
{
USES_CONVERSION;
LPCTSTR lpszT = OLE2CT(lpsz);
while (*lpszT)
{
AddChar(lpszT);
#ifndef _UNICODE
//fix bug with chinese path
if (IsDBCSLeadByte(*lpszT))
lpszT++;
#endif
lpszT++;
}
return TRUE;
}
编译时必须使用_ATL_STATIC_REGISTRY,即静态链接ATL代码,而不使用ATL.dll,否则无效,因为正是ATL.dll的代码出了问题。
2005年11月16日
#
碰到一个奇怪的问题。
症状 在一个.net workspace中包含多个project,其中两个project应用了同一个.net assamply。这两个project其中一个编译正常,另一个则报错:
error CS0246: The type or namespace name '***' could not be found (are you missing a using directive or an assembly reference?) 居然说我没有正确引用,可是检查项目设置都没有问题。关闭workspace在打开,好了。第二天再编译,又有问题,晕了
错误原因 google半天未果,自己研究。发现被引用的项目有一个Copy Local属性,默认为true,就是把应用的assamply拷贝到输出目录下。原来两个project都企图把同一个assamply拷贝过来,而拷贝成功后还锁定了这个文件。这样第一个项目操作成功并锁定文件后,第二个项目拷贝就失败了,因为无法覆盖被锁定的文件。
解决方法
原因找到了,解决就不难了,把其中一个项目中的引用assambly的Copy Local属性改成false,再编译,就一切OK了

同理,如果有多个project引用同一assamply,除了其中一个的Copy Local属性为true,其他改成false就行了。GAC中的assambly不存在此问题,因为默认Copy Local属性为false。
2005年11月4日
#
以前VC6的代码,从xml文件中读取属性文字(中文),返回BSTR类型,用OLE2T转换,然后显示,一直工作很正常,用来做本地化,比修改Res文件要方便的多。
同样的代码,在VS2003里面居然不能工作了,调试,发现BSTR返回正常,而通过OLE2T转换居然就全转成“???”了,晕。

做了无数尝试,发现用unicode编译就正常。可是原来的VC6项目不论是否Unicode编译都正常的,要把一个项目全部转成Unicode还是有相当工作量的,似乎也并没有必要。
寻找解决方法,看了一堆ATL的代码,ATL7提供了OLE2T的代替品COLE2T,用一个重载是COLE2T(bstr, codepage),发现给把第二个参数设置成CP_ACP,即ANSI code page就转换正常了。原来是codepage的问题,ATL搞了一大堆代码来获取转换用的codepage,看得人晕,而我要的只是ANSI code page。
继续找,终于找到了,ATL的代码通过一条宏定义进行了分支。如果定义了宏
_CONVERSION_DONT_USE_THREAD_LOCALE,则对当前code page的请求简单返回CP_ACP,否则,就搞了一大堆代码从当前线程中查询。那么我们要做的就是定义这个宏就可以了。试了一下,果然如此

最终解决方案,在stdafx.h的所有包含文件前面加上:
#define _CONVERSION_DONT_USE_THREAD_LOCALE
搞定
2005年9月20日
#
起因
上次完成了CAppBar的代码后,就一直想在标题栏的关闭按钮前面加一个按钮,来控制自动隐藏的设置。在标题栏上加按钮并不算特别麻烦,主要是控制WM_NCPAINT,把想要的效果画上去。当然,原理不复杂,要实现的好却不容易。上网搜了一下,有一些简单的例子用来讲述原理,没有找到比较好的实现,更谈不上可复用的代码了,那么只好自己实现了。
原理
首选必须明白原理,想在标题栏上创建CButton这样窗口按钮的努力是徒劳的。标题栏属于Non-Client区域,不能在其上创建子窗口,唯一的方法就是响应WM_NCPAINT消息,通过Window DC画上去,当然,还需要处理一些其他的鼠标消息以得到按钮的效果。这样,实现的思路就和CAppBar差不多了,再创建一个模板类,于是,CCaptionButton<T>就诞生了。 先看看效果
按钮图片
因为是画上去的按钮,我们需要准备一些按钮图片。一个按钮至少需要3个图片来表示3个状态:
另外,如果需要下面两种额外状态,就需要一共提供5个按钮图片
我们通过一个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函数,实际上我们得到了比自动计算强的多的功能,即我们可以在任意位置显示按钮,甚至可以控制按钮水平或者垂直排列,就象例子程序中看到的那样。
使用方法
总结一下使用方法
- 把CCaptionButton作为一个父类继承
- 使用CHAIN_MSG_MAP把消息传递到CCaptionButton类
- 调用AddButton函数添加一个或多个按钮
- 重载GetButtonPos函数提供每个按钮的显示位置
- 处理标题栏按钮产生的Command消息
- 可以调用CheckButton改变按钮的Check状态
- 可以调用EnableButton改变按钮的Enable状态
示例程序
示例程序中使用了5个标题按钮,并配合使用了CAppBar类,以实现桌面停靠的功能。5个标题按钮的排列使用了普通方式和垂直排列的方式,通过继承GetButtonPos函数实现。其中图钉按钮控制AppBar窗口的自动隐藏设置;其他4个按钮使窗口停靠到桌面的一边;并且把停靠到底部的按钮设置成Disabled,可以看到不同的效果
下载示例程序
2005年9月14日
#
关于Docking Window的文章有很多,基本都是讲主程序内部的工具栏小窗口的Docking,看看代码,一般都很复杂。关于桌面Docking的文章并不多见。实际上从Windows95开始,Win32 API就提供了一个叫做SHAppBarMessage的函数,用于实现完全等同于Windows Task Bar的功能。即可以停靠在屏幕在任意一边,并把自己排除到桌面空间之外。当其他应用程序窗口最大化的时候,不占据Task Bar的空间。同时,也可以设置成自动隐藏,让出所有的桌面空间。这样的窗口,就称为AppBar。
象QQ这样的程序可以停靠在窗口的一边,也可以自动隐藏,但似乎和AppBar的行为还是有不少差距的。本文提供的是对标准AppBar的一个实现。
利用SHAppBarMessage函数实现一个AppBar并不困难,
这篇文章介绍了几乎所有实现细节,但所提供的例子却是SDK风格的代码,基本没有实际使用价值。但是对于SDK文档是一个很好的补充,很多SDK中没有说清楚的问题在文中给出了解释。
另一个问题是,SHAppBarMessage函数并没有提供我想当然认为应该实现的功能。比如对拖动的支持,比如自动隐藏窗口的隐藏和出现功能都必须额外的代码实现。SHAppBarMessage函数所做的主要工作似乎是划分屏幕,使其他窗口不会占用AppBar的空间。当然,能实现这个也足够了,毕竟,没有SHAppBarMessage函数,其他功能肯定都可以实现,而这个划分屏幕的功能我不知道该怎么做。
写一个可重用的AppBar基础类的愿望马上出现在我头脑中。WTL由于其超低的耦合性成为我必然的选择。模板类CAppBar<T>就是工作的结果。这个类不只是对SHAppBarMessage函数的简单封装,同时提供了通过拖动实现停靠或脱离屏幕的功能,并提供自动隐藏的实现。
任何从CWindowImpl直接或间接继承的弹出式窗口类都可以使用CAppBar。使用的方法很简单:
1、把CAppBar作为一个父类继承
2、使用CHAIN_MSG_MAP把消息传递给CAppBar
3、在窗口创建时调用InitAppBar函数,参数可选
这样,这个窗口就具备了AppBar的功能了,下面你就可以通过拖动把它停靠在屏幕边上了。
当然,也可以通过调用DockAppBar停靠或脱离;或者调用SetAutoHide改变自动隐藏的设置。
如果在停靠状态改变时需要有特殊操作,可以重载OnDockingChanged函数。
基本就是这样,代码很简单,就象其他WTL类一样。希望下一版的WTL可以收录它
下载例子程序
2005年9月9日
#
招聘职位 |
| 系统开发中心 |
|
|
| |
| 高级软件工程师 |
| 电子邮箱:atahr@ata.net.cn |
|
| 发布日期:2005-09-08 |
工作地点:上海市 |
| 招聘人数:若干 |
学 历:本科 |
| 工作年限:三年以上 |
薪水范围:面议 |
| 外语要求:英语 熟练 |
接受简历语言:中文或英文 |
|
|
| 职位描述: |
职责描述: 1、 熟悉C++语言,三年以上Visual C++开发经验
2、 熟悉ATL/WTL以及Windows SDK开发
3、 一年以上的.net项目经验, 精通.net体系应用开发,精通C#桌面应用开发技术
4、 深入理解面向对象开发思想并具有实际开发经验
5、 熟悉XML语言,熟练使用DOM模型操作XML文档
6、 熟悉各种数据结构并能灵活运用,有使用STL的开发经验
7、 较强的分析、设计能力(有大型项目分析、设计经验者优先)
8、 有综合运用各种技术,并具有创新能力
9、 能够阅读和查找英文资料
| | | |
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,这也算是一个
好方法.
2005年6月30日
#
好久没写模板了,这次做一项目,发现一处非常适合使用模板应用,于是写了个模板类,大概类似于酱紫
//头文件
template <class TElement>
class TTTextT
{
protected:
vector<TElement> m_chVector;
public:
int Count();
}
//CPP文件
#include "TTText.h"
template <class TElement>
int TTTextT<TElement>::Count()
{
return m_chVector.size();
}
当然还有许多其他代码啦,分成头文件和CPP文件,初步编译没什么问题。
可是在使用的时候,却产生了连接问题,说什么unresolved external symbol,函数找不到

。忽然想起以前也碰到过类似问题,模板类的声名和实现如果放在两个文件里,就会发生这样的状况。当时没时间多想,把所有实现挪到头文件里就解决了。想看看人家的代码,不管是STL,ATL,WTL,都是所有内容全放头文件的。晕了

,最后还是把所有代码挪到头文件,就OK了。
可是为什么呢?查MSDN,查google,都没有找到满意的答案

。只看到一篇,说模板编译中不产生实际代码,只有加模板参数使用时才产生代码,因此如果分两个文件,连接时会出错,云云。不是特别明白,结论似乎是使用模板就应该把所有实现放在头文件中。看看STL/ATL/WTL,似乎也是。但问题是:既然这样,为什么允许下面这样的代码呢
template <class TElement>
int TTTextT<TElement>::Count()
{
return m_chVector.size();
}
2005年6月2日
#
上篇讲了COM接口的多线程访问问题,并用全局接口表的方法解决了。但有时候我们不能直接访问接口指针,而是通过一个封装类间接的访问。比如:
class SomeClass
{
private:
IMyInterface *m_pInt;
public:
void Method1()
{
//Init m_pInt
}
void Method2()
{
//call method of m_pInt
}
}
我们只能访问SomeClass的公共方法,而无法直接访问接口指针,这时就不能使用使用全局接口表的方法。如果需要在不同线程中调用SomeClass的方法,唯一的办法就是把所有的调用放在一个线程中。怎么做到这一点呢?用一个消息窗口来同步是一个简单的方法。
首先定义一个窗口类,把所有对SomeClass的操作定义成窗口消息,如下:
#define WM_METHOD1 WM_USER + 100
#define WM_METHOD2 WM_USER + 101
class CThreadWnd : public CWindowImpl<CThreadWnd>
{
private:
SomeClass m_someClass;
public:
BEGIN_MSG_MAP(CThreadWnd)
MESSAGE_HANDLER(WM_METHOD1, OnMethod1)
MESSAGE_HANDLER(WM_METHOD2, OnMethod2)
END_MSG_MAP()
LRESULT OnMethod1(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
LRESULT OnMethod2(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
}
在OnMethod1和OnMethod2中完成对m_someClass的方法调用,如果方法有参数,通过wParam和lParam传递。
然后,我们需要做的是在一个线程中创建这个窗口。并在需要调用SomeClass的方法时,通过向CThreadWnd窗口发送消息间接完成。例如:
CThreadWnd wndThread;
//In thread A
wndThread.SendMessage(WM_METHOD1);
//In thread B
wndThread.SendMessage(WM_METHOD2);
至于wndThread,可以保存在任何地方。而创建线程窗口的线程函数代码基本上应该是这样的:
UINT WINAPI ThreadProc(LPVOID lpParam)
{
::CoInitialize(0);
CThreadWnd *pWnd = (CThreadWnd*)lpParam;
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
pWnd->Create(NULL, CRect(0, 0, 0, 0), NULL, WS_POPUP);
theLoop.Run();
pWnd->DestroyWindow();
_Module.RemoveMessageLoop();
::CoUninitialize();
return 0;
}
创建线程的代码就不贴了,销毁线程则只要向线程窗口发送WM_QUIT消息即可。这个方法在实际应用中个人感觉非常有效。
有一个问题是当方法参数比较多时,很难通过wParam和lParam传递。这就需要另外定义一个结构,存放各种参数,然后通过wParam传递结构的地址。总之人是活的,灵活运用:P
2005年5月30日
#