Calling a C function from within a Julia program

Hi! I’m using Julia 1.6.1 on a MacBook Pro and trying to use the MISR-Toolkit library of C functions within Julia programs. This library is maintained by NASA/JPL and available from GitHub at

I was able to access function MtkVersion from this library (which requires no argument and retrieves the version number of the Toolkit), so the library is correctly installed:

julia> mtklib = "/Applications/Mtk-1.5.0/lib/libMisrToolkit.so"
"/Applications/Mtk-1.5.0/lib/libMisrToolkit.so"

julia> v = ccall((:MtkVersion, mtklib), Cstring, ())
Cstring(0x000000014487a6f0)

julia> unsafe_string(v)
"1.5.0"

I then wanted to use the C function that converts an ISO 8601 date and time specification (“yyyy-mm-ddThh:mm:ssZ”) into a Julian day number, which features the following header:

MTKt_status MtkDateTimeToJulian(
  const char *datetime, /**< [IN] Date and time (ISO 8601) */
  double *jd /**< [OUT] Julian date */ )
{
    ...
}

However, the following call generates an error message (3) indicating an incorrect argument:

julia> dt = "2021-06-27T10:53:29Z"
"2021-06-27T10:53:29Z"

julia> status = ccall((:MtkDateTimeToJulian, mtklib), Cint, (AbstractString, Float64), dt, jd)
3

Reading through the documentation at

https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/

and in particular the paragraph entitled “When to use T, Ptr{T} and Ref{T}”, I tried to refer to the output argument jd as a pointer type, but got the following error messages:

julia> status = ccall((:MtkDateTimeToJulian, mtklib), Cint, (AbstractString, Ptr{Float64}), dt, jd)
ERROR: MethodError: no method matching unsafe_convert(::Type{Ptr{Float64}}, ::Float64)
Closest candidates are:
  unsafe_convert(::Type{Ptr{Tv}}, ::SuiteSparse.CHOLMOD.Sparse{Tv}) where Tv at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/SuiteSparse/src/cholmod.jl:296
  unsafe_convert(::Type{Ptr{T}}, ::SharedArrays.SharedArray{T, N} where N) where T at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/SharedArrays/src/SharedArrays.jl:361
  unsafe_convert(::Type{Ptr{T}}, ::SharedArrays.SharedArray) where T at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.6/SharedArrays/src/SharedArrays.jl:362
  ...
Stacktrace:
 [1] top-level scope
   @ ./REPL[32]:1

I suspect the problem is specifically related to the characterization of the output argument jd. Thanks in advance for any help in figuring out how to successfully call that C function from a Julia program.

This is a pointer to double, so

julia> jd_ref = Ref{Float64}(0.0)

julia> status = ccall((:MtkDateTimeToJulian, mtklib), Cint, (AbstractString, Ptr{Float64}), dt, jd_ref)

julia> jd = jd_ref[]

@Gnimuc: Thanks for the quick answer. Here is the outcome:

julia> jd_ref = Ref{Float64}(0.0)
Base.RefValue{Float64}(0.0)

julia> status = ccall((:MtkDateTimeToJulian, mtklib), Cint, (AbstractString, Ptr{Float64}), dt, jd_ref)
3

julia> julia> jd = jd_ref[]
> (generic function with 1 method)

julia> jd = jd_ref[]
0.0

So the good point is that Julia does not complain about the ccall command, but I still get an error message (3, instead of 0), and the Julian day number is of course not 0 either. Any other thought?

AbstractString should be Cstring, I think.

@Gnimuc: I had tried this too before, but it does not seem to make any difference:

julia> status = ccall((:MtkDateTimeToJulian, mtklib), Cint, (String, Ptr{Float64}), dt, jd_ref)
3

Cstring and String are different types.

1 Like

@Gnimuc: Indeed, I just realized after pressing the “Reply” button… Now I get a 0 status (no error) and everything works fine:

julia> status = ccall((:MtkDateTimeToJulian, mtklib), Cint, (Cstring, Ptr{Float64}), dt, jd_ref)
0

julia> jd_ref[]
2.4593929538078704e6

Just one last question: is this syntax Ref{Float64}(0.0) the generic way to declare an output argument before calling a C function? Should I do this with all output variables in general? Thanks again for your prompt and efficient help.

is this syntax Ref{Float64}(0.0) the generic way to declare an output argument before calling a C function?

Yes. If you need to use the content(jd) of this Ref variable(jd_ref), it’s necessary to init it in this way(i.e. bind Ref(jd) to a variable name(jd_ref)). If not, you could use a tmp variable like:

jd = 0
ccall((:MtkDateTimeToJulian, mtklib), Cint, (Cstring, Ptr{Float64}), dt, Ref(jd))
# this time, the content of the tmp variable `Ref(jd)` will be populated by the C function,
# but there is no way to get that content on the Julia side.
# jd is still 0. 

BTW, you might be interested in CBinding.jl and Clang.jl which are two packages in the Julia ecosystem that can make life easier when calling C libs in Julia.

3 Likes

@Gnimuc: Great! I’ll look at those two packages. Thanks again: your help is greatly appreciated.

3 Likes

@Gnimuc: Sorry to bother you again, but I still have additional questions about using that C library… When I try to use the C function that converts a Float64 Julian day number into a string instead, I always get a MethodError: no method matching unsafe_convert... error, no matter what I use to initially declare the output variable and to define the type of argument in the ccall itself. For reference, the header of that other C function is as follows:

MTKt_status MtkJulianToDateTime(
  double jd, /**< [IN] Julian date */
  char datetime[MTKd_DATETIME_LEN] /**< [OUT] Date and time (ISO 8601) */ )
{
...
}

where the identifier MTKd_DATETIME_LEN is initialized to the constant 28 in an included .h file. Now, using a syntax similar to the cases above:

julia> mtklib = "/Applications/Mtk-1.5.0/lib/libMisrToolkit.so"
"/Applications/Mtk-1.5.0/lib/libMisrToolkit.so"

julia> jd = 2.4593929538078704e6
2.4593929538078704e6

julia> julia> dt_ref = Ref{String}("")
Base.RefValue{String}("")

julia> status = ccall((:MtkJulianToDateTime, mtklib), Cint, (Float64, Cstring), jd, dt_ref)
ERROR: MethodError: Cannot `convert` an object of type Base.RefValue{String} to an object of type Cstring
Closest candidates are:
  convert(::Type{Cstring}, ::Union{Ptr{Int8}, Ptr{Nothing}, Ptr{UInt8}}) at c.jl:167
  convert(::Type{T}, ::T) where T at essentials.jl:205
Stacktrace:
 [1] cconvert(T::Type, x::Base.RefValue{String})
   @ Base ./essentials.jl:396
 [2] top-level scope
   @ ./REPL[5]:1

or

julia> status = ccall((:MtkJulianToDateTime, mtklib), Cint, (Float64, String), jd, dt_ref)
ERROR: MethodError: Cannot `convert` an object of type Base.RefValue{String} to an object of type String
Closest candidates are:
  convert(::Type{String}, ::String) at essentials.jl:210
  convert(::Type{T}, ::T) where T<:AbstractString at strings/basic.jl:231
  convert(::Type{T}, ::AbstractString) where T<:AbstractString at strings/basic.jl:232
  ...
Stacktrace:
 [1] cconvert(T::Type, x::Base.RefValue{String})
   @ Base ./essentials.jl:396
 [2] top-level scope
   @ ./REPL[6]:1

How should output character strings be defined BEFORE the ccall command, as well as WITHIN the arguments of that command?

Lastly, I have already noted that other C functions in that library declare output arguments as char *arg or char **arg (instead of simply char arg): how should those arguments be referred to, initially and within the ccall?

ccall((:MtkJulianToDateTime, mtklib), Cint, (Float64, Cstring), jd, dt_ref)

The ccall macro implicitly calls Base.cconvert for converting the types of argvalues(jd, dt_ref) to corresponding argtypes(Float64, Cstring).

The error ERROR: MethodError: Cannot convert an object of type... means Julia fails to find a pre-defined cconvert method. In the case above, it’s Base.cconvert(Cstring, Ref{String}("")). Now we know the reason, but how to fix this?

IMO, the first step is to check whether argtypes are matched to those types used in C. Here is the function prototype of MtkJulianToDateTime:

MTKt_status MtkJulianToDateTime( double jd, char datetime[MTKd_DATETIME_LEN] );

The type of datetime is a constant char array, but it’s actually not. When it is used as the argument type in a function prototype, it actually decays to a char pointer char *, so the corresponding Julia type could be Cstring if it’s NUL-terminated or Ptr{Cuchar} if not. In practice, it’s OK to use Ptr{Cuchar} if we don’t know which is the case. You could refer to the Type Correspondences section for further details.

Another thing the C function prototype tells us is the size(MTKd_DATETIME_LEN) of datetime, so the C function MtkJulianToDateTime expects a pointer to a container that can be filled with MTKd_DATETIME_LEN chars. One way to do this in Julia is binding dt_ref to a Vector like dt_ref = Vector{Cuchar}(undef, MTKd_DATETIME_LEN). A Ref with NTuple{MTKd_DATETIME_LEN, Cuchar} as its field type can also work, but it’s not easy to work with this type in Julia.

The final version:

jd = 2.4593929538078704e6
dt_chars = Vector{Cuchar}(undef, MTKd_DATETIME_LEN)
status = ccall((:MtkJulianToDateTime, mtklib), Cint, (Float64, Ptr{Cuchar}), jd, dt_chars)
dt = String(dt_chars)

For char **arg, it’s a container that is populated with char *s, so if it is an input argument, Ptr{UInt8}[pointer("strings1"), pointer("strings2")] with type Vector{Ptr{UInt8}} would be fine.

@Gnimuc: Thanks for your continuing support. Here is the outcome:

julia> dt_chars = Vector{Cuchar}(undef, 28)
28-element Vector{UInt8}:
 0xe0
 0x68
 0x3c
 0x1b
 0x01
 0x00
 0x00
 0x00
 0x30
 0x53
    ⋮
 0x12
 0x01
 0x00
 0x00
 0x00
 0x00
 0x00
 0x00
 0x00

julia> status = ccall((:MtkJulianToDateTime, mtklib), Cint, (Float64, Ptr{Cuchar}), jd, dt_chars)
0

julia> dt = String(dt_chars)
"2021-06-27T10:53:29Z\0\0\0\0\0\0\0\0"

which is the correct outcome. Is it possible to generalize this? I will generally not know in advance how long the output string will be, so what should I specify instead of 28 in this particular case?

There are several ways to do this: read null terminated string from byte vector in julia - Stack Overflow

I added the generator script and generated the bindings using Clang.jl here:

You could pick up one that matches your local platform or just copy-paste the code you need.

Note that, Cchar is used everywhere instead of Cuchar. When converting a Vector{Cchar} to a String, you need to convert Vector{Cchar} to Vector{Cuchar} firstly. But I guess something like this can also work:

jd = 2.4593929538078704e6
dt_chars = Vector{Cuchar}(undef, MTKd_DATETIME_LEN)  # let `Base.cconvert` do the conversion for us   
status = ccall((:MtkJulianToDateTime, mtklib), Cint, (Float64, Ptr{Cchar}), jd, dt_chars)
dt = String(dt_chars)
1 Like

@Gnimuc: Wow! How can you generate so much code for various platforms in so little time? Amazing…

I’ll definitely explore this solution, and thanks for the link to the very useful Stack Overflow page showing how to remove the null characters at the end of a string.

In the meantime, my earlier question was somewhat different: when I call an arbitrary C function that outputs a string variable, I generally do not know what length to expect. For instance, a function may return the path (directory) and filename of a data file, which might be 150 characters long, while another could return a paragraph of text. Should I declare the output variable as dt_chars = Vector{Cuchar}(undef, 10000) just to be on the safe side (but that seems wasteful of memory), or is there a way to tell Julia and/or C that the length of the output variable will only be known at run time?

We don’t know unless the C API tells us.

In the case of this library, it uses constant array types in the function prototype instead of pointers. I think the purpose here is to use those fixed-size array types to inform users that the size of the array can be safely set to certain compiler time constant macro values.

Different C libraries have different API styles for this problem. For example, some libraries may add another variable to the function prototype and users need to query the size of the container in advance.

Hi @Gnimuc and colleagues,

I have hit a snag in my attempt to write Julia wrappers for the C functions contained in NASA’s MISR Toolkit available from GitHub - nasa/MISR-Toolkit: an API facilitating the access of MISR standard product files. Specifically, the problematic function is MtkSetRegionByPathBlockRange.c, whose first few lines read as follows:

#include "MisrSetRegion.h"
#include "MisrCoordQuery.h"
#include "MisrError.h"
#include "MisrUtil.h"
#include "MisrOrbitPath.h"
#include "MisrProjParam.h"

/** \brief Select region by path and block range
 *
 *  \return MTK_SUCCESS if successful.
 *
 *  \par Example:
 *  In this example, we select the region on path 39 starting at block 50 and ending at block 60. 
 *
 *  \code
 *  MTKt_region region = MTKT_REGION_INIT;
 *  status = MtkSetRegionByPathBlockRange(39, 50, 60, &region);
 *  \endcode
 */

MTKt_status MtkSetRegionByPathBlockRange(
  int path_number,    /**< [IN] Path */
  int start_block,    /**< [IN] Start Block */
  int end_block,      /**< [IN] End Block */
  MTKt_Region *region /**< [OUT] Region */ )
{
...
}

where the following macros are defined in the include files MisrMapQuery.h and MisrSetRegion.h:

typedef struct {
  double lat;			/**< Latitude in decimal degrees */
  double lon;			/**< Longitude in decimal degrees */
} MTKt_GeoCoord;

#define MTKT_GEOCOORD_INIT { 0.0, 0.0 }

typedef struct {
  MTKt_GeoCoord ctr;		/**< Center of region */
} MTKt_GeoCenter;

#define MTKT_GEOCENTER_INIT { MTKT_GEOCOORD_INIT }

typedef struct {
  double xlat;			/**< Som x or latitude extent in meters */
  double ylon;			/**< Som y or longitude extent in meters */
} MTKt_Extent;

#define MTKT_EXTENT_INIT { 0.0, 0.0 }

typedef struct {
  MTKt_GeoCenter geo;		/**< Region center coordinate in geographic lat/lon */
  MTKt_Extent hextent;		/**< Half of the region overall extent in meters (measured from geo.ctr) */
} MTKt_Region;

#define MTKT_REGION_INIT { MTKT_GEOCENTER_INIT, MTKT_EXTENT_INIT }

I have translated those macros in Julia as follows, and included them in the module MISRToolkit called below:

Base.@kwdef mutable struct MTKt_GeoCoord
    lat::Cdouble = 0.0
    lon::Cdouble = 0.0
end
export MTKt_GeoCoord

mutable struct MTKt_GeoCenter
    ctr::MTKt_GeoCoord
end
export MTKt_GeoCenter

Base.@kwdef mutable struct MTKt_Extent
    xlat::Cdouble = 0.0
    ylon::Cdouble = 0.0
end
export MTKt_Extent

mutable struct MTKt_Region
    geo::MTKt_GeoCenter
    hextent::MTKt_Extent
end
export MTKt_Region

I have encapsulated that C function in a Julia function as follows, where the location of the shared library mtklib is also specified in the module MISRToolkit:

function mtksetregionbypathblockrange!(misr_path, misr_block1, misr_block2, region)
    status = ccall((:MtkSetRegionByPathBlockRange, mtklib),
        MTKt_status,
        (Cint, Cint, Cint, Ptr{MTKt_Region}),
        misr_path, misr_block1, misr_block2, region)
    return status
end

and then created the test function

function chk_mtksetregionbypathblockrange()
    misr_path = convert(Int32, 37)
    misr_block1 = convert(Int32, 32)
    misr_block2 = convert(Int32, 40)
    region = MTKt_Region[]
    status = mtksetregionbypathblockrange!(misr_path, misr_block1, misr_block2, region)
#    println("typeof(region) = ", typeof(region))
#    println("num. elements in region = ", length(region))
#    for i in eachindex(region)
#        println(i, region(i))
#    end
    return status
end

I then re-start Julia and call that test function chk_ three times consecutively, first with all three println statements commented out, then with the first one un-commented, and then the second one also un-commented. This last attempt always results in a segmentation fault which crashes Julia, as does the for loop to print the elements of region:

michel@MicMac2:~$ jMtk
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.8.2 (2022-09-29)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> using MISRToolkit
[ Info: Precompiling MISRToolkit [d1318645-6a27-4cd6-895f-df1b29e726af]

julia> status = chk_mtksetregionbypathblockrange()
MTK_SUCCESS::MTKt_status = 0x00000000

julia> using MISRToolkit

julia> status = chk_mtksetregionbypathblockrange()
typeof(region) = Vector{MTKt_Region}
MTK_SUCCESS::MTKt_status = 0x00000000

julia> using MISRToolkit

julia> status = chk_mtksetregionbypathblockrange()
typeof(region) = 
signal (11): Segmentation fault: 11
in expression starting at REPL[6]:1
jl_gc_pool_alloc_inner at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
jl_gc_alloc at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
_new_array_ at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
_new_array at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
ijl_alloc_array_1d at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
Array at ./boot.jl:459 [inlined]
Dict at ./dict.jl:90
Set at ./set.jl:9 [inlined]
make_typealias at ./show.jl:531
show_typealias at ./show.jl:721
_show_type at ./show.jl:886
show at ./show.jl:881
unknown function (ip: 0x13d1b4566)
ijl_apply_generic at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
print at ./strings/io.jl:35
unknown function (ip: 0x13d1aeea6)
ijl_apply_generic at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
print at ./strings/io.jl:46
ijl_apply_generic at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
do_apply at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
println at ./strings/io.jl:75
ijl_apply_generic at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
println at ./coreio.jl:4
chk_mtksetregionbypathblockrange at /Users/michel/Codes/Julia/MISRToolkit/scripts/chk_mtksetregionbypathblockrange.jl:7
unknown function (ip: 0x13d1b4bb5)
ijl_apply_generic at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
do_call at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
eval_body at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
jl_interpret_toplevel_thunk at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
jl_toplevel_eval_flex at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
jl_toplevel_eval_flex at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
jl_toplevel_eval_flex at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
ijl_toplevel_eval_in at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
eval at ./boot.jl:368 [inlined]
eval_user_input at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-macmini-x64-6.0/build/default-macmini-x64-6-0/julialang/julia-release-1-dot-8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:151
repl_backend_loop at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-macmini-x64-6.0/build/default-macmini-x64-6-0/julialang/julia-release-1-dot-8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:247
start_repl_backend at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-macmini-x64-6.0/build/default-macmini-x64-6-0/julialang/julia-release-1-dot-8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:232
#run_repl#47 at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-macmini-x64-6.0/build/default-macmini-x64-6-0/julialang/julia-release-1-dot-8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:369
run_repl at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-macmini-x64-6.0/build/default-macmini-x64-6-0/julialang/julia-release-1-dot-8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:355
jfptr_run_repl_65939.clone_1 at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/sys.dylib (unknown line)
ijl_apply_generic at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
#967 at ./client.jl:419
jfptr_YY.967_55066.clone_1 at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/sys.dylib (unknown line)
ijl_apply_generic at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
jl_f__call_latest at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
#invokelatest#2 at ./essentials.jl:729 [inlined]
invokelatest at ./essentials.jl:726 [inlined]
run_main_repl at ./client.jl:404
exec_options at ./client.jl:318
_start at ./client.jl:522
jfptr__start_36967.clone_1 at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/sys.dylib (unknown line)
ijl_apply_generic at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
true_main at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
jl_repl_entrypoint at /Applications/Julia-1.8.app/Contents/Resources/julia/lib/julia/libjulia-internal.1.dylib (unknown line)
Allocations: 5507277 (Pool: 5499187; Big: 8090); GC: 6
/Users/michel/Codes/Scripts/bash/jMtk: line 4:  1187 Segmentation fault: 11  julia --project=.
michel@MicMac2:~$ 

I suspect something is improperly setup, though the return code is zero in the first two cases. I was surprised that a probable error on my part could crash Julia with a segmentation fault… But why is it that I cannot inquire about the length of the variable region, which appears to be a Vector, and why would trying to print some information on the screen in the chk_ function result in a segmentation fault? Thanks in advance for any suggestion on how to proceed, especially to retrieve the four numerical values of region after the ccall.

Hi @mmv, does the problem persist if you change region = MTKt_Region[] to region_ref = Ref{MTKt_Region}(); and use region = region_ref[] after running the function mtksetregionbypathblockrange!?

Another problem is the usage of mutable:

Base.@kwdef mutable struct MTKt_GeoCoord
    lat::Cdouble = 0.0
    lon::Cdouble = 0.0
end
export MTKt_GeoCoord

mutable struct MTKt_GeoCenter
    ctr::MTKt_GeoCoord
end
export MTKt_GeoCenter

Base.@kwdef mutable struct MTKt_Extent
    xlat::Cdouble = 0.0
    ylon::Cdouble = 0.0
end
export MTKt_Extent

mutable struct MTKt_Region
    geo::MTKt_GeoCenter
    hextent::MTKt_Extent
end
export MTKt_Region

MTKt_GeoCoord is used in MTKt_GeoCenter. If MTKt_GeoCoord is a mutable struct, Julia does not inline it in the struct MTKt_GeoCenter. As a result, the size of MTKt_GeoCenter on the Julia side and the size of MTKt_GeoCenter on the C side does match, which leads to segment fault at run time. So, only MTKt_Region can be mutable in your example(here, I’m assuming it is not used in other structs).

Hi @Gnimuc,

Thanks for your feedback. I have updated the chk_ function as follows:

function chk_mtksetregionbypathblockrange()
    misr_path = convert(Int32, 37)
    misr_block1 = convert(Int32, 32)
    misr_block2 = convert(Int32, 40)
    region_ref = Ref{MTKt_Region}(); 
    status = mtksetregionbypathblockrange!(misr_path, misr_block1, misr_block2, region_ref)
    region = region_ref[]
    println("typeof(region) = ", typeof(region))
    println("num. elements in region = ", length(region))
    for i in eachindex(region)
        println(i, region(i))
    end
    return status
end

and then tried to use it in a new session, with the following result:

michel@MicMac2:~$ jMtk
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.8.2 (2022-09-29)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> using MISRToolkit
[ Info: Precompiling MISRToolkit [d1318645-6a27-4cd6-895f-df1b29e726af]

julia> status = chk_mtksetregionbypathblockrange()
ERROR: UndefRefError: access to undefined reference
Stacktrace:
 [1] getproperty
   @ ./Base.jl:38 [inlined]
 [2] unsafe_convert
   @ ./refvalue.jl:42 [inlined]
 [3] mtksetregionbypathblockrange!
   @ ~/Codes/Julia/MISRToolkit/src/mtksetregionbypathblockrange.jl:2 [inlined]
 [4] chk_mtksetregionbypathblockrange()
   @ MISRToolkit ~/Codes/Julia/MISRToolkit/scripts/chk_mtksetregionbypathblockrange.jl:6
 [5] top-level scope
   @ REPL[2]:1

julia> 

As far as the typedef definitions are concerned, my initial translation was attempting to stick to the original C code as much as possible. Nevertheless, I have subsequently implemented the following simpler interpretation, which by-passes the intermediary steps:

Base.@kwdef mutable struct MTKt_Region
    lat::Cdouble = 0.0
    lon::Cdouble = 0.0
    xlat::Cdouble = 0.0
    ylon::Cdouble = 0.0
end

but that led to exactly the same error message (starting from a new Julia session, because the nature of the struct MTKt_Region cannot be modified)…