How to Expand Functionality¶
The wrapping features of Shroud are controlled by data files which are read in upon startup. This allows Shroud to wrap many types of arguments without any additional input from the user. However, there will always be situations that require some additional ability. This section describes the input data files and how a user may add to them.
Shroud can be thought of as a fancy macro processor. It reads the input and performs lots of redundant, tedious replacements. One function argument causes several layers of code to be generated which involve transformations of the input. To help manage the redundency, several layers of abstraction are provided.
The lowest layer is the typemap. It factors
out some common fields for individual types. For example, most
‘native’ types such as int and double will produce identical
statements except for things such as Fortran kind (C_INT,
C_DOUBLE), intrinsic functions (int, real) and C types
(int, double).
The next layer is the format dictionary. This is created for each argument for each language. Some values are computed from the argument description and attribute. Other values can be added directly by the user in the YAML file.
Finally, statements use format entries to generate code at the many locations required to create the wrappers.
Tutorial¶
This tutorial will work through how a statement group is developed
for a Fortran wrapper.
The function fetchCharPtr returns a pointer to a char array
in one of the arguments.
options:
debug: True
wrap_c: False
declarations:
- decl: void fetchCharPtr(char **fetch1+intent(out))
Note the use of the debug option.
This will write additional comments into the generated code that will list
the statement groups used for each argument and the function result.
Running Shroud will initially report that the statment group is not found.
Phase: FillMeta function
----------------------------------------
Node: fetchCharPtr
line 15
Unknown statement: f_out_char**_cdesc_pointer_library
The first step is to define the statement group by name.
Since this is for a builtin statement group, it is added to
shroud/fc-statements.json.
The group name is derived from the language, intent, type and defaults for the +api and +deref attributes.
The usage and notes sections are optional, but will help document the intended usage of the group.
{
"name": "f_out_char**_cdesc_pointer_library",
"usage": [
"char **arg +intent(out)"
],
"notes": [
"Return a Fortran pointer to C memory."
]
}
Shroud
will now generate wrapper in Three parts. The interface, the Fortran
wrapper and the C wrapper.
interface
! ----------------------------------------
! Function: void fetchCharPtr
! Statement: f_subroutine
! ----------------------------------------
! Argument: char **fetch1 +intent(out)
! Statement: f_out_char**_cdesc_pointer_library
subroutine fetch_char_ptr() &
bind(C, name="TES_fetchCharPtr_bufferify")
implicit none
end subroutine fetch_char_ptr
end interface
Since there are no code required in the Fortran wrapper, only the interface is necessary. The debug option will show how the Fortran wrapper would be created, but it is conditionally compiled out.
#if 0
! Only the interface is needed
! ----------------------------------------
! Function: void fetchCharPtr
! Statement: f_subroutine
! ----------------------------------------
! Argument: char **fetch1 +intent(out)
! Statement: f_out_char**_cdesc_pointer_library
subroutine fetch_char_ptr()
! splicer begin function.fetch_char_ptr
call c_fetch_char_ptr_bufferify(fetch1)
! splicer end function.fetch_char_ptr
end subroutine fetch_char_ptr
#endif
// ----------------------------------------
// Function: void fetchCharPtr
// Statement: f_subroutine
// ----------------------------------------
// Argument: char **fetch1 +intent(out)
// Statement: f_out_char**_cdesc_pointer_library
void TES_fetchCharPtr_bufferify(void)
{
// splicer begin function.fetchCharPtr_bufferify
fetchCharPtr();
// splicer end function.fetchCharPtr_bufferify
}
While it is possible to define every field of the statement group, it
is usually better to build up the group by using mixin groups. The
mixin groups are also in fc-statements.json. These groups
encapsulate parts of the wrapper that can be reused by many other
groups.
"name": "f_out_char**_cdesc_pointer_library",
"usage": [
"char **arg +intent(out)+deref(pointer)"
],
"notes": [
"Return a Fortran pointer to C memory."
],
"mixin": [
"f_mixin_dummy_arg",
"f_mixin_cdesc_char_pointer",
"f_mixin_cdesc_pass_to_cwrapper",
"c_mixin_cdesc_fill_char",
"c_mixin_local-cvar-ptr",
"c_mixin_arg-call-cvar-address"
],
The command line option --write-statements will create a
file with the final form of each statement group. The final statement
group becomes:
name: f_out_char**_cdesc_pointer_library
intent: out
comments:
- Assign Fortran pointer from cdesc using helper pointer_string.
- Pass cdesc as argument to C wrapper.
- Fill cdesc from char * in the C wrapper.
- Declare a local pointer variable in the C wrapper.
- Pass address of cxx variable to library.
notes:
- Return a Fortran pointer to C memory.
usage:
- char **arg +intent(out)+deref(pointer)
mixin_names:
- ' f_mixin_dummy_arg'
- ' f_mixin_cdesc_char_pointer'
- ' f_mixin_cdesc_pass_to_cwrapper'
- ' c_mixin_cdesc_fill_char'
- ' c_mixin_header_cstring'
- ' c_mixin_local-cvar-ptr'
- ' c_mixin_arg-call-cvar-address'
f_dummy_arg:
- '{f_var}'
f_dummy_decl:
- 'character(len=:){f_intent_attr}{f_deref_attr} :: {f_var}'
f_local_decl:
- 'type({F_array_type}) :: {f_var_cdesc}'
f_arg_call:
- '{f_var_cdesc}'
f_post_call:
- "call {f_helper_pointer_string}(\t{f_var_cdesc},\t {f_var})"
f_temps:
- cdesc
f_need_wrapper: true
i_dummy_arg:
- '{i_var_cdesc}'
i_dummy_decl:
- 'type({F_array_type}), intent(OUT) :: {i_var_cdesc}'
i_import:
- '{F_array_type}'
c_prototype:
- '{C_array_type} *{c_var_cdesc}'
c_pre_call:
- '{c_const}{c_type} *{c_var};'
c_arg_call:
- '&{cxx_var}'
c_post_call:
- "{c_var_cdesc}->base_addr =\t const_cast<char *>(\t{cxx_var});"
- '{c_var_cdesc}->type = {typemap.sh_type};'
- '{c_var_cdesc}->elem_len = {cxx_var} == {nullptr} ? 0 : {stdlib}strlen({cxx_var});'
- '{c_var_cdesc}->size = 1;'
- '{c_var_cdesc}->rank = 0;'
c_temps:
- cdesc
helper:
- pointer_string
- array_context
- type_defines
impl_header:
- <cstring>
The final Fortran wrapper become:
subroutine fetch_char_ptr_library(outstr)
character(len=:), intent(OUT), pointer :: outstr
! splicer begin function.fetch_char_ptr_library
type(CHA_SHROUD_array) :: SHT_outstr_cdesc
call c_fetch_char_ptr_library_bufferify(SHT_outstr_cdesc)
call CHA_SHROUD_pointer_string(SHT_outstr_cdesc, outstr)
! splicer end function.fetch_char_ptr_library
end subroutine fetch_char_ptr_library
With an interface:
interface
subroutine c_fetch_char_ptr_library(outstr) &
bind(C, name="CHA_fetchCharPtrLibrary")
use iso_c_binding, only : C_PTR
implicit none
type(C_PTR), intent(OUT) :: outstr
end subroutine c_fetch_char_ptr_library
end interface
And the C wrapper:
void CHA_fetchCharPtrLibrary_bufferify(
CHA_SHROUD_array *SHT_outstr_cdesc)
{
// splicer begin function.fetchCharPtrLibrary_bufferify
char *outstr;
fetchCharPtrLibrary(&outstr);
SHT_outstr_cdesc->base_addr = const_cast<char *>(outstr);
SHT_outstr_cdesc->type = SH_TYPE_CHAR;
SHT_outstr_cdesc->elem_len = outstr == nullptr ? 0 : std::strlen(outstr);
SHT_outstr_cdesc->size = 1;
SHT_outstr_cdesc->rank = 0;
// splicer end function.fetchCharPtrLibrary_bufferify
}
A Fortran helper function is also added to properly define the LEN of the CHARACTER argument.
! helper pointer_string
! Assign context to an assumed-length character pointer
subroutine CHA_SHROUD_pointer_string(context, var)
use iso_c_binding, only : c_associated, c_f_pointer, C_PTR
implicit none
type(CHA_SHROUD_array), intent(IN) :: context
character(len=:), pointer, intent(OUT) :: var
character(len=context%elem_len), pointer :: fptr
if (c_associated(context%base_addr)) then
call c_f_pointer(context%base_addr, fptr)
var => fptr
else
nullify(var)
endif
end subroutine CHA_SHROUD_pointer_string