由于最近一段时间要使用ATL开发相关的Ajax的组件,在开发过程中遇到的一些难点与大家分享一下
1)在COM中使用线程,要注意线程内与COM是否同步,使用散列集的方法可以保证
在创建线程前使用散列,参考代码:
HRESULT hr = CoMarshalInterThreadInterfaceInStream(IID_IReadXML, m_RXML, &m_pStr);//IID_IReadXML为接口IID,m_RXML为接口的智能指针CComQIPtr m_RXML,m_pStr为流IStream *m_pStr;
if(FAILED(hr))
{
return E_NOTIMPL;
}
DWORD thread;
::CreateThread(NULL, 0, BackSendThread, (void *)this, 0, &thread);
在线程中通过流来获取同步
参考代码:
DWORD WINAPI CXMLHttp::BackSendThread(void *pThis)
{
CoInitialize(NULL);
CXMLHttp *This = (CXMLHttp *)pThis;
HRESULT hRes;
CComQIPtr<IReadXML, &IID_IReadXML> m_spT;
m_spT.Release();
//接口散集
if (This->m_pStr)
{
hRes =::CoGetInterfaceAndReleaseStream(This->m_pStr, IID_IReadXML, (void**)&m_spT);
}
//m_spT->backSend(This->m_varBody);
m_spT->CallBackSend();
m_spT.Release();
CoUninitialize();
return 0;
}
如果需要消息循环:可以在主线程中使用AtlWaitWithMessageLoop(handle);这样会保证线程的同步运行,毕竟运行STA环境里的
2)与网络实名等软件使用全局HOOK技术的软件的冲突,这些软件通常会截取软件发出的系统信息,造成资源被占用,解决方法是自定义用户消息
WM_USER+1616等
参考代码:
发送消息
HWND Wnd= ::FindWindow(NULL,"XMLDlg");
IDispatch *temp = this;
if(Wnd)
{
::SendMessage(Wnd, WM_USER+1616, 0, (LPARAM)temp);
}
接收消息
1、先映射消息
BEGIN_MESSAGE_MAP(CXMLDlgDlg, CDHtmlDialog)
ON_WM_SYSCOMMAND()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_USER+1616, OnIDispatcMsg)
END_MESSAGE_MAP()
2、实现代码
LRESULT CXMLDlgDlg::OnIDispatcMsg(WPARAM wParam, LPARAM lParam)
{
m_spObj = (IDispatch *)lParam;
const IID IID_IXMLHttpEvents = {0x030AB05F, 0x2898, 0x4042, 0x88, 0xC1, 0x3D, 0x9D, 0x4F, 0x1F, 0xE9, 0xE7};
if(m_onCopyData)
{
m_sink.SetDispPatch( m_spObj );//用于回调函数访问控件接口(IReadXML)
CComQIPtr<IConnectionPointContainer> spContainer( m_spObj );
if( !spContainer )
{
AfxMessageBox( _T("组件没有提供连接点功能") );
return FALSE;
}
// 得到连接点接口
spContainer->FindConnectionPoint(
IID_IXMLHttpEvents,
&m_spCP );
if( !m_spCP )
{
AfxMessageBox( _T("没有找到连接点接口") );
return FALSE;
}
HRESULT hr = m_spCP->Advise( &m_sink, &m_dwCookie );
if( FAILED( hr ) )
{
AfxMessageBox( _T("连接失败") );
}
m_onCopyData = FALSE;
}
else
{
if( m_spCP )
{
m_spCP->Unadvise( m_dwCookie );
m_spCP.Release();
m_sink.SetDispPatch( m_spObj );
CComQIPtr<IConnectionPointContainer> spContainer( m_spObj );
if( !spContainer )
{
AfxMessageBox( _T("组件没有提供连接点功能") );
return FALSE;
}
// 得到连接点接口
spContainer->FindConnectionPoint(
IID_IXMLHttpEvents,
&m_spCP );
if( !m_spCP )
{
AfxMessageBox( _T("没有找到连接点接口") );
return FALSE;
}
HRESULT hr = m_spCP->Advise( &m_sink, &m_dwCookie );
if( FAILED( hr ) )
{
AfxMessageBox( _T("连接失败") );
}
m_onCopyData = FALSE;
}
}
return 0;
}
3)对于获取其它容器所创建的控件的接口(比如JS创建的控件),主动获取有可能会比较困难,可以逆向思维,让创建的控件将接口自已以消息的方式发送过来,这样要相对简单一些。
4)增加MFC的浏览器滚动条
在OnInitDialog()中 CDHtmlDialog::OnInitDialog();之前使用:SetHostFlags(DOCHOSTUIFLAG_NO3DBORDER | DOCHOSTUIFLAG_FLAT_SCROLLBAR );
参考代码:
BOOL CXMLDlgDlg::OnInitDialog()
{
SetHostFlags(DOCHOSTUIFLAG_NO3DBORDER | DOCHOSTUIFLAG_FLAT_SCROLLBAR );//增加浏览器滚动条
CDHtmlDialog::OnInitDialog();
// 将/“关于.../”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
return TRUE; // 除非设置了控件的焦点,否则返回 TRUE
}
5)直接调用JS函数
HRESULT CXMLHttp::CallJsFun(IDispatch *Fun)
{
//Fun为你的JS函数地址
try
{
HRESULT hr;
DISPPARAMS dispparams;
memset(&dispparams, 0, sizeof dispparams);
CComVariant vResult;
EXCEPINFO excepInfo;
memset(&excepInfo, 0, sizeof excepInfo);
UINT nArgErr = (UINT)-1;
hr = Fun->Invoke(0, IID_NULL, 0, DISPATCH_METHOD, &dispparams, &vResult, &excepInfo, &nArgErr);
}
return S_OK;
}
catch(...)
{
return E_FAIL;
}
6)接收回车键退出,且其它控件可以响应回车键
1:重载PreTranslateMessage函数:virtual BOOL PreTranslateMessage(MSG* pMsg);
2: 参考代码如下
BOOL CXXXDlg::PreTranslateMessage(MSG* pMsg)
{
if(pMsg->message == WM_KEYDOWN)
{
if (pMsg->wParam == VK_ESCAPE)//Esc键不退出程序
{
return TRUE;
}
else if(pMsg->wParam == VK_RETURN)
{
return FALSE;//对话框内部控件可以接收到回车消息!!如果你想将回车安全交给前台页面,请用DispatchMessage(pMsg);
//return TRUE;//对话框内部控件不可以接收到回车消息!!
//原因原来在这里,难为我困惑了那么久!!!
}
}
return CDialog::PreTranslateMessage(pMsg);
}