Using C struct in Julia without creating same struct in Julia again

Hi, we have ccall to call any c function. But, if the function we want to call has a parameter which is non-primitive type such as a struct, do we always have to create same struct in Julia? For example, I have this c code.

#include <StaticLibrary.h>

typedef struct 
{
    struct StaticLibrary* thing;
} Bar;

typedef struct 
{
    Bar b;
    char* t;
} Foo;

void myFunction(Foo* foo, char t*)
{
//code
}

I want to use myFunction with ccall I generate a shared library from my C code, and I use it. it works. But I need to create same structs in Julia too. so, Why? I can write same function in Julia too! And I also have a StaticLibrary and struct StaticLibrary* thing; is coming from there, I don’t have the declaration of that struct for creating it in julia. I hope, I could explain myself… Sorry for my English. So my question is “do we always have to create same struct in Julia to pass argument?”

If the interface of your C code is all about passing handles/opaque pointers, then there is no need to map those underlying structures on the Julia side. You could make everything a Ptr{Cvoid} or define a opaque singleton struct mutable struct Foo end and use Ptr{Foo}.

I tried mutable struct Foo end and using Ptr{Foo} . but I get an Error. It is Julia Part:

using Libdl
libJulia3D = dlopen("../build/libJulia3D.so")
    
j3d_window_create = dlsym(libJulia3D, "j3d_window_create")
j3d_window_initialize = dlsym(libJulia3D, "j3d_window_initialize")
j3d_window_destroy = dlsym(libJulia3D, "j3d_window_destroy")


mutable struct J3DWindow end

function init(window, width, height, title)
    ccall(j3d_window_create, Cchar, (Ptr{J3DWindow}, Csize_t, Csize_t, Cstring), window, width, height, title)
end

#Main
window = J3DWindow()

init(window, 600, 400, "abc")

and my c part is:

JULIA3D_CORE struct J3DWindow
{
    GLFWwindow* m_window;
    size_t m_width;
    size_t m_height;
    char* m_title;
};

JULIA3D_CORE bool j3d_window_create(J3DWindow* window, size_t width, size_t height, const char* title)
{
    window->m_width = width;
    window->m_height = height;
    window->m_title = title;
}

That’s because J3DWindow in C is not an opaque pointer.

typedef JULIA3D_CORE struct _J3DWindow
{
    GLFWwindow* m_window;
    size_t m_width;
    size_t m_height;
    char* m_title;
} *J3DWindow;

JULIA3D_CORE bool j3d_window_create(J3DWindow window, size_t width, size_t height, const char* title)
{
    window->m_width = width;
    window->m_height = height;
    window->m_title = title;
}

now, is it opaque right? if not, can you explain?

ohh sir, I changed
ccall(j3d_window_create, Cchar, (Ptr{J3DWindow}, Csize_t, Csize_t, Cstring), window, width, height, ) part as ccall(j3d_window_create, Cchar, (Ref{J3DWindow}, Csize_t, Csize_t, Cstring), window, width, height, ) it works, thanks for all your answers.

No.

https://stackoverflow.com/questions/7553750/what-is-an-opaque-pointer-in-c

In short, you should not expose the definition of _J3DWindow in the header file. To work with opaque pointers, static_cast/reinterpret_cast/C-style cast should be correctly used in the function definition.

This is not right. Ref{J3DWindow}/Ptr{J3DWindow} is a pointer to J3DWindow which is a pointer to _J3DWindow. But your C function expects a pointer to _J3DWindow.

yeah you are right but Ptr{J3DWindow} in Julia is _J3DWindow* in C. Yeah it is little bit confusing because of names.

Indeed. :sweat_smile:

BTW, you may find Clang.jl is useful for auto-generating Julia bindings from C headers.

1 Like

You might also want to look at using CBinding.jl for more faithfully preserved names and types in the bindings, so you might have fewer surprises.

I have a similar question to @m_yasinhan, with a slight differences.

The function that I want to call from C is:
usb_dev_handle* __stdcall xxusb_device_open(struct usb_device *dev);

Where usb_device is defined as:

struct usb_device {
  struct usb_device *next, *prev;

  char filename[LIBUSB_PATH_MAX];

  struct usb_bus *bus;

  struct usb_device_descriptor descriptor;
  struct usb_config_descriptor *config;

  void *dev;		/* Darwin support */

  unsigned char devnum;

  unsigned char num_children;
  struct usb_device **children;
};

In Julia, I defined two structs as:

mutable struct UDH end
mutable struct UD end
ud = UD()

Then, I call the c funtion as:

ccall((:xxusb_device_open, "libxxusb.dll"), Ref{UDH}, (Ref{UD},), ud)

#Also tried this
#ccall((:xxusb_device_open, "libxxusb.dll"), Ptr{UDH}, (Ptr{UD},), ud)

This way has always worked with me except for this function/struct where I get the following error:

Please submit a bug report with steps to reproduce this fault, and any error messages that follow (in their entirety). Thanks.
Exception: EXCEPTION_ACCESS_VIOLATION at 0x5b503d85 -- usb_set_configuration at C:\WINDOWS\SYSTEM32\libusb0.dll (unknown line)
in expression starting at REPL[5]:1
usb_set_configuration at C:\WINDOWS\SYSTEM32\libusb0.dll (unknown line)
xxusb_device_open at C:\WINDOWS\SYSTEM32\libxxusb.dll (unknown line)
top-level scope at .\REPL[5]:1
jl_fptr_args at /cygdrive/c/buildbot/worker/package_win32/build/src\gf.c:2016
_jl_invoke at /cygdrive/c/buildbot/worker/package_win32/build/src\gf.c:2247 [inlined]
jl_invoke at /cygdrive/c/buildbot/worker/package_win32/build/src\gf.c:2254
jl_toplevel_eval_flex at /cygdrive/c/buildbot/worker/package_win32/build/src\toplevel.c:876
jl_toplevel_eval_flex at /cygdrive/c/buildbot/worker/package_win32/build/src\toplevel.c:830
jl_toplevel_eval at /cygdrive/c/buildbot/worker/package_win32/build/src\toplevel.c:894 [inlined]
jl_toplevel_eval_in at /cygdrive/c/buildbot/worker/package_win32/build/src\toplevel.c:944
eval at .\boot.jl:373 [inlined]
eval_user_input at C:\buildbot\worker\package_win32\build\usr\share\julia\stdlib\v1.7\REPL\src\REPL.jl:150
repl_backend_loop at C:\buildbot\worker\package_win32\build\usr\share\julia\stdlib\v1.7\REPL\src\REPL.jl:246
start_repl_backend at C:\buildbot\worker\package_win32\build\usr\share\julia\stdlib\v1.7\REPL\src\REPL.jl:231
#run_repl#47 at C:\buildbot\worker\package_win32\build\usr\share\julia\stdlib\v1.7\REPL\src\REPL.jl:364
run_repl at C:\buildbot\worker\package_win32\build\usr\share\julia\stdlib\v1.7\REPL\src\REPL.jl:351
jl_fptr_args at /cygdrive/c/buildbot/worker/package_win32/build/src\gf.c:2016
_jl_invoke at /cygdrive/c/buildbot/worker/package_win32/build/src\gf.c:2228 [inlined]
jl_apply_generic at /cygdrive/c/buildbot/worker/package_win32/build/src\gf.c:2429
#936 at .\client.jl:394
_jfptr_YY.936_32814.clone_1 at C:\Users\alqasem.2\AppData\Local\Programs\Julia-1.7.3\lib\julia\sys.dll (unknown line)
_jl_invoke at /cygdrive/c/buildbot/worker/package_win32/build/src\gf.c:2228 [inlined]
jl_apply_generic at /cygdrive/c/buildbot/worker/package_win32/build/src\gf.c:2429
jl_apply at /cygdrive/c/buildbot/worker/package_win32/build/src\julia.h:1788 [inlined]
jl_f__call_latest at /cygdrive/c/buildbot/worker/package_win32/build/src\builtins.c:757
#invokelatest#2 at .\essentials.jl:716 [inlined]
invokelatest at .\essentials.jl:714 [inlined]
run_main_repl at .\client.jl:379
exec_options at .\client.jl:309
_start at .\client.jl:495
_jfptr__start_34933.clone_1 at C:\Users\alqasem.2\AppData\Local\Programs\Julia-1.7.3\lib\julia\sys.dll (unknown line)
_jl_invoke at /cygdrive/c/buildbot/worker/package_win32/build/src\gf.c:2228 [inlined]
jl_apply_generic at /cygdrive/c/buildbot/worker/package_win32/build/src\gf.c:2429
jl_apply at /cygdrive/c/buildbot/worker/package_win32/build/src\julia.h:1788 [inlined]
true_main at /cygdrive/c/buildbot/worker/package_win32/build/src\jlapi.c:559
jl_repl_entrypoint at /cygdrive/c/buildbot/worker/package_win32/build/src\jlapi.c:701
jl_load_repl at /cygdrive/c/buildbot/worker/package_win32/build/cli\loader_lib.c:220
mainCRTStartup at /cygdrive/c/buildbot/worker/package_win32/build/cli\loader_exe.c:42
BaseThreadInitThunk at C:\WINDOWS\System32\KERNEL32.DLL (unknown line)
RtlGetAppContainerNamedObjectPath at C:\WINDOWS\SYSTEM32\ntdll.dll (unknown line)
RtlGetAppContainerNamedObjectPath at C:\WINDOWS\SYSTEM32\ntdll.dll (unknown line)
Allocations: 2722 (Pool: 2717; Big: 5); GC: 0

Does anyone know why this cannot work, and how to fix it?

Thank you