3
votes

When I create an ActiveX control based on a TPanel (with no added code) in Delphi 7, I am able to add this to a MFC C++ application and have it run fine.

When I take the exact same code and compile it in Delphi XE4 (and XE2), MFC throws an assertion. I confirmed that the only changes are in the dcu, ocx and res files.

The assertion is happening on ASSERT(wFlags == DISPATCH_METHOD); in occsite.cpp (I included the source to this).

STDMETHODIMP COleControlSite::XEventSink::Invoke(
    DISPID dispid, REFIID, LCID, unsigned short wFlags,
    DISPPARAMS* pDispParams, VARIANT* pvarResult,
    EXCEPINFO* pExcepInfo, unsigned int* puArgError)
{
    UNUSED(wFlags);

    METHOD_PROLOGUE_EX(COleControlSite, EventSink)
    ASSERT(pThis->m_pCtrlCont != NULL);
    ASSERT(pThis->m_pCtrlCont->m_pWnd != NULL);
    ASSERT(wFlags == DISPATCH_METHOD);

    AFX_EVENT event(AFX_EVENT::event, dispid, pDispParams, pExcepInfo,
        puArgError);

    pThis->OnEvent(&event);

    if (pvarResult != NULL)
        ::VariantClear(pvarResult);

    return event.m_hResult;
}

The value of wFlags is DISPATCH_METHOD | DISPATCHPROPERTYGET.

Everything seems to work correctly after that (mouse events cause similar issues if you start in XE4, but D7 doesn't include them).

I tried this in both Visual Studio 2010 and Visual Studio 2012. In MFC, I am creating a new MFC dialog application, right clicking and selecting add ActiveX control. I am relatively new to MFC so I could be doing it wrong.

The host system in a Win 7 x64 system.

I can't leave the assertions in the code and really want to get this to work correctly so I can reuse a bunch of Delphi code in the future.

Any ideas what is happening or can anyone point me in a slightly better direction than head banging on a keyboard?

Update: 2013.09.18

Remy's answer below is correct, but here is some more information.

As of XE4, it seems the primary issues with this are the events sent back to the control host (i.e. OnClickEvent, OnMouseEnter, OnMouseLeave, OnConstrainedResize, OnCanResize or OnResizeEvent).

I found 3 possible solutions (will update again if I find anymore):

  1. Comment out the code calling these events (I didn't say they were good solutions).
  2. Comment out the lines in ComObjs.DispatchInvoke causing this to be set.
  3. Modify ComObjs to have an alternate DispatchInvoke and DispCallByID
    • The alternate DispCallID needs to call the alternate DispatchInvoke.
    • The alternate DispatchInvoke needs to have the code changing the flag removed
    • The global variable DispCallByIDProc needs to be set to the alternate DispCallByID procedure when being used by an event.
    • DispCallByIDProc needs to be set back afterward being set to the alternative (I do it as the first line in the alternate DispCallByID).

I used something like the following to surround where the event was being called:

FEvents <> nil then
try
    SetDispatchByCallID(True);
    FEvents.OnClick;
finally
    SetDispatchByCallID(False);
end;
1
My particular experiences with building ActiveX controls is that the last version of Delphi that worked flawlessly with ActiveX control creation was Delphi 7. Starting in 2007, and continuining in unicode Delphi versions, the compiler and the RTL do not work well for building ActiveX controls. When/if you find a workaround for this glitch, you'll simply keep running for 5 more microseconds until you hit the next glitch. I put about 100 hours into using Delphi XE2 to do ActiveX controls, and gave up. I doubt XE4 will be better, if anything, worse. I used MFC app as ActiveX container.Warren P

1 Answers

3
votes

The only time that DISPATCH_METHOD and DISPATCH_PROPERTYGET are allowed to be specified together like that is if the caller is calling Invoke() because the callee has both a method and a property that have the same name. In this case, COleControlSite::XEventSink is allow itself to be invoked only as a method. This would be very simple to fix on the XEvenSink side - simply change ASSERT(wFlags == DISPATCH_METHOD) to ASSERT(wFlags & DISPATCH_METHOD) instead. As for why Delphi would invoke XEventSink this way, the only thing I can find in Delphi that uses those flags together is the following logic in the DispatchInvoke() function of the ComObj unit:

procedure DispatchInvoke(const Dispatch: IDispatch; CallDesc: PCallDesc;
  DispIDs: PDispIDList; Params: Pointer; Result: PVariant);
var
  ..., InvKind: Integer;
  ...
begin
  ...
  InvKind := CallDesc^.CallType;
  ...
  if InvKind = DISPATCH_PROPERTYPUT then
  begin
    ...
  end
  else if (InvKind = DISPATCH_METHOD) and (CallDesc^.ArgCount = 0) and (Result <> nil) then
      InvKind := DISPATCH_METHOD or DISPATCH_PROPERTYGET; // <-- HERE

  ...
  Status := Dispatch.Invoke(..., InvKind, ..., Result, ...);
  ...
end;

However, that logic exists in DispatchInvoke() going all the way back to Delphi 5, at least. But maybe ArgCount was not 0 or Result was nil in earlier versions under the same conditions as your XE4 object is using? Hard to say for sure, as DispatchInvoke() gets called in a bunch of different places throughout the RTL, so you would have to track through the call stack to find out who is actually calling XEventSink and why the caller is specifying that particular flag combination.