How to create standalone applications for Windows with a [G]UI in Julia 1.11 or 1.12

As a new user allow me a general question on the state of the art of Julia and how to use it for commercial but OpenSource real-life applications.

We design and produce sensors of different types (humidity, temperature etc.). My job is to develop reference implementations on how to use the sensors, how to integrate them in products etc. I came to Julia because of its abilities to deal with scientific things like solving differential equations and machine learning.

The task is to get a program customers can use. That means I need a UI for my programs and a program that runs on Windows 10 or newer.

I already coded some nice solutions where for example measurement data is analyzed by pre-trained neuronal networks. All my programs are .jl files I execute in VS Code. But of course I cannot give customers bare code to execute it by themselves. We will provide the source code as OpenSource since the sensors are our business, not the code. However, I need a solution to compile a standalone executable out of my .jl files. For example the user must be able to enter parameters via a UI, use sliders to change values etc. (I e.g. chemists needs sensors to control reactions but he is no programmer. It is not his job to look at code but to do chemistry.)

I searched around but maybe it is better to ask you here what you can recommend for the 2 tasks I have:

A. standalone, precompiled application that runs under Windows
B. a UI

A paid service is acceptable as long as my customers don’t have to pay something for the final program. We will take over the costs.

I heard in this thread that Julia 1.12 will bring a solution for task A. . Can anybody please point me to that that I can give it a try (now that 1.12 beta is out).

I also have the need that the final application must provide reproducible run times. Meaning if e.g. 1000 measurement data are evaluated, this must take the same time every time the user does this. It is not acceptable that if the user runs the program the first time things are much slower than on the further executions. So during the start of the application everything must be already loaded before the first evaluation takes place.

Edit: In the past I wrote these programs using Lazarus (an IDE with a UI editor). For this I used for example FORTRAN DLL’s. This works fine. So maybe there is a way to source out my Julia libraries for the machine learning as a DLL?

7 Likes

Yes, if you already have a Lazarus-based UI, you can call your Julia code by using PackageCompiler in the Library mode.
You can use Julia with many of the existing widget toolkits (e.g. List of platform-independent GUI libraries - Wikipedia) by calling them as C libraries, or through available Julia wrappers. Alternatively, you could take the approach you’ve identified and build your GUI natively using one of these toolkits and then just call Julia.
Note that PackageCompiler as described will do this today, though the results may be a very big DLL. The 1.12 update takes a step toward trimming that down and will really start to make Julia practical for this sort of development. More to come as this is further refined and features like cross-compilation are made easier.
I’m interested in hearing more about how Lazarus works for you, if you go down this road. I’ve been experimenting with a number of toolkits, including Lazarus.

2 Likes

That would also be an option. But then I would have to write an installer like e.g. NSIS that installs Julia and all necessary Julia packages.

I heard about 500 to 1000 MB. Is that true? That would definitively too much to be accepted by my users.

  • Lazarus works great. It is in my opinion still the UI toolkit with which you are the fastest to create a UI because it is directly integrated in the IDE. Also, I always need to plot data and Lazarus’ TAChart component is great for that. And Lazarus’ is free also for commercial usage.
  • Qt is also great, but not free and the price policy is a problem as in the past they raised the prices out of a sudden. You can expect this to happen again.
  • GTK or wxWidgets both have no native charts component.

Regarding external DLLs, it is easy to include them in Lazarus. So what I would need is e.g. a DLL of Julia code wrapped as c-callable function as you linked

But before I dive deep into how I can create such a library, I would give it a try with an existing library. Do you have a link for me to get one to try out if they run in Lazarus?

PackageCompiler will actually do this for you by putting all of the dependencies in the library/bundle (e.g. libjulia.dll).

As with apps, generating a library bundles together Julia and all dependencies in a (hopefully) redistributable directory structure that can be moved to other machines (of the same architecture). (from the PackageCompiler documentation).

I heard about 500 to 1000 MB. Is that true?

Yup. But this is where the --trim option in 1.12 will start to make a big difference.

Qt is also great, but not free

While I’m not a fan of Qt, there is an LGPL-style license if you dynamically link. More or less the same as GTK – you’ll need to dynamically link to stay proprietary.

no native charts component

I’ll offer that the Julia ecosystem has lots of graphical functions with both web and OpenGL rendering capabilities. A little more work, but much more reward.

Do you have a link for me to get one to try out if they run in Lazarus?

Offhand, no, and note the above caveat about no cross-compilation. I’d recommend doing a “hello,world”-style test library that exposes a simple function, and do all of this on your machine first so you don’t have to try to get both the C ABI and machine portability working at the same time. This will also give you a sense of the size of literally a “hello,world” minimal library, as well as how PackageCompiler lays out the bundle. Note that, for deployment, there are some things in the bundle you can strip such as the C header file it generates.

There is a basic example in the PackageCompiler source: PackageCompiler.jl/examples/MyLib at master ¡ JuliaLang/PackageCompiler.jl

I found both Jeff’s JuliaCon Local 2023 talk and the JuliaCon 2024 talk highly informative regarding the options that already are there or will be in the future.

PackageCompiler will actually do this for you by putting all of the dependencies in the library/bundle (e.g. libjulia.dll).

How large are the dependencies, would you say, once --trim is enabled? I see that libjulia.dll and libjulia-internal.dll are about 15 MB combined, but are there others?

PackageCompiler.jl + CImGui.jl is my option. Here’s a MWE:

using PackageCompiler

create_app("CImGuiApp.jl", # folder name
    "CImGuiApp_Compiled",
    force=true,
    # incremental = true, # should keep false to create fresh apps
    include_lazy_artifacts=true,
    executables = [
        "CImGuiApp" => "julia_main",
    ],
)
module CImGuiApp

using CEnum
using CSyntax
using DocStringExtensions


using CImGui
using CImGui.lib
using CImGui.CSyntax

import GLFW
import ModernGL as GL

using ImPlot
ig = CImGui  # name
import CSyntax: @c, @cstatic

include("E://JuliaAstroSim//CImGui.jl//examples//demo_window.jl")

CImGui.set_backend(:GlfwOpenGL3)


function julia_demo(; engine=nothing)
    # setup Dear ImGui context
    ctx = CImGui.CreateContext()

    # ImPlot
    pctx = ImPlot.CreateContext()
    ImPlot.SetImGuiContext(ctx)
    show_demo_window = true


    # enable docking and multi-viewport
    io = CImGui.GetIO()
    io.ConfigFlags = unsafe_load(io.ConfigFlags) | CImGui.ImGuiConfigFlags_DockingEnable
    io.ConfigFlags = unsafe_load(io.ConfigFlags) | CImGui.ImGuiConfigFlags_ViewportsEnable

    # setup Dear ImGui style
    CImGui.StyleColorsDark()
    # CImGui.StyleColorsClassic()
    # CImGui.StyleColorsLight()

    # When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
    style = Ptr{ImGuiStyle}(CImGui.GetStyle())
    if unsafe_load(io.ConfigFlags) & ImGuiConfigFlags_ViewportsEnable == ImGuiConfigFlags_ViewportsEnable
        style.WindowRounding = 5.0f0
        col = CImGui.c_get(style.Colors, CImGui.ImGuiCol_WindowBg)
        CImGui.c_set!(style.Colors, CImGui.ImGuiCol_WindowBg, ImVec4(col.x, col.y, col.z, 1.0f0))
    end

    # load Fonts
    # - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use `CImGui.PushFont/PopFont` to select them.
    # - `CImGui.AddFontFromFileTTF` will return the `Ptr{ImFont}` so you can store it if you need to select the font among multiple.
    # - If the file cannot be loaded, the function will return C_NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
    # - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling `CImGui.Build()`/`GetTexDataAsXXXX()``, which `ImGui_ImplXXXX_NewFrame` below will call.
    # - Read 'fonts/README.txt' for more instructions and details.
    fonts_dir = joinpath(@__DIR__, "..", "fonts")
    fonts = unsafe_load(CImGui.GetIO().Fonts)
    # default_font = CImGui.AddFontDefault(fonts)
    # CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Cousine-Regular.ttf"), 15)
    # CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "DroidSans.ttf"), 16)
    # CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Karla-Regular.ttf"), 10)
    # CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "ProggyTiny.ttf"), 10)
    # CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Roboto-Medium.ttf"), 16)
    CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Mono Casual-Regular.ttf"), 16)
    CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Mono Linear-Regular.ttf"), 16)
    CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Sans Casual-Regular.ttf"), 16)
    CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Sans Linear-Regular.ttf"), 16)
    # @assert default_font != C_NULL

    # create texture for image drawing
    img_width, img_height = 256, 256
    image_id = nothing

    demo_open = true
    clear_color = Cfloat[0.45, 0.55, 0.60, 1.00]

    CImGui.render(ctx; engine, clear_color=Ref(clear_color)) do
        if isnothing(image_id)
            image_id = CImGui.create_image_texture(img_width, img_height)
        end

        demo_open && @c ShowJuliaDemoWindow(&demo_open)

        # show image example
        if CImGui.Begin("Image Demo")
            image = rand(GL.GLubyte, 4, img_width, img_height)
            CImGui.update_image_texture(image_id, image, img_width, img_height)
            CImGui.Image(image_id, CImGui.ImVec2(img_width, img_height))
            CImGui.End()
        end

        # ImPlot
        show_demo_window && @c ImPlot.ShowDemoWindow(&show_demo_window)
        @cstatic f=Cfloat(0.0) counter=Cint(0) begin
        if ig.Begin("Hello, world!")
            framerate = unsafe_load(ig.GetIO().Framerate)

            @c ig.Checkbox("Show ImPlot Demo", &show_demo_window)
            ig.Text(@sprintf("Application average %.3f ms/frame (%.1f FPS)",
                             1000 / framerate, framerate))

            ig.End()
        end
    end
    end
end


function julia_main()::Cint
    println("haha")
    # do something based on ARGS?

    # Run automatically if the script is launched from the command-line
    if !isempty(Base.PROGRAM_FILE)
        CImGui.set_backend(:GlfwOpenGL3)

        julia_demo()
    end

    return 0 # if things finished successfully
end

end # module CImGuiApp

6 Likes

I have compiled a non-trivial but light on dependencies project with the Julia 1.12 juliac script on Linux and manually pared down the dependent libraries. This left libjulia, libjulia-internal and eight more libraries with a total size of ~40 MB, plus 5 MB for the compiled binary.

I hope the picture will be similar on Windows but until I have managed to resolve a linker problem I don’t have any figures.

1 Like

Given that you are already giving users a source code, you also have the option to ship a precompiled Julia distribution that starts the application. When compressed in MSIX, archive, in my experience, gives around 300…400 MB installer, which may or may not be acceptable. This is the approach taken by AppBundler.jl project on which I have expanded in a recent answer: How are non programmers to use Julia Apps - #4 by Janis_Erdmanis

I also encourage the use of QML. LGPL license allows Qt to be used in a dynamical linking context even if the software product is proprietary. Their current strategy is to deprecate LGPL components with GPL ones, which is the only catch one needs to be aware of when using Qt/QML. The bindings in Julia via QML.jl are of superb quality.

1 Like

Thanks, 50 MB total sounds pretty reasonable.

I have not been able to get any juliac workflows working on Windows. Maybe you are running into similar errors.

I’m curious if you can provide more information on this, as we’re watching Qt very closely due to it being used internally for some projects. My understanding was that the essentials cannot be moved to a more restrictive license, as discussed here. This is a little dated; has something changed more recently? I would fully expect that anything new would not be LGPL, as this isn’t in the best interest of the Qt company.

Qt cannot move its already LGPL-licensed code to a more restricted license due to a KDE safeguard that entitles them to a BSD license if they do so or don’t release a new version within a 12-month period. However, they can release new components with more restrictive licenses, such as GPL. (this is discussed well in your provided source)

With the Qt5 to Qt6 transition, they deprecated GraphicalEffects and introduced a new effects system with better performance and API, which they released under a GPL license. Fortunately, there is a Qt5Compat that allows you to use the old graphical effects system to stay within the LGPL licensing boundary. So, it does not bother me much.

1 Like
Section split into PackageCompiler hang

Thanks. I gave it a try, and the result is disappointing: I took the example as it is and followed the instructions but it hang up at compiling a “nonincremental system image”. (I have no clue what that term actually means.)

Also, [Package Compiler] installed the mingw compiler despite I have the MSVC compiler installed that I use for ally my C++ projects. I could not tell PackageCompiler.jl to use that compiler.

I was also not able to understand how the example was created. There are many different files without a documentation. For example what is the file “generate_precompile.jl” for? Or what “precompilation” actually means. There is also a file “additional_precompile.jl”. What is that for, is it necessary and if yes, for what? Why can’t it be part of generate_precompile.jl, …

So in the end a spend a lot of time and nothing is clear to me. I just need to compile my Julia module to a DLL, no fancy stuff, no precompilation or whatever.

I searched for other docs and videos but cannot find something that helps me. There are only technical talks about PackageCompiler not step by step example of to create a DLL out of a module.

It seems I did not yet really understand the concept of Julia. How do people use Julia for real-life applications? I mean at the end of the day you must deliver a program that can be used by users. And with users I mean normal persons that cannot read or handle program code. I cannot give a user a script he has to execute.

1 Like

Many thanks. The point is that in my experience I spend 80% or more of the time on programming the UI. One needs to much time until everything is bug free, meaning only those knobs and sliders are currently active that should be allowed in a particular state etc.

I don’t know if I can achieve this with ImGui. It cost me about a year to learn Qt in a way to deliver program that can be used by end users. For Lazarus you can drop the components you like in the designer and its code is automatically added. You can also set up the different UI features via the UI of the Lazarus IDE. So in about 2 hours you can complete the design of your UI including menus, icons, file dialogs and the like. In principle this is like the Qt Creator.

Therefore to have a chance to deliver the code I already write in Julia I must for now go the way to compile it to a DLL and then use the DLL from either a Lazarus program of a C++ program using Qt.

2 Likes

This is a great observation. It’s good to think about Julia a little differently from “traditional” compiled languages, at least as it currently stands. It essentially compiles on the fly (JIT or AOT… that’s kind of an argument), and redoes this as necessary. This works well enough for interactive computing, but starts to run into problems in other scenarios. Precompilation more-or-less forces compilation when you want it to happen, thereby reducing that first-run pause.
Not sure why there would be a dependency on your C compiler setting?

Section split into PackageCompiler hang

Did you get an error or did it just hang? How long did you wait? It might not be the fastest thing… :wink:
Nonetheless, what you’re seeing is that, for this kind of use case, Julia is currently a little rough around the edges, as we say. But that’s why there’s a fair amount of work going into the compiler.

An alternative could be to provide them with an interactive Jupyter or Pluto notebook.

2 Likes

25 posts were merged into an existing topic: PackageCompiler hangs while compiling nonincremental system image

We have am misunderstanding: Nobody pays his lab workers 50 $ an hour to look at e.g. a FOR loop. Confronting hard-working and high-paid persons with bare programming code is not acceptable.

Just imagine the user changes something in the code, the code does then not work properly, in my case the sensor data is no longer read out or processed properly. Then this user fore sure looses an hour of work time, thus generating costs for his employer and in turn I get an angry email what happened. We have to give a guarantee that the software we deliver works.

OpenSource does not mean that users have to see the code just by working with a program. Interested users, researchers, developers etc. can take the code and modify it. But then they are responsible on their own.

2 Likes

You can make extremely visual dashboards with Pluto (or potentially other solutions) that completely hide the code from the “end user”. Of course, this still means that these users have to install Julia, but that might not nearly be as problematic as you think it is. If you look at the Interactivity notebook from the Pluto docs, click on the “Edit or run this notebook” button in the top right, the instructions under point 2 would be what is relevant to your users. I find it hard to imagine that professional engineers would not be willing or able to get this running (barring hurdles from corporate policies of “cannot install anything”).

I’m pretty sure 99% of Julia users do not deliver binaries. They deliver libraries, Jupyter/Pluto notebooks, Web dashboards, reports, etc. As a computational scientist, even when I was working with fully compiled languages such as Fortran, I’d still be “shipping” code and expect the “user” to be running make to compile it.

What you are asking for is at the very bleeding edge of Julia development, and is not a typical use case. There is no question that Julia would benefit from the work that is currently undergoing with juliac (e.g., to improve deployability of Julia in embedded systems, or to reasonably have development tools that don’t have to be written in Rust). But right now, your only options are solutions like PackageCompiler that produce extremely large binaries due to packaging the entire Julia runtime, or the extremely experimental juliac. Even the former one is not exactly mainstream, and I would be highly skeptical of the second one being stable enough for “deployment to customers” (but I haven’t tried it).

I wish you the best of luck in trying to figure out a solution, but it may not be possible with the current state of things. Personally, if I had to ship a GUI application, I would still be looking at a non-Julia frontend, maybe talking to a Julia backend. Or a web application / dashboard. That’s not to say that your desire to wrap existing Julia code in an easily shippable GUI is unreasonable. But my impression is that we’re still several years away from this really being something that can be done entirely in Julia. But maybe the people involved in the Herculean efforts towards that goal will assure me that the timeline might be sooner than “several years”; I was certainly pleasantly surprised by the juliac prototype, which I had thought to be near-impossible.

9 Likes