Solved

Problem with the serialization of a boolean value in delphi

Posted on 2007-11-21
17
1,688 Views
Last Modified: 2008-02-01
Hi All,

I'm having this wee problem whereby a Boolean value is not being serialized correctly (to lowercase true or false - following the xml standard) for an object that inherits from TRemotable - it always capitalises that first letter of each. I've tried overriding the ObjectToSoap method of the TRemotable class - but this would mean having to iterate through each node to determine if it contains that value "True" or "False", and lowercasing it - a time consuming process, is there an easier way? Any help would be greatly appreciated.

Thanks in advance...
0
Comment
Question by:rowan_massey
  • 7
  • 6
  • 4
17 Comments
 
LVL 19

Expert Comment

by:MerijnB
ID: 20327067
how do you convert the boolean to string, or is this done automatically by TRemotable?
0
 

Author Comment

by:rowan_massey
ID: 20327099
It all seems to be done by TRemotable - I'm trying to figure out where this is happening, I assumed that it would be in the ObjectToSoap function of the TRemotable class (Seems to be the most logical place for this to happen).
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 20327118
you would expect it to be there, if you step through the code, can't you see the string being build up?
0
 

Author Comment

by:rowan_massey
ID: 20327213
Hi MerijinB,

I followed the stack trace - and eventually got to this bit of code that should get executed - but doesn't seem to, you'll notice that at the very end that it should convert the boolean type to lowercase, which again doesn't seem to (When I look at the XML that I save the Boolean value is "True" - I hate delphi). The attribute of my class is definately of type boolean...DOOM!

function TSOAPDomConv.ConvertEnumToSoap(Info: PTypeInfo;
  P: Pointer; NumIndirect: Integer): InvString;
var
  Value: Pointer;
  I: Integer;
begin
  Value := P;
  for I := 0 to NumIndirect - 2 do
    Value := Pointer(PInteger(Value)^);
  if NumIndirect = 0 then
    Result := GetEnumName(Info, Byte(Value))
  else
    Result := GetEnumName(Info, PByte(Value)^);
  { NOTE: No need to use SameTypeInfo here since C++ has proper case }
  if Info = TypeInfo(System.Boolean) then
    Result := Lowercase(Result);
end;
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 20327240
I don't understand, is this piece executed or not?

If so, if you put a breakpoint here, can you see what's going wrong (or at least _where_ it is going wrong?)
0
 

Author Comment

by:rowan_massey
ID: 20327389
Hi Merijin,
This code seems to be one of delphi's units...any time I try and add the directory it's in to my projects search path (in a vain attempt to debug it) it keeps telling this:

[Pascal Fatal Error] Rio.pas(136): F2051 Unit WebServExp was compiled with a different version of IntfInfo.TIntfMetaData

Scalping couldn't be as painful as this...
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 20327426
if you enable "use debug dcu's" and just put a breakpoint there, won't it break?
0
 
LVL 28

Expert Comment

by:ciuly
ID: 20327550
I can tell you one thing: RTTI is sometimes a pain

my guess is that somewhere there is another type definition of boolean and your code uses that definition instead of system.boolean.

so, keep your mouse over that boolean declaration adn see where it goes, OR, rename it to system.boolean to be safe.

that shouild work just fine now.
0
Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

 

Author Comment

by:rowan_massey
ID: 20327832
Gents,

Just when I though I had it sorted...Thanks MerijnB, the use debug dcu's worked a treat - the problem is in the following bit of code - my kind value is coming up as tkClass, something which I wasn't expecting (I expected tkEnumeration) so it calls the ConvertObjectToSOAP procedure (attached below as well) - all of which leads me to nowhere I don't mind saying - there is nothing here that iterates through anything to determine if the node is boolean - DOOM! (again I appreciate the help...and patience)

procedure TSOAPDomConv.ConvertNativeDataToSoap(RootNode, Node: IXMLNode;
                const Name: InvString; Info: PTypeInfo; P: Pointer; NumIndirect: Integer);
var
  ElemNode: IXMLNode;
  TypeName: InvString;
  URI, S: InvString;
  IsNull: Boolean;
  I: Integer;
  IsScalar: Boolean;
begin
  case Info.Kind of
    tkClass:
      ConvertObjectToSOAP(Name, P, RootNode, Node, NumIndirect);
    tkDynArray:
      ConvertNativeArrayToSoap(RootNode, Node, Name, Info, P, NumIndirect);
    tkSet,
    tkMethod,
    tkArray,
    tkRecord,
    tkInterface:
      raise ESOAPDomConvertError.CreateFmt(SDataTypeNotSupported, [KindNameArray[Info.Kind]]);

    tkVariant:
    begin
      ConvertVariantToSoap(RootNode, Node, Name, Info, P, NumIndirect, NULL, False);
    end;
    else
    begin
      if Info.Kind = tkEnumeration then
      begin
        if not RemClassRegistry.InfoToURI(Info, URI, TypeName, IsScalar) then
          raise ESOAPDomConvertError.CreateFmt(SRemTypeNotRegistered, [Info.Name]);
        S := ConvertEnumToSoap(Info, P, NumIndirect);
        ElemNode := CreateScalarNodeXS(RootNode, Node, Name, URI, TypeName, S);
      end else
      begin
        if NumIndirect > 1 then
          for I := 0 to NumIndirect - 2 do
            P := Pointer(PInteger(P)^);
        TypeTranslator.CastNativeToSoap(Info, S, P, IsNull);
        if IsNull then
          CreateNULLNode(RootNode,ElemNode, Name)
        else
        begin
          if not RemTypeRegistry.TypeInfoToXSD(Info, URI, TypeName) then
            raise ESOAPDomConvertError.CreateFmt(SRemTypeNotRegistered, [Info.Name]);
          ElemNode := CreateScalarNodeXS(RootNode, Node, Name, URI, TypeName, S);
        end;
      end
    end;
  end;
end;

procedure TSOAPDomConv.ConvertObjectToSOAP(const Name: InvString;
  ObjP: Pointer; RootNode, Node: IXMLNOde; NumIndirect: Integer);
var
  ElemNode: IXMLNOde;
  I: Integer;
  ID: string;
  URI, TypeName: WideString;
  P: Pointer;
  Instance: TObject;
  MultiRef: Boolean;
begin
  P := ObjP;
  for I := 0 to NumIndirect - 1 do
    P := Pointer(PInteger(P)^);
  Instance := P;

  if Assigned(Instance) and not Instance.InheritsFrom(TRemotable) then
    raise ESOAPDomConvertError.CreateFmt(SUnsuportedClassType, [Instance.ClassName]);

  if not Assigned(Instance) then
    CreateNULLNode(RootNode, Node, Name)
  else
  begin
    { Retrieve URI of Type }
    if not RemClassRegistry.ClassToURI(Instance.ClassType, URI, TypeName) then
      raise ESOAPDomConvertError.CreateFmt(SRemTypeNotRegistered, [Instance.ClassName]);
    MultiRef := MultiRefObject(Instance.ClassType);
    { NOTE: SOAP Attachments will enter this path as they are never multirefed }
    if not MultiRef then
    begin
      if IsObjectWriting(Instance) then
        raise ESOAPDomConvertError.CreateFmt(SNoSerializeGraphs, [Instance.ClassName]);
      AddObjectAsWriting(Instance);
      { NOTE: Prefixing nodes can cause problems with some
              SOAP implementations. However, not doing so causes problems
              too ?? }
      CreateObjectNode(Instance, RootNode, Node, Name, URI, [ocoDontPrefixNode]);
      RemoveObjectAsWriting(Instance);
    end
    else
    begin
      ID := FindMultiRefNodeByInstance(Instance);
      { NOTE: Passing 'True' to prefix here can cause problems with some
              SOAP implementations. However, removing it can cause problems
              too ?? }
      if ID = '' then
        { NOTE: The ref'ed node must be of the TypeName - not the referring node name }
        ID := CreateObjectNode(Instance, RootNode, Node, TypeName, URI, []);
      ElemNode := Node.AddChild(Name, '' {No Namespace prefix});
      ElemNode.Attributes[SXMLHREF] := SHREFPre + ID;
    end;
  end;
end;
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 20327895
I think you'll have to dig a little further

Step into the call to CreateObjectNode() and see if you find more there
0
 
LVL 28

Expert Comment

by:ciuly
ID: 20327971
I'd rather see your object declaration and also the answer to my suggestion.
and with the object declaration, mention which fields are the ones that appear to have problems at serialization/deserialization
0
 

Author Comment

by:rowan_massey
ID: 20328398
Hi Lads,

Ciuly, below is an example of the class (Test) that I'm using (this is how it is called: webservice.CheckPrice(TestObject);) - the problem, as I stated above is that "Kind" in ConvertNativeDataToSoap is tkClass. What other than boolean could I change those types to?

MerijnB, I had gone it CreateObjectNode which does the following:
function TSOAPDomConv.CreateObjectNode(Instance: TObject; RootNode, ParentNode: IXMLNode;
                                       const Name, URI: InvString;
                                       ObjConvOpts: TObjectConvertOptions): InvString;
begin
  { Allow TRemotable_xxxx classes to perform custom serialization }
  if Assigned(Instance) and Instance.InheritsFrom(TRemotable) then
    TRemotable(Instance).ObjectToSOAP(RootNode, ParentNode, Self, Name, URI, ObjConvOpts, Result)
  else
    ObjInstanceToSOAP(Instance, RootNode, ParentNode, Name,
                      URI, ObjConvOpts, Result);
end;
The ObjectToSoap function calls the ObjConverter.ObjInstanceToSOAP which is defined in the ObjConverter Interface...

(**********************MY TEST CLASS*******************************)
Test = class(TRemotable)
  private
    FAccountId: WideString;
    FOrderQuoteId: WideString;
    FIsQuote: Boolean;
    FDateOpened: TXSDateTime;
    FStatus: WideString;
    FQuantity: TXSDecimal;
    FQuotedPrice: TXSDecimal;
    FDoNotReprice: Boolean;
  public
    destructor Destroy; override;
  published
    property AccountId: WideString read FAccountId write FAccountId;
    property OrderQuoteId: WideString read FOrderQuoteId write FOrderQuoteId;
    property IsQuote: Boolean read FIsQuote write FIsQuote;
    property DateOpened: TXSDateTime read FDateOpened write FDateOpened;
    property Status: WideString read FStatus write FStatus;
    property Quantity: TXSDecimal read FQuantity write FQuantity;
    property QuotedPrice: TXSDecimal read FQuotedPrice write FQuotedPrice;
    property DoNotReprice: Boolean read FDoNotReprice write FDoNotReprice;
  end;
0
 
LVL 19

Expert Comment

by:MerijnB
ID: 20328416
> The ObjectToSoap function calls the ObjConverter.ObjInstanceToSOAP which is defined in the ObjConverter Interface...

than ObjInstanceToSOAP is probably where the problem lies
0
 
LVL 28

Accepted Solution

by:
ciuly earned 250 total points
ID: 20328512
you are implying that there is a very serious bug with serializing/unserializing a boolean type property in something that has been around for years. I can understand a bug in case of some weird dynamic arrays or custom variants, but not with a delphi base type, but something that a lot of people have used for a long time is highly unlikely.
I will first go with a problem in your code rather than in the soap code :) I'm just sceptical, that's all ;)

btw, the order of the serialization is the order in which the properties appear in the a-z ordering of the property list.

so the second property serialized/unserialized is DateOpened. what type is TXSDateTime? is it a class? might explain why you are getting a class there.
the 3rd is the DoNotReprice:boolean which should go as an enumeration. are you even getting there?
0
 
LVL 19

Assisted Solution

by:MerijnB
MerijnB earned 250 total points
ID: 20328528
I'm hoping to guide rowan_massey to the place where things go wrong.
I don't expect he'll find the actual problem there, but I do hope that he finds a trigger to the place in his own code ;)
0
 

Author Comment

by:rowan_massey
ID: 20328820
Lads,
You both hit the nail on the head...I'm a spa! I was overriding the ObjectToSoap in a derived class of TRemotable and Test was derived from this class. What I had in the overrided ObjectToSoap was the following:
Result := inherited ObjectToSoap(RootNode, ParentNode, ObjConverter, Name, URI, ObjConvOpts+ocoDontSerializeProps, RefID);

The ocoDontSerializeProps doesn't "Serialise the properties" well well, how nice (I know, I know). The values are still getting written as "True" and "False" - but I suspect that that could be because of the convertion to string....I don't suppose any of you know any XML sniffer tool that would be quite handy?
0
 
LVL 28

Expert Comment

by:ciuly
ID: 20329942
xml sniffer? no. ethernet sniffer, yes. and a quite good one too (and free): ethereal http://www.ethereal.com/
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Introduction In my previous article (http://www.experts-exchange.com/Microsoft/Development/MS-SQL-Server/SSIS/A_9150-Loading-XML-Using-SSIS.html) I showed you how the XML Source component can be used to load XML files into a SQL Server database, us…
This video gives you a great overview about bandwidth monitoring with SNMP and WMI with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're looking for how to monitor bandwidth using netflow or packet s…
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now