Last active
January 9, 2025 07:09
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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