最近写的一个软件需要用到类似Flashget那样的收集篮,能接收IE的拖放,所以要在程序中实现一个IDropTarget的COM接口。我一直喜欢用pure C & SDK来写程序,但是这次为了完成这个COM接口,不得不使用C++来写(当然,用pure c也可以,但是定义一个模拟C++类的结构太麻烦了)。
以前用C+SDK写程序时,为了减小程序的体积,我都用"/Entry"参数来定义程序入口的(过去写木马程序时保留的坏习惯),这次也一样:
#pragma comment(linker,"/entry:WinEntry")
#define hInst ((HINSTANCE)0x00400000)
... ...
HWND g_hMainWnd;
CDropTarget DropTarget;
... ...
void WinEntry(void)
{
MSG msg;
OleInitialize(NULL);
g_hMainWnd=CreateDialog(hInst,MAKEINTRESOURCE(IDD_MAIN),GetDesktopWindow(),(DLGPROC)MainProc);
if(NULL==g_hMainWnd)goto End;
DropTarget.RegisterDragDrop(g_hMainWnd);
ShowWindow(g_hMainWnd,SW_SHOW);
while(GetMessage(&msg,NULL,0,0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
End:
DropTarget.RevokeDragDrop();
OleUninitialize();
ExitProcess(0);
}
其中的CDropTarget是我完成的封装IDropTarget接口的类,定义如下:
class CDropTarget:public IDropTarget{
public:
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef(void);
virtual ULONG STDMETHODCALLTYPE Release(void);
virtual HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *pDataObject,DWORD grfKeyState,POINTL pt,DWORD *pdwEffect);
virtual HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState,POINTL pt,DWORD *pdwEffect);
virtual HRESULT STDMETHODCALLTYPE DragLeave(void);
virtual HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObject,DWORD grfKeyState,POINTL pt,DWORD * pdwEffect);
BOOL RegisterDragDrop(HWND hWnd);
BOOL RevokeDragDrop();
private:
ULONG m_RefCount; //引用计数
HWND m_hTargetWnd; //注册为拖放窗口的窗口句柄
};
BOOL CDropTarget::RegisterDragDrop(HWND hWnd)
{
HRESULT hResult=::RegisterDragDrop(hWnd,this);
if(!SUCCEEDED(hResult))return FALSE;
m_hTargetWnd=hWnd;
return TRUE;
}
程序一开始先用OleInitialize函数初始化OLE库,然后调用RegisterDragDrop函数将程序主窗口注册为接受拖放的窗口。一切皆按照MSDN中所描述的那样进行,好像没什么错误。可是RegisterDragDrop函数总是返回"E_INVALIDARG"。我把代码从头到底看了半天,没有发现有什么错误。起初,我怀疑我的IDropTarget接口实现得有问题,但是检查后发现,直到RegisterDragDrop函数返回为止,CDropTarget中的所有成员函数根本没有被调用过。直到偶然瞥见MSDN中的一句话:"The RegisterDragDrop function also calls the IUnknown::AddRef method on the IDropTarget pointer."。也就是说RegisterDragDrop函数执行过程中没有调用CDropTarget的AddRef成员函数是不正常的。看来必须用调试器跟到RegisterDragDrop中去看看才能知道究竟错在哪里。我这个人比较懒,最讨厌看汇编代码了。哎,没办法,这次只好用ollydbg一步步跟踪看看了。
跟到RegisterDragDrop函数里面,发现该函数分别调用IsValidInterface和IsWindow函数对传入的两个参数的有效性进行检查。当RegisterDragDrop调用IsValidInterface函数对IDropTarget指针进行检查时,发生访问违例,由于SEH保护,程序才得以继续运行。晕,难道还要跟到IsValidInterface里去看?
先到MSDN里查查IsValidInterface的描述,看看有什么有用信息。不曾想,MSDN里对IsValidInterface的描述只有一句话:"This function is obsolete."。日啊,过时了你还用!操比尔他大爷的。
跟到IsValidInterface里一看,发现DropTarget的虚函数表指针竟然是NULL。超级晕!这才意识道,原来是DropTarget对象的没有初始化。也就是说,声明DropTarget对象时没有执行构造函数。对于VC编译器来说,一个包含有虚函数成员的类的默认构造函数的唯一作用就是初始化虚函数表指针。由于我用"/entry"编译参数定义程序的入口地址,跳过了RTL的初始化代码,导致在编译时,全局对象只分配了内存,而没有执行构造函数。
小贴士:对于C/C++语言来说,main或WinMain函数并不是程序真正的入口函数。在执行main或WinMain函数前,程序将先执行编译器生成的RTL代码。这部份的代码的作用包括:解析命令行参数,初始化全局对象等。
解决方法有2个:
1、声明全局对象的同时,不要自定义程序入口。
2、如果自定义程序入口的话,将对象放到入口函数中声明。
结论:我这个人不适合用太高级的东西。
Trackback: http://tb.donews.net/TrackBack.aspx?PostId=280922