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;

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

...全文
359 37 打赏 收藏 转发到动态 举报
写回复
用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)
Delphi制作DLL •一 Dll的制作一般分为以下几步: 1 在一个DLL工程里写一个过程或函数 2 写一个Exports关键字,在其下写过程的名称。不用写参数和调用后缀。 二参数传递 1 参数类型最好与window C++的参数类型一致。不要用DELPHI的数据类型。 2 最好有返回值[即使是一个过程],来报出调用成功或失败,或状态。成功或失败的返回值最好为1[成功]或0[失败].一句话,与windows c++兼容。 3 用stdcall声明后缀。 4 最好大小写敏感。 5 无须用far调用后缀,那只是为了与windows 16位程序兼容。 三 DLL的初始化和退出清理[如果需要初始化和退出清理] 1 DLLProc[SysUtils单元的一个Pointer]是DLL的入口。在此你可用你的函数替换了它的入口。但你的函数必须符合以下要求[其实就是一个回调函数]。如下: procedure DllEnterPoint(dwReason: DWORD);far;stdcall; dwReason参数有四种类型: DLL_PROCESS_ATTACH:进程进入时 DLL_PROCESS_DETACH进程退出时 DLL_THREAD_ATTACH 线程进入时 DLL_THREAD_DETACH 线程退出时 在初始化部分写: DLLProc := @DLLEnterPoint; DllEnterPoint(DLL_PROCESS_ATTACH); 2 如Form上有TdcomConnection组件,就Uses Activex,在初始化时写一句CoInitialize (nil); 3 在退出时一定保证DcomConnection.Connected := False,并且数据集已关闭。否则报地址错。 四全局变量的使用 在widnows 32位程序,两个应用程序的地址空间是相互没有联系的。虽然DLL在内存是一份, 但变量是在各进程的地址空间,因此你不能借助dll的全局变量来达到两个应用程序间的数据 传递,除非你用内存映像文件。 五、其他:调用方式按照标准的Windows调用方式. 六、关于参数传递 •Delphi程序之间调用DLL,如果要用String类型的话,要在引用的单元加上ShareMem 单元。 •如果Delphi写的DLL供其他开发工具使用的话,不要使用String类型,用PAnsiChar类型。 尽量使用标准DLL接口。指的是传递的参数类型及函数返回类型不能是Delphi特有的, 比如string(AnsiString),以及动态数组和含有这些类型成员的复合类型(如记录),也不 能是包含有这些类型成员数据成员的对象类型,以避免可能的错误。如果使用了string类型或 动态数组类型,且调用方不是Delphi程序,则基本上会报错。如果调用方是Delphi调用方或 被调用方没有在工程文件的第一包含单元不是ShareMem,也可能会出错。 七、关于回调Funciton 你可以把Callback函数看作是一种特殊的消息响应函数,一般来说我们不会自己调用这种函数, 而是有某些系统函数调用,而且不需要向后传递消息。 只要象C/C++这样支持函数指针的语言都 有回调函数的概念,它实际上是向被调用函数传一个你的函数地址,然后被调用函数向通过你传 入的函数地址来调用你的函数 。 以上是结构化回调,到高级语言Object Pascal、C++回调函数并没有退出,反而得到延伸与 扩展,在面向对像的回调,其实是指面向对像类对像的事件,事件就是原始的回调函数。面 向对像, 将回调函数定义成事件过程,在程序引用对像时,若指定了对像的过程事件后,那么在 要进行事件触发的地方检查事件过程是否分配,如果分的就执行事,也就是执行了回调函数

5,388

社区成员

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

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