# \[ANN\] JuliaLibWrapping.jl: bringing Julia libraries to C and Python users

**URL:** <https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150>\
**Category:** Package Announcements\
**Tags:** python, static-compilation\
**Created:** [September 1, 2026, 11:36pm UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150 "2026-09-01T23:36:24Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [September 1, 2026, 11:36pm UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/1 "2026-09-01T23:36:24Z")

</div>

I’m pleased to formally announce [JuliaLibWrapping.jl](https://github.com/JuliaInterop/JuliaLibWrapping.jl) (JLW), a tool for turning Julia packages into shared libraries with C and Python interfaces. This project has been a close collaboration with @Jorge_Vieyra, with important contributions from the [JuliaC.jl](https://github.com/JuliaLang/JuliaC.jl) developers and Frederick Ekre.

The overall goal is to extend our community’s impact, by making Julia packages available to users of other programming languages. Currently JLW supports Python and C, but R and Matlab are likely to follow soon.

JLW lets package authors expose a curated API that can be installed and used like a native component of the target ecosystem. Python users get an ordinary package with NumPy integration and Python exceptions; C users get a shared library and header. The package can bundle its Julia runtime dependencies, so users need neither a separate Julia installation nor a Julia project to manage.

This makes JLW an alternative to [JuliaCall](https://juliapy.github.io/PythonCall.jl/stable/juliacall/). JuliaCall offers Python rich, dynamic access to Julia itself, but requires that users install Julia. JLW instead exposes a bounded, compiled library interface: less open-ended, but encapsulated behind a stable ABI and usable from languages other than Python.

JLW builds on [JuliaC.jl](https://github.com/JuliaLang/JuliaC.jl) and includes `JLWInterop`, which provides common ABI conventions for arrays, strings, ownership, and error handling. The aim is to make these concerns shared infrastructure rather than something every package must design independently.

JuliaLibWrapping v0.2 and JLWInterop v0.2 are available now and require Julia 1.13 or later.

- Repository: [JuliaInterop/JuliaLibWrapping.jl](https://github.com/JuliaInterop/JuliaLibWrapping.jl)
- Documentation: [stable](https://JuliaInterop.github.io/JuliaLibWrapping.jl/stable/) | [development](https://JuliaInterop.github.io/JuliaLibWrapping.jl/dev/)
- End-to-end introduction: [Your first wrapper](https://JuliaInterop.github.io/JuliaLibWrapping.jl/stable/quickstart/)
- There’s a draft PR of a real-world binding [here](https://github.com/HolyLab/MatrixCovers.jl/pull/43). (The package itself is still under development, but it made a good test case.)

We expect further improvements in the coming months, but JLW should already be fairly usable now. We’d love feedback from package authors and users of mixed-language systems, so don’t hesitate to post here or file an issue.

---

<div class="post-metadata">

**Author:** ![ufechner7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ufechner7/32/51363_2.png) [@ufechner7](https://discourse.julialang.org/u/ufechner7)\
**Post date:** [September 2, 2026, 2:08am UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/2 "2026-09-02T02:08:06Z")

</div>

Great news!

Not clear to me: must the Julia libraries and package dependencies be compilable with JuliaC? Is there a list of supported packages somewhere?

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [September 2, 2026, 7:38am UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/3 "2026-09-02T07:38:32Z")

</div>

Yes, they have to be compatible with JuliaC, and no, there’s no list. My limited experience suggests that most packages will need a little help. For example, [Build a compiled .so and create a Python binding - Pull Request #43 - HolyLab/MatrixCovers.jl - GitHub](https://github.com/HolyLab/MatrixCovers.jl/pull/43) needed:

- two PRs:
  - [Make error interpolation trim-safe - Pull Request #41 - HolyLab/MatrixCovers.jl - GitHub](https://github.com/HolyLab/MatrixCovers.jl/pull/41)
  - [Remove dynamic dispatch from soft-cover internals - Pull Request #42 - HolyLab/MatrixCovers.jl - GitHub](https://github.com/HolyLab/MatrixCovers.jl/pull/42)  
that removed some instances of dynamic dispatch for normal Julia code (these were genuine package improvements)

- `lib/src/trimmability.jl` (~100 lines of real code, + Claude’s insanely long comments 🙂 ) as an “escape hatch” for deeply-nested `; kwargs...` forwarding, which inference eventually gives up on. This is a case where I felt the “fixes” would just make the package worse for Julia users, so these live only in the wrapper code.

For reference, the package as a whole is \>3800LOC excluding docstrings and comments. Overall these were pretty modest changes, and in my experience agents tend to figure them out quite easily.

I should say, though, that YMMV depending on the package. Some packages have dynamic dispatch nearly everywhere, and those will be much more difficult to wrap.

---

<div class="post-metadata">

**Author:** ![BjarkeHautop](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bjarkehautop/32/219291_2.png) [@BjarkeHautop](https://discourse.julialang.org/u/BjarkeHautop)\
**Post date:** [September 2, 2026, 7:55am UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/4 "2026-09-02T07:55:00Z")

</div>

Nice work!

What are your thoughts on the planned R package and how to make that work for package developers? CRAN requirements for packages are they should be at most 10MB [https://cran.r-project.org/](https://cran.r-project.org/), which seems hard to satisfy with this approach. Is it just meant for non-CRAN packages?

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [September 2, 2026, 8:05am UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/5 "2026-09-02T08:05:03Z")

</div>

No real idea yet. I agree that for some packages, that’s going to be a hard goal to meet. (For that MatrixCovers package, the `.so` was 5MB.) One current obstacle is that Julia’s runtime needs to be “privatized,” to allow users to combine two or more Julia packages in a single Python/R/whatever session. The better long-term solution will be to link to a single shared runtime, and that will allow the sizes of each of the libraries to shrink.

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [September 2, 2026, 8:43am UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/6 "2026-09-02T08:43:54Z")

</div>

Cool! This will be ideal for SolarPosition.jl as well, since it’s mostly base julia. Biggest challenge is that most packages I tried aren’t compatible with trimming, fixing that will take a lot of effort.

Maybe trivial now, but I would also be interested to see how you are going to actually ship the python bindings to pypi. I guess you want to do this automatically via a github action, to keep the Python package in sync with the Julia releases?

---

<div class="post-metadata">

**Author:** ![nilshg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nilshg/32/2283_2.png) [@nilshg](https://discourse.julialang.org/u/nilshg)\
**Post date:** [September 2, 2026, 9:57am UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/7 "2026-09-02T09:57:51Z")

</div>

Maybe of interest for @droodman

---

<div class="post-metadata">

**Author:** ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)\
**Post date:** [September 2, 2026, 11:38am UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/8 "2026-09-02T11:38:44Z")

</div>

I ran into an old problem of Julia’s runtime interfering with stdio in a C program. Here’s a MWE of the problem when embedding a Julia library in a C program. The Julia code exports a trivial function for computing an integer plus one. The C program runs a loop that keeps reading an integer from stdin, adding one to it (by calling the Julia function), and printing it to stdout, until reaching the end of stdin. The problem symptoms show up when I use pipes in a shell in Linux:

```plaintext
# { echo 1; echo 2; } | ./add-one-test
2
3

# { echo 1; sleep 1; echo 2; } | ./add-one-test
2

```

The second command should also print “3”. Instead, the program stops after the first value while the pipe is temporarily empty.

The Julia code is:

```julia
# mylib.jl
module mylib

using JLWInterop

addone(x) = x + 1

@api addone(x::Int64)::Int64

end

```

The C program is:

```c
/* add-one-test.c */

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

#include "mylib.h"

int main(void)
{
    int64_t input;

    while (scanf("%" SCNd64, &input) == 1) {
        JLWResult_Int64 result = mylib_addone(input);
        if (result.status.code != 0) {
            fprintf(stderr, "mylib_addone failed with status %" PRId32 "\n",
                    result.status.code);
            return 1;
        }

        printf("%" PRId64 "\n", result.value);
    }

    return 0;
}

```

---

<div class="post-metadata">

**Author:** ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)\
**Post date:** [September 2, 2026, 12:12pm UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/9 "2026-09-02T12:12:22Z")

</div>

The exact same problem also shows up when I use Python instead of C:

```python
from mylib_py import addone

while True:
    try:
        print(addone(int(input())), flush=True)
    except EOFError:
        break

```

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [September 2, 2026, 1:15pm UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/10 "2026-09-02T13:15:24Z")

</div>

> [@tim.holy](#):
>
> an ordinary package with NumPy integration and Python exceptions; C users get a shared library and header.

I’m looking this over. It seems well documented, uses `OffsetArray`s, so I suppose makes 0-based for C and Python users, everything as expected (only types compatible with C supported mapping to float and double, but both to float in Python, and BigInt or machine integers there?) except maybe:

> `Array{T,N}` arguments must be dense, writable when the Julia function mutates them, and use the carrier’s column-major layout. The generated Python helper accepts contiguous one-dimensional arrays. For `N ≥ 2`, it requires a Fortran-contiguous NumPy array; use `np.asfortranarray(a)` when appropriate.

I.e. Julia’s column-major is retained (same as Fortran and R, unlike C/C++ and Python’s row-major), as sort of expected. Non-issue for 1D, works for any other only C and Python users need to be aware of. Do you always make a C API, then optionally Python API, and it is then on top of the C API? Just like is often a thing for Python packages, but that C API is not your concern. R often wraps C++ code, while R is 1-based like Julia, so when that is supported, would R API be also built on the C API, or not because of OffsetArray? Do you think you could generate something callable from R in addition to C and Python? Ideally with one command and getting one .so out? And DLL etc. for Windows, that seemed supported.

Is the setup for the producer, rather than the consumer, still also rather simple? And what about when your code changes, do you need to regenerate and edit some files? JuliaCall will be simpler for the “producer”, basically no work, not much if anything produced, but is it a bit harder for the consumer?

| Declared Julia type | Argument carrier | Return carrier | Python value |
| --- | --- | --- | --- |
| scalar | same type, by value | same type, by value | int, float, or bool |
| String | CString{:borrowed} | CString{:owned} | str |
| Vector{String} | CStrArray{:borrowed} | CStrArray{:owned} | list[str] |
| Dict{String,V} for scalar V | CDict{:borrowed,V} | CDict{:owned,V} | dict[str, V] |
| .. | | | |

[C doesn’t have a Dict, so I’m not sure supported in the C API, CDict implies it might, or only in Python API, i.e. implementation detail for it?]

Python users may expect `dict` to be ordered (since the new default in all currently supported Python versions), so it may be surprising to them that it isn’t. At least could be documented. If you use `OrderedDict`, is that also supported and mapped to (or from) Python’s dict? Are only `Vector`s (and `Dict`s) of `String`s supported, or such just an example?

Do you support same or more types as JuliaCall? As an example, I’m not sure if this is only for JuliaCall or in both direction:

> **[NumpyDates - Reference · PythonCall & JuliaCall](https://juliapy.github.io/PythonCall.jl/stable/pythoncall-reference/#NumpyDates)**
>
> Convert x to a Python object, of type Py. | Documentation for PythonCall & JuliaCall.

> 0.9.32 (2026-05-14)  
> ..  
> JuliaCall now launches Julia with 1 thread by default.  
> ..  
> Initial experimental support for free-threaded Python 3.14.

I suppose you also support 1 thread, not more or non-default?

---

<div class="post-metadata">

**Author:** ![asinghvi17](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asinghvi17/32/8272_2.png) [@asinghvi17](https://discourse.julialang.org/u/asinghvi17)\
**Post date:** [September 2, 2026, 6:26pm UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/11 "2026-09-02T18:26:14Z")

</div>

I’m not super familiar with how the Python and R packaging ecosystems work, but do we know what would be needed to get these distributed there? I have a couple of potential packages I’d like to do this for 😃

I assume we’d have to lock the versions of libraries Julia needs, like openssl, openblas, etc. That could be a bit of a sticking point, though if we get native linking that all goes away.

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [September 2, 2026, 7:08pm UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/12 "2026-09-02T19:08:37Z")

</div>

> **[Bundling for distribution - Building and distribution · JuliaLibWrapping.jl](https://juliainterop.github.io/JuliaLibWrapping.jl/dev/building/#Bundling-for-distribution)**
>
> standard\_build handles the conventional package layout. Use build\_library when you need to select targets, paths, bundling, or other compilation options explicitly. | Documentation for JuliaLibWrapping.jl.

> Test a distribution with `pip install` in a clean virtual environment on such a machine.

I’m not involved with the project just posting what seems relevant above and:

> Pass `privatize = false` only when that isolation is knowingly unnecessary.
> 
> Only the Julia runtime libraries are privatized. BLAS, Fortran, and unwind libraries may still be shared. Bundles produced by different Julia versions have not been established as mutually compatible. This behavior has been measured on Linux; macOS and Windows follow different loader rules and have not been tested equivalently.

> [@asinghvi17](#):
>
> I assume we’d have to lock the versions of libraries Julia needs, like openssl, openblas, etc

I suppose this will not be a problem, no more than for `JuliaCall` / PythonCall.jl (not sure about for R for the future or or what currently happens when it’s used with RCall.jl, hopefully also just works…) that would have had the exact same version problems. For at least OpenBLAS (maybe OpenSSL, as I recall there was such a problem, I think already solved).

[The rest of my answer is in edit history (my whole post was flagged, I guess for the AI answer in the end, so I deleted it, I still think that part was correct).]

---

<div class="post-metadata">

**Author:** ![cshen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshen/32/217287_2.png) [@cshen](https://discourse.julialang.org/u/cshen)\
**Post date:** [September 3, 2026, 7:45am UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/13 "2026-09-03T07:45:57Z")

</div>

> [@tim.holy](#):
>
> Yes, they have to be compatible with JuliaC,

I guess this is to be read as “trimmable”? Or does this also work with a package compiled with JuliaC without the `--trim` flag?

---

<div class="post-metadata">

**Author:** ![Jorge\_Vieyra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jorge_vieyra/32/6527_2.png) [@Jorge\_Vieyra](https://discourse.julialang.org/u/Jorge_Vieyra)\
**Post date:** [September 3, 2026, 11:04am UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/14 "2026-09-03T11:04:42Z")

</div>

@cshen `--trim` is a requirement. When we say JuliaC we imply `--trim`. But might change at some point if JuliaC expands for other cases.

---

<div class="post-metadata">

**Author:** ![Jorge\_Vieyra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jorge_vieyra/32/6527_2.png) [@Jorge\_Vieyra](https://discourse.julialang.org/u/Jorge_Vieyra)\
**Post date:** [September 3, 2026, 11:14am UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/15 "2026-09-03T11:14:41Z")

</div>

@greatpet `scanf` fails when reading from a non-blocking pipe because it treats the error (`EAGAIN/errno=11`) as `EOF`. This happens because Julia’s runtime initializes and sets `O_NONBLOCK=1` on `stdio` pipes (like `fd 0`) during startup, which is an unwanted side effect when running Julia as a library inside another process. The flag persists after initialization, causing subsequent reads to fail if the pipe is empty (only visible with input gaps, like using `sleep`). A fix in your code would require saving and restoring the file descriptor flags around Julia’s initialization call. Perhaps JuliaC can insert those, not sure.

try:

```julia-auto
int f0 = fcntl(0, F_GETFL);
JLWResult_Int64 result = mylib_addone(input);
fcntl(0, F_SETFL, f0);

```

Not sure if this is a JuliaC issue or a Julia issue, but maybe this can be fixed on JuliaC.

---

<div class="post-metadata">

**Author:** ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)\
**Post date:** [September 3, 2026, 12:36pm UTC](https://discourse.julialang.org/t/ann-julialibwrapping-jl-bringing-julia-libraries-to-c-and-python-users/139150/16 "2026-09-03T12:36:21Z")

</div>

Thanks, your workaround works. I think it’s a Julia issue, not JuliaC issue. In Julia’s `src/init.c`, the function `init_jl_` calls `init_stdio()`:  
[https://github.com/JuliaLang/julia/blob/9d314127b97e6dee14735fa8d6db2d524718b6d8/src/init.c#L724](https://github.com/JuliaLang/julia/blob/9d314127b97e6dee14735fa8d6db2d524718b6d8/src/init.c#L724)  
It would be nice if the Julia runtime supports optional lazy initialization of IO, delaying it until actual Julia IO methods are called.
