Delphi 7和Delphi XE4在MFC应用程序中使用ActiveX的区别

3
当我在Delphi 7中创建基于TPanel的ActiveX控件(没有添加代码),我可以将其添加到MFC C ++应用程序中并使其正常运行。
当我使用完全相同的代码并在Delphi XE4(和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_METHOD | DISPATCHPROPERTYGET。

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

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

宿主系统是Win 7 x64系统。

我不能在代码中留下断言,真的希望能正确地使其工作,这样我将来可以重用大量的Delphi代码。

有什么想法或者有人能指点我一个比在键盘上敲头更好的方向吗?

更新:2013.09.18

Remy的答案是正确的,但这里还有更多信息。

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

我在构建ActiveX控件方面的特定经验是,与ActiveX控件创建无缝工作的Delphi版本是Delphi 7。从2007年开始,在Unicode Delphi版本中,编译器和RTL对于构建ActiveX控件效果不佳。如果您找到了此故障的解决方法,那么您只需继续运行5微秒,直到遇到下一个故障。我花了大约100个小时使用Delphi XE2来制作ActiveX控件,并放弃了。我怀疑XE4会更好,如果有什么不同,那就更糟糕了。我使用MFC应用程序作为ActiveX容器。 - Warren P
1个回答

4
只有在调用者调用Invoke()时,才允许同时指定DISPATCH_METHOD和DISPATCH_PROPERTYGET这样的组合,因为被调用方既有方法又有与其同名的属性。在这种情况下,COleControlSite::XEventSink只能作为方法被调用。要解决这个问题很简单,只需将ASSERT(wFlags == DISPATCH_METHOD)更改为ASSERT(wFlags & DISPATCH_METHOD)即可。至于为什么Delphi会以这种方式调用XEventSink,我只能在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;

然而,至少从Delphi 5开始,这种逻辑就存在于DispatchInvoke()中。但也许在早期版本中,ArgCount不为0或Result在与XE4对象使用相同条件的情况下为nil?很难说,因为DispatchInvoke()在RTL中的许多不同位置都被调用,所以您必须跟踪调用堆栈才能找出谁实际上正在调用XEventSink,以及为什么调用者指定了特定的标志组合。

谢谢,Remy。我花了很大的力气才说服技术支持这是个问题。似乎只有将事件发送回OCX主机时才会出现问题。在D7下,DispatchInvoke从未被调用,而是调用了DispCall。我仍在研究建议的解决方法http://qc.embarcadero.com/wc/qcmain.aspx?d=115373。 - tmjac2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接