Thursday, January 14, 2010

Set an ActiveX as Trusted and make calls to Javascript functions

There comes a time when you need to write an ActiveX for reasons that really don't matter in this post but you know those times.  I personally try to avoid them due to their "Windows Only Support" but in a recent project where we had to integrate a java web based application with an application I wrote in C# that was a Winforms application the need was eminent.  We needed a way to fire up the winforms application from the web, pass it some parameters, and when it closes notify the page as well as give the page some data the user entered.  Well getting a web page to open a windows application is pretty simple however we needed much more than just opening the applciation and forgetting about it.  A problem we were facing was how are we going to:

1. Notify the calling page when the form closes
2. Return user entered data back into the page

The client has a policy on their in-house applications that they only support IE.  So we knew from that we could build an ActiveX and not worry about other browsers.  I mean hey, IE works very well with ActiveX and FireFox not so much.  We needed the code to notify the page via javascript because a postback was not allowed for this application.  It appeared the java web app uses a redirect similar to the way Server.Transfer works in ASP.NET in which the Url stays on the base directory.  If a postback was fired then the user would be taken back to their dashboard and lose what they had entered.  So the idea to write an ActiveX was decided as the approach we would take.  We still needed to know if it was possible to make a call to a Javascript function from an ActiveX.  After much research I found a solution using the IOleClientSite Interface.  This interface allows you to get the container the ActiveX is hosted in.  Then using the IHTMLDocument2 interface we can gain access to the DOM.

Here is an example:

/// <summary>
/// Calls the JScript within the current DOM this activeX is placed in.
/// </summary>
/// <param name="key">The key.</param>
private void CallJScript(string key)
{
try
{
// Get a handle on the current Page
Type typeIOleObject = this.GetType().GetInterface("IOleObject", true);

// Get the ClientSite
object oleClientSite = typeIOleObject.InvokeMember("GetClientSite",
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public,
null, this, null);

// Obtain the container
IOleClientSite oleClientSite2 = oleClientSite as IOleClientSite;
IOleContainer pOleContainer;
oleClientSite2.GetContainer(out pOleContainer);

IHTMLDocument2 pDoc1 = (IHTMLDocument2)pOleContainer;

// Gain access to its DOM
object script = pDoc1.Script;

// Create the arguments that will be passed
// as parameters to the javascript function
object[] args = new object[1];
args[0] = key;

// Invoke the script
script.GetType().InvokeMember("YourJavaScriptFunction", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, script, args);
}
catch
{
// TODO: Log Message
}
}


This seemed simple enough until we put the ActiveX on the page and discovered it wasn't working.  I was so confused because from everything I had read this is how you do it.  After more research and debugging I discovered the ActiveX didn't have permission to access the DOM; it was being denied by IE.  So once again more research on how to get access to the ActiveX because setting the site as Trusted wasn't enough.  I came across another site (I wish I could find it again to display here) which led me in the right direction on how to get this working.  You have to create an interface named IObjectSafety which will let IE know the ActiveX is safe.  To make this work you create an enum that is Serializable and ComVisible like so:



[Serializable, ComVisible(true)]
public enum ObjectSafetyOptions
{
INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001,
INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002,
INTERFACE_USES_DISPEX = 0x00000004,
INTERFACE_USES_SECURITY_MANAGER = 0x00000008
};


Then you need to create an Interface called IObjectSafety like so:



/// <summary> 
/// IObjectSafety lets IE know this ActiveX is safe
/// </summary>
[ComImport(), Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IObjectSafety
{
[PreserveSig]
long GetInterfaceSafetyOptions(ref Guid iid, out int pdwSupportedOptions, out int pdwEnabledOptions);

[PreserveSig]
long SetInterfaceSafetyOptions(ref Guid iid, int dwOptionSetMask, int dwEnabledOptions);
};


 
And then you create your control (I used UserControl) which will be used to talk between the Page and the Windows Form. 



[ClassInterface(ClassInterfaceType.AutoDual),
ComSourceInterfaces(typeof(ControlEvents))]
[Guid("F7A01F7B-C6B8-4076-9313-A976EE45C472")]
[ComVisible(true)]
public partial class MyClass : UserControl, IObjectSafety
{
...
}


The IObjectSafety member was implemented as:



#region IObjectSafety Members
private ObjectSafetyOptions m_options =
ObjectSafetyOptions.INTERFACESAFE_FOR_UNTRUSTED_CALLER |
ObjectSafetyOptions.INTERFACESAFE_FOR_UNTRUSTED_DATA;

/// <summary>
/// Gets the interface safety options.
/// </summary>
/// <param name="iid">The iid.</param>
/// <param name="pdwSupportedOptions">The PDW supported options.</param>
/// <param name="pdwEnabledOptions">The PDW enabled options.</param>
/// <returns></returns>
public long GetInterfaceSafetyOptions(ref Guid iid, out int pdwSupportedOptions, out int pdwEnabledOptions)
{
pdwSupportedOptions = (int)m_options;
pdwEnabledOptions = (int)m_options;
return 0;
}

/// <summary>
/// Sets the interface safety options.
/// </summary>
/// <param name="iid">The iid.</param>
/// <param name="dwOptionSetMask">The option set mask.</param>
/// <param name="dwEnabledOptions">The enabled options.</param>
/// <returns></returns>
public long SetInterfaceSafetyOptions(ref Guid iid, int dwOptionSetMask, int dwEnabledOptions)
{
return 0;
}

#endregion


 
This is all it took to make the ActiveX have the permission it needed to gain access to the DOM and make DOM calls.  The use of the IObjectSafety makes an ActiveX that was once "untrusted" become "trusted".  I hope this helps you out and saves you the time I spent trying to get this working on our project.  As always, Happy Coding.

No comments:

 
Creative Commons License
Blogged Information and Code is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.