Skip to content

Instantly share code, notes, and snippets.

@TheGag96
Last active January 9, 2025 07:09
Show Gist options
  • Save TheGag96/b410b0129e9c3a457f2f77b2bf63825f to your computer and use it in GitHub Desktop.
Save TheGag96/b410b0129e9c3a457f2f77b2bf63825f to your computer and use it in GitHub Desktop.
Jai metaprogram: Automagically turn modules with #foreign procedures into dynamically-loaded function pointers
late_load_import :: ($module: Type) -> string #compile_time {
#insert -> string {
module_name := name_of_module(module);
return sprint("LIBRARIES :: Late_Load_Module_%.LIBRARIES;", module_name);
};
using_statements: String_Builder;
for LIBRARIES {
print_to_builder(*using_statements, "using __late_load_library_%;\n", it);
}
return builder_to_string(*using_statements);
}
// @TODO: Helpful error reporting
late_load :: ($module: Type) -> string #expand {
#insert -> string {
module_name := name_of_module(module);
return sprint("LIBRARIES :: Late_Load_Module_%.LIBRARIES;", module_name);
};
// @TODO: Could I generate different code in the metaprogram that would make this less icky?
#insert -> string {
#import "Basic";
loads: String_Builder;
for LIBRARIES {
// @Incomplete:
dynlib_name := it;
if OS == .LINUX && !starts_with(dynlib_name, "lib") {
dynlib_name = sprint("lib%", dynlib_name);
}
print_to_builder(*loads, "late_load(\"%1.so\", type_info(Late_Load_Library_%1), xx *__late_load_library_%1);\n", it);
}
return builder_to_string(*loads);
}
}
// Stolen from modules/GL/GL.jai!
late_load :: (library: *u8, info_struct: *Type_Info_Struct, struct_bytes: *u8, GetProcAddress: (proc: *u8) -> *void #c_call = null) {
#import "Compiler";
table := get_type_table();
// @Incomplete: Other OSs
#if OS == .WINDOWS {
Windows :: #import "Windows";
lib := Windows.LoadLibraryA(library);
}
else #if OS == .LINUX {
POSIX :: #import "POSIX";
lib := POSIX.dlopen(library, POSIX.RTLD_NOW);
}
if !lib return;
for * info_struct.members {
is_intentional_void_pointer := (it.flags & .PROCEDURE_WITH_VOID_POINTER_TYPE_INFO) != 0; // Let us use #type_info_procedures_are_void_pointers.
if (it.type.type == .PROCEDURE) || is_intentional_void_pointer {
ptype := cast(*Type_Info_Procedure) it.type;
if !is_intentional_void_pointer {
assert ((ptype.procedure_flags & .IS_C_CALL) != 0,
"Proc \"%\" is not a #c_call! (member_address %, type_address %, procedure_flags = %, % arguments, % returns, index = %)",
it.name, it, ptype, formatInt(ptype.procedure_flags, base=16), ptype.argument_types.count, ptype.return_types.count,
array_find(table, it.type));
}
// Okay, do the work.
c_string := it.name.data; // Strings in Type_Info are guaranteed to be zero-terminated now. -jblow, 23 December 2018
address : *void;
if GetProcAddress {
address = GetProcAddress(c_string);
} else {
#if OS == .WINDOWS {
address = Windows.GetProcAddress(lib, c_string);
}
else #if OS == .LINUX {
address = POSIX.dlsym(lib, c_string);
}
}
if address {
dest := cast(**void) (struct_bytes + it.offset_in_bytes);
<< dest = address;
}
}
}
}
#scope_module
#import "Basic";
#import "String";
name_of_module :: ($module: Type) -> string #compile_time {
ti := type_info(module);
found, left, right := split_from_left(ti.name, "#import ");
return right;
}
late_load_import :: ($module: Type) -> string #compile_time {
#insert -> string {
module_name := name_of_module(module);
return sprint("LIBRARIES :: Late_Load_Module_%.LIBRARIES;", module_name);
};
using_statements: String_Builder;
for LIBRARIES {
print_to_builder(*using_statements, "using __late_load_library_%;\n", it);
}
return builder_to_string(*using_statements);
}
// @TODO: Helpful error reporting
late_load :: ($module: Type, GetProcAddress: (proc: *u8) -> *void #c_call = null) -> string #expand {
#insert -> string {
module_name := name_of_module(module);
return sprint("LIBRARIES :: Late_Load_Module_%.LIBRARIES;", module_name);
};
// @TODO: Could I generate different code in the metaprogram that would make this less icky?
#insert -> string {
#import "Basic";
loads: String_Builder;
for LIBRARIES {
// @Incomplete:
dynlib_name := it;
if OS == .LINUX && !starts_with(dynlib_name, "lib") {
dynlib_name = sprint("lib%", dynlib_name);
}
print_to_builder(*loads, "late_load(\"%1.so\", type_info(Late_Load_Library_%1), xx *__late_load_library_%1, GetProcAddress);\n", it);
}
return builder_to_string(*loads);
}
}
// Stolen from modules/GL/GL.jai!
late_load :: (library: *u8, info_struct: *Type_Info_Struct, struct_bytes: *u8, GetProcAddress: (proc: *u8) -> *void #c_call = null) {
#import "Compiler";
table := get_type_table();
// @Incomplete: Other OSs
#if OS == .WINDOWS {
Windows :: #import "Windows";
lib := Windows.LoadLibraryA(library);
}
else #if OS == .LINUX {
POSIX :: #import "POSIX";
lib := POSIX.dlopen(library, POSIX.RTLD_NOW);
}
if !lib return;
for * info_struct.members {
is_intentional_void_pointer := (it.flags & .PROCEDURE_WITH_VOID_POINTER_TYPE_INFO) != 0; // Let us use #type_info_procedures_are_void_pointers.
if (it.type.type == .PROCEDURE) || is_intentional_void_pointer {
ptype := cast(*Type_Info_Procedure) it.type;
if !is_intentional_void_pointer {
assert ((ptype.procedure_flags & .IS_C_CALL) != 0,
"Proc \"%\" is not a #c_call! (member_address %, type_address %, procedure_flags = %, % arguments, % returns, index = %)",
it.name, it, ptype, formatInt(ptype.procedure_flags, base=16), ptype.argument_types.count, ptype.return_types.count,
array_find(table, it.type));
}
// Okay, do the work.
c_string := it.name.data; // Strings in Type_Info are guaranteed to be zero-terminated now. -jblow, 23 December 2018
address : *void;
if GetProcAddress {
address = GetProcAddress(c_string);
} else {
#if OS == .WINDOWS {
address = Windows.GetProcAddress(lib, c_string);
}
else #if OS == .LINUX {
address = POSIX.dlsym(lib, c_string);
}
}
if address {
dest := cast(**void) (struct_bytes + it.offset_in_bytes);
<< dest = address;
}
}
}
}
#scope_module
#import "Basic";
#import "String";
name_of_module :: ($module: Type) -> string #compile_time {
ti := type_info(module);
found, left, right := split_from_left(ti.name, "#import ");
return right;
}
#import "Basic";
#import "Late_Load";
SDL :: #import "SDL"; @LateLoad
#insert #run late_load_import(SDL); // This imports function pointers of all of SDL's function into the current scope.
main :: () {
log("%", SDL_Init); // This will be null.
late_load(SDL);
log("%", SDL_Init); // If the load worked, this should be the actual function!
// A limitation: We imported all the functions from the SDL module into the current scope, but not any of the other
// declarations...
SDL_Init(SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_JOYSTICK);
// But, the beauty of all of this: `readelf -d ./late_load` won't have any "NEEDED" entry for libSDL2.so!!
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment