tags:

views:

98

answers:

1

I am fairly new to Delphi & have to code a SOAP client. Importing the WSDL generates this code (which I obviously can't change as I obviously have to comply with the server side)

  DataPart            = class;             
  Message             = class;             
  eMessage            = class;             

  eventType = ( send, delete, etc );

  DataPart = class(TRemotable)
  private
    FhasData: Boolean;
    Fdata: TByteDynArray;
  published
    property hasData: Boolean read FhasData write FhasData;
    property data: TByteDynArray read Fdata write Fdata;
  end;
  Message = class(TRemotable)
  private
    FMessageID: Int64;
    Ftimestamp: TXSDateTime;
    Fevent: eventType;
    FmagicNumber: WideString;
    FDataPart: DataPart;
  published
    property MessageID: Int64 read FMessageID write FMessageID;
    property timestamp: TXSDateTime read Ftimestamp write Ftimestamp;
    property event: eventType read Fevent write Fevent;
    property magicNumber: WideString read FmagicNumber write FmagicNumber;
    property DataPart: DataPart read FDataPart write FDataPart;
  end;

  eMessage = class(TRemotable)
  private
    FencryptedMessage: TByteDynArray;
    Fdata: DataPart;
  published
    property encryptedMessage: TByteDynArray read FencryptedMessage write FencryptedMessage;
    property data: DataPart read Fdata write Fdata;
  end;

  MyApplicationPortType = interface(IInvokable)
  ['{99767D33-6B4A-7547-4DAC-0608095CAC70}']

    function  sendMessage(const encryptedMessage: TByteDynArray; const data: DataPart): WideString; stdcall;
  end;

Can anyone code me an example with dummy values that will call sendMessage() and not cause an access violation? I really don't know how to handle TByteDynArray


[Edit] as requested, here's my code, BUT - disclaimer - I had to hack it about a lot (reduce it) before posting, so it may not compile. Both parms to sendMessage() are non-null

  var theMessageArray: TByteDynArray;
      theResult : WideString;
      messageData : TByteDynArray;
      i : Integer;
begin
  theMessage.messageID := theMessage.messageID + 1;
  theMessage.timestamp := TXSDateTime.Create();
  theMessage.timestamp.AsDateTime := Now();
  theMessage.event := delete;
  theMessage.magicNumber  := 'magic # ' + IntToStr(theMessage.messageID);

  SetLength(messageData, 1);
  messageData[0] := 0;

  theMessage.dataPart.hasData := True;
  messageData := theMessage.dataPart.messageData;

  SetLength(messageData, $1000 * dataSize);

  for i := 0 to $1000 * dataSize - 1 do
        messageData[i] := i and $FF;

  theMessage.DataPart.messageData := messageData;

  theMessageArray := TByteDynArray(theMessage);
  theResult := (HTTPRIO1 as MyApplicationPortType).sendMessage(theMessageArray, theMessage.dataPart);
+2  A: 

New Idea: Do you have range checking on in this unit? Add {$R+}

If you want to use a dynamic array type, you must explicity set its length in the constructor before you access it, and when copying/assigning, you must be very careful as well.

Not only must you call SetLength on each TByteDynArray before accessing its elements:

SetLength(Fdata, MyDesiredLengthWhichIsGreaterThanZero):

You must also be careful here, I think this could get you in trouble:

  property data: TByteDynArray read Fdata write Fdata;

Your auto-generator made that code for you, and if you really know you want a dynamic array, you apparently CAN make it published. (Updated: I was wrong about that initially).

TRemotable, as Rob points out, does not work with indexed properties but does work fine with "array of byte" (TByteDynArray) properties, so if you do everything right, you do not need to stop using TByteDynArray (I was wrong about that initially).

If it was me writing this from scratch, I would use a "string" type instead like TBytes. I am wondering why it didn't use TBytes, but I understand that you are implementing a SOAP client using some auto-generated WSDL-generator-code. So given that, it should be eminently possible to make your code not crash.

see also this Related question

I do not know how to write a SOAP client, but it looks like your code does some dodgy things. It looks like you need to fix your dynamic array handling, including the "uh-oh, why are you doing a Cast here" problem Rob pointed out to you. However, it does not look like you are free to just change types either, as it looks like you must use types that are known by and handled by your TRemotable mechanisms.

As for your request, this should work:

  procedure TestMe( whatever:TWhatever );
  var 
    FData:TByteDynArray;
  begin
     SetLength(FData,2); 
     FData[0] := 10; 
     FData[1] := 20;
     sendMessage(FData, whatever);
  end;
Warren P
Thanks, Warren (+1). I just now posted my code (with a lot of not-relevant stuff deleted). Yes, I set the length. I can write the Get/Set() but can't I just do it by accessing the property directly? (told you I am new to Delphi)
Mawg
You didn't set the length of fdata. And you thought that SetLength(SomethingElse,10), and then assigning fdata = somthingelse would work, which it won't.
Warren P
You need to read the delphi language guide, and understand when to use a dynamic array, and when not to, and in this case, don't use a dynamic array type at all.
Warren P
I don't think your suggested fix is compatible with TRemotable. The array needs to be defined in the class such that consumers of TRemotables know how to get the entire contents of the array. With an array property, that can't happen (because the consumer doesn't know what the valid index values are).
Rob Kennedy
In which case, my suggested fix is not to use dynamic arrays at all. Use a remotable/RTTI'able type like TBytes.
Warren P
Sounds like you guys know what you are talking about - and I don't. Given that the classes whcih I have to use are auto-generated by Delphi from the WSDL I think I ought to stick to them (although I could make some stuff public, but not change data-types). Can you show me how to code this thing? I just don't get it all, sorry.
Mawg
If you are using auto-generated code generated for you, you have to change it when it's wrong. See the related question I liked for you: http://stackoverflow.com/questions/3154879/delphi-problem-setting-length-of-a-tbytedynarray
Warren P
Setting the length of the local `messageData` variable and then assigning it to the dynamic-array property seems like the correct thing to do. It's perfectly safe to assign one dynamic array to another. The reference count gets updated, so the array remains alive until everyone is finished with it. I think the root of this may come down to determining just what array of bytes the `sendMessage` function *expects* to receive. So, Mawg, what is a MyApplicationPortType, and what kind of encrypted message are you supposed to give it?
Rob Kennedy
My TBytes suggestion is probably not compatible with WSDLIntf unit, which has specific support for TByteDynArray.
Warren P
hi, Rob, MyApplicationPortType is just a rename for the real thing - it publishes the SendMessage() function. True, I ought to encrypt theMessageArray before sending, but let's just omit that step here, to simplify.
Mawg
Hmmmm, if I breakpoint at that cast, then the structure theMessage, when seen in the debugger, seems to contain pointer values for the wide string member(s). So, how am I supposed to cast the entire object to a TByteDynArray?
Mawg
Please see also http://stackoverflow.com/questions/3163116/delphi-7-converting-serializing-an-object-to-tbytedynarray-for-soap
Mawg