2026-04-08 12:16 AM
2026-04-10 1:03 AM - edited 2026-04-10 5:21 AM
@bbee wrote:To be able to build a correct C++ Wrapper around the HAL C-Funktions you should extend the Handle-Typedefs with some sort of User Data Pointer
Exactly. Like I wrote in my comment above. It was declined for HAL V1. But it is present in HAL V2 as an opion (can be enabled with a define).
2026-04-10 7:09 AM - edited 2026-04-10 7:29 AM
Every plain pointer stored in writable memory is a vulnerability in small embedded systems. So these additional "context" pointers, while convenient for some developers or for rapid prototyping, aren't 100% good thing.
(let alone the ugly typecasts, sugared by c++ reinterpret_cast gimmicks)
In the context of the given example - usually a handler is shared by only a small number of instances. For more robustness, it can be coded like this:
extern "C" void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
switch(huart){
case &huart1:
gBspUart1->OnTxComplete();
break;
case &huart4:
gBspUart4->OnTxComplete();
break;
default: // no more known UARTs
Error_Handler();
}
}It exposes more stuff as globals, but the addresses are constant (resolved by linker in build time)
2026-04-10 7:51 AM - edited 2026-04-10 7:59 AM
@Pavel A. wrote:Every plain pointer stored in writable memory is a vulnerability in small embedded systems. So these additional "context" pointers, while convenient for some developers or for rapid prototyping, aren't 100% good thing.
You are right in case of safety critical systems. But the HAL uses pointers everywhere. Once you need to take potential data corruption of pointers very serious nothing of the HAL is safe. There are ways to add some redundancy to context pointers.
You can do some checks on a pointer value in a callback function before it's used. Such as checking if the pointer is not NULL, is in RAM region, and is aligned properly for object type. Still no guarantee the pointer is pointing to the correct instance or even the correct type.
You can additionally do some sanity checks on the dereferenced object such as checking if values of the object make sense.
Another option is add a reference to the handle/peripheral to the object. So that you can check in the callback function if the context referenced by the handle also refers back to the handle. Like a doubly linked list. Small chance a random block of memory contains that specific address.
If you really want to avoid using pointers you also use the void* of the context pointer as a 32 bit data type. You could squeeze in an enum, and a CRC if you are really paranoid. Then you check for valid enum range and check CRC and use a switch statement to select your file scope object.
Ultimately embedded software should do some checks to ensure everything is running correctly. Brownout and watchdog are helpful. But not sufficient. Memory can get corrupted by cosmic radiation, firmware bugs, silicon bugs, incorrect DMA configuration, etc.
@Pavel A. wrote:(let alone the ugly typecasts, sugared by c++ reinterpret_cast gimmicks)
Nothing wrong this those. It's clear what the intention is. If you want to avoid casting we need to make the pointer type of the user context also configurable with a define per peripheral. That seems like a sane option.
EDIT: (this was posted after I clicked reply)
@Pavel A. wrote:In the context of the given example - usually a handler is shared by only a small number of instances. For more robustness, it can be coded like this:
It exposes more stuff as globals, but the addresses are constant (resolved by linker in build time)
This doesn't work. You cannot put addresses of objects in a case value. I tried. You have to use enums instead.