2

When i send to client a string (tkString), client tells me (inside WMClientRecv) access violation when trying to rebuild TValue, why ? -_-" with other typed data it works well...

for example, Integer (tkEnum)

here's server code (sends invoke data to client)

procedure TServerClass.InvokeMethod(const InvokableMethod: TInvokeableMethod; const MethodArg: TValue);
var
  InvokeRec : packed record
    Method: Array[0..19] of Char;
    ArgRawSize: Integer;
    ArgTypeInf: TTypeInfo;
    ArgRawData: Array[0..255] of Byte; 
  end;
begin
  // copy method name to record
  lstrcpy(InvokeRec.Method, PChar(InvokableMethod));

  // copy arg array to record
  InvokeRec.ArgRawSize := MethodArg.DataSize;

  if (MethodArg.DataSize <> 0) then
  begin
    MethodArg.ExtractRawData(PByte(@InvokeRec.ArgRawData[0]));

    InvokeRec.ArgTypeInf := MethodArg.TypeInfo^;
  end; 

  // send record
  ServerSocket.Socket.Connections[idxSocket].SendBuf(PByte(@InvokeRec)^, SizeOf(InvokeRec));
end;

Client reads it as:

procedure TClientClass.ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
var
  Buffer: PByte;
  BufLen: Integer;

  InvokeRec: TInvokeRec;
begin
  BufLen := Socket.ReceiveLength;

  Memo1.Lines.Append(Format('%d: Received %d bytes', [Memo1.Lines.Count, BufLen]));

  // dump keep-alive from recv buffer
  if (BufLen = 64) then
  begin
    GetMem(Buffer, BufLen);
    try
      Socket.ReceiveBuf(Buffer^, BufLen);
    finally
      FreeMem(Buffer, BufLen);
    end;
    Exit;
  end;

  Socket.ReceiveBuf(PByte(@InvokeRec)^, BufLen);
  SendMessage(Handle, WM_ClientRecv, 0, LPARAM(@InvokeRec));
end;

Routine of WMClientRecv:

procedure TClientClass.WMClientRecv(var Msg: TMessage);
var
  // we send to server
  ReadRec: TSharedRec;

  // we recv from server
  InvokeRecPtr: PInvokeRec;

  Value: TValue;
begin
  InvokeRecPtr := PInvokeRec(Msg.LParam);

  case InvokeRecPtr.ArgRawSize of
  0:
    Value.Empty;
  else
    TValue.Make(PByte(@InvokeRecPtr.ArgRawData)^, PTypeInfo(@InvokeRecPtr.ArgTypeInf), Value);
  end;

  InvokableSubclass.ExecMethod(InvokeRecPtr.Method, Value);
end;

And ExceMethod:

procedure TInvokableSubclass.ExecMethod(const vmName: string; const Arg0: TValue);
var
  LContext: TRttiContext;
begin
  //
  FSendMode := TextMode;

  //
  if (Arg0.DataSize = 0) then
  begin
    LContext.GetType(TInvokableSubclass).GetMethod(vmName).Invoke(Self, []);
    Exit;
  end;

  LContext.GetType(TInvokableSubclass).GetMethod(vmName).Invoke(Self, Arg0);
end;

2 Answers2

4

You are trying to serialize a variable of type TTypeInfo. Take a look at its declaration:

TTypeInfo = record
  Kind: TTypeKind;
  Name: ShortString;
 {TypeData: TTypeData}
end;

Your code will serialize the first two fields, but will fail to serialize the hidden field that is part of the implementation detail. This is a rather special type. You cannot simply assign one TTypeInfo variable to another. You are meant to pass around PTypeInfo variables.

In fact, consider the code that sends the type information:

InvokeRec.ArgTypeInf := MethodArg.TypeInfo^;

This code is already broken because it is assigning to a TTypeInfo, which as I stated above cannot be done with simple assignment.

Now, you obviously cannot trivially serialize a PTypeInfo because that only has meaning in the address space of the process that owns it. And you cannot readily call GetTypeData() and serialize the PTypeData that is returned. That's a complex structure which also contains hard to serialize pointers.

I suspect the simplest approach will be to roll your own type information serialization mechanism. Perhaps it's enough to send the name of the type and then get the receiver to look it up using that name.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • @0x90, For serializing objects, see [Delphi JSON library for XE2 available for object serialization](http://stackoverflow.com/q/7731833/576719). – LU RD Nov 13 '12 at 14:21
  • I also added a boilerplate serialization unit including most TValue types here, [Convert Record to Serialized Form Data for sending via HTTP](http://stackoverflow.com/a/11514088/576719). – LU RD Nov 13 '12 at 14:28
  • First of all, thank you for the reply ;] now second, while i debugged the client, it did send the TTypeInfo record completely (as i said, TTypeKind shows tkEnun (for Integer) and Name shows 'Integer'). and when i invoke method with a Boolean (which works too) it shows in Kind (TTypeInfo) 'tkEnumeration' and in Name it shows 'Boolean'. it's just that with string (Kind = tkUString) i have problems.. –  Nov 13 '12 at 15:37
  • i don't know how to use that JSON, can i have example for my situation ? i never used it -.-". –  Nov 13 '12 at 15:39
  • It doesn't send the whole of the type data. Because the Delphi declaration lies about what's in the record. That commented out field indicates that there is in fact more information stored after the `Name` field. Since the compiler doesn't know about it, it cannot copy it. Take a look at the implementation of `TypInfo.GetTypeData`. – David Heffernan Nov 13 '12 at 15:47
  • I see... so what can i do to serialize this ? –  Nov 13 '12 at 15:50
  • I'm not really sure. You asked why there was an access violation, which I think I have answered. I'm not sure I can advise you how best to solve this problem. You can keep it simple and support a limited number of types and pass them by name, say. Or by an enumeration. Since this is bespoke you can do whatever you like, but I really don't know how best to solve your problem. I don't know your overall scope. I personally would lean towards a 3rd party RPC solution. But you've already said in a previous question that you don't want to do that. – David Heffernan Nov 13 '12 at 15:53
  • @0x90: I already gave you a solution to serialize records (from your previous question), no need for TValue... – whosrdaddy Nov 13 '12 at 19:29
0

As I just commented on your other question, TTypeInfo is variable length and contains pointers to other data, so you can't transmit it as-is and have it be meaningful on the receiving end. You will have to transmit the TValue.TypeInfo.Name string and then you can use TRttiContext.FindType(Name).Handle on the receiving end to get the appropriate TTypeInfo to pass to TValue.Make().

Community
  • 1
  • 1
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Gives me access violation, or i'm doing it wrong. `procedure TClientClass.WMClientRecv(var Msg: TMessage); var // we send to server ReadRec: TSharedRec; // we recv from server InvokeRecPtr: PInvokeRec; Value: TValue; LContext: TRttiContext; begin InvokeRecPtr := PInvokeRec(Msg.LParam); case InvokeRecPtr.ArgRawSize of 0: Value.Empty; else TValue.Make(PByte(@InvokeRecPtr.ArgRawData)^, LContext.FindType(InvokeRecPtr.ArgTypeInf).Handle, Value); end;` –  Nov 13 '12 at 20:23
  • var InvokeRec : packed record Method: Array[0..19] of Char; ArgRawSize: Integer; ArgTypeInf: Array[0..19] of Char; ArgRawData: Array[0..255] of Byte; end; S: String; –  Nov 13 '12 at 20:24
  • @0x90: [Read the documentation](http://stackoverflow.com/editing-help#comment-formatting). – Remy Lebeau Nov 13 '12 at 20:37
  • 1
    @0x90 you can't format large blocks of code in comments. More than one liners need to be in questions. – David Heffernan Nov 13 '12 at 21:03
  • Remy can you help me with the access violation at recv side ? –  Nov 13 '12 at 23:18
  • Honestly, your code is way more complicated than I care to touch. I strongly suggest you take a step back and re-evaluate what you are attempting to do. You need a better serialization layer in your code, and you need to keep it simple. Instead of trying to transmit `TValue` and `TTypeInfo` data, I would serialize them into much simpler TLV (type, length, value) records and transmit those instead. That would be much easier to code and manage, and it keeps your transmission protocol simple and clean. – Remy Lebeau Nov 14 '12 at 02:48
  • 3
    It looks like you are attempting to perform class method invokations over socket connections. I strongly suggest you use an established protocol for such things, such as COM or RPC. If you must use socket connections, then look into using SOAP, XML-RPC, or JSON-RPC. – Remy Lebeau Nov 14 '12 at 02:51
  • @RemyLebeau I suggested exactly that in a previous question, but 0x90 reckoned that it would be easy enough to do it his/her self! – David Heffernan Nov 14 '12 at 06:53
  • @RemyLebeau if there's a good COM or RPC example in Delphi (with no use of 3rd party stuff) then i would look at it.. –  Nov 14 '12 at 15:57
  • @0x90 That's a different topic. Feel free to ask another question here. But surely this question has been answered. – David Heffernan Nov 14 '12 at 17:00