gpt4 book ai didi

c++ - 编写从C++应用程序链接的Delphi DLL:访问C++接口(interface)成员函数会导致访问冲突

转载 作者:行者123 更新时间:2023-12-03 18:16:52 26 4
gpt4 key购买 nike

我需要编写一个DLL(在Delphi 2009中),该DLL将链接到用MS VC++编写的第三方应用程序中。这个概念与插件系统非常相似,这意味着该应用程序无需DLL即可完美运行,并在存在DLL时加载它。

在某些事件上,应用程序调用DLL导出的函数。文档中列出了已定义的功能,所谓的SDK提供了一些示例代码,当然也包括C++。我无权访问应用程序本身的源代码。

以下是篇幅冗长的介绍以及一些代码示例。 问题(将在本文底部再次询问)是:我必须如何在Delphi DLL中实现作为接口(interface)的指针传递的应用程序C++类?我已经阅读了关于stackoverflow和其他资源的几个线程,但是大多数线程都涉及修改两端(C++ applicaton和Delphi DLL),这不是一个选择。因此,我正在寻找可以帮助将C++ DLL代码转换为Delphi DLL代码的人。

被调用的函数通常接收一些参数(主要是TCHAR *,int和一些ENUM),并具有HRESULT类型的返回值。它们中的一些确实也具有称为“类似于COM的接口(interface)”的指针的参数,旨在使调用应用程序内部定义的成员函数成为可能。在翻译过程中,我只是在类型前面加上了“T”,并在一个单独的单元中声明了相应的类型(TCHAR *成为PTCHAR,并定义为PTCHAR = PAnsiChar。这很容易替换类型,如果需要的话) 。

当前,应用程序已经调用了DLL函数,并且DLL中的代码具有对“标准”参数(如字符串或整数)的完全访问权限。返回值被传递回应用程序,因此可以认为导出功能的实现是正确的。在较短的示例中(在Delphi实现中有效):

// C++ function defined in the SDK
DLLAPI int FPHOOK_OnStartFlowInstance(const TCHAR* strSvcAppName,
const TCHAR* strAppName,
const FLOW_SECTION_TYPE eSectionType,
IIFlowContext* pContext)
{
return 0;
}


// Delphi translation of the same function
function FPHOOK_OnStartFlowInstance( const strSvcAppName : PTCHAR;
const strAppName : PTCHAR;
const eSectionType : TFLOW_SECTION_TYPE;
pContext : PIIFlowContext) : Int; stdcall;
begin
dbg('ENTER FPHOOK_OnStartFlowInstance: strSvcAppName = ''%s'', strAppName = ''%s''',[String(strSvcAppName),String(strAppName)]);
result := 0;
end;

现在,问题是我需要调用成员函数之一。这是类(C++)的定义。接口(interface)(Delphi)。我省去了大多数功能,只是为了节省空间,但如果有帮助,将很乐意提供更多源代码。
// (shortened) class definition from C++
class IIFlowContext : virtual public CIUnknown
{
// Operation
public:
virtual HRESULT getContextID(/*[out]*/unsigned short* pContextId) = 0;
virtual HRESULT cleanExecutionState() = 0;
/* skipped some other 'virtual HRESULT ...' */
};

// (shortened) interface declaration from Delphi
type IIFlowContext = Interface(IUnknown)
function getContextID(pContextId : Punsigned_short) : HRESULT; stdcall;
function cleanExecutionState : HRESULT; stdcall;
// skipped some other 'function ...'
end;

如果现在尝试访问成员函数之一:
function FPHOOK_OnStartFlowInstance( ...,pContext : PIIFlowContext) : Int; stdcall; 
var fphookResult : HRESULT;
begin
try
fphookResult := pContext.cleanExecutionState;
except On E: Exception do
dbg('FPHOOK_OnStartFlowInstance, pContext.cleanExecutionState: %s::%s',[E.ClassName,E.Message]);
end;
result := 0;
end;

一个EAccessViolation错误被except块捕获并写入调试日志。我已经尝试过不同的约定(不确定“约定”在这里是否正确),例如cdecl或safecall而不是stdcall,它们都具有相同的结果。

这是我目前根本不知道要看的地方的地方……我从来没有做过C++(甚至C)程序员,所以我对Delphi的翻译很可能是错误的。也许我还有其他要点。

无论如何,如果有更多(或更多)经验的人能给我一些提示,我会很高兴。

提前致谢

帕特里克

// 2010-11-05:我从评论,答案和答案中提取的内容

雷姆科建议将参数定义为
var pContext : IIFlowContext;

给出的结果几乎与我最初的尝试相同
pContext : PIIFlowContext;

在这两种情况下都会抛出异常,但是变量的内容不同。在下面列出了不同测试用例的地方,将提供更多信息。

Barry提到Delphi中的接口(interface)(与C++相反)已经是指针。因此,尽管C++需要传递一个指向该类的指针(也称为传递作为引用),但Delphi已经期望有对该类的引用。因此,该参数应声明为
pContext : IIFlowContext;

也就是说,既不作为指向接口(interface)的指针,也不与var修饰符一起使用。

我运行了以下三个测试用例,所有这些用例在dll导出的函数的第一条指令中都有一个调试断点:

1)将参数声明为接口(interface)的指针
pContext : PIIFlowContext;

结果:根据调试器,pContext包含一个指向内存地址$ EF83B8的指针。调用接口(interface)方法之一将导致跳转到内存地址$ 560004C2t,并引发EAccessViolation异常。

2)声明参数作为对接口(interface)的引用
var pContext : IIFlowContext;

结果:调试器将pContext的内容显示为“Pointer($ 4592DC)as IIFlowContext”。调用interfaces方法会导致跳转到相同的内存地址$ 560004C2,然后引发相同的执行。

3)将参数声明为接口(interface)本身(不带修饰符)
pContext : IIFlowContext;

结果:导出的dll函数甚至都没有被调用。在跳入dll函数之前,将引发EAccessViolation(并由调试器捕获)。

从以上内容可以得出结论,无论参数声明为var pContext:IIFlowContext还是pContext:PIIFlowContext都没有太大的区别,但是如果将其声明为pContext:IIFlowContext则是一个明显的区别。

根据要求,这是调试器 View 的输出。在注释中,我注意到了执行左侧操作后的寄存器值:
SystemHook.pas.180: fcnRslt := pContext.cleanExecutionState;
028A3065 8B4514 mov eax,[ebp+$14] // EAX now = $00EF83D0
028A3068 8B00 mov eax,[eax] // EAX now = $004592DC
028A306A 50 push eax
028A306B 8B00 mov eax,[eax] // EAX now = $0041DE86
028A306D FF5010 call dword ptr [eax+$10] // <-- Throws Exception, EAX+$10 contains $560004C2
028A3070 59 pop ecx
028A3071 8BD8 mov ebx,eax

无论参数是指向接口(interface)的指针还是var引用,反汇编都是完全相同的。

我还有什么需要提供的吗?

我想到的另一个问题是...

在SDK的原始头文件中,该类定义为
class IIFlowContext : virtual public CIUnknown

反过来,CIUnknown在另一个头文件(win_unknown.h)中定义为
class CIUnknown
{
// Operation
public:
virtual HRESULT QueryInterface(REFIID iid, void ** ppvObject) = 0;
virtual unsigned long AddRef(void) = 0;
virtual unsigned long Release(void) = 0;
static bool IsEqualIID(REFIID iid1, REFIID iid2)
{
if (memcmp(&iid1, &iid2, sizeof(IID)) == 0)
return true;

return false;
}
};

将IUnknown用作Delphi接口(interface)的基础可以吗?我猜不是,因为据我所知,IUnknown没有实现IsEqualIID,因此VMT会有变化。但是,我将如何在Delphi中实现呢? C++静态与Delphi类函数相同吗?

// 2010-11-18:一些更新

不幸的是,我还没有找到一种使它工作的方法。确实改变了行为的一件事是将接口(interface)引用传递为
const pContext : IIFlowContext;

如Barry所述,这阻止了delphi在接口(interface)上“自动”调用_AddRef()。这样,我就可以发起和调试对接口(interface)成员函数的调用。现在我可以跟踪执行一段时间,甚至可以看到对Windows API的一些调用(例如CriticalSections),但有时仍会引发EAccessViolation错误。

目前,我没有其他想法。我想我将尝试使用MSVC++编译器,以便像SDK所建议的那样构建DLL。如果可行,那么也许可以使用C++通过Delphi代码创建包装器来解决。

无论如何,到目前为止非常感谢您的帮助!但是,任何其他输入将不胜感激。

最佳答案

根据您最近的评论,我想我知道发生了什么事。我应该早点发现它。

C++端的IIFlowContext是一个类; IIFlowContext* pContext正在传递指向该类的指针,这是用C++表示COM样式的接口(interface)的方式。

但是Delphi接口(interface)已经是指针了。之所以假设是间接的,是因为Delphi类永远不会像C++类那样按值传递。您应该在Delphi入口点中直接使用IIFlowContext,不要使用varconst修饰符。

接口(interface)方法声明可能仍然存在问题;有了更多信息,它将更加清晰:请参阅我对您问题的最新评论。

关于c++ - 编写从C++应用程序链接的Delphi DLL:访问C++接口(interface)成员函数会导致访问冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4095932/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com