Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
manwell77t
Explorer

Suppose you're calling a remote enabled function module and its signature parameter's data types don't exist in local data dictionary (for example because of different sap product such as SRM and R/3 for example). Thinking about a structure data type probably you don't have defined in your calling system data dictionary even the most of data elements used for defining each field. In order to pass importing parameters and receive result you must replicate signature data types. So, usually, data types are simply replicated on calling system; coding them (for example in a type group) or defining them in local data dictionary. For deeper structures with tens of fields could be a long and annoying job. But what if a structure data type defined on remote system, maybe because of an upgrade or because of a custom include, changes ? That generates a field displacement between local and remote structures that could even lead to a dump (or simply to a field misread). In that case you must realign local data types with remote ones. So, how to avoid definitely field displacement and dumps ? The only way to completely avoid any intervention is to dynamically build structure data types at runtime respecting remote definition.   Runtime generation of structures (and internal table) data types based on   structure data types defined in local dictionary is quite easy by means of cl_abap_structdescr and cl_abap_tabledescr classes. Method describe_by_name of class cl_abap_structdescr allows us to know "building details" of a   specific structure data type.


A quick example using structure BAPIRET2
* data declarations  

data: lo_struct type ref to cl_abap_structdescr,
lt_comp type cl_abap_structdescr=>component_table,
lo_run_st type ref to cl_abap_structdescr,
lo_run_tt type ref to cl_abap_tabledescr,
lo_struc type ref to data,
lo_ttype type ref to data.

field-symbols: <fs_table> type standard table,
<fs_line> type any.


* describe local structure
lo_struct ?= cl_abap_structdescr=>describe_by_name( 'BAPIRET2' ).


* get local structure components
lt_comp = lo_struct->get_components( ).

* create structure and table type descriptor
lo_run_st = cl_abap_structdescr=>create( lt_comp ).
lo_run_tt = cl_abap_tabledescr=>create( p_line_type = lo_struct
p_table_kind = cl_abap_tabledescr=>tablekind_std
p_unique = abap_false ).

* handle runtime data type by means of generic variables
create data: lo_struc type handle lo_run_st,
lo_ttype type handle lo_run_tt.

assign: lo_struc->* to <fs_line>,
lo_ttype->* to <fs_table>.

By means of standard function module 'DDIF_FIELDINFO_GET' (defined as remote   enabled and for sure present in both sap system) is possible to know building   details (field by field) of a specific structure data type. So, instead of   building our structure data type using details returned by the static method describe_by_name of cl_abap_structdescr  class we  could build it using abap predefined data types (because data element may not exist locally) by means of what 'DDIF_FIELDINFO_GET' returns in table parameter "dfies_tab".


That snippet (the code of my zcl_dyn_remote_type_builder=>get_elemdescr method) shows how to build structure components (so a descriptor, an instance of cl_abap_elemdescr) starting from knowledge of internal length, internal type and decimal places. That code was recently updated (thanks to my colleague fabrizio.gemma) for solving an INT1 and INT2 bug.



  data: lx_pir  type ref to cx_parameter_invalid_range.

data: lv_int2 type int2,
lv_int1 type int1.

try.

case i_inttype.

when cl_abap_elemdescr=>typekind_int.
result = cl_abap_elemdescr=>get_i( ).

when cl_abap_elemdescr=>typekind_int1.
result ?= cl_abap_elemdescr=>describe_by_data( p_data = lv_int1 ).

when cl_abap_elemdescr=>typekind_int2.
result ?= cl_abap_elemdescr=>describe_by_data( p_data = lv_int2 ).

when cl_abap_elemdescr=>typekind_float.
result = cl_abap_elemdescr=>get_f( ).

when cl_abap_elemdescr=>typekind_date.
result = cl_abap_elemdescr=>get_d( ).
when cl_abap_elemdescr=>typekind_packed.
result = cl_abap_elemdescr=>get_p( p_length = i_intlen
p_decimals = i_decimals ).

when cl_abap_elemdescr=>typekind_char.
result = cl_abap_elemdescr=>get_c( p_length = i_intlen ).

when cl_abap_elemdescr=>typekind_time.
result = cl_abap_elemdescr=>get_t( ).

when cl_abap_elemdescr=>typekind_num.
result = cl_abap_elemdescr=>get_n( p_length = i_intlen ).

when cl_abap_elemdescr=>typekind_hex.
result = cl_abap_elemdescr=>get_x( p_length = i_intlen ).

when cl_abap_elemdescr=>typekind_string.
result = cl_abap_elemdescr=>get_string( ).

when cl_abap_elemdescr=>typekind_xstring.
result = cl_abap_elemdescr=>get_xstring( ).

when others.

raise exception type zcx_dyn_remote_type_builder
exporting
textid = zcx_dyn_remote_type_builder=>no_inttype
inttype = i_inttype.

endcase.

catch cx_parameter_invalid_range into lx_pir.
raise exception lx_pir.

endtry.

and here is how to use it (fixing also a unicode bug found in previous version of code):



* data declaration

data: lo_elem type ref to cl_abap_elemdescr.

data: lt_fields type standard table of dfies,
ls_comp type abap_componentdescr,
ls_dfies type dfies,
ls_tmp_dfies type dfies,
ls_x030l type x030l,
lv_intlen type i,
lv_decimals type i,
lv_off type i,
lv_count type numc3.

data: lx_parameter_invalid_range type ref to cx_parameter_invalid_range.

* rfc destination check
if not i_rfcdest is initial.

call function 'RFC_CHECK_DESTINATION'
exporting
mydest = i_rfcdest
mytype = zcl_dyn_remote_type_builder=>rfctype3
exceptions
empty_destination = 1
invalid_logical_destination = 2
destination_with_special_char = 3
internal_destination_id = 4
empty_rfctype = 5
others = 6.

if sy-subrc ne 0.

raise exception type zcx_dyn_remote_type_builder
exporting
textid = zcx_dyn_remote_type_builder=>rfc_unreachable
rfcdest = i_rfcdest.

endif.

endif.

* get dictionary information from remote system
call function 'DDIF_FIELDINFO_GET' destination i_rfcdest
exporting
tabname = i_struct
importing
x030l_wa = ls_x030l
tables
dfies_tab = lt_fields
exceptions
not_found = 1
internal_error = 2
others = 3.

if sy-subrc ne 0.

raise exception type zcx_dyn_remote_type_builder
exporting
textid = zcx_dyn_remote_type_builder=>no_struc
struct = i_struct.

endif.

* component table builder
try.

* build structure field by field
loop at lt_fields into ls_dfies.

case ls_dfies-inttype.

when cl_abap_elemdescr=>typekind_char or
cl_abap_elemdescr=>typekind_date or
cl_abap_elemdescr=>typekind_time or
cl_abap_elemdescr=>typekind_num.

lv_intlen = ls_dfies-intlen / ls_x030l-unicodelg.

when others.

lv_intlen = ls_dfies-intlen.

endcase.

lv_decimals = ls_dfies-decimals.

* offset management with a dummy filed (if needed)
lv_off = ls_dfies-offset - ( ls_tmp_dfies-offset + ls_tmp_dfies-intlen ).

if lv_off gt 0.

lo_elem = cl_abap_elemdescr=>get_x( lv_off ).
ls_comp-type = lo_elem.
add 1 to lv_count.

concatenate zcl_dyn_remote_type_builder=>offset '_' lv_count into ls_comp-name.

append ls_comp to result.

free: lo_elem.

endif.

* field management by means of internal abap types
ls_comp-name = ls_dfies-fieldname.

* build element
lo_elem = zcl_dyn_remote_type_builder=>get_elemdescr( i_inttype = ls_dfies-inttype
i_intlen = lv_intlen
i_decimals = lv_decimals ).

* assign element
ls_comp-type = lo_elem.

append ls_comp to result.

ls_tmp_dfies = ls_dfies.

clear: ls_dfies.

endloop.

catch cx_parameter_invalid_range into lx_parameter_invalid_range.

raise exception lx_parameter_invalid_range.

endtry.

In this nugg you could see a concrete example of how it works.

I would like to special thank my colleague fabrizio.gemma for discovering and solving a bug involving datatype INT1 and INT2 on method GET_ELEM_DESCR of ZCL_DYN_REMOTE_TYPE_BUILDER class and hans.pettersson for discovering and solving a bug concerning substructure includes. The code shown two boxes above has been updated according to their suggestion: since there isn't any GET_* method in CL_ABAP_ELEMDESCR class for datatype INT1 and INT2 Fabrizio solved applying the DESCRIBE_BY_DATA method to declared variables of these types and casting the result; Hans solved the problem reviewing offset calculation logic and dummy field generation.


Starting from this point i extended remote runtime data-typing to handle also nested structures and nested internal tables (the nugg already includes these changes). For more details i refer you to my new blog.


The nugg contains:

  • class ZCL_DYN_REMOTE_TYPE_BUILDER whose static methods + CREATE_STRUCT_TYPE+ and CREATE_TABLE_TYPE build structype and tabletype object that a variable could handle at runtime.

  • exception class ZCX_DYN_REMOTE_TYPE_BUILDER (and message classZDYNTYPEBUILDER)

  • program ZDYNSTRUCTBUILDERDEMO that shows an example remote calling function module "BAPI_USER_GET_DETAIL" (supposing result structures aren't available locally)


Recently i spent a little bit of my free time to modernize my code, reviewing and rewriting it in a full object oriented way. I also improved it with additional features for both remote typing and querying.


The new nugg can be downloaded there. There's a program named zsrqldemo showing some code examples.

12 Comments
andrea_olivieri
Contributor
0 Kudos
Thanks you for sharing your great idea.
This way avoids replication of the FM interface's structures in the client system.
The benefits are obvious, especially after a release upgrade.
Former Member
0 Kudos
Nice idea and it avoids data redundancy across sysems.
manwell77t
Explorer
0 Kudos

fixed a unicode bug!

manwell77t
Explorer
0 Kudos

Thanks to my colleague fabrizio.gemmafor discovering and solving a INT1 and INT2 datatype bug!

Former Member
0 Kudos

thanks to you manuel for this great blog!

Former Member
0 Kudos

Nice, but the Unicode alignment logic doesn't hold (unless all components are clike). There are two flaws, as I see it:

1. In the calculation of the gap length (lv_off), both offsets (may be built up of both clike and non-clike components) are divided by the Unicode character representation, but the length of the preceding component (lv_prelen) is divided by the Unicode character representation only for clike.

This can result in situations like the following, assuming unicodelg = 2:

NAME INTTYPE INTLEN OFFSET

A         X                        1              0

B         C                        8              2

C         X                        8            10

D         I                         4             20

Here, the alignment gap between A and B is 1 byte, but it won't be filled by a dummy component since it is calculated as:

   lv_off = 2 / 2 - ( 0 / 2 + 1 ) = 0

The gap between C and D is 2 bytes but is calculated as:

  lv_off = 20 / 2 - ( 10 / 2 + 8 ) = -3

2. The gaps are assumed to be full character gaps (dummy components are created with cl_abap_elemdescr=>get_c). Single-byte gaps like in the first example above cannot be filled.

My suggestion would be to simply calculate a possible gap as:

  lv_off =     ls_dfies-offset -   ( ls_tmp_dfies-offset + ls_tmp_dfies-intlen ).

and to create any dummy components with cl_abap_elemdescr=>get_x( lv_off ).

manwell77t
Explorer
0 Kudos

Hi Hans,

it was just a meditation looking at the code or did you notice a bug, a field misplacement or anything else?

Ask this because i tested the suggested case from a Unicode System to both Unicode/Non Unicode RFC connected systems and i didn't get any issue. In Unicode-Unicode RFC connection my dynamically typed line is exactly as dictionary defined. In Unicode-Non Unicode RFC connection my dynamically typed line has an additional field named OFFSET_01 between field 3 and field 4 but no side-effects: moving corresponding fields from a valued structure typed as dictionary and my dynamically typed line everything's fine.

Former Member
0 Kudos

Hi Manuel,

Actually found it as a bug. I had developed something similar to your solution but got alignment problems when calling a Unicode system. I then found your article and tested its filling logic, which took care of most, but not all, of the gaps (four out of six in my case). That's when I started to analyse it in more depth and drew the conclusions above. I switched to the logic I indicated, and that solved the problem.

manwell77t
Explorer
0 Kudos

Hi Hans,

i explored and tested my code much more in depth (in fact it is quite rare to exchange x data type) but sincerely i didn't notice any issues. What i did is to test an rfc call to a module exporting a valued line with 4 fields: F1[RAW(1)], F2[CHAR(4)], F3[RAW(8)], F4[INT4(10)] and assigning the result to a line dynamically built [based on dictionary on same rfc destination]. I tested all 4 cases [Unicode->NonUnicode, Unicode->Unicode, NonUnicode->Unicode and NonUnicode->NonUnicode] and i didn't notice any field misplacement or truncation. Everything seems to be fine. In Unicode->NonUnicode and Non Unicode->NonUnicode call there's a generated offset field between F3 and F4 but field mapping is fine; the offset field remains empty when returning from rfc call.

You downloaded my github nugg or, since you developed something similar, you adapted your developments based on my filing logic?

Anyway, if you want to go deeper into the problem we can share much more technical details by email.

Former Member
0 Kudos

Hi Manuel,

I didn't download your code, I incorporated your filling logic into my development. Will drop you a mail with the details.

manwell77t
Explorer
0 Kudos

Hi Hans, thanks for sharing with me your scenario. I did some tests and you're right. The issue is due to the presence of structure includes; as far as i tested your fix seems ok for both Unicode/Non Unicode communication. So i updated my nugg (and my blog). Thank you very much.