1.3k views
in System Integration by
Hello,

We are currently implementing a GUI application that heavily relies on list views to display large amounts of data, up to several hundred entries of up to 5 fields per line and up to 100 chars per line. List contents is to be populated by native "C" code (i.e. separate "C" code module, no "native" statements embedded in Chora code), while list data structures (arrays of strings) are defined in Embedded Wizard. GUI data is stored in an Autoobject in a centralized Data class.

We'd like to thoroughly understand how to correctly populate such list entries from "C" code, and also how to de-populate such lists, in order to make sure any data objects be properly disposed of and not generate any memory leakage.

How to approach this topic best? Is there any in-depth documentation of list & array access and string data exchange available? All documentation I have found does not thoroughly cover this topic.
(Existing articles on how to exchange strings and arrays between GUI and "C" already read, they do not fully document the API or memory management.)

Thanks!

1 Answer

0 votes
by

Hello,

the provided documentation demonstrates the recommended approach how to exchange strings or any other data between GUI and target system. If you are looking for more low-level functionality, these is documented in the header files of RTE (Runtime Environment). In particular the file ewrte.h contains all data structures and functions inclusive the description.

The usage of not officially documented functionality is however not the recommended way. We feel free to change, remove or add new functionality in the future causing your implementation to eventually fail when you go to a newer Embedded Wizard version. Only the oficially documented parts are stable.

Generally, all dynamicaly reserved memory (for Chora objects, strings, resources, etc.) is managed by Embedded Wizard own Garbage Collection. You don't need to worry about it as long as you follow the sugestions in the provided documentation.

When working with list views, the view loads its contents (the items) dynamically just in the moment when the user scrolls the list. Even if you have millions of list items, the list view will maintain only few items in the memory. You don't need to worry about it. Just implement the OnLoad slot method to feed the view with the actually requested item contents. 

In the simple case, the implementation of OnLoad calls your 'C' functions to get the data (strings) for the just loaded item. The returned strnigs have to be created by using one of the documented EwNewStringXXX() functions (e.g. EwNewStringAnsi()). As long the string is needed in the running application (e.g. it is visible in the list view), the memory for the string is occupied. As soon as the user has scrolled the list and new strings are loaded, the old strings are released automatically. There is no explicit free or delete function.

The recommended approach is to implement in your Driver Class a method to serve as interface to your device where the many strings are maneged. The following could be the declaration of such method:

string GetItemContent( int32 aItemNo, int32 aFieldNo ).

The OnLoad slot method can then call GetItemContent() with the the number of the just requested list item passed in the first argument and the part of the item (the field number) in the second argument. The implementation of GetItemContent() should contain native statements to call your particular 'C' functions to obtain the requested information as Embedded Wizard strings.

In your question you are speaking about the array. Is your intention to use the array as a kind of cache? If yes, it is not necessary. The list view maintains its own item cache. Just ensure, that the 'C' function in your device are fast to provide the requested information quickly and maintain the implementation of the method GetItemContent() and OnLoad as simple as possible.

Does it help you?

Best regards 

Paul Banach

by

Hello Paul,

Thanks for explaining the details. Yes indeed, it helps.
We now have implemented it this way, and from what we have seen so far, it works as expected.

One question though:
Where is the 'C' prototype of the method -- let's call it GetItemContent() -- to be placed? Apparently the generated code calls the 'C' function without including any prototype from a 'H' file (and without letting me specify a 'H' file from which to include). I think there must be a better solution than manually modifying Application.c to include the proper function prototype, as Application.c is part of the Generated Code?

Thanks,

Steffen Schmid

by

Hello,

you can use a inline code member to place the missing #include statement.

Please never try to modify the generated code - as your changes will be overwritten the next time you generate the code again...

Best regards,

Manfred.

by

Hello,

in the above description GetItemContent() is a Chora method. Here the necessary prototype is generated automatically. I suppose you refer to your own 'C' functions, you intend to call from native statements used within GetItemContent(). In such case you can include the necessary header files as explained in the answer of Manfred by using the inline member or you declare the 'C' functions in-place, just within the native statement where you intend to call them:

native ( ... )
{
  extern const char* SomeFunction( int SomeParameter, ... );

  ...
}

Best regards

Paul banach

by

Hello Paul and Manfred,

Thanks for explaining the inline code member. And yes, I was referring to the 'C' function and not to the Chora method. Generated code being overwritten is obvious, so this indeed is the reason not to modify it...

We are experiencing another issue though:
Initial display of list contents works fine, but as soon as the Garbage Collector tries to dispose of strings it no longer needs, the system reports a panic. At first glance, it may look as if we fed a foreign string to Embedded Wizard, but we are pretty sure we don't. Any string passed to EmWi has previously been allocated using EwNewStringAnsi(). The list has multiple columns, each of which is a string.

So our 'C' function (called RetrieveListItem())takes two parameters. One is the index, the other one holds the data to display. Preferring to return all fields of the list element at once, the data parameter is not literally a string, it is a complex data object of the same type as one list element. For the sake of simplicity, let's assume it consists of a 'FirstName' and a 'LastName' string (pointers indeed). Each of these pointers is assigned a string using EwNewStringAnsi() then.

Our 'C' source code looks like this:

void RetrieveListItem (XInt32 u32Idx, ApplicationListDataObject aList)
{
  switch (u32Idx) {
    case 0:            
      aList->LastName  = EwNewStringAnsi ("Xyz");
      aList->FirstName = EwNewStringAnsi ("Abc");
      break;
    case 1:
      ...            
      break;
    default:
      ...
      break;
  }
}

Apparently there is something wrong with this solution:
When scrolling the 'Xyz' entry out of view, the garbage collector tries to free the string:

[FATAL ERROR in ewstring.c:1375] Unmanaged string 'Xyz‘ 
PANIC: System halted

When disposing of the whole list, another error is generated:

[FATAL ERROR in ewstring.c:1388] Trying to release a string with usage counter 0.
PANIC: System halted

Could you let us know how to implement this correctly please?

Thanks.

 

by

Hello,

you are trying to assign a new created string to a Chora variable directly in ANSI C. This is a possible approach, but not the recommended one. Doing this you have to know many details of how internally Chora objects are managed and how Garbage Collection is working. Therefore in the chapter Integrating with the device we don't address this posibility and describe only approaches wich are as far as safe possible.

In your particular case, I would re-implement the above ANSI C code in Chora. The idea, you reduce the ANSI C code to the absolut necessary minmum where you exchange the values for LastName or FirstName without accessing the Chora object. The access to the Chora object occurs in the Chora syntax:

var int32                       u32Idx = ...
var Application::ListDataObject aList  = ...;

var string firstName = "";
var string lastName  = "";

// Execute the native code to obtain the information for the
// item number u32Idx. Limit the 'native' code to the possible
// minimum
native ( u32Idx, firstName, lastName )
{
  switch ( u32Idx )
  {
    case 0:            
      lastName  = EwNewStringAnsi ("Xyz");
      firstName = EwNewStringAnsi ("Abc");
      break;
    case 1:
      ...            
      break;
    default:
      ...
      break;
  }
}

// Again in 'Chora' assign the retrieved information to the
// ListDataObject.
aList.FirstName = firstName;
aList.LastName  = lastName;

With the above example, you limit the native statement to query the values for the both names only. To exhange the values between ANSI C and Chora local variables firstName and lastName are used. This is the safe and reccomended approach how to exchange data.

When you compile the above example you will get the following corresponding ANSI C code:

  XInt32 u32Idx;
  ExampleListDataObject aList;
  XString firstName;
  XString lastName;

  /* Dummy expressions to avoid the 'C' warning 'unused argument'. */
  EW_UNUSED_ARG( _this );

  u32Idx = ...;
  aList = ...;
  firstName = 0;
  lastName = 0;
  {
    switch ( u32Idx )
    {
      case 0:            
        lastName  = EwNewStringAnsi ("Xyz");
        firstName = EwNewStringAnsi ("Abc");
        break;
      case 1:
        ...
        break;
      default:
        ...
        break;
    }
  }
  EwRetainString( &aList->FirstName, firstName );
  EwRetainString( &aList->LastName, lastName );

Interesting in the resulting code are the last two rows. As you see, the assignment of a string to a Chora variable does not correspond to a simple ANSI C assignment (A = B ). It is an invocation of a function EwRetainString(). This function takes care of the correct releasing the old string and retaining the new string referenced by the variable e.g. aList->FirstName. If the invocation is missing (as in your implementation), the string you have just created by using EwNewStringAnsi() is released during the next garabage collection. Consequently, the variable e.g. aList->FirstName will refer to an invalid memory area. This memory does not contain a correct (managed) string anymore.

The usage of the function EwRetainString() is one of the many details we try to hide from the user to not overstrain them with complex details. For all usual application cases it should not be necessary to access Chora objects directly from the C code. Following the instructions in the section ter Integrating with the device should be sufficient. Anyway, if you are interested in the description of the function, you will find it in ewrte.h.

Does it solve your problem?

Best regards

Paul Banach

 

by

Hello Paul,

Thanks a lot for the detailed explanation.
Still we need to follow the approach designated "possible but not recommended. Invocating EwRetainString(), I can confirm that a first test shows that it actually solves the issue.
We need to go for this approach because the code snippet I provided, for reason of clarity, hides one crucial point: The members of the list view are not just constants, they are instead populated by accessing a dynamic data source that is not available to Chora.
So there is no other choice than accessing the data source through a 'C' protocol stack.
I will have a closer look at the documentation of EwRetainString() and implement it this way.

Thanks again,

Steffen

by

Hello Steffen,

in such case I would implement in the class Example::ListDataObject a method e.g. Initialize() expecting in its arguments all the values you want to assign to the object. The following screen shot demonstrates the implementation of such method together with the ListDataObject class:

Once the method is implemented, you can call it from the C code instead of directly accessing the Chora variables. In this manner you don't need to worry about all the difficult aspects like EwRetainString(). Please note the attribute Generator is set true for the affected method. This is important because we want the method to be invoked from the C code only. If not used elsewhere in your project the method would be then eliminated during code generation.

How you call a Chora method from C code is described in the section Invoke GUI application code from device. Your particular code could see as follows:

void RetrieveListItem (XInt32 u32Idx, ApplicationListDataObject aList)
{
  switch (u32Idx) {
    case 0:            
      ApplicationListDataObject__Initialize( aList, EwNewStringAnsi ("Abc"), 
                                                    EwNewStringAnsi ("Xyz"))
      break;
    case 1:
      ...            
      break;
    default:
      ...
      break;
  }
}

With this approach you can populate your list contents in C code and 'feed' the GUI application with data by calling its methods. Accessing the Chora variables is avoided. The additional method provides also a kind of interface between the device and the GUI application. In case of problems you can add code to observe what happens at this interface.

Another advantage of this approach is, you can add more code to the Initialize() method. For example, you can verify whether the received values are valid, or you can broadcast notifications in your GUI application every time new data are received.

What do you think?

Best regards

Paul Banach

by
Hello Paul,

Thanks for your suggestion. This approach is worth considering, although we will have to modify our code once again.

Even though this approach is documented as the recommended way of feeding data from 'C' into the GUI, using this to fill list items is obvious in hindsight only.
Adding this example to the documentation section would be worth considering.

Thanks agin,

Steffen

Ask Embedded Wizard

Welcome to the question and answer site for Embedded Wizard users and UI developers.

Ask your question and receive answers from the Embedded Wizard support team or from other members of the community!

Embedded Wizard Website | Privacy Policy | Imprint

...