MFC应用程序中Delphi 7和Delphi XE4之间的ActiveX差异

ActiveX differences between Delphi 7 and Delphi XE4 in an MFC application

本文关键字:Delphi 之间 ActiveX 差异 XE4 应用程序 MFC      更新时间:2023-10-16

当我在Delphi7中基于TPanel(没有添加代码)创建ActiveX控件时,我可以将其添加到MFC C++应用程序中,并使其正常运行。

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

断言发生在occsite.cpp中的ASSERT(wFlags == DISPATCH_METHOD);上(我包含了它的来源)。

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_MMETHOD|DISPATCHROPERTYGET。

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

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

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

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

你知道发生了什么吗?或者有人能给我一个比敲击键盘更好的方向吗?

更新:2013.09.18

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

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

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

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

我用下面这样的东西来围绕事件的调用位置:

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

唯一允许像这样同时指定DISPATCH_METHODDISPATCH_PROPERTYGET的时间是,如果调用者正在调用Invoke(),因为被调用者具有相同名称的方法和属性。在这种情况下,COleControlSite::XEventSink只允许将其本身作为一个方法调用。这在XEvenSink侧非常简单——只需将ASSERT(wFlags == DISPATCH_METHOD)更改为ASSERT(wFlags & DISPATCH_METHOD)即可。至于为什么Delphi会以这种方式调用XEventSink,我在Delphi中唯一能找到的将这些标志一起使用的是ComObj单元的DispatchInvoke()函数中的以下逻辑:

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()中,至少可以追溯到Delphi5。但是,在与XE4对象使用的条件相同的条件下,早期版本中的ArgCount可能不是0,或者Resultnil?很难说,因为DispatchInvoke()在整个RTL的许多不同位置被调用,所以您必须跟踪调用堆栈,以找出谁真正在调用XEventSink,以及调用者为什么指定特定的标志组合。