Calling C# BHO methods from Javascript (still not working)
Asked Answered
S

2

6

I followed this answer exactly and read and reread all google findings. Unfortunately mostly all of them simply the copy and pastes of the referenced answer (including the 'Stop banging your head against the wall and go celebrate!') sentence and it does not work for me... So after a half day working I am really about to start banging my head...

My simple error: The javascript windows.myExtension object is 'undefined' so calling Foo on it throws error. See the full source below. It seems the property set is not viewable in javascript side.

Some more information:

  • I use the Debugger.Launch() statement for convenient way to debug my extension, and the breakpoint is hit, and all BHO extension functions are properly called and run.
  • The commented alternative (with property.SetProperty) also does not work, with the same error:

    console.log(window.myExtension); // writes 'undefined', why?

  • Using VS 2010, Windows 7 x64, IE 9

Please let me help running this... Thx in advance

The simple test page:

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript">
    console.log(window.myExtension);  // Writes undefined why? It should be an object...
    var result = window.myExtension.Foo("bar"); // Obviously throws and error if window.myExtension is undefined 
    </script>
    <title></title>
</head>
<body>

</body>
</html>

BrowserHelperObject.cs

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Expando;

using Microsoft.Win32;

using SHDocVw;

namespace IEExtensionTest
{
[ComVisible(true)]
[Guid("DA8EA345-02AE-434E-82E9-448E3DB7629E")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MyExtension")]
[ComDefaultInterface(typeof(IExtension))]
public class BrowserHelperObject : IObjectWithSite, IExtension
{
    private WebBrowser webBrowser;

    public int Foo(string s)
    {
        return 0;
    }

    public void OnDocumentComplete(dynamic frame, ref dynamic url)
    {
        Debugger.Launch();
        dynamic window = webBrowser.Document.parentWindow;
        var windowEx = (IExpando)window;
        windowEx.AddProperty("myExtension");
        window.myExtension = this;
        //var property = windowEx.AddProperty("MyExtension");
        //property.SetValue(windowEx, this, null);
    }


    public static string BHOKEYNAME = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects";

    [ComRegisterFunction]
    public static void RegisterBHO(Type type)
    {
        RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(BHOKEYNAME, true);

        if (registryKey == null)
            registryKey = Registry.LocalMachine.CreateSubKey(BHOKEYNAME);

        string guid = type.GUID.ToString("B");
        RegistryKey ourKey = registryKey.OpenSubKey(guid);

        if (ourKey == null)
            ourKey = registryKey.CreateSubKey(guid);

        ourKey.SetValue("Alright", 1);
        registryKey.Close();
        ourKey.Close();
    }

    [ComUnregisterFunction]
    public static void UnregisterBHO(Type type)
    {
        RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(BHOKEYNAME, true);
        string guid = type.GUID.ToString("B");

        if (registryKey != null)
            registryKey.DeleteSubKey(guid, false);
    }

    public int SetSite(object site)
    {

        if (site != null)
        {
            webBrowser = (WebBrowser)site;
            webBrowser.DocumentComplete += OnDocumentComplete;
        }
        else
        {
            webBrowser.DocumentComplete -= OnDocumentComplete;
            webBrowser = null;
        }

        return 0;

    }

    public int GetSite(ref Guid guid, out IntPtr ppvSite)
    {
        IntPtr punk = Marshal.GetIUnknownForObject(webBrowser);
        int hr = Marshal.QueryInterface(punk, ref guid, out ppvSite);
        Marshal.Release(punk);

        return hr;
    }
}

IObjectWithSite.cs

using System;
using System.Runtime.InteropServices;

namespace IEExtensionTest
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")]
public interface IObjectWithSite
{
    [PreserveSig]
    int SetSite([MarshalAs(UnmanagedType.IUnknown)] object site);

    [PreserveSig]
    int GetSite(ref Guid guid, out IntPtr ppvSite);
}
}

IExtension.cs

using System;
using System.Runtime.InteropServices;

namespace IEExtensionTest
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")]
public interface IObjectWithSite
{
    [PreserveSig]
    int SetSite([MarshalAs(UnmanagedType.IUnknown)] object site);

    [PreserveSig]
    int GetSite(ref Guid guid, out IntPtr ppvSite);
}
}

Post build step is configured as follows (and runs properly):

"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe" /f /i "$(TargetDir)$(TargetFileName)"
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" /unregister "$(TargetDir)$(TargetFileName)"
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" "$(TargetDir)$(TargetFileName)"
Sumter answered 25/2, 2013 at 14:8 Comment(6)
Just taking a stab at this, but it looks like it only adds myExtension on document complete. Try this hack test: setTimeout(function() { console.log(window.myExtension); }, 1000); after 1 second it should output something to the console. At that point, the document should be loaded and you should have your answer. Please report back after this test.Miss
Thx for your idea. I've already tried it and now again. It is not a timing problem. window.myExtension is still undefined in the delayed call :-(Sumter
@Dalorzo Please stop with trivial edits such as adding tags where not needed. I've glanced over your history, and most of your recent suggested edits should have been rejected. You're unnecesarily bumping old questions to the front page by doing such trivial (and often wrong and/or incomplete) edits.Predecessor
@Rob W I do not considered them trivial. I was hoping those would get additional people to look an unanswered question. At end old questions deserves answer.Adulteress
@Sumter Hi, you succeded so solve this?Chicane
to be honest, I do not remember... not even the problem. Its gone now I am free. Sorry man.Sumter
H
1

I have a hack-y solution to this. It is working for me right now so I am posting it here. If I encounter any issue with it, I will update this post.

@Eli Gassert identifies the problem correctly. In the SetSite function we are adding a handler to DocumentComplete event. So it would not have executed when we are in $.ready().

So what I have done is to add handler to BeforeScriptExecute event. This is the relevant part of my BHO.cs

public int SetSite(object site)
    {
        this.site = site;
        if(site != null)
        {
            webBrowser = (IWebBrowser2)site;
            ((DWebBrowserEvents2_Event)webBrowser).BeforeScriptExecute += S2_BeforeScriptExecute;
            //((DWebBrowserEvents2_Event)webBrowser).DocumentComplete += S2_DocumentComplete;
        }
        else
        {
            ((DWebBrowserEvents2_Event)webBrowser).BeforeScriptExecute -= S2_BeforeScriptExecute;
            //((DWebBrowserEvents2_Event)webBrowser).DocumentComplete -= S2_DocumentComplete;
            webBrowser = null;
        }
        return 0;
    }

The signature for the handler is different, and here is the code for the handler. This is still in BHO.cs

   private void S2_BeforeScriptExecute(object pDispWindow)
    {
        //if (pDisp != this.site) { return; }

        dynamic window = webBrowser.Document.parentWindow;
        IExpando windowEx = (IExpando)window;
        windowEx.AddProperty("myprop");
        window.myprop = this;
    }

I just pasted the code from the DocumentComplete method and commented out the condition at the top.

As a result, I am able to see myprop in the jquery $.ready(). This has solved my immediate problem and I am moving ahead with my code.

Needless to say, my addon is only providing methods that will be called from javascript and does not need to do anything with document contents.

I don't yet know what is the use for pDispWindow and what is the impact of not checking if it is null, etc.

Harveyharvie answered 27/1, 2016 at 13:10 Comment(1)
This solution does not work if someone opens a link in a new tab or presses the refresh button more than 2 times.Broadcaster
A
0

Try with window.external.myExtension(); External is as far as I remember the object that holds your form. Also if this doesn't work - be sure to try simple things first and work up instead the opposite.

Here's a simple form that should work for you:

[PermissionSet(SecurityAction.Demand, Name = "FullTrust")] // Note full trust.
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public partial class BasicJSScriptableForm : Form
{
  private void BasicJSScriptableForm _Load(object sender, EventArgs e){
     this.WebBrowser1.Navigate("yourpage");
  }
  public string TestMethod(string input){
     return string.Format("echo: {0}", input);
  }
}

Then in the page:

$(document).ready(function() {
   alert(window.external.TestMethod("It will hopefully work."));
}
Adsorbate answered 27/2, 2014 at 16:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.