Step by step instruction and example how to create standalone applications for Windows with a [G]UI

After many hours of troubles, discussions and problems, gave this issue a final try and here is my solution.

I created a complete repository called “Juliastic” that provides these features:

  1. step by step instructions how to compile a DLL using PackageCompiler.
    Note: you must use Julia 1.10.x, with 1.11.4 I got a lot of issues I cannot explain.
  2. instructions how to test the DLL in a C program
  3. a small Lazarus example project that uses the DLL you will compile when following the above instructions

Summing it up over the last days, I had to spend many hours to resolve all issues.

Please give it a try and report any errors you encountered. Please also feel free to fix mistakes you see directly at the Juliastic Wiki.
If you have instructions for other UI widgetsets and OSes, you can also directly create a Wiki page.

25 Likes

I have one issue I did not yet understand: In the examples folder of PackageCompiler there is a file “additional_precompile.jl”. What does it do, why is it there and why can’t is content be directly part of “generate_precompile.jl”.

@ianshmean, @kristoffer.carlsson , do you know this maybe?

Glad you figured it out and thanks for reporting back! This is great.

It could be! There’s nothing fundamentally special about either mechanism for specifying what should get compiled. You can either generate such a list of precompile statements by hand, or semi-automatically (e.g., with an interactive julia --trace-compile=additional_precompile.jl session) or fully automatedly with the execution file. I’m sure the example is only using both so it can demonstrate both kwargs in its doc:

julia> create_library("MyLib", "MyLibCompiled";
                      lib_name="libinc",
                      precompile_execution_file="MyLib/build/generate_precompile.jl",
                      precompile_statements_file="MyLib/build/additional_precompile.jl",
                      header_files = ["MyLib/build/mylib.h"])

Neither of these kwargs are required; you can use both methods, one, or none. Use what works best for you!

3 Likes

Thanks. Now I need to understand what precompile(Tuple{typeof(MyLib.increment64), Clong})
actually does.

And last but not least to understand the concept of precompilation correctly. I mean in generate_precompile.jl a function is called 10 times in a row. Why that, why not just once, 4, 9 or 100 times?

It’s just telling Julia what functions to compile for which argument types (ahead of time). precompile(Tuple{typeof(MyLib.increment64), Clong}) means “compile MyLib.increment64(::Clong)”. It’s the mechanism to move Julia’s just-barely-ahead-of-time (JIT) compiler further ahead of time.

What you pass to precompile_execution_file could indeed be just a single iteration. Or maybe two. Whatever Julia compiles in the process of running that file, will effectively become a precompile directive like that. The more representative your precompiles are, the less time you’ll spend waiting for the JIT later on.

4 Likes

Thanks. I performed now some tests and the outcome is:

  • no matter of precompile() is run 10 times or the function is executed 10 times, the resulting library runs in both cases with the same speed
  • executing the function more than 5 times brings no benefit

I updated my repository accordingly.

Do you need a Codeberg account to do this?

great work, thanks.

Yes.
By the way, I recently discovered Codeberg as alternative to GitHub and it is working well (as far as I can tell after just 1 month of actively using it). It is organized the same way as e.g. KDE - a German assembly where everybody can become a member. So in contrast to GitHub and Gitlab, there is no big tech company behind it.

5 Likes

Thanks, this is certainly a step forward!

I didn’t see any mention of codesigning. This is a requirement if you intend to distribute executables. How do you handle it? Perhaps Windows is more lenient in that respect?

Thanks for having a look. Yes, I did not mention this because this is part of the release process of a program (e.g. on Windows you would sign the installer).

My instruction only covers the process of using the compiled DLL for another UI program. From then on the program can be developed as usual (creating installers, beta testing etc.) but this is independent of whether it contains a DLL created with Julia.

4 Likes

This has not been my experience on MacOS. I’m shipping libraries, not executables.

I have no access to MacOS.
That is why I setup a Wiki. I would be happy, if you

  • make a Codeberg login
  • click on the link for the MacOS Wiki page and create a new page
  • fill in the instructions for MacOS.

The aim is to help people. The information can later be put into other Wiki, official docs or whatever. Important is in my opinion to collect them at first.

1 Like

I can help write the instructions once I get it to work.

1 Like

Just for the records: I added now code that shows how Julia structs and tensors can be exported and used in C or Lazarus.

1 Like

Just for the records: I added now code that shows how C-arrays of double can be imported to Julia and how bools are handled in Lazarus.

(For the array handling it was surprisingly tricky to find out how it is done.)

1 Like

And it was more tricky. I used the code my my real-life application and got random crashes. It turned out that two things are crucial:

  • assure that Julia does not own the data from the external C-caller (in function unsafe_wrap). Otherwise the garbage collector of Julia might free it. The owner is the C-caller and he will free it when no longer used.
  • for every data that is output as pointer, one must create a persistent copy and allocate memory for it. The memory must be free’d once Julia is closed.

I updates the repository accordingly. I also added an example how to input/output a static array to/from Julia.

I made a new release.

Since I use the code for a real-world application (that I will also publish here when completely ready), and I stress-tested it now for a while, I treat the code as production-ready. Therefore the release got the version “1.0”.

3 Likes

It is ready and I published it in this new thread.

Regarding the repository with the instructions, I made a final release.
Thereby I improved the Julia code to make it more flexible for vectors and tensors in different data types and added some safe guards to the Lazarus example program.
The latter turned out to be important in case the compiled library has issues. Now the Lazarus code tests if the library contains every C-callable function and if not, the program terminates correctly.

As I consider the task to get a step by step instruction done and as i was able to use it for a feature-complete real-world program, I treat this thread as resolved.

6 Likes