3

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);
Mawg says reinstate Monica
  • 38,334
  • 103
  • 306
  • 551
  • 3
    This is just an interface. What are you doing that causes an access violation? – Mason Wheeler Jul 01 '10 at 12:37
  • +1 for a good question. There are two ways to solve this - show you my code & figure why it doesn't work, or someone posts some code that does work & I can compare it wit my own. Ok, I will post my code, but the important thing (I think) is that both params are non-null at the call – Mawg says reinstate Monica Jul 01 '10 at 13:17
  • You really are snatching defeat from the jaws of victory because you used TByteDynArray instead of just TBytes (which is really just a string). – Warren P Jul 01 '10 at 13:36
  • what version of delphi? Please update (retag). – Warren P Jul 01 '10 at 13:40
  • Just because that's generated code doesn't mean you can't change it. The generator might have made mistakes. You're merely restricted in the kinds of changes you can make. Whatever you change it to must remain compatible with the way TRemotable descendants get serialized. It's even possible that whatever you got from the generator wasn't compatible in the first place. – Rob Kennedy Jul 01 '10 at 13:43
  • Where did the TByteDynArray come from? Who decided to use that? – Warren P Jul 01 '10 at 13:45
  • I'm really curious where TByteDynArray is defined in your version of delphi. In delphi 2010, the type is not defined any more. At least not that I can find. It is in System unit according to the help. – Warren P Jul 01 '10 at 13:51
  • 4
    The AV source is likely this: `theMessageArray := TByteDynArray(theMessage);` The variable isn't a dynamic array; type-casting won't make it one. The assignment will attempt to increment the refcount of the supposed array, and it may even run without crashing. Afterward, `sendMessage` attempts to use that memory as though it were an array. It reads the "length," and tries to access that many bytes. That length doesn't accurately reflect how many bytes are there, or maybe the refcount modification has trashed memory. Rule of thumb: if you type-cast a dynamic array, there's something wrong. – Rob Kennedy Jul 01 '10 at 13:55
  • @Rob: True. I can not get TByteDynArray type to compile in my delphi 2007 or 2010. Where the heck is it? – Warren P Jul 01 '10 at 13:57
  • Aha. Weird alias from Types, also referenced by literal name in WSDLIntf. A truly puzzling thing that WSDL code in Delphi chose to use a dynamic array type to hold soap byte data? – Warren P Jul 01 '10 at 13:58
  • Not a weird alias at all. It saves everyone from having to define it themselves when they want to define a dynamic-array parameter (not an open-array parameter). – Rob Kennedy Jul 01 '10 at 14:02
  • So why is TByteDynArray used in a remoting framework like WSDLIntf, instead of a more sensible string-type like TBytes? :-) – Warren P Jul 01 '10 at 14:08
  • Warren, TByteDynArray is in "types" in D7. Rob (+1), The AV source is likely this: theMessageArray := TByteDynArray(theMessage); ... can you tell me how to correct the code? The function SendMessage() requires a TByteDynArray as its 1st param – Mawg says reinstate Monica Jul 02 '10 at 02:04

1 Answers1

3

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;
Community
  • 1
  • 1
Warren P
  • 65,725
  • 40
  • 181
  • 316
  • 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 says reinstate Monica Jul 01 '10 at 13:38
  • 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 Jul 01 '10 at 13:38
  • 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 Jul 01 '10 at 13:39
  • 1
    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 Jul 01 '10 at 13:40
  • In which case, my suggested fix is not to use dynamic arrays at all. Use a remotable/RTTI'able type like TBytes. – Warren P Jul 01 '10 at 13:42
  • 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 says reinstate Monica Jul 01 '10 at 13:46
  • 2
    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 Jul 01 '10 at 13:56
  • 1
    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 Jul 01 '10 at 14:08
  • My TBytes suggestion is probably not compatible with WSDLIntf unit, which has specific support for TByteDynArray. – Warren P Jul 01 '10 at 14:09
  • 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 says reinstate Monica Jul 02 '10 at 02:31
  • 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 says reinstate Monica Jul 02 '10 at 02:35
  • Please see also http://stackoverflow.com/questions/3163116/delphi-7-converting-serializing-an-object-to-tbytedynarray-for-soap – Mawg says reinstate Monica Jul 03 '10 at 03:56