讨论几个Object Pascal的简单问题,有请基本功很扎实的朋友 (100分)

A

arcore

Unregistered / Unconfirmed
GUEST, unregistred user!
有结构如下:
PDataItem = ^TDataItem;
TDataItem = Record
DataID : integer;
DataTile : string[50];
DataValue : XXXXXXX;//XXXXX是我要问的问题
end;
现在,小弟定义了一个TList,实例化以后,List中每一个Item都是一个PDataItem。

在TDataItem中,DataValue的取值可能是字符串、字符、整数、浮点数,还有可能是二进制流,小弟的问题如下:
A、小弟想把DataValue的类型定义为String,因为整数与浮点数也可以保存在String中,但小弟想知道,用String来保存二进制数据流可行吗?
B、如果把DataValue的类型定义为PChar,对于这个值,要使用GetMem与FreeMem存取值,小弟想知道的是,在执行New(PDataItem)以后,PDataItem.DataValue是否为nil?或是说说怎么判断PDataItem.DataValue中有值呢?因为在存值的时候用到了GetMem,所以,在TList实例Free之前要对每一个Item的DataValue进行FreeMem。
C、还有一种方案,就是用“可变记录”。用过“可变记录”的朋友请讲讲相关问题;

谢谢。
 
小弟刚才查了查相关资料,对于上面讲的C中“可变记录”方案是行不通的了。因为“可变记录”的可变部分是要求有占相同内存的数据类型,不符合我的要求。

那大家再谈谈“变体Variant”类型吧。
 
A.可以,string带有长度信息
B.是nil没错
C.你想知道什么?
 
把 datavalue 定义成 无类型指针
 
PDataItem = ^TDataItem;
TDataItem = Record
DataID : integer;
DataTile : string[50];
DataValue :pointer;/TObject
//建议
end;
DataValue :pointer;/TObject;扩展性很大。
如像小弟以前就用过TStringList中的AddObject,我直接把我的Edit的都给了它。然后再取出来用。一直很好。

B.=nil

定义成PChar,要getmen,当然也得要freemem了。
 
PDataItem = ^TDataItem;
TDataItem = Record
DataID : integer;
DataTile : string[50];
DataLen: Int64
//数据长度,读的时候读到这里,然后向后读这个长度就是数据
DataValue : String;//保存的数据
end;
另外字符串可以存储任何东西,只是有些例如#0后面的显示不出来。但是确实是有的
 
DataValue定义为Variant是可以的,Delphi会自动进行数据转换。
 
不要指望任何带有指针性质的变量可以被自动的保存到流中,像Pointer、String、
Variant、TObject、动态数组等等,它们都需要程序员编写专门的读/写代码。老老实实的
写过程吧——最好用一个类将创建、读取、写入都封装起来。
 
Variant很吃內存,也慢,用string很好...
 
小弟再说详细一些吧。因为这个函数有可能要用到DLL中的,所以,不能封装成类的。
==========================================================================
PDataItem = ^TDataItem;
TDataItem = Record
DataID : integer;
DataTile : string[50];
DataValue : PChar;//小弟目前是定义成PChar的。
end;
{再说说DataValue的取值,它的值有可能是Char, String, Integer, array....等等}
另有一DLL,DLL中有一过程,需要操作一array,array的数据类型为TDataItem。

小弟现在是这样做的:
var
List : TList;
DataItem : PDataItem;
begin
List := TList.Create;
try
New(DataItem);
//记录字段赋值;
with DataItem^ do
begin
DataID := 1;
GetMem(DataValue, LenOfDataValue);
DataValue^:= XXXXXXX;
end;
List.add(DataItem);
finally
{在这里对每个DataItem的DataValue进行FreeMem}
List.Free;
end;
end

===============================================================================
但是现在就有问题了
1、我的DataValue有可能是一个二进制数据流,也有可能本身就是一个数组。PChar能作为一个数组吗?所以,我想把DataValue改为Variant类型的。
2、TList不能作为DLL的函数参数的;所以,我想改为array,或是Variant array。
==============================================================================
==============================================================================
总而方之呢,就是这样的:有一个DLL,其中有一个过程ProcdureXXXX,它的参数要求是一个数组或是列表,每一个数组或列表元素都是一个记录,在这个记录中,有一个字段DataValue,字段本身的数据类型是不定的,而且有可能这个字段本身也是一个数组。

不知道大家有没有搞清楚了?现在再谈谈怎么样实现是最佳方案吧。
 
希望能给你一些启发
type
THandleType = (Window,MenuItem);

PWindowInfo = ^TWindowInfo;
TWindowInfo = record
Enabled : Boolean;
IsWindow : Boolean;
Text : String;
ClassName : String;
end;

PMenuItemInfo = ^TMenuItemInfo;
TMenuItemInfo = record
Caption : String;
SubMenu : Boolean;
Index : Integer;
Command : Integer;
end;

PHandleInfo = ^THandleInfo;
THandleInfo = record
HWnd : HWnd;
HandleType : THandleType;
HandleInfo : Pointer;
end;
 
谢谢轻舞肥羊,我前明白你的意思。
其实,我的真正的结构是这样的:
PDataItem = ^TDataItem;
TDataItem = Record
DataID : integer;
DataType : string[2];//表示数据类型,因为这个数据类型与Object Pascal的数据类型不一样,所以不能用TVarType;
DataLen : integer;//DataValue字段的长度;
DataValue : PChar;//小弟目前是定义成PChar的。
end;
这个结构就有像轻舞肥羊上面所说的意思;
 
可不可以这样做?
==========================
PDataItem = ^TDataItem;
TDataItem = Record
DataID : integer;
DataType : string[2];//表示数据类型,因为这个数据类型与Object Pascal的数据类型不一样,所以不能用TVarType;
DataLen : integer;//DataValue字段的长度;
DataValue : Pointer;//定义成无类型指针;
end;

然后:
var
DataArray : array of TDataItem;
begin
//调用DLL中的过程
DllProcedure(DataArray);
end;

再然后,DLL中这样:
procedure DllProcedure(DataArray : pointer);
type
TDataArray = array of TDataItem;
var
A :TDataArray;
i,j:integer;
begin
A:=TDataArray(DataArray);
j:=length(A);
for i := 0 to j-1 do
begin
{do something...}
end;
end



这样可以吗?我是要在Dll中的过程中更改数组的长度的。
 
关于DataValue的定义,上面很多人说得都不错,我觉得基本原理都一样:申请内存,填入数据

照此原理,用TMemoryStream也不错,把各种各样乱七八糟的数据写到流里面。
 
Variant最简单。效率是在解决了问题之后才需要考虑的。
你自己估计一下这个函数会不会每秒被调用1次以上?
如果“是”——架构不对,重新设计吧;如果“否”——使用Variant,它帮你搞定一切。
 
小弟已经决定用Pointer了。

但是还是有一个问题:如果DataValue字段的取值是一个array of ADataType,在Dll过程中要怎么样动态生成这个array呢?
 
很简单,巧用强制类型转换。
type
TADataTypeArr = array of ADataType;

....
DataValue := nil
// 这句必须,除非以前用Setlength分配过
SetLength(TADataTypeArr(DataValue), 15);
 
Another_eYes:谢谢,你的方法不把过程放在DLL中也可以,但是放在DLL中,也不行。

小弟做了一个小测试程序,把代码贴出来,大家看看,一起讨论一下。
开发环境:Win2000 Pro + D7, no serivce pack。
========================================================
Dll代码
========================================================
library dllarray;

{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }

uses
SysUtils,Classes,windows;

type
PDataItem = ^TDataItem;
TDataItem = record
DataID : integer;
DataType : string[2]
//IN - Integer , ST- String, AR - array;
DataValue : Pointer;
end;

PDataArray = ^TDataArray;
TDataArray = array of TDataItem;

PSQDataItem = ^TSQDataItem;
TSQDataItem = record
ItemTag : integer;
ItemLength : integer;
ItemValue : Pointer;
end;

PSQDataArray = ^TSQDataArray;
TSQDataArray = array of TSQDataItem;

procedure TempProc(DataArray: Pointer
var Len: integer);
var
tempAry: PDataArray;
tempLen: integer;
I, J: integer;
tempStr: string;
tempInt: integer;
//SQDataArray : TSQDataArray;
begin
tempAry := DataArray
// 区别处
tempLen := 9;
SetLength(tempAry^, tempLen);
J := 1;
for I := 0 to tempLen - 1 do
begin
tempAry^.DataID := I;
case J of
1 : //Integer Value
begin
tempAry^.DataType := 'IN';
tempInt := 1000 + I;
GetMem(tempAry^.DataValue, SizeOf(Integer));
Integer(tempAry^.DataValue^) := tempInt;
end;
2 : //String Value
begin
tempAry^.DataType := 'ST';
tempStr := 'String Value' + IntToStr(I + 1);
GetMem(tempAry^.DataValue, Length(tempStr));
StrPCopy(tempAry^.DataValue, tempStr);
end;
3 : //array value
begin
tempAry^.DataType := 'AR';
tempAry^.DataValue := nil;
SetLength(TSQDataArray(tempAry^.DataValue), 2);
TSQDataArray(tempAry^.DataValue)[0].ItemTag := 0;
TSQDataArray(tempAry^.DataValue)[0].ItemLength := 10;
TSQDataArray(tempAry^.DataValue)[1].ItemTag := 1;
TSQDataArray(tempAry^.DataValue)[1].ItemLength := 20;
{
tempAry^.DataType := 'AR';
SetLength(SQDataArray, 2);
SQDataArray[0].ItemTag := 0;
SQDataArray[0].ItemLength := 10;
SQDataArray[1].ItemTag := 1;
SQDataArray[1].ItemLength := 20;
GetMem(tempAry^.DataValue, SizeOf(TSQDataItem) * Length(SQDataArray));
ZeroMemory(tempAry^.DataValue, SizeOf(TSQDataItem) * Length(SQDataArray));
TSQDataArray(tempAry^.DataValue^) := Copy(SQDataArray, 0, Length(SQDataArray));
}
end;
end;//case...
Inc(J);
if J > 3 then J := 1;
end;
Len := tempLen;
end;


exports
TempProc;
end.
==========================================================
//调用程序代码
=========================================================
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
PDataItem = ^TDataItem;
TDataItem = record
DataID : integer;
DataType : string[2]
//IN - Integer , ST- String, AR - array;
DataValue: Pointer;
end;

PDataArray = ^TDataArray;
TDataArray = array of TDataItem;

PSQDataItem = ^TSQDataItem;
TSQDataItem = record
ItemTag : integer;
ItemLength : integer;
ItemValue : Pointer;
end;

PSQDataArray = ^TSQDataArray;
TSQDataArray = array of TSQDataItem;

TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

procedure TempProcDll(DataArray: Pointer
var Len: integer);

implementation

{$R *.dfm}

procedure TempProcDll
external 'dllarray.dll' name 'TempProc'


// 无类型指针,可用于 DLL (我猜的)
// 参数 DataArray 前的 var 不是必须

procedure TempProc(DataArray: Pointer
var Len: integer);
var
tempAry: PDataArray;
tempLen: integer;
I, J: integer;
tempStr: string;
tempInt: integer;
//SQDataArray : TSQDataArray;
begin
tempAry := DataArray
// 区别处
tempLen := 9;
SetLength(tempAry^, tempLen);
J := 1;
for I := 0 to tempLen - 1 do
begin
tempAry^.DataID := I;
case J of
1 : //Integer Value
begin
tempAry^.DataType := 'IN';
tempInt := 1000 + I;
GetMem(tempAry^.DataValue, SizeOf(Integer));
Integer(tempAry^.DataValue^) := tempInt;
end;
2 : //String Value
begin
tempAry^.DataType := 'ST';
tempStr := 'String Value' + IntToStr(I + 1);
GetMem(tempAry^.DataValue, Length(tempStr));
StrPCopy(tempAry^.DataValue, tempStr);
end;
3 : //array value
begin
tempAry^.DataType := 'AR';
tempAry^.DataValue := nil;
SetLength(TSQDataArray(tempAry^.DataValue), 2);
TSQDataArray(tempAry^.DataValue)[0].ItemTag := 0;
TSQDataArray(tempAry^.DataValue)[0].ItemLength := 10;
TSQDataArray(tempAry^.DataValue)[1].ItemTag := 1;
TSQDataArray(tempAry^.DataValue)[1].ItemLength := 20;

{SetLength(SQDataArray, 2);
SQDataArray[0].ItemTag := 0;
SQDataArray[0].ItemLength := 10;
SQDataArray[1].ItemTag := 1;
SQDataArray[1].ItemLength := 20;
GetMem(tempAry^.DataValue, SizeOf(TSQDataItem) * Length(SQDataArray));
ZeroMemory(tempAry^.DataValue, SizeOf(TSQDataItem) * Length(SQDataArray));
TSQDataArray(tempAry^.DataValue^) := Copy(SQDataArray);}

end;
end;//case...
Inc(J);
if J > 3 then J := 1;
end;

Len := tempLen;
end;

// 标准的 Delphi 动态数组,带引用计数
// 参数 DataArray 前的 var 必须

procedure TempProc1(var DataArray: TDataArray
var Len: integer);
var
tempAry: TDataArray;
tempLen: integer;
I, J : integer;
tempStr: string;
tempInt: integer;
SQDataArray : TSQDataArray;
begin
tempLen := 9;
SetLength(tempAry, tempLen);
J := 1;
for I := 0 to tempLen - 1 do
begin
tempAry.DataID := I;
case J of
1 : //Integer Value
begin
tempAry.DataType := 'IN';
tempInt := 1000 + I;
GetMem(tempAry.DataValue, SizeOf(Integer));
Integer(tempAry.DataValue^) := tempInt;
end;
2 : //String Value
begin
tempAry.DataType := 'ST';
tempStr := 'String Value' + IntToStr(I + 1);
GetMem(tempAry.DataValue, Length(tempStr));
StrPCopy(tempAry.DataValue, tempStr);
end;
3 : //array value
begin
tempAry.DataType := 'AR';
SetLength(SQDataArray, 2);
SQDataArray[0].ItemTag := 0;
SQDataArray[0].ItemLength := 10;
SQDataArray[1].ItemTag := 1;
SQDataArray[1].ItemLength := 20;
GetMem(tempAry.DataValue, SizeOf(SQDataArray));
TSQDataArray(tempAry.DataValue^) := Copy(SQDataArray);
end;
end;//case...
Inc(J)

if J > 3 then J := 1;

end;

DataArray := tempAry
//区别处
Len := tempLen;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
tempAry : TDataArray;
ArrayLen: integer;
I, J : integer;
P : Pointer;
strMsg : string;
SQDataArray : TSQDataArray;
begin
ArrayLen := 0;

SetLength(tempAry, 0);
P := Pointer(@tempAry);
//本单元过程
//TempProc(P, ArrayLen);

//调用Dll过程
try
TempProcDll(P, ArrayLen);
except
ShowMessage('Dll error!');
Exit;
end;
strMsg := 'Pointer parameter, array length: ' + IntToStr(Length(tempAry)) + #13 + #10;
for I := Low(tempAry) to High(tempAry) do
begin
strMsg := strMsg + #13 + #10;
if tempAry.DataType = 'IN' then
begin
strMsg := strMsg +'The ' + IntToStr(I + 1) + ' element, Integer Data, Value:' + IntToStr(Integer(tempAry.DataValue^));
end;

if tempAry.DataType = 'ST' then
begin
strMsg := strMsg +'The ' + IntToStr(I + 1) + ' element, string Data, Value:' + PChar(tempAry.DataValue);
end;
SetLength(SQDataArray, 0);
if tempAry.DataType = 'AR' then
begin
//ShowMessage(IntToStr(Length(TSQDataArray(tempAry.DataValue))));
//SQDataArray := tempAry.DataValue;
SQDataArray := TSQDataArray(tempAry.DataValue);
//ShowMessage(IntToStr(Length(SQDataArray)));
strMsg := strMsg + 'The ' + IntToStr(I + 1) + ' element, array Data, Value:' + #13 + #10;
for J := Low(SQDataArray) to High(SQDataArray) do
begin
strMsg := strMsg + 'Array element ' + IntToStr(J + 1) + ', ItemTag:' + IntToStr(SQDataArray[J].ItemTag) + ', ItemLength:' + IntToStr(SQDataArray[J].ItemLength) + #13 + #10;
if SQDataArray[J].ItemValue <> nil then
FreeMem(SQDataArray[J].ItemValue);
end;
end;
// 释放 tempAry.DataValue
if tempAry.DataType = 'AR' then
SetLength(SQDataArray, 0)
else
FreeMem(tempAry.DataValue);
end;

ShowMessage(strMsg);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
tempAry : TDataArray;
ArrayLen: integer;
I, J : integer;
strMsg : string;
SQDataArray : PSQDataArray;
begin
ArrayLen := 0;

SetLength(tempAry, 0);
TempProc1(tempAry, ArrayLen)
// TDataArray
strMsg := 'Pointer parameter, array length: ' + IntToStr(Length(tempAry));
for I := Low(tempAry) to High(tempAry) do
begin
strMsg := strMsg + #13 + #10;
if tempAry.DataType = 'IN' then
strMsg := strMsg + 'The ' + IntToStr(I + 1) + ' element, Integer Data, Value:' + IntToStr(Integer(tempAry.DataValue^));

if tempAry.DataType = 'ST' then
strMsg := strMsg + 'The ' + IntToStr(I + 1) + ' element, string Data, Value:' + PChar(tempAry.DataValue);

if tempAry.DataType = 'AR' then
begin
SQDataArray := tempAry.DataValue;
strMsg := strMsg + 'The ' + IntToStr(I + 1) + ' element, array Data, Value:' + #13 + #10;
for J := Low(SQDataArray^) to High(SQDataArray^) do
begin
strMsg := strMsg + 'Array element ' + IntToStr(J + 1) + ', ItemTag:' + IntToStr(SQDataArray^[J].ItemTag) + ', ItemLength:' + IntToStr(SQDataArray^[J].ItemLength) + #13 + #10;
if SQDataArray^[J].ItemValue <> nil then
FreeMem(SQDataArray^[J].ItemValue);
end;
end;
// 释放 tempAry
FreeMem(tempAry.DataValue);
end;
ShowMessage(strMsg);
end;

end.
====================================================================
说明:
1、上面的代码,在传过程参数array时都是用的一个pointer,并传入一个数组长度(我这个Demo中并未用到数组长度)。
2、照朋友的建议,我的Record中的DataValue字段是用的Pointer。另一个可以嵌套的array的元素数据类型,也是一个record,其字段ItemValue也是用的Pointer。
3、在那个过程中,把DataValue字段生成一个Array也用了两种方式,一种是如上楼所说的类型强制转换,一种是GetMem方式。
4、在调入过程的时候,处理DataValue为数组值时也用了两种方式,一种是TSQDataArray,一种是PSQDataArray。PSQDataArray的处理方式如下:
===================
procedure TForm1.Button1Click(Sender: TObject);
var
tempAry : TDataArray;
ArrayLen: integer;
I, J : integer;
P : Pointer;
strMsg : string;
SQDataArray : PSQDataArray;
begin
ArrayLen := 0;

SetLength(tempAry, 0);
P := Pointer(@tempAry);
//本单元过程
//TempProc(P, ArrayLen);

//调用Dll过程
try
TempProcDll(P, ArrayLen);
except
ShowMessage('Dll error!');
Exit;
end;
strMsg := 'Pointer parameter, array length: ' + IntToStr(Length(tempAry)) + #13 + #10;
for I := Low(tempAry) to High(tempAry) do
begin
strMsg := strMsg + #13 + #10;
if tempAry.DataType = 'IN' then
begin
strMsg := strMsg +'The ' + IntToStr(I + 1) + ' element, Integer Data, Value:' + IntToStr(Integer(tempAry.DataValue^));
end;

if tempAry.DataType = 'ST' then
begin
strMsg := strMsg +'The ' + IntToStr(I + 1) + ' element, string Data, Value:' + PChar(tempAry.DataValue);
end;
//SetLength(SQDataArray, 0);
if tempAry.DataType = 'AR' then
begin
SQDataArray := tempAry.DataValue;
//SQDataArray := TSQDataArray(tempAry.DataValue);
//ShowMessage(IntToStr(Length(SQDataArray)));
strMsg := strMsg + 'The ' + IntToStr(I + 1) + ' element, array Data, Value:' + #13 + #10;
for J := Low(SQDataArray^) to High(SQDataArray^) do
begin
strMsg := strMsg + 'Array element ' + IntToStr(J + 1) + ', ItemTag:' + IntToStr(SQDataArray^[J].ItemTag) + ', ItemLength:' + IntToStr(SQDataArray^[J].ItemLength) + #13 + #10;
if SQDataArray^[J].ItemValue <> nil then
FreeMem(SQDataArray^[J].ItemValue);
end;
end;
// 释放 tempAry.DataValue
//if tempAry.DataType = 'AR' then
// SetLength(SQDataArray, 0)
//else
FreeMem(tempAry.DataValue);
end;

ShowMessage(strMsg);
end;
===============

可以看出,上面的代码用了两种方式:本单元中的Procdure和DLL中的Procedure。两个Procedure中的代码一样的。

我的问题:调用本单元中的Porcedure不会出错,调用DLL中的在第二次或是以上就要出错,出错信息是指针错误;

只要第一个数组的数组元素的DataValue不是'AR'(即Array),就没有问题;所以,请大家主要想一想在Record的DataValue字段中,套一个动态数组时的情况(在DLL中)。

谢谢。
 
代码贴出来,看起来比较麻烦,但其实很简单。有兴趣的朋友,可以COPY出来,在Delphi中运行一下,就可以看出效果了。
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
D
回复
0
查看
919
DelphiTeacher的专栏
D
顶部