There is a protocol for how modules communicate. Here is a high-level, broad overview of how it works:
- A library is created for code you want to 'share'. These are commonly called DLLs or SOs depending on your platform.
- Each function you want to expose (entry point) will be available to the outside world to bind to. There are protocols to how to bind such as the calling convention which specifies the order the parameters are passed, who cleans up the stack, how many parameters get stored in registers and which ones, etc. See cdecl, stdcall, etc for examples of calling conventions here.
- The calling module will then either statically or dynamically bind to the shared library.
- Once your calling library is bound to the shared library it can then specify it wants to bind to a particular entry point. This is generally done by name, however most platforms also offer the option of binding by index (faster, yet more brittle if your module changes and entry points are reordered).
- You will also generally declare the function you want to call in your module somewhere so that your language can do static type checking, knows what the calling convention is etc.
For your scenario of calling Scheme from C++, the Scheme interpreter most likely exports a function that dynamically binds to a Scheme function/object and calls that. If the Scheme module is compiled it probably has the option of exporting an entry point so your C++ module could bind to that. I am not very familiar with Scheme so someone else can probably answer the specifics of that particular binding better than I.