dll中的函数要返回pchar怎么做?(100分)

  • 主题发起人 主题发起人 FreeAndNil
  • 开始时间 开始时间
F

FreeAndNil

Unregistered / Unconfirmed
GUEST, unregistred user!
dll中导出的函数返回值为pchar,应该在dll中分配内存,但是在哪里释放?在调用处释放要报地址错误,怎么办?
dll的函数中(示意):
function getvalue():pchar;
begin
Result := AllocMem(100);
end;

哪里释放?

顺便再问三个问题:
1、AllocMem和GetMem有什么不同?
2、都是用FreeMem释放吗?
3、FreeMem的Size参数是干嘛的?省略了FreeMem知道要释放多少吗?比如申请了1000,只用了100?

谢谢。

相关补充问题:
在dll代码AllocMem的内存,在exe代码中FreeMem就报错,内存实际是没有释放的--怎么解决?
 
忘记写调用处代码了:

调用dll的exe代码(示意):

var
a:pchar;
begin
a := getvalue;
freemem(a);//到这里还不报地址错误
end;//离开过程就报地址错误
 
procedure getvalue(value:pchar;BufferLen :integer);
begin
StrCpy(...);
end;


调用者负责分配释放内存,原则上不在dll中分配内存

1.没什么不同,GetMe封装一下
2.可以
3.手头没帮助,看一下就知道了
 
也就是说,dll中的函数不能返回pchar喽?
 
PChar是个指针类型! 没有返回不返回的啊! 只要你传进去了(没有释放). 就可以使用的.
 
不是不能,只是不推荐返回pchar
 
不推荐是什么意思?可以用吗?我现在的问题是:在dll代码AllocMem的内存,在exe代码中FreeMem就报错,为什么?

申请内存的负责释放这个道理我知道,但是作为需要返回指针的函数,怎么释放指针返回值(返回值不能在外部创建)?如果必须是函数,必须要返回pchar,该怎么做?

我看了些windows单元的api申明,也有返回指针的api,它们是怎么释放的?
 
windows好像都是调用两次,第一次获得结果长度,第二次写结果,类似于multibytetowidechar。
不行的话,也可以直接用globalalloc/globalfree/heapalloc/heapfree分配。
 
帮我解决一下这个问题吧:在dll代码AllocMem的内存,在exe代码中FreeMem就报错,内存实际是没有释放的。
 
====================================
帮我解决一下这个问题吧:在dll代码AllocMem的内存,在exe代码中FreeMem就报错,内存实际是没有释放的。
====================================
常识性错误,这种内存分配行为和释放行为不在同一模块里面的程序设计风格是被明令禁止的。建议楼主去看看Delphi的帮助,搜索一下“Libraries and packages”这个主题,那里面对此有详细的说明。

DLL函数返回指针和函数动态分配/释放内存是两码事,不能混为一谈。不可否认有的DLL函数的确会返回指针,但并不表示该函数里面一定会动态分配内存。事实上,DLL函数只是负责数据处理(比如转换、复制、统计),但待处理数据具体是存储在哪里的,是怎么存储进去的,它是不需要关心的。待处理数据的准备(存储空间的分配、数据载入)和清理(存储空间释放)这些工作都是由调用DLL的那个程序负责的。DLL函数内部当然也可以动态分配内存,但这个动态分配的内存一定要由这个函数或同一个DLL里面的其他函数释放掉,不能让调用DLL的程序来释放。这是DLL编程的一个通用的铁律和常识,并不是Delphi所独有的,所有编程语言在编写DLL时都要遵守这个守则,因为这涉及到的是Windows内存共享的问题。楼主既然知道“谁调用谁释放”的道理,怎么还会有“但是作为需要返回指针的函数,怎么释放指针返回值(返回值不能在外部创建)?如果必须是函数,必须要返回pchar,该怎么做?”这样的疑问,你这种做法是明显违法上述DLL编程守则的--因为DLL和调用它的程序是两个不同的进程,一个进程(DLL)分配的内存怎么能让另一个进程(主程序)帮你释放?

返回值怎么会不能在外部创建?这只是你没有转变好思想罢了,如果用编写常规的同一模块内的函数的思想来编写DLL函数的话,当然会觉得把函数处理结果所需的存储空间交由调用这个函数的外部程序来分配是“不可理喻”的一件事情--但事实上,这就是DLL函数的“标准行为方式”。我举个简单的例子,假设要在DLL里面写一个过滤掉字符串里面所有重复字符的函数(为了简化问题,字符仅限于ASCII集合),函数的输入是一个待处理的源字符串,可能包含重复的字符;输出是一个处理结果字符串,包含源字符串中所有出现过的字符;源串中重复出现过的字符在目标串中只出现一次。

按照楼主的理解,函数处理结果(即目标串)所需的存储空间应该是由DLL函数分配的,然后函数在处理完成后把指向目标串的指针作为函数返回值返回给调用程序。如果你写的是和主程序在同一模块的非DLL函数的话,这么理解是没有任何问题的,但写在DLL里面的话就不能这么来理解了。因为DLL和主程序不在同一模块,把DLL分配的内存交由主程序去释放是铁定会造成“非法内存访问”错误的。所以必须对函数的设计作一定的修改,以符合“谁分配谁释放”的原则。下面是我根据上面假设的这个处理需求所写的一个DLL形式的函数--

funciton DelReduChar(Source,Dest:PChar):integer; stdcall;
var
i,j:integer;
ExistChars:set of char;
begin
if Source<>nil then
begin
ExistChars:=[];
j:=0;
for i:=0 to Length(Source)-1 do
if not (Source in ExistChars) then
begin
Include(ExistChars,Source);
if Dest<>nil then
Dest[j]:=Source;
inc(j)
end;
Result:=j
end
else
Result:=0
end;

这就是数据处理类型DLL函数基本框架--即函数本身不进行任何为处理结果准备的存储空间分配动作,而是通过一个指针参数让主调程序自己提供存放处理结果的缓冲区的地址。函数根据这个地址把处理结果输出到主程序指定的缓冲区去,并通过函数返回值返回处理结果实际所需的存储空间的大小。也就是说,在DLL函数里面,如果处理结果所需的存储空间长度是不定的,那么就不能由函数自己分配内存,并把处理结果的地址返回给主程序的方式。而应该采用一边进行处理,一边根据主程序是否已经提供了结果缓冲区地址决定是否输出处理结果,处理完成后把存放最终结果所需的存储空间的容量返回给主程序的方式。

因此,主程序在调用上面这个实例函数时,就应该是这样的--
...
var
SourceStr,DestStr:string;
Size:integer;
...
Size:=DelReduChar(Pointer(SourceStr),nil);
// 第一步:用空目标指针第一次调用函数,探测处理结果所需的内存
if Size>0 then
begin
SetLength(DestStr,Size); // 第二步:为处理结果分配内存

// 第三步:用指向缓冲区的指针第二次调用函数,得到处理结果
if DelReduChar(Pointer(SourceStr),Pointer(DestStr))=Size then
showmessage(DestStr)
end;
...

也就是说,动态数据处理结果DLL函数的标准调用过程是这样的:首先用空目标指针探测到处理结果所需的内存大小,然后分配内存,最后用指向缓冲区的指针再次调用函数得到处理结果。就正如前面sed朋友所说的那样“需要调用两次”。用这种思维方式来编写和调用DLL函数,就不会出现在DLL里面分配,在DLL外面释放的这种“需要”了,完全符合“谁分配谁释放”的原则。顺带说一下,上面我举的这个例子程序没有给出释放内存的代码,原因是string的内存是动态管理的,当DestStr所在的那个模块结束的时候,内存管理模块会自动释放掉它占用的内存。所以我没有必要在自己的程序里面写上“SetLength(DestStr,0)”之类的“废话”。
 
我赞成三楼的意见,由调用责负分配及释放
 
看看我的問題, 有做過的朋友提供demo, 謝謝!
http://www.delphibbs.com/delphibbs/DispQ.asp?LID=3305823
同時to FreeAndNil, 不要光說, 最好demo和理論一起寫, 這樣更有說服力, 謝謝!
 
谁申请的谁释放,DLL里面申请的就要在DLL中释放,不能在EXE中释放
 
谁申请的谁释放,DLL里面申请的就要在DLL中释放,不能在EXE中释放
说到这里就扯远了.就谈到windows的内存管理和进程间通信了. 在win32
下每个进程都有4GB大的虚拟内存,并不是物理内存. 所以在dll返回的指针跟在
调用EXE指针所指到的物理内存就不是相同的. 比如A是EXE是调用dll的 进程,
B是DLL的进程. 那么在A进程的地址是$00005058 跟B进程的地址 $00005058 是相同的,这只不过是虚拟地址相同而已. 但实际提交的物理地址确不相同的. 也可能B进程的地址 $00005058 已经提交到物理地址了.但A进程的A的地址是$00005058 还没有提交.但你却要访问A进程的这个地址,那么不就访问违规了! 这只是举个例子!想要了解更多请多看操作系统的内存管理吧!
 
简单的问题复杂化了, 看一下windows api 里面如何返回 pchar 就很清楚了
 
比如 取计算机名称
BOOL GetComputerName(

LPTSTR lpBuffer, // address of name buffer
LPDWORD nSize // address of size of name buffer
);
微软为什么不写成:
GetComputerName():PChar ?

大家想一下应该就清楚了
 
其实是楼主没有想啊! 早说过是指针了.
 
我一直认为dll并非进程,而是和调用程序同一个进程,难道我一直错了?不解。

韦剑及楼上几位的回答让我获益蜚浅,GetComputerName之类的方式我也是知道的,我之所以有这样的想法,是因为要写个dll,给用户调用,用户要求只要一个无参数的函数,返回一字符串,所以才引发了这个问题,是否用户的要求的确是无非实现的?
 
//dll
library Project1;

uses
SysUtils,
Classes;

{$R *.res}

var
sToday : array[0..10] of Char;

function GetDate() : PChar; stdcall;
begin
StrPCopy(sToday, FormatDateTime('YYYY-MM-DD', Date));
Result := sToday;
end;

exports
GetDate;

begin
end.

//exe
procedure TForm1.Button3Click(Sender: TObject);
var
sToday : array[0..10] of Char;
begin
StrCopy(sToday, GetDate());
Edit1.Text := TButton(Sender).Caption + ' : ' + sToday ;
end;
 
楼上朋友的程序对楼主而言帮助不大,因为函数的处理结果所占用的内存大小是固定的,无所谓动态分配。而楼主所要讨论的是动态内存分配的问题。
 
后退
顶部