Dynamic Libraries (Linux) - Petr - 12-15-2025
I will add SO files (Linux dynamic libraries) to this thread.
A question to the developers first. In the following program, I spent an hour searching for the reason why the IDE (4.2.0) in Linux (virtualized Ubuntu in Windows 10) does not see the SO file. How is it possible that the library name must be different from the actual library name on disk? Why? I don't understand!
I will gradually add more things here, but please be patient.
Zip file contains BAS code, C code for SO library and SO library.
RE: Dynamic Libraries (Linux) - SpriggsySpriggs - 12-15-2025
What's the reason for using a dynamic library rather than compiling with a header into the code?
It works just fine by removing the EXPORT tags on the file, making it a .h header, adding the flags to the compiler in QB64, and then building.
Code: (Select All)
'Declare Dynamic Library "listpick"
' Function PickFromListUTF8& (title As String, prompt As String, items As String, ByVal startIndex As Long)
' Function PickFromListUTF8_Text& (title As String, prompt As String, items As String, ByVal startIndex As Long, outText As String, ByVal outBytes As Long)
'End Declare
Declare CustomType Library "ListPick_GTK"
Function PickFromListUTF8& (title As String, prompt As String, items As String, ByVal startIndex As Long)
Function PickFromListUTF8_Text& (title As String, prompt As String, items As String, ByVal startIndex As Long, outText As String, ByVal outBytes As Long)
End Declare
' --- use ---
title$ = "Select item" + Chr$(0)
prompt$ = "Write for filter, Enter=OK, Esc=Cancel" + Chr$(0)
item:
Data "Alpha","Beta","Gamma","Delta","Etha","Theta","Omikron","Mi","Psi","Kocici","Kachni","Rozum","Zelenej","Linux","To je ale dilo"
Restore item
For f = 1 To 15
Read i$
items$ = items$ + i$ + Chr$(10)
Next f
items$ = items$ + Chr$(0)
out$ = Space$(2048) ' buffer, in which C write text
idx& = PickFromListUTF8_Text&(title$, prompt$, items$, 1, out$, Len(out$))
out$ = ZTrim$(out$)
Print "idx="; idx&, " text="; out$
Function ZTrim$ (s$)
Dim p As Long
p = InStr(s$, Chr$(0))
If p > 0 Then ZTrim$ = Left$(s$, p - 1) Else ZTrim$ = s$
End Function
Code: (Select All) // listpick_gtk.c (GTK3, UTF-8)
// build (bash/zsh):
// gcc -O2 -fPIC -shared -o liblistpick.so listpick_gtk.c $(pkg-config --cflags --libs gtk+-3.0)
// build (fish):
// gcc -O2 -fPIC -shared -o liblistpick.so listpick_gtk.c (pkg-config --cflags --libs gtk+-3.0)
#include <gtk/gtk.h>
#include <string.h>
typedef struct {
GtkWidget *dialog;
GtkWidget *entry;
GtkWidget *list; // GtkListBox
GPtrArray *rows; // index(orig) -> GtkListBoxRow*
int bestOrig; // preferovaný origin index
} Picker;
static int gtk_ready = 0;
static gboolean row_is_effectively_visible(GtkWidget *w) {
// u GtkListBox filtrování typicky schovává řádky přes child_visible, ne přes visible
return gtk_widget_get_child_visible(w);
}
static gboolean contains_nocase_utf8(const char *hay, const char *needle) {
if (!needle || !*needle) return TRUE;
if (!hay) return FALSE;
char *h = g_utf8_casefold(hay, -1);
char *n = g_utf8_casefold(needle, -1);
gboolean ok = (g_strstr_len(h, -1, n) != NULL);
g_free(h);
g_free(n);
return ok;
}
static gboolean row_filter(GtkListBoxRow *row, gpointer user_data) {
Picker *p = (Picker *)user_data;
const char *needle = gtk_entry_get_text(GTK_ENTRY(p->entry));
if (!needle || !*needle) return TRUE;
GtkWidget *child = gtk_bin_get_child(GTK_BIN(row)); // GtkLabel
const char *text = gtk_label_get_text(GTK_LABEL(child));
return contains_nocase_utf8(text, needle);
}
static void ensure_selection(Picker *p) {
GtkListBox *box = GTK_LIST_BOX(p->list);
GtkListBoxRow *sel = gtk_list_box_get_selected_row(box);
if (sel && row_is_effectively_visible(GTK_WIDGET(sel))) return;
// 1) zkus bestOrig (naposledy vybraná položka), pokud je viditelná ve filtru
if (p->bestOrig >= 0 && p->bestOrig < (int)p->rows->len) {
GtkListBoxRow *want = (GtkListBoxRow *)g_ptr_array_index(p->rows, p->bestOrig);
if (want && row_is_effectively_visible(GTK_WIDGET(want))) {
gtk_list_box_select_row(box, want);
return;
}
}
// 2) první viditelný řádek
GList *children = gtk_container_get_children(GTK_CONTAINER(box));
for (GList *l = children; l; l = l->next) {
GtkWidget *w = GTK_WIDGET(l->data);
if (row_is_effectively_visible(w)) {
gtk_list_box_select_row(box, GTK_LIST_BOX_ROW(w));
break;
}
}
g_list_free(children);
}
static void on_entry_changed(GtkEditable *editable, gpointer user_data) {
(void)editable;
Picker *p = (Picker *)user_data;
gtk_list_box_invalidate_filter(GTK_LIST_BOX(p->list));
while (gtk_events_pending()) gtk_main_iteration(); // ať se child_visible přepočte hned
ensure_selection(p);
}
static void on_row_selected(GtkListBox *box, GtkListBoxRow *row, gpointer user_data) {
(void)box;
Picker *p = (Picker *)user_data;
if (!row) return;
int orig = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(row), "orig"));
p->bestOrig = orig;
}
static void on_row_activated(GtkListBox *box, GtkListBoxRow *row, gpointer user_data) {
(void)box; (void)row;
Picker *p = (Picker *)user_data;
gtk_dialog_response(GTK_DIALOG(p->dialog), GTK_RESPONSE_OK);
}
static gboolean on_key_press(GtkWidget *w, GdkEventKey *e, gpointer user_data) {
Picker *p = (Picker *)user_data;
if (e->keyval == GDK_KEY_Escape) {
gtk_dialog_response(GTK_DIALOG(p->dialog), GTK_RESPONSE_CANCEL);
return TRUE;
}
if (e->keyval == GDK_KEY_Return || e->keyval == GDK_KEY_KP_Enter) {
// před OK ještě pro jistotu dorovnej selection podle filtru
ensure_selection(p);
gtk_dialog_response(GTK_DIALOG(p->dialog), GTK_RESPONSE_OK);
return TRUE;
}
if (w == p->entry && (e->keyval == GDK_KEY_Down || e->keyval == GDK_KEY_Up)) {
gtk_widget_grab_focus(p->list);
return FALSE;
}
return FALSE;
}
static void copy_out(char *out, int outBytes, const char *src) {
if (!out || outBytes <= 0) return;
out[0] = 0;
if (!src) return;
g_strlcpy(out, src, (gsize)outBytes);
}
static int pick_run_utf8(const char *title, const char *prompt, const char *items, int startIndex,
char *out, int outBytes) {
if (!gtk_ready) {
int argc = 0;
char **argv = NULL;
gtk_ready = gtk_init_check(&argc, &argv) ? 1 : 0;
if (!gtk_ready) {
copy_out(out, outBytes, "");
return -1;
}
}
Picker p;
memset(&p, 0, sizeof(p));
p.bestOrig = (startIndex >= 0) ? startIndex : 0;
p.rows = g_ptr_array_new();
p.dialog = gtk_dialog_new_with_buttons(
title ? title : "Select",
NULL,
GTK_DIALOG_MODAL,
"_Cancel", GTK_RESPONSE_CANCEL,
"_OK", GTK_RESPONSE_OK,
NULL
);
gtk_window_set_default_size(GTK_WINDOW(p.dialog), 520, 380);
GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(p.dialog));
GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
gtk_widget_set_hexpand(vbox, TRUE);
gtk_widget_set_vexpand(vbox, TRUE);
gtk_box_pack_start(GTK_BOX(content), vbox, TRUE, TRUE, 0);
GtkWidget *lbl = gtk_label_new(prompt ? prompt : "");
gtk_label_set_xalign(GTK_LABEL(lbl), 0.0f);
gtk_label_set_line_wrap(GTK_LABEL(lbl), TRUE);
gtk_box_pack_start(GTK_BOX(vbox), lbl, FALSE, FALSE, 0);
p.entry = gtk_entry_new();
gtk_box_pack_start(GTK_BOX(vbox), p.entry, FALSE, FALSE, 0);
GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_widget_set_hexpand(sw, TRUE);
gtk_widget_set_vexpand(sw, TRUE);
gtk_widget_set_halign(sw, GTK_ALIGN_FILL);
gtk_widget_set_valign(sw, GTK_ALIGN_FILL);
gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
p.list = gtk_list_box_new();
gtk_list_box_set_selection_mode(GTK_LIST_BOX(p.list), GTK_SELECTION_SINGLE);
gtk_widget_set_hexpand(p.list, TRUE);
gtk_widget_set_vexpand(p.list, TRUE);
gtk_widget_set_halign(p.list, GTK_ALIGN_FILL);
gtk_widget_set_valign(p.list, GTK_ALIGN_FILL);
gtk_container_add(GTK_CONTAINER(sw), p.list);
// naplň řádky (items jsou UTF-8, oddělené '\n')
const char *s = items ? items : "";
int orig = 0;
while (*s) {
const char *e = strchr(s, '\n');
size_t len = e ? (size_t)(e - s) : strlen(s);
while (len && s[len - 1] == '\r') len--;
char *line = g_strndup(s, len);
GtkWidget *row = gtk_list_box_row_new();
GtkWidget *t = gtk_label_new(line);
gtk_label_set_xalign(GTK_LABEL(t), 0.0f);
gtk_label_set_ellipsize(GTK_LABEL(t), PANGO_ELLIPSIZE_END);
gtk_container_add(GTK_CONTAINER(row), t);
g_object_set_data(G_OBJECT(row), "orig", GINT_TO_POINTER(orig));
gtk_list_box_insert(GTK_LIST_BOX(p.list), row, -1);
g_ptr_array_add(p.rows, row);
g_free(line);
orig++;
if (!e) break;
s = e + 1;
}
// filtering
gtk_list_box_set_filter_func(GTK_LIST_BOX(p.list), row_filter, &p, NULL);
// signály
g_signal_connect(p.entry, "changed", G_CALLBACK(on_entry_changed), &p);
g_signal_connect(p.list, "row-selected", G_CALLBACK(on_row_selected), &p);
g_signal_connect(p.list, "row-activated", G_CALLBACK(on_row_activated), &p);
g_signal_connect(p.entry, "key-press-event", G_CALLBACK(on_key_press), &p);
g_signal_connect(p.list, "key-press-event", G_CALLBACK(on_key_press), &p);
gtk_widget_show_all(p.list);
gtk_list_box_invalidate_filter(GTK_LIST_BOX(p.list));
gtk_widget_show_all(p.dialog);
// start selection
if (startIndex >= 0 && startIndex < (int)p.rows->len) {
GtkListBoxRow *r = (GtkListBoxRow *)g_ptr_array_index(p.rows, startIndex);
gtk_list_box_select_row(GTK_LIST_BOX(p.list), r);
} else {
ensure_selection(&p);
}
gtk_widget_grab_focus(p.entry);
int resp = gtk_dialog_run(GTK_DIALOG(p.dialog));
// při OK ještě jednou dorovnej selection podle filtru (klik na OK myší by jinak mohl vrátit starou selection)
if (resp == GTK_RESPONSE_OK) {
gtk_list_box_invalidate_filter(GTK_LIST_BOX(p.list));
while (gtk_events_pending()) gtk_main_iteration();
ensure_selection(&p);
}
int result = -1;
if (resp == GTK_RESPONSE_OK) {
GtkListBoxRow *sel = gtk_list_box_get_selected_row(GTK_LIST_BOX(p.list));
if (sel && row_is_effectively_visible(GTK_WIDGET(sel))) {
result = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sel), "orig"));
GtkWidget *child = gtk_bin_get_child(GTK_BIN(sel));
copy_out(out, outBytes, gtk_label_get_text(GTK_LABEL(child)));
} else {
copy_out(out, outBytes, "");
result = -1;
}
} else {
copy_out(out, outBytes, "");
result = -1;
}
gtk_widget_destroy(p.dialog);
while (gtk_events_pending()) gtk_main_iteration();
g_ptr_array_free(p.rows, TRUE);
return result;
}
// -------- exporty pro QB64PE (UTF-8) --------
int PickFromListUTF8(const char *titleUtf8, const char *promptUtf8,
const char *itemsUtf8, int startIndex) {
return pick_run_utf8(titleUtf8, promptUtf8, itemsUtf8, startIndex, NULL, 0);
}
int PickFromListUTF8_Text(const char *titleUtf8, const char *promptUtf8,
const char *itemsUtf8, int startIndex,
char *outUtf8, int outBytes) {
return pick_run_utf8(titleUtf8, promptUtf8, itemsUtf8, startIndex, outUtf8, outBytes);
}
RE: Dynamic Libraries (Linux) - luke - 12-15-2025
(12-15-2025, 11:53 AM)Petr Wrote: How is it possible that the library name must be different from the actual library name on disk? Why? I don't understand! There is an extremely strong Linux (and other Unix?) convention that the dynamic library "foo" can be found on disk in the file "libfoo.so" and QB64 follows suit here.
RE: Dynamic Libraries (Linux) - Petr - 12-15-2025
Thank you for the explanation regarding Linux conventions.
Could you explain why you chose .h over .so? that the code simply doesn't run on my machine. SO works correctly.
Of course, there are always multiple ways to approach this.
RE: Dynamic Libraries (Linux) - SMcNeill - 12-15-2025
Isn't .so similar to the equivalent to a windows .dll?
The .h is the text code before compilation. The .so the (shared object) library after compilation.
Or am I completely off the mark here on this?
RE: Dynamic Libraries (Linux) - Petr - 12-15-2025
@SMcNeill
You're not wrong, it's just as you write. What surprises me is that the compiler reports a missing GTK. But according to Linux, GTK is installed there. It lists the version, it's installed correctly. But I doesn't have deep knowledge about Linux, so I can only guess why it doesn't work for me. H files works fine for me in Windows.
RE: Dynamic Libraries (Linux) - Petr - 12-15-2025
Input Box for Linux. This time use H file.
Code: (Select All)
Declare CustomType Library "./IbLNX"
Function InputBoxUTF8_Text& (title As String, prompt As String, def As String, outText As String, ByVal outBytes As Long)
End Declare
_Title "Linux InputBox"
title$ = "Insert text" + Chr$(0)
prompt$ = "Write value:" + Chr$(0)
def$ = "default" + Chr$(0)
outt$ = Space$(2048)
ok& = InputBoxUTF8_Text&(title$, prompt$, def$, outt$, Len(outt$))
If ok& = 1 Then
outt$ = ZTrim$(outt$)
Print outt$
Else
Print "cancel/error"; ok&
End If
Function ZTrim$ (s$)
Dim p As Long
p = InStr(s$, Chr$(0))
If p > 0 Then
ZTrim$ = Left$(s$, p - 1)
Else
ZTrim$ = s$
End If
End Function
RE: Dynamic Libraries (Linux) - SpriggsySpriggs - 12-15-2025
(12-15-2025, 04:32 PM)Petr Wrote: Thank you for the explanation regarding Linux conventions.
Could you explain why you chose .h over .so? that the code simply doesn't run on my machine. SO works correctly.
Of course, there are always multiple ways to approach this.
Did you make sure to add the flags to the compiler in QB64? If you don't, compilation will fail.
RE: Dynamic Libraries (Linux) - Petr - 12-15-2025
No. Well, do you really expect users to feed the compiler extra flags every time they want to use this utility? That's highly impractical.
RE: Dynamic Libraries (Linux) - SpriggsySpriggs - 12-15-2025
It's a library. With requirements. Once it is embedded in a program, that's it. You wouldn't have to make sure you are deploying your program with another file. You just have a comment in the library or a readme file telling them how to use it. That's it. "Add these flags to QB64 to build with this header". If they have a problem with that, then they're incredibly lazy. Besides, they'd still need the packages installed to use it anyways, regardless if it is a header or SO.
|