A few years ago, I made my own MegaCart. But I never found enough free time to make a game that really required it, and I found the use from sdcc too cumbersome (similart to how this one works).
So I started on building support for bank-switching into sdcc. I just had a look at the current state. It would work for bankswitched RAM, but needs a bit of work for the ROM. When I find a bit of time, I'll fix it. Once that is done, using the MegaCart from sdcc should be much simpler. We would use named address spaces (see section 3.6.2 in the sdcc manual, which also contains a small example of how this works for RAM).
You just write a function to switch to a particular bank, declare banks for your variables and then just access them normally:
__addressmod set_bank_a bank_a;
__addressmod set_bank_b bank_b;
const bank_a char d = 'c'; // A const char in bank a.
const bank_a char *const bank_b c = &d; // A const pointer in bank b that points to a const char in bank a.
return *c; // sdcc will automatically insert the calls to set_bank_a() and set_bank_b() as necessary.
Actually, sdcc already generates correct code for the function f() in this example, but messes up the initialization of c and d (sdcc behaves as if they were in RAM).
sdcc will try to minimize the calls to the bank selection function, and will only insert the minimum number necessary. Read "Optimal Placement of Bank Selection Instructions in Polynomial Time" for the details. However, it does not yet do inter-procedural analysis, so it doesn't know which bank is active after a function call.
P.S.: All this is about data in banked memory, not calling functions in banked memory. To me, the latter problem seemed less interesting from a theoretical computer science perspective, so I never bothered with it.