The good news: Yes, of course it is possible, and done very very often. I will restrict my answer to Windows. In Linux or other systems exist similar scenarios, however different in detail.
First let's clarify what a "module" might be. I assume from your question that you mean by "module" a DLL e.g. loaded and called by some exe binary. This is a "tightly coupled" scenario: The module to be called resides in the same 2GB memory virtual address space as the calling module. In case of a "loosely coupled" scenario where both modules reside in different processes, you need one of the possibilities of "Remote Procedure Calls", like XML-RPC, SOAP or - in a .NET environment - Remoting. There exist many others. The following applies to a Windows DLL in tightly coupling.
Case1: Function is exported by the module. This is straightforward:
- Call the "LoadLibrary" API in one of its flavors to get the address of the module (Note that the handle returned is directly the module address). For this to work, you need the filename of the module.
- Call "GetProcAddress" to get the address of the function within the module. You need the name or the ordinal number of the exported function.
- Construct a function pointer with the correct signature from the obtained address.
- Call the function
This is the scenario for "normal" programs. It has the advantage that you do not need a static library to link the addresses of the DLL, and that the DLL needs not be present at startup of the exe. It has the disadvantage that the generation of the correct function pointer is a bit tedious.
Case2: Function is NOT exported by the module, base address of module to be called is not known.
This is the more interesting case where you e.g. want to patch an exe file and call the function of interest in the module DLL, or just want to jump to some known address relative to the modules starting address.
It works in principle - on an assembler and machine code level - in exactly the same way as described above, with one little difficulty: How do you call in your patch the "LoadLibrary" function? This is not so trivial as it might seem at first glance.
The "LoadLibrary" function resides in the module "Kernel32.dll", necessary for every windows program. You might call "LoadLibraryA("Kernel32.dll"), and you see the recursion problem of the "cat biting into its tail". This doesn't work. And don't rely on kernel32.dll having a fixed and not changing address. This will NOT work in the world of ASLR.
This is a general problem of all shellcode programming. Fortunately, there is a method how to reliably obtain the address of Kernel32.dll (and also ntdll.dll). You might want to download the excellent and rather old paper of Skape describing this and many other interesting things from here http://www.hick.org/code/skape/papers/win32-shellcode.pdf. The method worked for me identically in Windows XP, Windows7 and Windows10. Skapes approach works in the 32-bit world. For a corrsponding solution in 64 bit Windows, you might want to consult https://www.tophertimzen.com/blog/windowsx64Shellcode/ (but beware of possible bugs in the solution).
Have fun!