2017-10-17 119 views
0

因此,我有一个使用WM_COPYDATA来允许应用程序进行通信的类。如何在应用程序之间发送包含字符串的记录

type 
    TMyRec = record 
    Name: string[255]; // I want just string 
    Age: integer; 
    Birthday: TDateTime; 
    end; 

function TAppCommunication.SendRecord(const ARecordType: ShortString; const ARecordToSend: Pointer; ARecordSize: Integer): Boolean; 
var 
    _Stream: TMemoryStream; 
begin 
    _Stream := TMemoryStream.Create; 
    try 
    _Stream.WriteBuffer(ARecordType, 1 + Length(ARecordType)); 
    _Stream.WriteBuffer(ARecordToSend^, ARecordSize); 
    _Stream.Position := 0; 
    Result := SendStreamData(_Stream, TCopyDataType.cdtRecord); 
    finally 
    FreeAndNil(_Stream); 
    end; 
end; 

function TAppCommunication.SendStreamData(const AStream: TMemoryStream; 
    const ADataType: TCopyDataType): Boolean; 
var 
    _CopyDataStruct: TCopyDataStruct; 
begin 
    Result := False; 

    if AStream.Size = 0 then 
    Exit; 

    _CopyDataStruct.dwData := integer(ADataType); 
    _CopyDataStruct.cbData := AStream.Size; 
    _CopyDataStruct.lpData := AStream.Memory; 

    Result := SendData(_CopyDataStruct); 
end; 

function TAppCommunication.SendData(const ADataToSend: TCopyDataStruct) 
    : Boolean; 
var 
    _SendResponse: integer; 
    _ReceiverHandle: THandle; 
begin 
    Result := False; 

    _ReceiverHandle := GetRemoteReceiverHandle; 
    if (_ReceiverHandle = 0) then 
    Exit; 

    _SendResponse := SendMessage(_ReceiverHandle, WM_COPYDATA, 
    WPARAM(FLocalReceiverForm.Handle), LPARAM(@ADataToSend)); 

    Result := _SendResponse <> 0; 
end; 

Sender应用程序:

procedure TSenderMainForm.BitBtn1Click(Sender: TObject); 
var 
    _AppCommunication: TAppCommunication; 
    _ms: TMemoryStream; 
    _Rec: TMyRec; 
    _Record: TAttrData; 
begin 
    _AppCommunication := TAppCommunication.Create('LocalReceiverName', OnAppMessageReceived); 
    _ms := TMemoryStream.Create; 
    try 
    _AppCommunication.SetRemoteReceiverName('LocalReceiverNameServer'); 
    _AppCommunication.SendString('ąčęėįšųūž123'); 
    _AppCommunication.SendInteger(998); 
    _AppCommunication.SendDouble(0.95); 

    _Rec.Name := 'Edijs'; 
    _Rec.Age := 29; 
    _Rec.Birthday := EncodeDate(1988, 10, 06); 
    _Record.Len := 1988; 
    _AppCommunication.SendRecord(TTypeInfo(System.TypeInfo(TMyRec)^).Name, @_Rec, SizeOf(_Rec)); 
    finally 
    FreeAndNil(_ms); 
    FreeAndNil(_AppCommunication); 
    end; 
end; 

接收机应用程式:

procedure TReceiverMainForm.OnAppMessageReceived(const ASender 
    : TPair<HWND, string>; const AReceivedData: TCopyDataStruct; 
    var AResult: integer); 
var 
    _MyRec: TMyRec; 
    _RecType: ShortString; 
    _RecData: Pointer; 
begin 
    ... 
    else 
    begin 
    if (AReceivedData.dwData) = Ord(TCopyDataType.cdtRecord) then 
    begin 
    _RecType := PShortString(AReceivedData.lpData)^; 
     _RecData := PByte(AReceivedData.lpData)+1+Length(_RecType); 
     if (_RecType = TTypeInfo(System.TypeInfo(TMyRec)^).Name) then 
     begin 
     _MyRec := TMyRec(_RecData^); 
     ShowMessage(_MyRec.Name + ', Age: ' + IntToStr(_MyRec.Age) + ', birthday: ' + 
      DateToStr(_MyRec.Birthday)); 
     end; 
    end; 
    AResult := -1; 
    end; 
end; 

的问题是,碰撞,当我在TMyRec改变Name: string[255];Name: string;发生。我如何克服这一点?我不想编辑所有的记录来将字符串更改为别的东西,我想要一个函数发送所有类型的记录(只要我的想法不会包含对象)。

EDITED: 二手答案由雷米提供,并且取得了一定的调整,所以我会通过能够只使用一个SendRecord函数发送任何类型的记录:

function TAppCommunication.SendRecord(const ARecordToSend, ARecordTypInfo: Pointer): Boolean; 
var 
    _Stream: TMemoryStream; 
    _RType: TRTTIType; 
    _RFields: TArray<TRttiField>; 
    i: Integer; 
begin 
    _Stream := TMemoryStream.Create; 
    try 
    _RType := TRTTIContext.Create.GetType(ARecordTypInfo); 

    _Stream.WriteString(_RType.ToString); 
    _RFields := _RType.GetFields; 
    for i := 0 to High(_RFields) do 
    begin 
     if _RFields[i].FieldType.TypeKind = TTypeKind.tkUString then 
     _Stream.WriteString(_RFields[i].GetValue(ARecordToSend).ToString) 
     else if _RFields[i].FieldType.TypeKind = TTypeKind.tkInteger then 
     _Stream.WriteInteger(_RFields[i].GetValue(ARecordToSend).AsType<integer>) 
     else if _RFields[i].FieldType.TypeKind = TTypeKind.tkFloat then 
     _Stream.WriteDouble(_RFields[i].GetValue(ARecordToSend).AsType<Double>) 
    end; 
    _Stream.Position := 0; 
    Result := SendStreamData(_Stream, TCopyDataType.cdtRecord); 
    finally 
    FreeAndNil(_Stream); 
    end; 
end; 

发件人:

_AppCommunication.SendRecord(@_Rec, System.TypeInfo(TMyRec)); 
+0

'if(_RecType = TTypeInfo(System.TypeInfo(TMyRec)^)。Name)then'完全是过度杀伤。正如我在前面的回答中所示,只需使用if(_RecType ='TMyRec')then'。 –

+0

这是为了防止某人稍后重新命名记录。相信我,这样的事情发生,只有客户会发现它发生了。 –

+0

你不能重命名通过通信发送的记录而不会破坏协议 –

回答

1

A ShortString具有最大256字节的固定大小(1字节长+最多255个AnsiChar s),因此很容易嵌入记录并按原样发送。

另一方面,String是指向动态分配内存的指针,用于Char的数组。所以,它需要更多的工作才能来回序列化。

要做你在问什么,你不能简单地用String替换ShortString而不改变其他所有的东西来解决这个差别。

您已经有了基本框架发送变长字符串(发送数据之前发送的长度),这样你就可以就此展开处理string值,例如:

type 
    TMyRec = record 
    Name: string; 
    Age: integer; 
    Birthday: TDateTime; 
    end; 

    TStreamHelper = class helper for TStream 
    public 
    function ReadInteger: Integer; 
    function ReadDouble: Double; 
    function ReadString: String; 
    ... 
    procedure WriteInteger(Value: Integer); 
    procedure WriteDouble(Strm: Value: Double); 
    procedure WriteString(const Value: String); 
    end; 

function TStreamHelper.ReadInteger: Integer; 
begin 
    Self.ReadBuffer(Result, SizeOf(Integer)); 
end; 

function TStreamHelper.ReadDouble: Double; 
begin 
    Self.ReadBuffer(Result, SizeOf(Double)); 
end; 

function TStreamHelper.ReadString: String; 
var 
    _Bytes: TBytes; 
    _Len: Integer; 
begin 
    _Len := ReadInteger; 
    SetLength(_Bytes, _Len); 
    Self.ReadBuffer(PByte(_Bytes)^, _Len); 
    Result := TEncoding.UTF8.GetString(_Bytes); 
end; 

... 

procedure TStreamHelper.WriteInteger(Value: Integer); 
begin 
    Self.WriteBuffer(Value, SizeOf(Value)); 
end; 

procedure TStreamHelper.WriteDouble(Value: Double); 
begin 
    Self.WriteBuffer(Value, SizeOf(Value)); 
end; 

procedure TStreamHelper.WriteString(const Value: String); 
var 
    _Bytes: TBytes; 
    _Len: Integer; 
begin 
    _Bytes := TEncoding.UTF8.GetBytes(Value); 
    _Len := Length(_Bytes); 
    WriteInteger(_Len); 
    Self.WriteBuffer(PByte(_Bytes)^, _Len); 
end; 

function TAppCommunication.SendRecord(const ARecord: TMyRec): Boolean; 
var 
    _Stream: TMemoryStream; 
begin 
    _Stream := TMemoryStream.Create; 
    try 
    _Stream.WriteString('TMyRec'); 
    _Stream.WriteString(ARecord.Name); 
    _Stream.WriteInteger(ARecord.Age); 
    _Stream.WriteDouble(ARecord.Birthday); 
    _Stream.Position := 0; 
    Result := SendStreamData(_Stream, TCopyDataType.cdtRecord); 
    finally 
    FreeAndNil(_Stream); 
    end; 
end; 

// more overloads of SendRecord() 
// for other kinds of records as needed... 

procedure TSenderMainForm.BitBtn1Click(Sender: TObject); 
var 
    ... 
    _Rec: TMyRec; 
begin 
    ... 
    _Rec.Name := 'Edijs'; 
    _Rec.Age := 29; 
    _Rec.Birthday := EncodeDate(1988, 10, 06); 
    _AppCommunication.SendRecord(_Rec); 
    ... 
end; 

type 
    TReadOnlyMemoryStream = class(TCustomMemoryStream) 
    public 
    constructor Create(APtr: Pointer; ASize: NativeInt); 
    function Write(const Buffer; Count: Longint): Longint; override; 
    end; 

constructor TReadOnlyMemoryStream.Create(APtr: Pointer; ASize: NativeInt); 
begin 
    inherited Create; 
    SetPointer(APtr, ASize); 
end; 

function TReadOnlyMemoryStream.Write(const Buffer; Count: Longint): Longint; 
begin 
    Result := 0; 
end; 

procedure TReceiverMainForm.OnAppMessageReceived(const ASender : TPair<HWND, string>; const AReceivedData: TCopyDataStruct; var AResult: integer); 
var 
    ... 
    _Stream: TReadOnlyMemoryStream; 
    _MyRec: TMyRec; 
    _RecType: String; 
begin 
    ... 
    else 
    begin 
    if (AReceivedData.dwData = Ord(TCopyDataType.cdtRecord)) then 
    begin 
     _Stream := TReadOnlyMemoryStream(AReceivedData.lpData, AReceivedData.cbData); 
     try 
     _RecType := _Stream.ReadString; 
     if (_RecType = 'TMyRec') then 
     begin 
      _MyRec.Name := _Stream.ReadString; 
      _MyRec.Age := _Stream.ReadInteger; 
      _MyRec.Birthday := _Stream.ReadDouble; 
      ShowMessage(_MyRec.Name + ', Age: ' + IntToStr(_MyRec.Age) + ', birthday: ' + DateToStr(_MyRec.Birthday)); 
     end; 
     finally 
     _Stream.Free; 
     end; 
    end; 
    AResult := -1; 
    end; 
end; 
+0

SendRecord应该可以处理任何类型的记录,但似乎我不得不看看。也许序列化? –

+0

@EdijsKolesnikovičs是的,这是序列化的基础知识。将结构化/动态数据转换为平面格式进行传输,然后将其转换回来。单个函数处理多种类型是很困难的,特别是在处理动态数据时。但是有办法做到这一点,即使是专用的库,基于RTTI的方法(使用扩展RTTI,而不是传统的RTTI),如果您能承受复杂性和开销。就个人而言,我更喜欢更简单的方法 –

相关问题