Exposing methods to JS on a CAxWindow from a BHO ( C++ ) - c++

I have this BHO which I successfully exposed method to JS from it using this thread: Calling BHO method from Javascript?.
When I open a CAxWindow in order to host HTML docs, I'd like to use this exported method but it seems that it doesn't work for that window as well.
I tried to make a custom class like:
class Bubble:
public CAxWindow,
public IDispEventImpl<1, Bubble, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 0>
{
public:
BEGIN_SINK_MAP(Bubble)
SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE , OnDocumentComplete)
SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOWNLOADCOMPLETE , OnDownloadComplete)
SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, BeforeNavigate2)
SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_ONQUIT, OnQuit)
SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_NAVIGATEERROR, NavigateError)
SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2 , OnNavigateComplete2)
END_SINK_MAP()
To try repeat the process of exposing the methods on document complete but it seems that the event is not being fired.
So basically my question is: is there anyway to expose methods to js on my CAxWindow?
Many thanks!

IDispEventImpl implements sink interface to handle event methods calls. You cannot extend it with your own additional methods directly. Additionally, JavaScript does not really see this interface from scripting code because it is connected to ActiveX control site, not the scripting engine. IDispEventImpl is at all a simplified implementation of IDispatch COM interface, reference counter free, suitable for event IDispatch::Invoke call on the connection point sink interface.
You need to either implement a type library enabled COM object with IDispatch interface (type library is used by scripting engine to discover actual methods), or custom IDispatch or IDispatchEx interface implementation (yes, this can be implemented directly on CAxWindow class as additional base class/interface) handling method name resolution without type library. Then you will pass this object to the scripting engine as external object or otherwise.

Related

Getting appobject instance from within running COM exe (Office)

I have code running within a COM exe process. Specifically, I have an addin that operates within any Office product.
My code has an IDispatch* that is explicitly given to it. Specifically, I am given an IDispatch* through my implementation of _IDTExtensibility2::OnConnection.
The IDispatch* I receive is an interface to a specific coclass that is not the coclass which is marked with the [appobject] IDL attribute. Specifically, the interface is to a the coclass called Application (same name in all Office products), but the coclass marked [appobject] within any Office product typelib is called Global.
From within this process, is there a way to get the singleton instance of the the [appobject] coclass? Here's what I have tried so far:
STDMETHODIMP Addin::OnConnection(LPDISPATCH application, ext_ConnectMode connectMode, LPDISPATCH addInInst, SAFEARRAY** custom)
{
// get type info of Application object
ITypeInfoPtr typeInfoPtr;
application->GetTypeInfo(0, LOCALE_USER_DEFAULT, &typeInfoPtr);
// get type lib of Office product
ITypeLibPtr typeLibPtr;
UINT typeInfoIndex;
typeInfoPtr->GetContainingTypeLib(&typeLibPtr, &typeInfoIndex);
...
}
So I can get the ITypeInfo* and ITypeLib* for the Application class and its containing type library. I know I can use ITypeComp* to bind to global functions/variables by using the ITypeComp* returned from ITypeLib::GetTypeComp. Something like this:
// get type comp for the typelib
ITypeCompPtr typeCompPtr;
typeLibPtr->GetTypeComp(&typeCompPtr);
// bind to global property-get function "Application"
ITypeInfoPtr boundTypeInfoPtr;
DESCKIND descKind;
BINDPTR bindPtr;
ULONG hash = LHashValOfNameSys(SYS_WIN32, LOCALE_USER_DEFAULT, L"Application");
typeCompPtr->Bind(L"Application", hash, INVOKE_PROPERTYGET, &boundTypeInfoPtr, &descKind, &bindPtr);
...
However, this is for late-binding these function/variable calls. I actually want to perform early binding -- I have the necessary DISPIDs handy. What I don't have is a IDispatch* to the singleton instance of the [appobject] class.
Alternatively, I can scan the type lib for the coclass that has the [appobject] attribute. However, once I get the ITypeInfo* for that coclass, I don't know what to do with it. If I call CreateInstance on it, a new process is started as per usual COM activation rules.
How can I get an IDispatch* to this [appobject] instance?

Upgrading COM call-back interface

We have a problem with COM Connection point callback interfaces
In our Sample.idl we have few call-back interfaces ISomeEvents
interface ISomeEvents : IUnknown
{
HRESULT Event1([in]int nData);
HRESULT Event2([in]int nData);
HRESULT Event3([in]int nData);
}
And in the CoClass we have the following statement
coclass MyComp
{
[default] interface IMyInterface;
interface IMyInterFace2;
[default, source] interface ISomeEvents;
};
Now whenever we add new interfaces as part of enhancement,this does not break the existing client, but if the enhancement has
Any modifications to call-back then we endup updating the interface ISomeEvents, which is breaking existing clients, we are forced to do this because I think we can
Have only one [defaut,source] Interface.
Can anyone tell me What is the workaround for this?
Regards
tom
If you are developing a COM object where the client(s) are live/in production, you really shouldn't change an existing interface, any interface. That has always been the fundamental rule of COM development: "interfaces are immutable"; exceptions are made during early development, of course.
(Here's a quote for example: "COM interfaces are immutable. You cannot define a new version of an old interface and give it the same identifier." (http://msdn.microsoft.com/en-us/library/windows/desktop/ms688484(v=vs.85).aspx). Look up "interfaces are immutable" on the web for plenty more)
In this case, you should create new interfaces ISomeEvents2, ISomeEvents3, so on as needed, and have those interfaces inherit from the previous version. Make the new interface your new default source.
interface ISomeEvents1 : ISomeEvents
{
/* new enhanced events here, for use by newly compiled clients */
}
Eventually, if you need more changes:
interface ISomeEvents2 : ISomeEvents1
{
/* Even newer enhanced events here, for even newer clients */
}
And so on.

Should I write components in Delphi instead of C++ Builder? How do I add events to a component?

I use C++ Builder (XE2) and I would need to develop some VCL components that would also be used in Delphi. As I understand C++ Builder supports Delphi code and Delphi components but not the other way around? If so, it would be better to start writing it in Delphi so that I don't do a double job?
Second part of my question is more technical; I know how to add a property in a VCL component but don't know how to add events. Could someone give me an example please (no matter Delphi or C++ Builder).
Thanks.
As I understand C++ Builder supports Delphi code and Delphi components but not the other way around?
On source level - yes.
But if you choose to distribute your library sourceless - BPL+DCP+DCU - then it would not matter, except for maybe some small incompatibilities, like Delphi lacking [] operator and C++ lacking virtual overloaded constructors.
Turns out this estimation was wrong. Read Remy's comment below
Most close to you example ov events is the VCL itself, sources are usually shipped with Delphi. If you have Delphi Starter/Trial without VCL sources - then get any opensource VCL library or component. Such as JediVCL or basically almost ANY VCL component with sources. For example any "FWS" (Free with sources) component 99% uses events.
Most basic and widely used event notifications type - such as TButton.OnClick, TForm.OnCreate and a lot of - is TNotifyEvent
Open Delphi Help for that type. Scroll to "See also" and see two links there.
Procedural types are like int (*f)(void) in C.
Events creating manual
Such as:
(borrowed code from about.delphi.com)
type
TState = (stStarted, stStopped);
TStateChangeEvent = procedure
(Sender : TObject; State : TState) of object;
TThirdComponent = class(TSecondComponent) // or whatever
private
{ Private declarations }
FState : TState;
FOnStart,
FOnStop : TNotifyEvent;
FOnStateChange : TStateChangeEvent;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
procedure Start; override;
procedure Stop; override;
property State : TState
read FState;
published
{ Published declarations }
property OnStart : TNotifyEvent
read FOnStart
write FOnStart;
property OnStateChange : TStateChangeEvent
read FOnStateChange
write FOnStateChange;
property OnStop : TNotifyEvent
read FOnStop
write FOnStop;
end
Then you can do
procedure TThirdComponent.Start;
begin
inherited;
FState := stStarted;
if Assigned(OnStart) then OnStart(Self);
if Assigned(OnStateChange) then
OnStateChange(Self, State);
end;

Need help with events in COM in pure C++!

guys! Very important question:
Please, look at my project (300Kb). I can`t use MFC/ATL, pure C++ only.
I have COM library (niapi.dll), but no sources/headers available, dll only.
There is class for connecting to server (NiApi::SrvrSession), class has login event handler (NiApi::SrvrSession::OnLogin).
I used
#import "NiApi.dll"
to generate wrappers/information,
then
ISrvrSessionPtr session(L"NiApi.SrvrSession");
to create object, then trying
session->put_OnLogin();
to assign events, but there is no one put_On or such member.
niapi.tlh have _ISrvrSessionEvents struct inside, but it have no relations with SrvrSession.
I need to use events from NiApi::SrvrSession for handling connection status.
Please help or my boss kill me! (sorry for broken english, I read better than speak;)
COM events are handled via connection points. You need to write your own COM object that implements whichever event interface you are interested in. Then you need to connect it to the COM object that fires the events. First you QI the COM object for its IConnectionPointContainer, then find the corresponding connection point of the GUID of the event interface. The you call its Advise method to connect it to your event sink.
class CSrvrSessionEvents: public _ISrvrSessionEvents
{
public:
HRESULT OnLogin(long LoginResult)
{
// do something
return S_OK;
}
// implement rest of _ISrvrSessionEvents
};
ISrvrSession* pSrvrSession = ...; // get ISrvrSession from somewhere
_ISrvrSessionEvents* pSrvrSessionEvents = new CSrvrSessionEvents();
IConnectionPointContainer* pCPC = NULL;
pSrvrSession->QueryInterface(IID_IConnectionPointContainer, &pCPC);
IConnectionPoint* pCP = NULL;
pCPC->FindConnectionPoint(__uuidof(_ISrvrSessionEvents), &pCP);
DWORD dwCookie = 0;
pCP->Advise(pSrvrSessionEvents, &dwCookie);
pSrvrSession->Connect(); // I assume this fires some events
pCP->Unadvise(dwCookie);
What is really necessary, is to carefully read
codeproject_TEventHandler.
All explained here.
The put_ prefix is the default prefix for the raw interface (customizable via the raw_property_prefixes attribute). Since you are not using the raw interface, use session->OnLogin=... instead.
For event handling see ADO Events Model Example (VC++)

Handle HTMLElementEvents2 when DWebBrowserEvents2 has been handled using ATL's macros

I'm creating a Browser Helper Object using VS2008, C++. My class has been derived from IDispEventImpl among many others
class ATL_NO_VTABLE CHelloWorldBHO :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CHelloWorldBHO, &CLSID_HelloWorldBHO>,
public IObjectWithSiteImpl<CHelloWorldBHO>,
public IDispatchImpl<IHelloWorldBHO, &IID_IHelloWorldBHO, &LIBID_HelloWorldLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IDispEventImpl<1, CHelloWorldBHO, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1>
{
.
.
.
BEGIN_SINK_MAP(CHelloWorldBHO)
SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete)
SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, BeforeNavigate2)//Handle BeforeNavigate2
END_SINK_MAP()
.
.
.
}
As apparent from the code above, my DWebBrowserEvents2 are handled using the ATL's macros. Now I want to handle HTMLElementEvents2 (to detect clicks, scrollbars, etc.) For that, I QueryInterface() the IHTMLDocument2 object for IHTMLElement, QueryInterface() that for IConnectionPointContainer and call IConnectionPointContainer::FindConnectionPoint(DIID_HTMLElementEvents2). (See msdn's article on handling HTMLElementEvents2). The problem is, when I overwrite IDispatch::Invoke in my class, the DWebBrowserEvents2 handles (created using ATL macros) fail. Is there a way to handle HTMLElementEvents2 without overwriting Invoke, or implement invoke in such a way that it only handles HTMLElementEvents2?
Thanks, Any help will be appreciated.
There is no real need to override Invoke or get IConnectionPointContainer. Since this is an ATL project, Implementing another IDispEventImpl:
public IDispEventImpl<2, CHelloWorldBHO, &DIID_HTMLTextContainerEvents2, &LIBID_MSHTML, 4, 0>
does the trick. Then, sink the entry as:
SINK_ENTRY_EX(2, DIID_HTMLTextContainerEvents2, DISPID_ONSCROLL, OnScroll)
In OnDocumentComplete, call IWebBrowser2::get_Document, IHTMLDocument2::get_body, and then call DispEventAdvise to start receiving events.
Note that I've used DIID_HTMLTextContainerEvents2 instead of DIID_HTMLElementEvents. That's because the body object does not support HTMLElementEvents2, and for my purpose (to handle scrolling) this works just fine!