Skip to main content
GameDev.net gamedev.net
✓ Solved 🔒 Locked

Is it possible to hide a function in C? (extern a function declaration)

Started by TooOld2rock-nRoll May 11 at 12:00 PM 5 replies 1.1k views
Original Post
TooOld2rock-nRoll
TooOld2rock-nRoll

Last one was fun, lets see how long it lasts :D

Lets say I create a non static function that only exists in a source file, how can I extern it in another source file?

Yes yes, I can just tell the user not to touch it and put in a header file, but this one would be very bad to use and VERY hard to debug once gone wrong.

All the OpenGl stuff that require binding is currently being handled by local atomic flags at my APIs that interface with the specific areas, for instance, the ID of the last bound Texture is always known and if the user tries to do it repeatedly, the API just ignores it.

If the user, for some exceptional genius idea, get hold of a Texture ID and call glBindTexture(), every call to my Texture API would behave in a undefined way.

Therefore, I would like very much to keep the getTextureID() function hidden, only being useful for my other APIs that may require it.

Before you ask, yes, this is a problem with my google-fu again. Any search I make with the word "extern" only leads to AI slop with beginners topics on how header files work.

No, I don't use Google. DuckDuckGo served me well for years and has only recently completely turned to the dark side of AI. I see myself more and more depending on Reddit for lots of very normal searches.

Which makes me think, Forums may well make a come back, since this is the only proper solution to the sea of slop. Reddit basically works as one, just for everything all at once.

Do you guys know of Hubzilla? I was very interested on it for a wile, was never able to make a fully working server though and every "public" ones turned private because of bots and span.

"No, you're never too old to rock and roll
If you're too young to die"
Accepted Answer

The technical term is "linkage", along with terms "linkage of identifiers", "external linkage", and "internal linkage". That may help your search results.

When building any file into an object file, library, archive, or similar, it will either generate a name and externally-viewable address that the linker can use, "external linkage", or it won't be created, called "internal linkage" when it's only within the file. If there is no name/address given, it has "no linkage" which potentially can be completely optimized away.


Everything else follows from that.


If your code can reference it in another file using a name, that's external linkage, and any code can reference it elsewhere using the name. You can obscure it by not including the name, the declaration, in a header file that you give to others, but someone poking around the binaries can still discover that name and use the function. This is how a lot of undocumented functions are discovered. The group doesn't give out the header file with the information, but the library contains the name and someone used a tool like dumpbin to show all the linkable functions.

You can give it internal linkage within the implementation file by marking the function as 'static'. You can only use it inside that file, which might be useful in your situation if you can keep all the stuff inside a single compilation unit. If you want to access it in multiple files, that's not really an option for you. It's possible to obscure it in other ways, like using a helper function that returns the address of the inaccessible function, but then you've really just moved the linkage problem elsewhere.

Alberth
Alberth

Nothing stops you from including the same 'static' function in more than one file. It duplicates its code of course, but the function doesn't have an externally known name then.

TooOld2rock-nRoll
TooOld2rock-nRoll

frob wrote:

The technical term is "linkage", along with terms "linkage of identifiers", "external linkage", and "internal linkage". That may help your search results.

Aha! Yes, that indeed pointed me to the right answer, but only in a single stackoverflow question, all other entries are repetitions of the same.

In summary, the extern keyword works differently for functions and variables.

At the beginning of times, you the developer had to explicitly declare a function static or extern. If the prototype was in a header file and declared static, every source file was expected to implement a different version of that same function; if declared external, it behaves as we expect today, the function prototype has global access and will be (probably) implemented in a source file of same name as the header.

So no, you can't hide a function in a source file and extern it to other source files.

My path is clear than, I will create a function pointer that is hidden in the source file and extern that instead. Ugly as hell, but it works I guess....

Its very interesting though that creating a global static function behaves VERY MUCH like a pure virtual method in C++. There are some pretty interesting consequences to this.... hum....

frob wrote:

This is how a lot of undocumented functions are discovered. The group doesn't give out the header file with the information, but the library contains the name and someone used a tool like dumpbin to show all the linkable functions.

If someone put that much effort to break their own code in the long run, let them cook.

I'm not hiding useful and important stuff "for security reasons" (damn that bring me back so much bad memories....), in this particular case, if the user requires to access the native OpenGl functions, they just need to call my API bind function and carry on.

"No, you're never too old to rock and roll
If you're too young to die"
RmbRT
RmbRT

Create a static library that contains all the functions you want to wall off. Inside that library, you use external linkage, so that multiple .c files can see it. And then when you link that library, you make it so that the function is not exported from that library. I don't know exactly how that works, but in my C WASM projects, there is something similar (using Clang here):

#define IMPORT_MODULE_NAME "env"
#ifdef __cplusplus
#define EXPORT __attribute__((visibility("default"))) extern "C"
#define IMPORT __attribute__((import_module(IMPORT_MODULE_NAME))) extern "C"
#else
#define EXPORT __attribute__((visibility("default")))
#define IMPORT __attribute__((import_module(IMPORT_MODULE_NAME))) extern
#endif

In WASM, you provide a .wasm file, which is basically a static library. Any function marked as "default" visibility is visible to the JS side when you import that .wasm file. And upon instantiation of the .wasm module in JS, you can provide JS-side functions that get linked to the .wasm file's “imported” functions.

I used these compiler flags (-fvisibility=hidden being the important part) in my custom build tool:

#define CLANG_WASM_ARGS "--target=wasm32 -msimd128 -mrelaxed-simd -mbulk-memory -nostdlib -flto -ffreestanding -ffunction-sections -fdata-sections -fvisibility=hidden -Wall -Wextra -Wno-unused-function -Werror -Wfatal-errors -include rmbrt-runtime.h -O2"
#define WASM_LINKER "wasm-ld --allow-undefined --lto-O2 -O2 --gc-sections --no-entry --export-dynamic" " -s"

I'm not sure whether these are technically the correct linker flags or not. Basically you will want to look into how to make libraries and how to control which symbols get exported from the library and which ones don't. The -s linker flag strips out names or debug info or something. --gc-sections removes unused functions, I think. I got those flags to work out somehow after trial and error, but I'm sure there are some unnecessary or unintended flags in there as well, but at least it works. I based that on this tutorial (which itself is based on this example).

And then you link all the code that uses the internals into the library, and only export the functions that external code should be able to see. And then the rest of the code can link against that normally.

For example in my main file, I do stuff like this:

// a wrapper function around console.log(), injected by the JS runtime
IMPORT void log_int(char const * name, u16 size, int x);
// The C-callable version of it.
void logint(char const * msg, int x)
{
	u16 i = 0;
	while(msg[i]) i++;
	log_int(msg, i, x);
}
// An exported function that gets called by JS.
EXPORT bool on_frame(u16 w, u16 h, u32 dt)
{
	// ... per-frame logic
}

TooOld2rock-nRoll said:
In summary, the extern keyword works differently for functions and variables.

No, it is exactly the same. I can say extern int x; and it just says: “expect such an int to exist, regardless of wheter I provide it in this compilation unit (.c file + included headers)”. static int x; means: “declare this variable, and do not expose it to other compilation units”. And int x; means: “declare this variable in this compilation unit, and expose it to other units as well”. And exactly the same is true for functions. Although when you do void foo();, it is usually implicitly extern, but I think that's something you can override via compiler flags. So the difference is mostly what the default behaviour is when you omit the keywords.

Alberth said:
Nothing stops you from including the same 'static' function in more than one file. It duplicates its code of course, but the function doesn't have an externally known name then.

There is also static inline, which will also de-duplicate the function across compilation units, if it has not been inlined (after all, inline does not actually control whether the function call gets inlined…).

Walk with God.
RmbRT
RmbRT

TooOld2rock-nRoll said:
No, I don't use Google. DuckDuckGo served me well for years and has only recently completely turned to the dark side of AI. I see myself more and more depending on Reddit for lots of very normal searches.

Which makes me think, Forums may well make a come back, since this is the only proper solution to the sea of slop. Reddit basically works as one, just for everything all at once.

Yeah, I hope so. Although forums also get plagued by AI spam. Really, the problem is that ads exist on the web. If ads didn't exist, AI & non-AI SEO griefing wouldn't be a thing. Search engines would still work perfectly fine. But initially, search engines were made in a non-adverserial setting, meaning, they did not even have to consider whether a site is malicious in the sense that it is just there to waste time and space and attention, and clog up the search results. In an adversarial setting, it is almost impossible to make a search engine. Unless you start introducing active human curation of information. Because AI can't curate, as seen from how badly AI search engines perform.

Walk with God.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.