3

当我在 Delphi 7 中创建基于 TPanel(没有添加代码)的 ActiveX 控件时,我可以将它添加到 MFC C++ 应用程序并让它运行良好。

当我使用完全相同的代码并在 Delphi XE4(和 XE2)中编译它时,MFC 会抛出一个断言。我确认唯一的变化是在 dcu、ocx 和 res 文件中。

断言发生ASSERT(wFlags == DISPATCH_METHOD);在 occsite.cpp 中(我在其中包含了源代码)。

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;
}

wFlags 的值为 DISPATCH_METHOD | 调度属性获取。

之后一切似乎都正常工作(如果您从 XE4 开始,鼠标事件会导致类似的问题,但 D7 不包括它们)。

我在 Visual Studio 2010 和 Visual Studio 2012 中都试过这个。在 MFC 中,我正在创建一个新的 MFC 对话框应用程序,右键单击并选择添加 ActiveX 控件。我对 MFC 比较陌生,所以我可能做错了。

Win 7 x64 系统中的主机系统。

我不能把断言留在代码中,我真的想让它正常工作,这样我将来可以重用一堆 Delphi 代码。

任何想法正在发生什么,或者谁能指出我比敲击键盘更好的方向?

更新:2013.09.18

雷米在下面的回答是正确的,但这里有更多信息。

从 XE4 开始,主要问题似乎是发送回控制主机的事件(即 OnClickEvent、OnMouseEnter、OnMouseLeave、OnConstrainedResize、OnCanResize 或 OnResizeEvent)。

我找到了 3 个可能的解决方案(如果我找到了,会再次更新):

  1. 注释掉调用这些事件的代码(我没有说它们是好的解决方案)。
  2. 注释掉 ComObjs.DispatchInvoke 中导致设置的行。
  3. 修改 ComObjs 以具有备用 DispatchInvoke 和 DispCallByID
    • 备用 DispCallID 需要调用备用 DispatchInvoke。
    • 备用 DispatchInvoke 需要删除更改标志的代码
    • 全局变量 DispCallByIDProc 在被事件使用时需要设置为备用 DispCallByID 过程。
    • DispCallByIDProc 需要在设置为备用之后重新设置(我将其作为备用 DispCallByID 中的第一行)。

我使用了类似下面的东西来包围事件被调用的位置:

FEvents <> nil then
try
    SetDispatchByCallID(True);
    FEvents.OnClick;
finally
    SetDispatchByCallID(False);
end;
4

1 回答 1

3

唯一允许同时指定 和 的情况是调用者正在调用DISPATCH_METHOD,因为被调用者同时具有同名的方法和属性。在这种情况下,只允许自己作为方法被调用。这将很容易在侧面修复 - 只需改为改为。至于为什么Delphi会以这种方式调用,我在Delphi中唯一能找到将这些标志一起使用的是单元函数中的以下逻辑:DISPATCH_PROPERTYGETInvoke()COleControlSite::XEventSinkXEvenSinkASSERT(wFlags == DISPATCH_METHOD)ASSERT(wFlags & DISPATCH_METHOD)XEventSinkDispatchInvoke()ComObj

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;

但是,这种逻辑DispatchInvoke()至少存在于一直追溯到 Delphi 5 中。但可能ArgCount不是 0 或者Resultnil与您的 XE4 对象使用的条件相同的早期版本中?很难确定,因为DispatchInvoke()在整个 RTL 中的许多不同位置都被调用,因此您必须跟踪调用堆栈以找出实际调用XEventSink者以及调用者指定特定标志组合的原因。

于 2013-08-15T23:38:54.867 回答