C stubs for a binary library (on windows)

Hi there!
I am writing an ocaml wrapper for an existing windows library. I have a .h and a .lib. I don’t know which compiler has been used to produce the .lib, most likely cl.
I’m using the Ocaml for windows environment, which comes with recent versions of ocaml and opam, as well as a pre-configured C.compiler:

$ $CC --version
x86_64-w64-mingw32-gcc (GCC) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

I can build and link my stubs and library correctly. However, at execution time, I get a segfault. I did a minimum example with just a single C file and my library, compiled with the provided $CC: still a segfault.

I then downloaded another C compiler (TDM GCC). I’ve compiled my minimal C example: it works !
So I’ve tried to use this C compiler for ocamlopt, by putting the following dune file at the root of my project:

(env
    (_ 
        (ocamlopt_flags (-cc gcc))
    )
)

Hélas, when I build, it does not find flexdll symbols:

$ dune build tests/api/main.exe
ocamlopt tests/api/main.exe (exit 2)
(cd _build/default && C:\OCaml64\home\XXXXX\.opam\4.10.0+mingw64c\bin\ocamlopt.opt.exe -w @1..3@5..28@30..39@43@46..47@49..57@61..62-40 -strict-sequence -strict-formats -short-paths -keep-locs -cc gcc -o tests/api/main.exe src/api/api.cmxa -I src/api -I src/api/lib tests/api/.main.eobjs/native/dune__exe__Main.cmx)
C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/OCaml64/home/amonnier/.opam/4.10.0+mingw64c/lib/ocaml\libasmrun.a(win32_n.o): in function `caml_dlopen':
/home/appveyor/.opam/ocaml-variants.4.10.0+mingw64c/.opam-switch/build/ocaml-variants.4.10.0+mingw64/runtime/win32.c:232: undefined reference to `flexdll_wdlopen'

[… similarly missing symbols: flexdll_wdlopen, flexdll_dump_exports, flexdll_dlopen, flexdll_dlclose, flexdll_dlsym, flexdll_dlerror]

    /lib/libmingw32.a(lib64_libmingw32_a-crt0_c.o): in function `main':
    C:/crossdev/src/mingw-w64-v7-git20191109/mingw-w64-crt/crt/crt0_c.c:18: undefined reference to `WinMain'
    collect2.exe: error: ld returned 1 exit status
    File "caml_startup", line 1:
    Error: Error during linking

I’m running short of ideas: does it make sense to try to use this other compiler ? are there extra flags needed to tell it where to find these flexdll and WinMain symbols ? And why the hell do I get a segfault with $CC and not with gcc ?

Any clue will be deeply appreciated,

It is hard to say without more information, but I don’t think that using -cc will work: especially on Windows, the linking step is delicate and it uses flexlink, it won’t work with a plain C compiler. If it were me, I would try to understand why/where it segfaults first.

Best wishes,
Nicolás

By the way, are you sure you can use a static library compiled by msvc with mingw?

Best wishes,
Nicolás

Well, it works with TDM GCC, which as far as I understand is a flavor of mingw. I could not spot anything related to static library linking. I could try other compilers though, maybe msys2 ?

Trying to understand the detail of the segfault is a good idea. There is no gdb provided with the mingw coming with the ocaml install. I tried with the one coming with TDM GCC, it works but does not give very valuable information:

(gdb) break main
Breakpoint 1 at 0x40155d: file api_test.c, line 6.
(gdb) run
Starting program: C:\Users\XXXX\repos\XXXX\src\api\bin\a1.exe
[New Thread 55804.0xd08c]

Thread 1 hit Breakpoint 1, main () at api_test.c:6
6           int ret = 0;
(gdb) step
7           ret = Process_InitParams("A", "B", 15.5);
(gdb) step
0x00000000000086d8 in ?? ()
(gdb)

Without surprise, the segfault is on the call to the function implemented in the static lib.
If I look in the executable, it seems to be properly defined:

$ nm a.exe | grep Process_InitParams
00000000004083a8 D __imp_Process_InitParams
00000000004015a0 T Process_InitParams

If I run gdb on the same exe but compiled with TDM GCC, I have the expected behavior (in that case an error as the parameters passed to the Process_InitParams function are dummy ones) :

(gdb) break main
Breakpoint 1 at 0x40156d: file api_test.c, line 6.
(gdb) run
Starting program: C:\Users\XXXX\repos\XXXX\src\api\bin\a2.exe
[New Thread 59032.0xb368]
[New Thread 59032.0xb718]
[New Thread 59032.0x3198]
[New Thread 59032.0xfbfc]

Thread 1 hit Breakpoint 1, main () at api_test.c:6
6           int ret = 0;
(gdb) step
7           ret = Process_InitParams("A", "B", 15.5);
(gdb) step
[New Thread 59032.0xc6ac]
[New Thread 59032.0xee30]
[New Thread 59032.0xe9dc]
C:\ANSYS/configurations.cfg
Cnet notification level 4 : Process::init --> Configuration B doesn't exist ! Invalid configuration: B
8           printf("%d\n", ret);
(gdb) step
9           return ret;
(gdb) step
10      }

My main C file is really the most stupid thing one could write:

#include <XXXX.h>
#include <stdio.h>
int main(void)
{
    int ret = 0;
    ret = Process_InitParams("A", "B", 15.5);
    printf("%d\n", ret);
    return ret;
}

I build my executables with the following commands:
$CC api_test.c -Iinclude -Llib -lXXXX -g
to use the mingw provided with ocaml and
gcc api_test.c -Iinclude -Llib -lXXXX -g
for the one provided by TDM GCC.
They are using exactly the same gcc version.

$ $CC --version
x86_64-w64-mingw32-gcc (GCC) 9.2.0

and

$ gcc --version
gcc.exe (tdm64-1) 9.2.0

OK, after some research by a colleague, it appears it’s a known issue of mingw not being able to link with msvc-produced libraries in some cases.

For future reference, the solution has been to use opam to create a 4.10.0+msvc64c switch (opam create switch 4.10.0+msvc64c) in order to install the ocaml compiler compiled with MSVC. Then use ocaml-env:

eval $(ocaml-env cygwin --ms=vs2017)

in the cygwin environment to setup everything before the build.
Works like a charm. Thanks @oandrieu !

1 Like