Possible Codegen/Nougat bug?

I have a service written in Delphi which has the following structure define, along w/ the following service method:

TMillerAdInformation = class(TROComplexType)
//details removed
end;

function OpenAd(const AAdNumber: AnsiString; const AEntryYear: AnsiString; var AAdData: TMillerAdInformation; out ALog: AnsiString; const ADatabase: Integer): Boolean;

I have written a .NET application in Oxy that accesses this (and other) methods in an asynchronous way via anonymous methods, and everything works great.

I am now trying to write my first “real” Nougat app for OSX and i’m essentially rewriting my .NET app in Nougat. In the codegen for Nougat, the class/method looks like this:

type
TMillerAdInformation = public class(ROComplexType)

method beginOpenAd(
  AAdNumber: String;
  AEntryYear: String;
  AAdData: ^TMillerAdInformation;
  ADatabase: Integer
) startWithBlock(___block: block(arg: ROAsyncRequest)): ROAsyncRequest;

method endOpenAd(
  AAdData: ^TMillerAdInformation;
  ALog: ^String;
  __asyncRequest: ROAsyncRequest): Boolean;

What I am seeing in Nougat is that when I call beginOpenAd w/ an async call, it doesn’t seem to complete.

that call is the 3rd in a series of nested anon method calls so that I could call 3 methods from my service in sequence. here is the code:

ROService.beginAdExists(AdNumEdit.stringValue,AdYearEdit.stringValue,PubNumEdit.stringValue,True,0) startWithBlock(
method (aRequest4: ROAsyncRequest)
(* ANONYMOUS METHOD CALL #1 *****)
begin
AdExists := ROService.endAdExists(aRequest4);

if AdExists then
    NSLog('Ad found!!!')
else
    NSLog('Ad not found.');

if AdExists then
    begin
    // get ad number first.....
    ROService.beginMillerNumForPubNum(AdNumEdit.stringValue,AdYearEdit.stringValue,PubNumEdit.stringValue,0) startWithBlock( 
        (* ANONYMOUS METHOD CALL #2 ******)
        method (aRequest5: ROAsyncRequest)
        begin
            MillerAdNum := ROService.endMillerNumForPubNum(aRequest5);
                    
            NSLog('Miller Ad #'+MillerAdNum);

            NSLog('Calling Open Ad....');
            ROService.beginOpenAd(MillerAdNum,AdYearEdit.stringValue,@AdInfo,0) startWithBlock(
            (* ANONYMOUS METHOD CALL #3 ******)
            method (aRequest6: ROAsyncRequest)
                begin
                AdOpened := ROService.endOpenAd(@AdInfo,@LogStr,aRequest6); 

                if AdOpened then
                    NSLog('OpenAd succeeded')
                else
                    NSLog('OpenAd failed');
                end);
        end);
    end;
end);

What I see (or don’t see) is the last NSLog() calls. I see ‘Calling Open Ad…’, but nothing after that.

Is there a bug in the Nougat codegen?

details: Windows 7, VS 2012, Oxygene 6.20.60.1393, ROSDK for .NET/Cocoa/Delphi 7.0.70.1095

Thanks!

Alan

As a followup …

I added some debug logging to the main OpenAd() call in my Delphi service.

When called synchronously, all is working.

when called asynchronously from a .NET app, all is working.

When called asynchronously from a Mac OSX app, its as if the call is never made. No debug output at all.

Alan

Can you give your RODL, please? You can send it to support@remobjects.com.

Did you find any problems in my code? In the codegen? or in the ROSDK/Cocoa itself?

It appears to me that the service call is in fact NOT getting called at all. I’m not sure how that would happen, but I get zero debug information when I call that method asynchronously from Nougat.

Hope to hear from you soon.

Alan

ok, looking at this now. ObjC Codegen has this:

- (ROAsyncRequest *)beginOpenAd
:(NSString*)_p_AAdNumber
:(NSString*)_p_AEntryYear
:(TMillerAdInformation**)_p_AAdData
:(int32)_p_ADatabase
{
    ROMessage *__localMessage = [[self __message] copy];
    #if !RO_TARGET_DOES_ARC
    [__localMessage autorelease];
    #endif
    [__localMessage initializeAsRequestMessage:[self __clientChannel] libraryName:@"AdEntryLibrary" interfaceName:[self __getActiveInterfaceName] messageName:@"OpenAd"];
    [__localMessage writeAnsiString:_p_AAdNumber withName:@"AAdNumber"];
    [__localMessage writeAnsiString:_p_AEntryYear withName:@"AEntryYear"];
    [__localMessage writeComplex:*_p_AAdData withName:@"AAdData"];
    [__localMessage writeInt32:_p_ADatabase withName:@"ADatabase"];
    [__localMessage finalizeMessage];
    return [[self __clientChannel] asyncDispatch:__localMessage withProxy:self start:YES];
}

and Nougat has this:

method AdEntryService_AsyncProxy.beginOpenAd(
    AAdNumber: String;
    AEntryYear: String;
    AAdData: ^TMillerAdInformation;
    ADatabase: Integer): ROAsyncRequest;
begin
    var __localMessage: ROMessage := self.__message.copy;
    __localMessage.initializeAsRequestMessage(self.__clientChannel)
             libraryName("AdEntryLibrary")
             interfaceName(self.__getActiveInterfaceName)
             messageName("OpenAd");
    __localMessage.writeAnsiString(AAdNumber) withName("AAdNumber");
    __localMessage.writeAnsiString(AEntryYear) withName("AEntryYear");
    __localMessage.writeComplex(AAdData^) withName("AAdData");
    __localMessage.writeInt32(ADatabase) withName("ADatabase");
    __localMessage.finalizeMessage;
    exit self.__clientChannel.asyncDispatch(__localMessage) withProxy(self) start(true);
end;

This does look the same to me. Both calls get passed a POINTER TO a TMillerAdInformation object pointer. Both dereference that pointer when passing it to writeComplex, so that writeComplex should receive the proper object. (An argument can be made that it’s useless to pass the pointer as “var”, for begin*, as you won’t be getting anything back. But that’s not the problem, afaict).

So this does not sound like a problem in he RODL codegen for Nougat (altho we should fix the extra ^/* for both Nougat and ObjC). Can you set a breakpoint on the line to writeComplex, step into it (VS should ask you to browse for ROBinMessage.m, if you are using the Debug version of the RO/DA libraries) and see if you can inspect the object reference being passed in?

I see two potential issues (which i cannot easily investigate further myself, today)

(a) there’s something wrong in the underlying RO library that we just haven’t noticed ourselves, due to VAR parameters being so rare. I don’t think any of MY own apps use them (though DA might, and if it does, that means this is not the problem)

(b) there’s a bug in Oxygene WRT passing object refs with extra ^.

Can you also try one more thing? can you change the code to

   method AdEntryService_AsyncProxy.beginOpenAd(
    ...
    var AAdData: TMillerAdInformation;
    ...): ROAsyncRequest;
begin
    ...
    __localMessage.writeComplex(AAdData) withName("AAdData");
    ...
end;

i.e. change it form being passed as a “pointer to” to being passed as “var” (which SHOULD be the same,on technical level, but i might need to re-read up on http://wiki.oxygenelanguage.com/en/Pointer_References_in_Oxygene_for_Cocoa ;).

you could also try dropping the var altogether, like

   method AdEntryService_AsyncProxy.beginOpenAd(
    ...
    AAdData: TMillerAdInformation;
    ...): ROAsyncRequest;
begin
    ...
    __localMessage.writeComplex(AAdData) withName("AAdData");
    ...
end;

(although this won’t work for the end* part)

lemme know if this helps, and i’ll maybe be ale to dig some further myself, tomorrow.

Hi Marc -

I did a little testing tonight, and tried the following -

  • Changing the begin/end methods to use VAR params instead of @ params
  • Changing the begin/end methods to not use VAR or @, just passing the class
  • Changing the end method to use OUT params

All of these changes, unfortunately, didn’t change the end result. it still appears as if the async method isn’t being called at all. So confused.

I did try debugging into the call, but I have to admit, i’m still a bit confused by VS/nougat debugging. I’ve attached a screenshot of when I tried inspecting the AAdData param in the beginOpenAd call. Let me know if it looks right to you. I don’t see any field level data, it seems to be showing pointers?

Alan

I added one more line of debug info to my Nougat app:

NSLog('Calling Open Ad....');
ROService.beginOpenAd(MillerAdNum,AdYearEdit.stringValue,var AdInfo,0) startWithBlock(
(* ANONYMOUS METHOD CALL #3 *******)
method (aRequest6: ROAsyncRequest)
    begin
    NSLog('Waiting for endOpenAd...');
    AdOpened := ROService.endOpenAd(var AdInfo,var LogStr,aRequest6); 

    if AdOpened then
        NSLog('OpenAd succeeded')
    else
        NSLog('OpenAd failed');
    end);

I do see 'Waiting for endOpenAd" in the console, but nothing after. Also, I still don’t get any debug output from my ROSDK service, again - as if its not even getting called.

Alan

Ah, so the call does happen, and returns. I’m guessing it throws an exception, which would throw on the call to end*, and would perfectly fit your symptoms. Try adding a try/except block inside the anonymous method, or assign/implement the request:didfail: (or similarly named) delegate method, to see what is happening.

This IS the expects flow for when an exception is thrown — the block will be called, and calling end* will throw the actual exception.

Thanks marc

I will try this asap. I may not have time today but will followup when I know more. Thanks again.

Alan

I think i figured out the root of the problem.

So I had a little time tonight, and I sat down and added a simple try/except block around the endOpenAd method. It did throw an exception, and I got this message that there was an EROException raised on the server trying to read a Currency field. I had never seen this error before, so I quickly added some debug, and what I found out surprised me.

In the cocoa interface, the currency fields are NSDecimalNumber fields. IN my instantiated ROComplexType, the field it was failing on server side had a null value in the NSDecimalNumber (Currency) field. This explains everything.

What I’m essentially doing is passing a newly instantiated TMillerAdInformation class/structure to my service. When I instantiate my class in Nougat, all the expected currency fields are null when they hit the server. What can I do about this??

Alan

Just thinking…

should I override the init method of my ROComplex type to instantiate the NSDecimalNumber fields w/ property zero values?

For now, that sounds like a reasonable workaround, yes. IMHO this is a bug in RO though, it should send the currency values in no compatible format. I’ll have this looked into…

Yeah, this is definitely a bug in the SDK. That TMillerAdInformation structure is a pretty big ROComplex type. It has a lot of currency fields in it. Initializing the NSDecimalNumber does solve the exception, but there are a lot of those fields to initialize. How big of a fix would this be on your end? If I go and add overrides to the .init methods of all my structures that contain NSDecimalNumbers, am I correct in my understanding that if I ever revise that RODL and reimport it…my manually added code will be deleted?

the team is looking at this now. Imhop this just needs to be fixed in RO/Cocoa, so that a “nil” NSDecimal will be streamed like a proper “zero” currency.

Not trying to be a pest, but any word from the team on this issue?

Not yet. I’ll know more tomorrow. Sorry.

hmm. we’re not finding any problems with NSDecimals. nil NSDecimals are being streaked correctly as zero currency values to the server…

How can that be? I’m very confused. I pass to my server the rather large TMillerAdInformation RO Struct. That struct is comprised of several other structs and individual variables. In order for that one call in question (and maybe others) to properly work, I have to go in to the individual type declarations in my intf, override the init method of each struct, and manually instantiate each NSDecimalNumber like this:

     Rate := new NSDecimalNumber withString('0.00');

if I don’t do that, each NSDecimalNumber field in each of my structs are nil, as if they haven’t been instatiated at all. Are you testing w/ NSDecimalNumbers as part of a struct? Or just individual variables?

One other thought…

Are you passing the nil decimals alone, or part of a struct?

Also, remember in my case I was passing a struct as a VAR parameter. I’m not sure if that makes a major difference or not?

Hi Alan,

I’ve setup a simple testcase at my side.
Server exposes both plain method with Currency parameters and also TestMethod which takes a structure with Currency field (structure passed as var parameter)
I’ve tried to test all possible calls. Finally what I get is:

2013-11-14 20:36:37.072 CocoaApplication1[85931:303] !!![CLIENT] Called method with plain Currency parameters: (null) + 2.2 = 2.2

2013-11-14 20:36:37.073 CocoaApplication1[85931:303] !!![CLIENT] TestStruct.CurrencyField = (null) <<< this what will be sent to server
!!![SERVER] Get currency as: 0.0000 <<< this what came to the server
!!![SERVER] New currency value is: 2.3400 <<< this what server returns
2013-11-14 20:36:37.113 CocoaApplication1[85931:303] !!![CLIENT] Called SYNC method with var parameter taking structure with Currency: !!![CLIENT] TestStruct.CurrencyField: 2.34 <<< this was arrived at client from the server

the same for async call

2013-11-14 20:36:37.113 CocoaApplication1[85931:303] !!![CLIENT] TestStruct.CurrencyField = (null)
2013-11-14 20:36:37.114 CocoaApplication1[85931:303] result: !!![CLIENT] TestStruct.CurrencyField: 2.34
!!![SERVER] Get currency as: 0.0000
!!![SERVER] New currency value is: 2.3400
2013-11-14 20:36:37.124 CocoaApplication1[85931:303] !!![CLIENT] Called ASYNC method with var parameter taking structure with Currency: TestStruct.CurrencyField: 2.34

so all worked as expected. Non-initialized structures has currency (NSDecimalNumber) as nil which was passed as 0 to the server.
Can you take a look (and test) my test-case? I’ll attach it here.
TestCurrency.zip (474.8 KB)

Thanks!