I am working with a webbrowser host on c++, I managed to sink event and I am running this void on DISPID_DOCUMENTCOMPLETE:
void DocumentComplete(LPDISPATCH pDisp, VARIANT *url)
{
READYSTATE rState;
iBrowser->get_ReadyState(&rState);
if(rState == READYSTATE_COMPLETE)
{
HRESULT hr;
IDispatch *pHtmlDoc = NULL;
IHTMLDocument2 *pDocument = NULL;
IHTMLElement *pBody = NULL;
IHTMLElement *lpParentElm = NULL;
BSTR bstrHTMLText;
hr = iBrowser->get_Document(&pHtmlDoc);
hr = pHtmlDoc->QueryInterface(IID_IHTMLDocument2, (void**)&pDocument);
if( (FAILED(hr)) || !pDocument)
{
MessageBox(NULL, "QueryInterface failed", "WebBrowser", MB_OK);
}
hr = pDocument->get_body( &pBody );
if( (!SUCCEEDED(hr)) || !pBody)
{
MessageBox(NULL, "get_body failed", "WebBrowser", MB_OK);
}
pBody->get_parentElement(&lpParentElm);
lpParentElm->get_outerHTML(&bstrHTMLText);
_bstr_t bstr_t(bstrHTMLText);
std::string sTemp(bstr_t);
MessageBox(NULL, sTemp.c_str(), "WebBrowser", MB_OK);
}
}
I don't much about c++, I built this code from watching other codes in google. Now I know I have to use ->Release, but do I have to use all these?:
pHtmlDoc->Release();
pDocument->Release();
pBody->Release();
lpParentElm->Release();
iBrowser->Release();
Because on the examples I used to build my code it was using it only for the IHTMLElement(s).
Yes, you do have to call Release() on those pointers, otherwise objects will leak. The same goes for BSTRs.
You'll be much better off if you use smart pointers for that - ATL::CComPtr/ATL::CComBSTR or _com_ptr_t/_bstr_t.
You should wrap those objects into a CComPtr or one of its variants. That will handle the release for you. It goes with the concept of RAII.
Yes you do. But not on the iBrowser, you didn't acquire that pointer inside this code.
Beware that your error checking isn't sufficient, your code will bomb when get_Document() fails. Same for get_parentElement(). And after the message box is dismissed.
Related
Does anyone know why IGlobalInterfaceTable::RegisterInterfaceInGlobal might return E_NOTIMPL?
I'm using ATL's CComGITPtr and simply assigning it to a COM pointer. The assignment, in turn, calls the following ATL code:
HRESULT Attach(_In_ T* p) throw()
{
if (p)
{
CComPtr<IGlobalInterfaceTable> spGIT;
HRESULT hr = E_FAIL;
hr = AtlGetGITPtr(&spGIT);
ATLASSERT(spGIT != NULL);
ATLASSERT(SUCCEEDED(hr));
if (FAILED(hr))
return hr;
if (m_dwCookie != 0)
hr = spGIT->RevokeInterfaceFromGlobal(m_dwCookie);
if (FAILED(hr))
return hr;
return spGIT->RegisterInterfaceInGlobal(p, __uuidof(T), &m_dwCookie);
}
else
{
return Revoke();
}
}
Apparently that last call to spGIT->RegisterInterfaceInGlobal results in E_NOTIMPL.
I'm performing the assignment in the context of an in-proc Explorer extension, and I'm doing it on the main UI thread.
Also, I'm not sure if this is related, but I get the same error when trying to use RoGetAgileReference (on Windows 8.1), doing something like this (with an IShellItemArray):
HRESULT hr;
CComPtr<IAgileReference> par;
hr = RoGetAgileReference(AGILEREFERENCE_DEFAULT, IID_IShellItemArray, psia, &par);
Any ideas what might be going wrong?
I have recently began working with Dynamic Virtual Channels.
https://learn.microsoft.com/en-us/windows/desktop/termserv/dvc-implementation-details
I have used Static channels for a long time and was able to develop a single plugin that could listen on several channels.
My problem is I am not able to get the Dynamic Virtual Channels to work with multiple listeners. I need to use DVCs to solve some concurrency issues I am having with static channels.
Here is my Initialize method for a single channel:
HRESULT DVCPlugin::Initialize(__in IWTSVirtualChannelManager *pChannelMgr)
{
HRESULT hr;
CComObject<ListenerCallback> *pListenerCallback;
CComPtr<ListenerCallback> ptrListenerCallback;
CComPtr<IWTSListener> ptrListener;
// Create an instance of the DVCPlugin object.
hr = CComObject<ListenerCallback>::CreateInstance(&pListenerCallback);
ptrListenerCallback = pListenerCallback;
// Attach the callback to the endpoint.
hr = pChannelMgr->CreateListener(
DVC_CHANNEL,
0,
(ListenerCallback*)ptrListenerCallback,
&ptrListener);
if (hr == S_OK) {
MessageBox(NULL, L"DVC plugin is enabled!", L"Info...", MB_OK | MB_ICONWARNING);
}
else {
MessageBox(NULL, L"DVC plugin initialize failure!", L"Info...", MB_OK | MB_ICONWARNING);
}
return hr;
}
This works perfect for a SINGLE channel. When I try to add additional listeners, only the LAST one will be active. Anyone know how to add an additional channel to the initialize method?
My code is based on this:
https://learn.microsoft.com/en-us/windows/desktop/termserv/dvc-client-plug-in-example
Found a way to get it to work. It requires a bit more "duplicated-ish" code but it works.
I first needed to create an additional ListenerCallback and ChannelCallback for each channel I want to add. These also come with additional OnNewChannelConnection and OnDataReceived functions for each channel.
The Initialize method then needed to be modified a bit. Likely can be cleaned up more, but works.
HRESULT DVCPlugin::Initialize(__in IWTSVirtualChannelManager *pChannelMgr)
{
HRESULT hr = S_OK;
// chanA
CComObject<ListenerCallback_chanA> *pListenerCallback_chanA;
CComPtr<ListenerCallback_chanA> ptrListenerCallback_chanA;
CComPtr<IWTSListener> ptrListener_chanA;
// Create an instance of the DVCPlugin object.
HRESULT hr_chanA = CComObject<ListenerCallback_chanA>::CreateInstance(&pListenerCallback_chanA);
ptrListenerCallback_chanA = pListenerCallback_chanA;
// Attach the callback to the endpoint.
hr_chanA = pChannelMgr->CreateListener(
DVC_chanA,
0,
(ListenerCallback_chanA*)ptrListenerCallback_chanA,
&ptrListener_chanA);
if (hr_chanA != S_OK) {
hr = hr_chanA;
}
// chanB
CComObject<ListenerCallback_chanB> *pListenerCallback_chanB;
CComPtr<ListenerCallback_chanB> ptrListenerCallback_chanB;
CComPtr<IWTSListener> ptrListener_chanB;
// Create an instance of the DVCPlugin object.
HRESULT hr_chanB = CComObject<ListenerCallback_chanB>::CreateInstance(&pListenerCallback_chanB);
ptrListenerCallback_chanB = pListenerCallback_chanB;
hr_chanB = pChannelMgr->CreateListener(
DVC_chanB,
0,
(ListenerCallback_chanB*)ptrListenerCallback_chanB,
&ptrListener_chanB);
if (hr_chanB != S_OK) {
hr = hr_chanB;
// All listeners created check
if (hr == S_OK) {
MessageBox(NULL, L"DVC plugin is enabled!", L"Info...", MB_OK | MB_ICONWARNING);
}
else {
MessageBox(NULL, L"DVC plugin is NOT ENABLED!", L"Info...", MB_OK | MB_ICONWARNING);
}
return hr;
}
I have a method that register functions and interface in C++, and I am using CoMarshalInterThreadInterfaceInStream to marshal an interface pointer to a method in another thread.
In RegisterFunction:
HRESULT hr = CoMarshalInterThreadInterfaceInStream(IID_Function, function, &stream);
Releasing it in GetFunction:
HRESULT hr = CoGetInterfaceAndReleaseStream(stream, IID_Function, reinterpret_cast<void**>(&function));
Since I will be calling GetFunction more than once and CoGetInterfaceAndReleaseStream only releases a stream once, how do I save the stream to use it again?
I tried using IGlobalInterfaceTable, but I can't get it to work.
I registered the Inteface successfully in RegisterFunction:
DWORD dwCookie = 0;
int a = 0;
if (pGIT == NULL) {
HRESULT hr = CoCreateInstance(CLSID_StdGlobalInterfaceTable,
NULL,
CLSCTX_INPROC_SERVER,
IID_IGlobalInterfaceTable,
(void **)&pGIT);
if (hr != S_OK) {
throw _com_error(hr);
}
}
if (dwCookie == 0) {
HRESULT hr = pGIT->RegisterInterfaceInGlobal(function, IID_Function, &dwCookie);
if (hr != S_OK) {
throw _com_error(hr);
}
}
But when I tried to retrieve it in GetFunction:
IGlobalInterfaceTable* GIT = NULL;
if (GIT == NULL) {
hr = CoCreateInstance(CLSID_StdGlobalInterfaceTable,
NULL,
CLSCTX_INPROC_SERVER,
IID_IGlobalInterfaceTable,
(void **)&GIT);
if (FAILED(hr)) {
exit(0);
}
}
hr = pGIT->GetInterfaceFromGlobal(dwCookie, IID_Function, (void**)&function);
if (FAILED(hr)) {
exit(0);
}
I get an HR error of invalidArg when I tried to RegisterInterfaceInGlobal (even though it uses the same parameter as CoMarshalInterThreadInterfaceInStream except for the cookie)
As SoronelHaetir stated, you can't do this with CoMarshalInterThreadInterfaceInStream. It can only (un)marshal an interface one time.
Look at using IGlobalInterfaceTable instead. You can register your COM object in the table one time, and then retrieve the object from the table in any thread/apartment as many times as you want, until you revoke the object from the table.
You can't do it with CoMarshalInterThreadInterfaceInStream, it is a convenience method for interface pointers that will be extracted just once.
Instead use CoMarshalInterface/CoUnmarshalInterface and use a stream created with ShCreateMemStream.
I’m stuck with an issue and I’m pretty sure that this forum will be able to help me for this.
I’m trying to add few automation objects to the scripts (hosted via a separate process (ATL EXE server)).
Also I am trying to sink the events from these automation objects inside my ATL EXE script engine.
To achive this I’m using CDispExSinkConnector as described in http://www.codeproject.com/Articles/3326/Extending-the-Internet-Explorer-Scripting-Engine
As described I’m using IDispatchEx::GetDispID()to create new members inside the scripting engine. Once the member DISPID has been created, I’m using InvokeEx()to set the property value.
But these method calls return RPC_E_SYS_CALL_FAILED.
Note- Here I get the IDispEx pointer from an OUT-OF-PROC IDispatch pointer.
More detailed descriptions:
I do have a BrowserCtrl hosted by CAxWindow in an ATL EXE server which I’m using to create HTML plug-ins for my MFC client application.
The ATL EXE server provides wrapper methods to IWebBrowser2 methods so that I can call methods like IWebBrowser2Ptr->Navigate2().
Also I can subscribe to IWebBrowser2 events like OnDocumentComplete(….)in this ATL EXE server and ultimately forward those to my MFC Client application to handle those there.
Below are few code snippets for my ATL EXE server (BrowserCtrl):
1st is CreateControl() which creates the CAxWindow, creates the WebBrowser Control and host the control on internal default CAxHostWindow.
STDMETHODIMP BrowserCtrl::CreateControl(ULONG hwndParent, LONG left,LONG top, LONG right, LONG bottom)
{
CRect rc(left, top, right, bottom);
// Create the AX host window. m_wndBrowserCtrl is CAxWindow object
HWND hwndAxHost = m_wndBrowserCtrl.Create(reinterpret_cast<HWND>(hwndParent), &rc, L"Shell.Explorer", WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, ID_BROWSERCTRL );
//Obtain a pointer to the container object
CComQIPtr<IAxWinHostWindow> ptrWinHost;
HRESULT hr = m_wndBrowserCtrl.QueryHost(&ptrWinHost);
LPOLESTR lpszCLSID = NULL;
hr = StringFromCLSID(CLSID_WebBrowser, &lpszCLSID);
// Create the WebBrowser Ctrl
CComPtr<IUnknown> cpIUnknown = NULL;
hr = ptrWinHost->CreateControlEx(lpszCLSID, m_wndBrowserCtrl, NULL, &cpIUnknown, __uuidof(SHDocVw::DWebBrowserEvents2), reinterpret_cast<IUnknown*>(reinterpret_cast<DWebBrowserEvents2Impl*>(this)));
// Get the IWebBrowser2 interface pointer for the control
// CComQIPtr<IWebBrowser2> m_cpIBrowser;
m_cpIBrowser = cpIUnknown;
return S_OK;
}
//Now the wrapper method for IWebBrowser2 methods
STDMETHODIMP BrowserCtrl::Navigate2(VARIANT *pwszUrl, VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData, VARIANT *Headers)
{
HRESULT hr = m_cpIBrowser->Navigate2(pwszUrl, Flags,TargetFrameName,PostData,Headers);
}
//Now the event handlers for the IWebBrowser2 events:
void __stdcall BrowserCtrl::OnDocumentComplete(IDispatch* pDisp, VARIANT* pvaURL)
{
Fire_OnDocumentComplete(pDisp , pvaURL);// forwarding to the MGC client APP
}
Now the issue:
1.On the MFC Client APP and in the event handler function OnDocumentComplete() I do the following:
void MFCClientApp::HandleDocumentComplete(LPDISPATCH pIDisp, VARIANT* pvaURL)
{
CComPtr<IDispatch> pDispDoc;
CComQIPtr<IDispatch> pScriptDisp;
CComQIPtr<IDispatchEx> pDispEx;
CComQIPtr<IHTMLDocument> pHTMLDoc;
CComQIPtr<IWebBrowser2> pWebBrowser;
if (pIDisp != NULL)
{
pWebBrowser = pIDisp;
//CComPtr<IDispatch> ptrIDisp;
//BOOL bResult = GetOPBrowser()->GetDisp(&ptrIDisp);
//pWebBrowser = ptrIDisp;
pWebBrowser->get_Document(&pDispDoc);
if (pDispDoc != NULL)
{
// Get the IDispatchEx interface
pHTMLDoc = pDispDoc;
if (pHTMLDoc != NULL)
{
pHTMLDoc->get_Script(&pScriptDisp);
pDispEx = pScriptDisp;
if (pDispEx != NULL)
{
CDispExSinkConnector pDispExSinkConnector = new CDispExSinkConnector();
Hr = pDispExSinkConnector->SetDispEx(pDispEx); // here the Hr returned is 0x80010100 “System Call Failed” i.e. RPC_E_SYS_CALL_FAILED
……
……..
………
}
Here is the SetDispEx() and internal related functions:
void CDispExSinkConnector::SetDispEx(IDispatchEx *pDispEx)
{
CComBSTR bstrThisGuid("AC0B188C-6B55-408f-9E8C-821B9B5467CB"); // #!I8NIGNORE
HRESULT hr = m_pDispExGIT.Attach(pDispEx);//add it to the GIT Table
ASSERT(pDispEx);
// We need to add ourselves to the script engine
AddNamedObject(bstrThisGuid, this);
}
HRESULT CDispExSinkConnector::AddNamedObject(BSTR bsName, IDispatch *pDisp)
{
HRESULT hr = 0;
CComPtr<IDispatchEx> pDispEx;
GetDispEx(&pDispEx);
if(pDispEx == NULL)
return E_FAIL;
if(!bsName || SysStringLen(bsName) == 0 || !pDisp)
return E_INVALIDARG;
// Also, add our root objects into the script namespace
DISPID dispIdThis = 0;
hr = pDispEx->GetDispID(bsName, fdexNameEnsure | fdexNameCaseSensitive,&dispIdThis); // here the Hr returned is 0x80010100 “System Call Failed” i.e. RPC_E_SYS_CALL_FAILED
if (hr == DISP_E_UNKNOWNNAME)
…
..
}
Please help me to find out the root cause and a solution to resolve this issue.
Your quick help will be much appreciated.
Thanks,
SP
My problems started after converting my VS2003 project to VS2008. Solution contains 3 projects. Projects are DLL's. There were A LOT of compilation errors, then some linker errors... Well, I fought them off. Now it just simply doesn't work ;)
So, one of this DLL's is suppoused to communicate with Word by COM.
Word::_ApplicationPtr d_pApp;
Word::_DocumentPtr d_pDoc;
void MSWord2003::init()
{
free();
HRESULT hr;
CLSID clsid;
CLSIDFromProgID(L"Word.Application", &clsid);
// Get an interface to the running instance, if any..
IUnknown *pUnk;
hr = GetActiveObject(clsid, NULL, (IUnknown**)&pUnk);
if(hr!=S_OK)
throw MSWord::MSWordException("Nie znaleziono działającej aplikacji MSWord.");
IDispatch* d_pDispApp;
hr = pUnk->QueryInterface(IID_IDispatch, (void**)&d_pDispApp);
if(hr!=S_OK)
throw MSWord::MSWordException("Nie udało się połączyć z aplikacją MSWord.");
pUnk->Release();
pUnk = 0;
d_pApp = d_pDispApp;
d_pDoc = d_pApp->ActiveDocument;
d_pDispApp->AddRef();
d_currIdx = -1;
paragraphsCount = d_pDoc->GetParagraphs()->Count;
footnotesCount = d_pDoc->GetFootnotes()->Count;
endnotesCount = d_pDoc->GetEndnotes()->Count;
}
void MSWord2003::free()
{
if(d_pApp!=0)
{
d_pApp->Release();
d_pApp=0;
}
}
This code works on VS2003 (and different machine, I don't have VS2003 on my computer) while in VS2008 it works only if it is called by main thread.
When called by a new thread (wich is initialized by CoInitialize) d_pApp is not initialized properly - its ptr shows 0.
While debugging I reached code in comip.h:
template<typename _InterfacePtr> HRESULT _QueryInterface(_InterfacePtr p) throw()
{
HRESULT hr;
// Can't QI NULL
//
if (p != NULL) {
// Query for this interface
//
Interface* pInterface;
hr = p->QueryInterface(GetIID(), reinterpret_cast<void**>(&pInterface));
// Save the interface without AddRef()ing.
//
Attach(SUCCEEDED(hr)? pInterface: NULL);
}
else {
operator=(static_cast<Interface*>(NULL));
hr = E_NOINTERFACE;
}
return hr;
}
In a new thread, QueryInterface returns E_NOINTERFACE, although GetIID() returns the same thing for both threads. And that is where I got stuck - I have no idea, what causes this behaviour...
IMO you should initialize COM not with CoInitialize, but with CoInitializeEx, specifying COINIT_MULTITHREADED. Otherwise you'll have separate single-threaded COM apartment for every thread.