Search code examples
c++buildertedgebrowser

How to write a lambda function which gets executed after TEdgeBrowser ExecuteScript?


I have TEdgeBrowser and I am using ExecuteScript method from the DefaultInterface like this:

EdgeBrowser1->DefaultInterface->ExecuteScript()

ExecuteScript, when called this way takes 2 parameters:

System::WideChar * javaScript and const _di_ICoreWebView2ExecuteScriptCompletedHandler handler

What I want to write is a lambda function (using clang compiler) which gets executed (and destroyed) after a script is executed...

Something like:

    EdgeBrowser1->DefaultInterface->(L"alert('Hello World!')",
        _di_ICoreWebView2ExecuteScriptCompletedHandler(
            new TCppInterfacedObject<TCoreWebView2ExecuteScriptCompletedHandler>(
                [](HRESULT errorCode, PCWSTR resultObjectAsJson) -> HRESULT {
                    if (FAILED(errorCode))
                    {
                        ShowMessage("Failed to execute script");
                        return errorCode;
                    }
                    ShowMessage("Script executed successfully");
                    return S_OK;
                })));

However, TCoreWebView2ExecuteScriptCompletedHandler doesn't seem to be defined? What do I need to insert there to make it work?

EDIT:

A few issues I encountered with otherwise excellent reply by @RemyLebeau

  1. in WebView2.hpp the second parameter of Invoke is defined as System::WideChar * so I couldn't use LPCWSTR as it reported that the virtual method Invoke is unimplemented. Also there wasn't a __stdcall. Corrected by using instead:
HRESULT __stdcall Invoke(HRESULT errorCode, System::WideChar* resultObjectAsJson)
  1. is the use of INTFOBJECT_IMPL_IUNKNOWN really necessary since the TCppInterfacedObject documentation describes that it already implements IUnknown methods like QueryInterface, AddRef, and Release? When commented out, there is no warning that those methods are unimplemented, like when it is when I "manually" implement the ICoreWebView2ExecuteScriptCompletedHandler

  2. Is it necessary to Release() the object before it returns S_OK? I know that there is reference counting involved but does the ExecuteScript call then does that? Basically, after executing the lambda or instance of ICoreWebView2ExecuteScriptCompletedHandler, it is no longer needed to be in memory.


Solution

  • The TEdgeBrowser::DefaultInterface property is the raw ICoreWebView2* interface pointer provided by Microsoft, just wrapped in the DelphiInterface class (that is what the _di_ prefix refers to). Microsoft has no concept of _di_ wrapper types.

    The 2nd parameter of ICoreWebView2::ExecuteScript() expects a pointer to an object that implements the ICoreWebView2ExecuteScriptCompletedHandler interface. You can't pass in a lambda where an interface is expected. So, you need to write a class that implements ICoreWebView2ExecuteScriptCompletedHandler, and then pass an object instance of that class to ExecuteScript().

    The way you are trying to use TCppInterfacedObject for that purpose is not correct. Try this instead:

    class TCoreWebView2ExecuteScriptCompletedHandler : public TCppInterfacedObject<ICoreWebView2ExecuteScriptCompletedHandler>
    {
    public:
        HRESULT __stdcall Invoke(HRESULT errorCode, WideChar* resultObjectAsJson)
        {
            if (FAILED(errorCode))
            {
                ShowMessage("Failed to execute script");
                return errorCode;
            }
            ShowMessage("Script executed successfully");
            return S_OK;
        }
    };
    
    EdgeBrowser1->DefaultInterface->ExecuteScript(
        L"alert('Hello World!')",
        _di_ICoreWebView2ExecuteScriptCompletedHandler(
            new TCoreWebView2ExecuteScriptCompletedHandler()
        )
    );
    

    If you really want to use a lambda, then you would need something more like this instead:

    #include <functional>
    
    class TCoreWebView2ExecuteScriptCompletedHandler : public TCppInterfacedObject<ICoreWebView2ExecuteScriptCompletedHandler>
    {
    public:
        using funcType = std::function<HRESULT(HRESULT, LPCWSTR)>;
    
        TCoreWebView2ExecuteScriptCompletedHandler(funcType func) : m_func(func) {}
    
        HRESULT __stdcall Invoke(HRESULT errorCode, WideChar* resultObjectAsJson)
        {
            return m_func(errorCode, resultObjectAsJson);
        }
    
    private:
        funcType m_func;
    };
    
    EdgeBrowser1->DefaultInterface->ExecuteScript(
        L"alert('Hello World!')",
        _di_ICoreWebView2ExecuteScriptCompletedHandler(
            new TCoreWebView2ExecuteScriptCompletedHandler(
                [](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT
                {
                    if (FAILED(errorCode))
                    {
                        ShowMessage("Failed to execute script");
                        return errorCode;
                    }
                    ShowMessage("Script executed successfully");
                    return S_OK;
                }
            }
        )
    );
    

    Alternatively:

    template<typename FuncType>
    class TCoreWebView2ExecuteScriptCompletedHandler : public TCppInterfacedObject<ICoreWebView2ExecuteScriptCompletedHandler>
    {
    public:
        TCoreWebView2ExecuteScriptCompletedHandler(FuncType func) : m_func(func) {}
    
        HRESULT __stdcall Invoke(HRESULT errorCode, WideChar* resultObjectAsJson)
        {
            return m_func(errorCode, resultObjectAsJson);
        }
    
    private:
        FuncType m_func;
    };
    
    auto func = [](HRESULT errorCode, LPCWSTR resultObjectAsJson) -> HRESULT
    {
        if (FAILED(errorCode))
        {
            ShowMessage("Failed to execute script");
            return errorCode;
        }
        ShowMessage("Script executed successfully");
        return S_OK;
    };
    
    EdgeBrowser1->DefaultInterface->ExecuteScript(
        L"alert('Hello World!')",
        _di_ICoreWebView2ExecuteScriptCompletedHandler(
            new TCoreWebView2ExecuteScriptCompletedHandler<decltype(func)>(func)
        )
    );
    

    And no, you should not call Release() from inside the handler itself. ExecuteScript() will AddRef() the handler that you pass in if needed, and then Release() it when done using it. You should only AddRef()/Release() your own references to the handler.