Delphi 调用c++做的DLL中的函数出现问题。

houjbf 2011-09-18 11:56:14
Delphi 调用c++做的DLL中的函数出现一个问题:

C++返回的结构在Delphi中居然为随机变量。请教高手指点一下,看问题出在哪里?


//============================================================


c++中的函数定义如下:

#include <windows.h>

//SIZE 为标准Windows 结构.
extern "C" __declspec(dllexport) SIZE __cdecl cvGetSize3(SIZE p)
{
SIZE sz = {0};

sz.cx = sizeof(sz);
sz.cy = 200;

return sz; //返回SIZE.
}



//============================================================
//Delphi 代码:

function cvGetSize3( const s: SIZE ) : SIZE; cdecl; external testDll;

procedure TForm1.Button2Click(Sender: TObject);
var
x: SIZE;
begin
X.cx := 1;
x.cy := 2;
x := cvGetSize3(x);
showmessage(Format('sz.x: %d, %d', [x.cx, x.cy]));

//Showmessage 结果为 "sz.x: 4214743, 22525520"
//请教高手指点一下,看问题出在哪里?为什么不是返回预期的值?

end;

//============================================================

...全文
367 37 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
37 条回复
切换为时间正序
请发表友善的回复…
发表回复
houjbf 2011-09-23
  • 打赏
  • 举报
回复
我出差回来了。
感谢大家,特别感谢<快乐老猫>和<littlestone08>两位了!
此前我也是有点想当然了,感觉不就是返回个结构值而已吗,看了两位的深度分析可以判定此题注定是个世界难题了。我打算暂时不去考虑这个问题了,毕竟做软件还得考虑进度啊!以后有时间再去深入研究了。
分不多,谢谢大家了。
快乐老猫 2011-09-23
  • 打赏
  • 举报
回复
[Quote=引用 36 楼 houjbf 的回复:]
我出差回来了。
感谢大家,特别感谢<快乐老猫>和<littlestone08>两位了!
此前我也是有点想当然了,感觉不就是返回个结构值而已吗,看了两位的深度分析可以判定此题注定是个世界难题了。我打算暂时不去考虑这个问题了,毕竟做软件还得考虑进度啊!以后有时间再去深入研究了。
分不多,谢谢大家了。
[/Quote]

这不是世界难题,问题是返回结构这个东西没有一个统一标准,每个公司各行其道,接口不统一,就没法衔接。所以用大家通用的格式是最可靠的。
快乐老猫 2011-09-22
  • 打赏
  • 举报
回复
第三图应为:Delphi 调用 VC DLL 后,ECX出栈前的反汇编及寄存器
第四图应为:Delphi 调用 VC DLL 后,ECX出栈后的反汇编及寄存器


可以看到,VC用ECX返回结构数据,可惜返回后被Delphi的出栈动作覆盖了。
无法返回正确的结果。
快乐老猫 2011-09-22
  • 打赏
  • 举报
回复
贴一些截图吧

[img=http://hi.csdn.net/attachment/201109/22/2145392_13166670376Zz0.jpg]VC DLL 返回结构的反汇编[/img]
VC DLL 返回结构的反汇编

[img=http://hi.csdn.net/attachment/201109/22/2145392_1316667038n7GX.jpg]VC DLL 返回结构的寄存器[/img]
VC DLL 返回结构的寄存器

[img=http://hi.csdn.net/attachment/201109/22/2145392_1316667051ntnz.jpg]Delphi 调用 VC DLL 前的反汇编及寄存器[/img]
Delphi 调用 VC DLL 前的反汇编及寄存器

[img=http://hi.csdn.net/attachment/201109/22/2145392_1316667042wS8u.jpg]Delphi 调用 VC DLL 后的反汇编及寄存器[/img]
Delphi 调用 VC DLL 后的反汇编及寄存器

[img=http://hi.csdn.net/attachment/201109/22/2145392_13166670441M8Z.jpg]Delphi 变量[/img]
Delphi 变量
plutu 2011-09-22
  • 打赏
  • 举报
回复
老猫是全才,啥都会啊,赞一个先
快乐老猫 2011-09-22
  • 打赏
  • 举报
回复
17楼代码重新整理:
===================================
在 VC 中写
cvGetSize3(SIZE p)
这样的代码,参数 p 是值参,在函数内修改是无法返回的。

所以在 VC 中的如下定义:
extern "C" __declspec(dllexport) SIZE __cdecl cvGetSize3(SIZE p)


在 Delphi 中声明如下:
function cvGetSize3(s: SIZE): SIZE; cdecl; external 'test2.dll';


VC 中的 __cdecl 这个定义,和 Delphi 中的 cdecl 有些不同。在 Delphi 中,cdecl 的返回值在 EAX 中,因此,返回一个结构是无法实现的。他可以返回指针,但是指针指向的空间必须是在堆中申请的,一般建议在调用端申请和销毁。

问题由此产生,Delphi 和 VC 的堆栈可能不同步,或传入传出的数据指向有偏差,不但无法得到正确的结果,还会引发系统稳定问题。

所以,如下代码在 Delphi 中调用会出现问题:
extern "C" __declspec(dllexport) SIZE __cdecl cvGetSize3(SIZE p)
{
SIZE sz = {0};

sz.cx = sizeof(sz);
sz.cy = 200;
p.cx = 1000;
return sz; //返回SIZE.
}


可以修改为如下代码及声明:
extern "C" __declspec(dllexport) long __cdecl NewcvGetSize3(SIZE *p)
{
(*p).cx = sizeof(p);
(*p).cy = 200;

return 0; //返回SIZE.
}


function NewcvGetSize3(var s: SIZE): LongInt; cdecl; external 'test1.dll';


如果 VC 的代码接口不可以修改,那么做一个函数转换,也就是二次封装,实现方法如下:

extern "C" __declspec(dllexport) long __cdecl NewcvGetSize3Call(SIZE *p)
{
SIZE sz = {0};
HINSTANCE hInst = NULL;
typedef SIZE (__cdecl *FUNcvGetSize3)(SIZE p);
FUNcvGetSize3 funGetSize3;

hInst = LoadLibrary(_T("test2.dll"));
if (!hInst)
return -1;

funGetSize3 = (FUNcvGetSize3)GetProcAddress(hInst,"cvGetSize3");
if (!funGetSize3)
return -1;

*p = funGetSize3(sz);
FreeLibrary(hInst);
return 12345; //返回SIZE.
}


function NewcvGetSize3Call(var s: SIZE): LongInt; cdecl; external 'test1.dll';


procedure TForm1.Button1Click(Sender: TObject);
var
x: SIZE;
begin
inherited;
X.cx := 1;
x.cy := 2;
NewcvGetSize3Call(x);
showmessage(Format('sz.x: %d, %d', [x.cx, x.cy]));
end;

======================

在这里,test1.dll 是用户提供的 DLL 文件名,test1.dll 是你新创建的 VC DLL 代码。
以上代码由 VC2008 & DELPHI 7 联合调试成功。
还是那句话,不要在一个 DLL 函数中返回结构,一个正确的 API 书写规则就是仅仅返回一个 32 位的数据,超过长度应该使用参数传递指针。
快乐老猫 2011-09-21
  • 打赏
  • 举报
回复
准备参加非诚勿扰吗,哈哈
houjbf 2011-09-21
  • 打赏
  • 举报
回复
下午准备去江苏了,请大家继续,中间我可能上不了网,两天后回来给大家回复。
快乐老猫 2011-09-21
  • 打赏
  • 举报
回复
qq:35166488
可以加我
houjbf 2011-09-21
  • 打赏
  • 举报
回复
我的测试工程附件,感谢大家帮忙。

点击下载 vcDLL 工程附件

+目录说明
-bin //执行文件目录
-D14demo //Delphi2010 demo,我主要用这个做项目,delphi7已不怎么用了。
-D7demo //Delphi7 demo, 同样的问题。
-Ex05 //VC++ DLL
-vcDLLs.sln // VC++ 工程解决方案。
houjbf 2011-09-21
  • 打赏
  • 举报
回复
我的测试工程附件,感谢大家帮忙。

vcDllshttp://download.csdn.net/detail/houjbf/3621497
+
-bin //执行文件目录
-D14demo //Delphi2010 demo
-D7demo //Delphi7 demo
-Ex05 //VC++ DLL
-vcDLLs.sln // VC++ 工程解决方案。
littlestone08 2011-09-21
  • 打赏
  • 举报
回复
同时看看了老猫的回复,感觉老猫的回复应该是对的VC中是__cdecl,BCB(delhi)中的是cdecl,感觉问题应该就在这里.我不熟悉C编译器,也不知道不同的编译器如何实现,返回生成导出函数名这一过程肯定是不同的.问题应该出现在编译的DLL中,加你Q了,把你的DLL给我吧,我这里没有VC,只好看二进制了.
littlestone08 2011-09-21
  • 打赏
  • 举报
回复
老猫水平真是不洼啊,嘎嘎,顶一下
littlestone08 2011-09-21
  • 打赏
  • 举报
回复
折腾一天汇编得出结论,老猫在17楼中的说法是正确的。VC中不但cdecl不能返回结构,stdcall也不能。应该是只支持标准的数据类型。
dll函数中虽然能操作出结构,但是用ECX返回的是堆栈中的指针,而D中直接无视ECX,应该是stdcall并没有规定非标准类型如何返回形成的不同的编译器之间的实现差异,而且,我感觉可能会造成你的应用程序的堆栈会不断的增长或是减少,然后挂后了。

至于BCB能正常返回,应该是因为Delphi,BCB是同一家公司的原因,在实现上也类似。

当然,如果你要都是在同一个环境中使用的话,应该是没问题的,但当然是不推荐的做法。

到最后你的终极解决的标准方案应该是写一个转换库


houjbf 2011-09-20
  • 打赏
  • 举报
回复
其实,快乐老猫对Delphi和C++对栈的处理的论述好像是有道理的,我因此特意查看了MSDN关于函数返回用户定义类型的说明,也没有看到什么特别的说明。但可以明确的说,在VC++环境里,不管是DLL或EXE都可以正确返回一个结构类型的值的;只不过返回一个结构涉及到值拷贝,效率会受一点影响,特别是对一个超级大结构。但是,用 a = getA() 这种样式在程序可读性上要更好些的。

感谢大家参与和解答,我的QQ为759500596(闻天有声),欢迎大家加入讨论。
houjbf 2011-09-20
  • 打赏
  • 举报
回复
TO: <littlestone08>
知音啊!太理解我的想法了。

谢谢你给的几个解答,以下分别讨论:
1. “sz 这个变量是局部变量,也就是在栈中分配的”
我也尝试过在堆中new 一个变量返回,实际上也是得不到返回的结果。
注:别人要求必须以结构返回,而我必须照做,没商量余地的。

extern "C" __declspec(dllexport) SIZE __cdecl cvGetSize3()
{
SIZE *sz = new SIZE;

sz->cx = 2;
sz->cy = sizeof(sz);

return *sz; //这里我故意不去释放 sz 就是为了看能不能正确返回 sz 的值。
}

2. ”Delphi声明中的const 似乎也应该去掉,不过,既然你不用参数,也就无所谓了”
这个同意,真的无所谓了,之前例子中加了个 const 仅仅是为了测试一下不同条件下的情况而已。


3. “注意声明方式,因为你的是cdecl方式,前面必须要加一个下划线。在我这里,不加的话,根本就找不到这个函数。”
在我这里,情况刚好相反。我的不能加下滑线,而且只能这样声明才可正确连接:
function cvGetSize3() : SIZE; cdecl; external testDll; //输入参数(const s: SIZE)干脆就都不要了


下面这个最重要!
4. “刚才我把你的编译了一下,调用了一下,返回的是8,200,一切正常。“
似乎接近答案了。--先高兴一下。
我的确实得不到。请配合对照一下开发环境好吗,这可能是解决问题的关键了吧!
我的开发环境是:
Delphi2010
1)Version 14.0.3593.25826 IDE: 使用默认配置。
2)结构字节对齐为默认值(Quad word)

VC++2008 SP1
1)版本 9.0.30729.1 sp1
2)结构字节对齐为也为默认值(Quad word)//在两个环境下 sizeof(int) = 4;




littlestone08 2011-09-20
  • 打赏
  • 举报
回复
我的環境是BCB2010(还是让网上朋友帮我编译的),DELPHI5,够古老吧.

返回数据结构肯定是没问题的,我感觉哪里别着劲.

你返回*sz,DELPHI中不能用,我感觉肯定不是返回方式的问题,这样按理说没问题的.如果方便的话,你留下QQ号,我加上你,你把你编译的DLL给我,我在我这里试试.

如果你想要的话,我也可以把我写的调用正确的东西给你.

写转换库,没必要,而且,如果你下次再有这样的问题,你怎么办呢?
houjbf 2011-09-20
  • 打赏
  • 举报
回复
TO:<快乐老猫>
如果实在不行,就只能再写个转换库。难道返回结构数据就真的不行吗?
看 <littlestone08> 说的倒是有点希望。

等 <littlestone08> 的回复。
快乐老猫 2011-09-20
  • 打赏
  • 举报
回复
在vc里面写东西,80%的代码不是你关注的。记得04年用2003写个破代码,就是读写注册表然后在一个窗体的一堆文本框、列表框上显示修改,猫了个咪的,600多行代码,还不算系统生成的。
加载更多回复(17)

5,928

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 开发及应用
社区管理员
  • VCL组件开发及应用社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧