So here’s today’s little gotcha: TRemotable out parameters in Delphi SOAP interface function calls must always be initialised before the call. If you don’t do this, at some point your client application will crash with an access violation in TValue.Make (System.Rtti.pas).
It turns out that when the TRIO component parses the Run Time Type Information (RTTI), it conflates the var and out modifiers on the parameters, and treats out parameters as var parameters (see function TRttiMethod.GetInvokeInfo). This means that when the function is called, the TValue.Make function which collects RTTI for the passed parameters will assume that a non-nil object reference is always pointing to a valid and initialised object, even for an out parameter, and will dereference it to get additional RTTI.
The workaround is simple: just initialise all out parameters to nil before the function call. This is good practice anyway.
I think the proper fix in the Delphi library code would be to treat out and var parameters differently in the SOAP calls, and always clear out parameters to nil within the SOAP framework.
Here’s some example code to reproduce the problem:
unit TestOutParams; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Soap.SoapHTTPClient, Soap.InvokeRegistry; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); end; type ITestOutParam = interface(IInvokable) ['{343E9171-4300-4523-A926-4904EDD652E1}'] function TestOutParam(out p5: TSOAPAttachment): Boolean; stdcall; end; var Form1: TForm1; implementation {$R *.dfm} function GetITestOutParam: ITestOutParam; var RIO: THTTPRIO; begin Result := nil; RIO := THTTPRIO.Create(nil); try Result := (RIO as ITestOutParam); finally if (Result = nil) then RIO.Free; end; end; procedure TForm1.Button1Click(Sender: TObject); var FSOAP: ITestOutParam; FOutputParameter: TSOAPAttachment; begin FOutputParameter := Pointer(8); // simulate uninitialized local variable pointing to protected non-nil address FSOAP := GetITestOutParam; Assert(Assigned(FSOAP)); FSOAP.TestOutParam(FOutputParameter); // this will crash end; initialization InvRegistry.RegisterInterface(TypeInfo(ITestOutParam)); end.