Page 1 of 1

Exporting procedures when compiling against static library

Posted: Sun May 11, 2025 1:54 pm
by tored
I wonder if this is possible with PureBasic, the idea is that create a static library in another language and to compile that with PureBasic to a binary and avoid any extra dynamic dll files. The static library exports functions that PureBasic imports but the static library also imports procedures that PureBasic exports.

I will use dlang and the dmd compiler (ldc2 works as well) to test this. We will compile our dlang code with the betterC flag to avoid any GC stuff.

We will begin to test dlang -> pb with a static library

myfunc.d

Code: Select all

module myfunc;

extern(C) export int addNumbers(int a, int b)
{
    return a + b;
}
We compile this to a obj file with dmd (we use -m64 flag to get COFF format) and then we use polib (from PureBasic installation) to create lib file

Code: Select all

dmd -betterC -m64 -c myfunc.d
polib /out:myfunc.lib myfunc.obj
We import addNumbers() to pb

main.pb

Code: Select all

Import "myfunc.lib"
  addNumbers.l(a.l, b.l)
EndImport

Define result = addNumbers(5, 2)

MessageRequester("Result", "D addNumbers(): " + Str(result))

Code: Select all

pbcompiler main.pb
This should say 7 in the message requester, thus we can create a standalone binary from a static library.

The next step is to export a procedure from pb to dlang to test pb -> dlang

mylib.pb

Code: Select all

ProcedureDLL.l multiplier(value.l)
  ProcedureReturn value * 10
EndProcedure

Code: Select all

pbcompiler /dll mylib.pb /output mylib.dll
This will produce mylib.dll and mylib.lib as a dynamic library.

main.d

Code: Select all

extern(C) int multiplier(int value);

int addNumbers(int a, int b)
{
    int sum = a + b;
    int result = multiplier(sum);
    return result;
}

extern(C) int main()
{
    import core.stdc.stdio : printf;

    int r = addNumbers(2, 5);
    printf("Result: %d\n", r);

    return 0;
}

Code: Select all

dmd -betterC main.d mylib.lib -L/subsystem:console -of=maind.exe
This will create a maind.exe, when running it should say 70. (maind.exe binary requires mylib.dll to be present.)

Now we want to test pb -> dlang ->pb

However this time we shouldn't be using our mylib.dll, it ought to be possible to export our multiplier procedure during compile time that the static lib can use

myfunc.d

Code: Select all

module myfunc;

extern(C) int multiplier(int value);

extern(C) export int addNumbers(int a, int b)
{
    int sum = a + b;
    return multiplier(sum);
}

Code: Select all

dmd -betterC -m64 -c myfunc.d
polib /out:myfunc.lib myfunc.obj
main.pb

Code: Select all

ProcedureDLL.l multiplier(value.l)
  ProcedureReturn value * 10
EndProcedure

Import "myfunc.lib"
  addNumbers.l(a.l, b.l)
EndImport

Define result = addNumbers(5, 2)

MessageRequester("Result", "D addNumbers(): " + Str(result))

Code: Select all

pbcompiler main.pb

Code: Select all

PureBasic 6.20 (Windows - x64)
Compiling main.pb
Loading external libraries...
Starting compilation...
12 lines processed.
Creating and launching executable.
Error: Linker
error: undefined symbol: multiplier
>>> referenced by myfunc.lib(myfunc.obj):(addNumbers)
The linker can't find the multiplier symbol. How can we make sure to export multiplier() during compile time so that the static library myfunc.lib works?

I have tested to export with ProcedureC but that didn't make any difference.

Re: Exporting procedures when compiling against static library

Posted: Mon May 12, 2025 10:33 am
by tored
I managed to get it working for the original ASM backend by adding this line.

Code: Select all

!public _Procedure0 as 'multiplier'
I figured this out by digging around in the forum and looking at the asm output.

To my limited understanding, by exporting multiplier with ProcedureDLL we get a symbol called _Procedure0, however it doesn't have a the correct name we want and it is not accessible during the linking process, by declaring it public and giving it an alias the linker can find it.

Complete example

Compile the D code to create a static lib, same as before

myfunc.d

Code: Select all

module myfunc;

extern(C) int multiplier(int value);

extern(C) export int addNumbers(int a, int b)
{
    int sum = a + b;
    return multiplier(sum);
}

Code: Select all

dmd -betterC -m64 -c myfunc.d
polib /out:myfunc.lib myfunc.obj
main.pb

Code: Select all

!public _Procedure0 as 'multiplier'
ProcedureDLL.l multiplier(value.l)
  ProcedureReturn value * 10
EndProcedure 

Import "myfunc.lib"
   addNumbers.l(a.l, b.l)
EndImport

Define result = addNumbers(5, 2)

MessageRequester("Result", "D addNumbers(): " + Str(result))
This will now create a working standalone binary.

Code: Select all

pbcompiler main.pb
Should show a message request that says 70. Hallelujah, praise the compiler gods!

How to get this working with the C backend is the next problem to investigate.