"Interface not supported" when casting TRORemoteService component as service interface after code-first conversion

Hi,

I just converted my server to code-first and all compiles okay and mostly runs okay.

There are a few instances in my client app where I use the following code

(RORemoteService1 as IMyService).SomeMethod;

which causes the error Interface not supported

This happens at various places in the client using the same cast operation. (Explicit service creation - CoMyService.create… - appears to work okay.)

a) Does this strategy work with code-first clients? If so, how ?

b) Will this make older clients that were compiled against the RODL-first server incompatible with the new code-first server release? (That would be a huge problem for me)

Thanks v.much

Hi,

client-side for RODL-based and CodeFirst-based services are the same, i.e. they are compatible from this point.


you may have this case: https://docs.remotingsdk.com/BreakingChanges/#March2015ROD

Check that your service name in TRORemoteService is correct.
for example, in this scenario:

RORemoteService1.ServiceName := 'OtherService';
(RORemoteService1 as IMyService).SomeMethod;

OtherService.SomeMethod will be called instead of MyService.SomeMethod

Hi Evgeny,

I don’t think that’s the issue … the error is different (it’s not unknown method it’s “Interface not supported”)

In most cases the TRORemoteService.ServiceName is set at design-time using the drop down list.

Only in one place do I assign the service name manually:

var
  fFileService : TFileService;    
begin
  fRORemoteService := TRORemoteService.Create(nil);
  fRORemoteService.Channel := fROChannel;
  fRORemoteService.Message := fROMessage;
  fRORemoteService.ServiceName := 'FileService';
  fFileService := fRORemoteService as IFileService;   // "Interface not supported" error on this line
 [....snip...]
end;

and this is my service declaration.

{ TFileService }
[ROService]
[RODocumentation('Service for transferring files between server and client')]
TFileService = class(TDataAbstractService)
   DASchema1: TDASchema;
   Bin2DataStreamer: TDABin2DataStreamer;
   [...]

initialization 
  RegisterCodeFirstService(TFileService); // fake declaration for RTTI
  fClassFactory := TROClassFactory.Create('FileService', {$IFDEF FPC}@{$ENDIF}Create_FileService, TRORTTIInvoker);
  // RegisterForZeroConf(fClassFactory,'_FileService_rosdk._tcp.');

I have multiple services in the server, and this only happens with (so far) two of them, although I have yet to conduct extensive testing.

One of the services works fine using the (RORemoteService1 as IServiceName).methodCall code.

Thanks in advance,

have you regenerated _Intf for client after switching to CodeFirst ?

Yes … by the “Connect to Remoting SDK Server” menu option which re-created the xxxxxLibrary_Intf file and a xxxLibrary_ServerAccess file.

Inside the Intf file are the following lines:

const IFileService_IID: TGUID = '{1E20B1DC-7D09-48B0-919A-5A5DFB81C62B}';

and

{ Forward declarations }
IFileService = interface;

and

  IFileService = interface(IROService)
  ['{1E20B1DC-7D09-48B0-919A-5A5DFB81C62B}']
	procedure UploadChunk(const isFirst: Boolean; const filename: UnicodeString; const filedata: Binary);
	procedure UploadFinished(const filenanme: UnicodeString; const filesize: Int64);
    // etc.....

Thanks

can you create a simple testcase that reproduces this error, pls?
you can send it directly to support@

I can try. The problem is in a large app with many dependencies/comps other than RO though.

check for RORemoteService1.ServiceName in such calls. it should be set to XXXX for casting to IXXXX.
it can be more easy than creating testcase

I’ve just realised, that my previous statement that some of the services were working was incorrect. I realise that code wasn’t being called.

In fact all attempts to cast a TRORemoteService to a service interface fails with “Interface not supported”.

Answer to your question: yes, the service names are correct. The RORemoteService components feed into a RemoteDataAdapter that in turn expose the correct schema datatables that I use in the TDAMemDataTables – so they have to be correct.

I can’t reproduce this issue with simple testcase: testcase.zip (127.2 KB)

Thanks, didn’t think you would be able to as it does seem basic functionality.

If it helps, I have connected the previous client (that was built against RODL server) to the code-first server and the functions that use the (TRORemoteService as IMyService).methodCall code work fine.

So the problem must lie in the new “codefirst client” as the server looks okay?

in general, you can receive server instance via CoXXXX.Create method and use it instead receiving it via TRORemoteService.

it will be the same as (RORemoteService1 as IXXXX) but a bit faster because RORemoteService performs some additional actions like looking for proxy class in registered proxy list.

1 Like

Which begs the question: what changed client side between the working and broken version. Do you still have the old and the new _Intf file, can you compare them? Did anything else change?

To me this sounds like a problem with mismatched interface GUIDs, somehow

Hi marc,

The difference is that the server has been converted to code-first from a RODL first (via the conversion wizard - which did a good job btw).

So the xxxLibrary_Intf file has to be generated client-side using the “Connect to Remoting SDK Server” IDE menu.

I compared the interface ID’s from the RODL-first server-side to the code-first client-side generated ID’s and the Library ID was the same GUID, but the service GUIDs are different.

I pasted the old GUIDs into the new code-first unit and re-compiled, but unfortunately ran into the same problem.

Whilst I can code-around this, I’m wary that this problem could be indicative of a deeper problem with the new code-first server/client setup ?

Thanks

Edit: forgot to mention, I confirmed the problem is purely client-side because I ran the code-first client against the most recent build of the RODL first server and the same problem arose.

Yeah, it’s definitely weird. It’s almost as if the client has two separate sets of GUIDs, one on the interface types you use in code, and another that TRORemoteService uses to cast — and they don’t match.

I don’t recall the details (Eugene will know better, as its been, quite literally, a decade+ since I myself was involved with writing that code ;), but iirc TRORemoteService does some pretty low-level hacking to support the casting on Delphi/COM interface level.

Thanks marc.

I just did a check for duplicate interface declarations & the only thing grep search found was the backup made by the code-first wizard & the interface file generated client-side.

I’m stumped.

I’ll try to replicate the problem in another test client, but I shall code-around the problem in the real application.

thanks

1 Like

Hopefully Eugene will have more ideas. Have you tried tracing into the cast to see whats going on?

essentially

function TRORemoteService.QueryInterface(const IID: TGUID; out Obj): HResult;
var 
  proxyclass : TROProxyClass;
  proxy : TROProxy;
begin
  result := inherited QueryInterface(IID, Obj);

  if (result <> S_OK) then begin
    proxyclass := FindProxyClass(IID, TRUE);

    if (proxyclass=NIL) then Exit
    else begin
      CheckCanConnect(false);
      proxy := proxyclass.Create(Self);
      proxy.GetInterface(IID, Obj);
      result := S_OK;
    end;
  end;
end;

should be where the magic happens. Check what Guid is stored in IID, and step into FindProxyClass to see which ones it knows about…

1 Like

Hi marc & evgeny,

Thanks for your insights marc, it’s helped solve the problem.

I built a simple app just to login to the server and then logout using (RORemoteServer as ILoginService).Logout as I normally do & which has been failling all afternoon.

The error occurred as expected, so (as a hail-mary) I regenerated the Intf unit again. Hey presto the simple login/out app started working fine.

Recompiled the big app and that is now working too.

Based on marcs comments, I looked at the xxxLibrary_Intf file and saw that the interface IID’s are declared as constants, but the interface declarations use the same string literals, not the declared constants.

So I’m guessing that the two may have become out of sync somehow… ?

We can replicate the problem in a simple client app by altering the IxxxxxService_IID : TGuid = '{....}’ constant declaration so that it no longer matches the actual TGuid used in the interface declaration.

Thanks for all your help

Cheers

1 Like

How I can reproduce this behavior w/o manual changes?

Sorry, I haven’t been able to reproduce this.