2017-10-17 2 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; 

보낸 응용 프로그램 :

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; 

문제가 충돌 내가 TMyRecName: string;Name: string[255];을 변경할 때 발생합니다. 어떻게 이것을 극복 할 수 있습니까? 문자열을 다른 것으로 변경하기 위해 모든 레코드를 편집하고 싶지 않고 모든 종류의 레코드를 보낼 수있는 한 가지 기능을 원합니다. (내 아이디어가 객체를 포함하지 않을 경우). 사용 대답은 레미 제공하고 나는 단지 하나의 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) ^). 이름) then'은 완전히 과잉입니다. 이전 답변에서 보았 듯이 대신'if (_RecType = 'TMyRec') then'을 사용하십시오. –

+0

나중에 누군가가 레코드의 이름을 바꿀 경우를 대비하여. 저를 믿으십시오, 그런 일은 일어나고 클라이언트 만 그것이 일어난 것을 알아낼 것입니다. –

+0

프로토콜을 깨지 않고 통신을 통해 전송 된 레코드의 이름을 바꿀 수는 없습니다. –

답변

1

ShortString은 최대 256 바이트의 고정 크기 (1 바이트 길이 + 최대까지)를 갖기 때문에 레코드에 삽입하고있는 그대로 보내는 것이 쉽습니다.

StringChar 어레이에 대해 동적으로 할당 된 메모리에 대한 포인터입니다. 따라서 앞뒤로 직렬화하기 위해서는 좀 더 많은 작업이 필요합니다.

묻고있는 것을 수행하려면를 입력하고 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 사용) 등을 수행 할 수있는 방법이 있습니다. 개인적으로, 나는 더 간단한 접근법을 선호한다. –

관련 문제