Smart way to transform stm32 function into class - c++

I'm trying to build a class for stm32 peripheral function like i2c, uart. But I ran into some situation where I don't know what to do is the best way.
Below is my I2C init function:
void init_I2C1(void)
{
/* RCC initialization */
RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct = {
.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7,
.GPIO_Mode = GPIO_Mode_AF,
.GPIO_Speed = GPIO_Speed_50MHz,
.GPIO_OType = GPIO_OType_PP,
.GPIO_PuPd = GPIO_PuPd_UP
};
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1);
I2C_InitTypeDef I2C_InitStruct = {
.I2C_ClockSpeed = 100000,
.I2C_Mode = I2C_Mode_I2C,
.I2C_DutyCycle = I2C_DutyCycle_2,
.I2C_OwnAddress1 = 0x75,
.I2C_Ack = I2C_Ack_Enable,
.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit
};
I2C_Init(I2C1, &I2C_InitStruct);
I2C_Cmd(I2C1, ENABLE);
}
But There is 3 pair of I2C that I can use, so I want to write a general class so I can just specify the number to the class instead of wirte 3 similar functions.
Therefore, I need to find a way to replace those constants.
For those constant just represent a continuous value, I can just use bit shift operation.
ex.
#define GPIO_PinSource6 ((uint8_t)0x06)
#define GPIO_PinSource7 ((uint8_t)0x07)
But other constants are complicated. They are pointers of some structure, so I cannot just just bit shift them. And there are not array either so I also cannot use an interger to go through.
ex.
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE
#define I2C1_BASE (APB1PERIPH_BASE + 0x5400)
#define I2C2_BASE (APB1PERIPH_BASE + 0x5800)
#define I2C1 ((I2C_TypeDef *) I2C1_BASE) //the constant I need to use
#define I2C2 ((I2C_TypeDef *) I2C2_BASE) //the constant I need to use
The solution I have now is to trace back to the numerical value then do the bit shifting thing.For example,
I2C_TypeDef * I2C_Type_Ptr = (I2C_TypeDef *)(APB1PERIPH_BASE + (0x5400 << i2c_number));
I2C_Init(I2C_Type_Ptr, &I2C_InitStruct);
But it looks ugly and stupid. I wonder whether there is a more efficient way to do this.
Here is the whole code of the class I have done so far.
//Just for I2C1 & I2C2, there all use portB
I2C::I2C(uint8_t number, uint8_t port_SCL, uint8_t port_SDA)
{
/* RCC initialization */
RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd((RCC_APB1Periph_I2C1 << (number - 1)), ENABLE);
GPIO_InitTypeDef GPIO_InitStruct = {
.GPIO_Pin = (GPIO_Pin_0 << port_SCL) | (GPIO_Pin_0 << port_SDA),
.GPIO_Mode = GPIO_Mode_AF,
.GPIO_Speed = GPIO_Speed_50MHz,
.GPIO_OType = GPIO_OType_PP,
.GPIO_PuPd = GPIO_PuPd_UP
};
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOB, (GPIO_PinSource0 + port_SCL), GPIO_AF_I2C1); //all I2C AF selection are all the same (0x04)
GPIO_PinAFConfig(GPIOB, (GPIO_PinSource0 + port_SDA), GPIO_AF_I2C1);
I2C_InitTypeDef I2C_InitStruct = {
.I2C_ClockSpeed = 100000,
.I2C_Mode = I2C_Mode_I2C,
.I2C_DutyCycle = I2C_DutyCycle_2,
.I2C_OwnAddress1 = 0x75,
.I2C_Ack = I2C_Ack_Enable,
.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit
};
I2C_TypeDef * I2C_Type_Ptr = (I2C_TypeDef *)(APB1PERIPH_BASE + (0x5400 << number));
I2C_Init(I2C_Type_Ptr, &I2C_InitStruct);
I2C_Cmd(I2C_Type_Ptr, ENABLE);
}
Thank you for your help~~
(P.S. The library I use is "STM32F4xx_StdPeriph_Driver")

If you're not worried about hiding the ST libraries completely, then you could just use the I2C_TypeDef * like so:
I2C::I2C(I2C_TypeDef*, uint8_t port_SCL, uint8_t port_SDA)
Ohterwise, I would just write a function like the below:
I2C_TypeDef* getI2CTypeDef(uint8_t instance)
{
switch (instance) {
case 1U:
return I2C1;
case 2U:
return I2C2;
case 3U:
return I2C3;
}
return I2C1;
}
A couple of observations not directly related to your question:
Instead of using a plain uint8_t for the I2C instance, use an enum class. This will give you a better type safety.
StdPeriph driver is no longer supported. If you care about portability to newer chips, better switch to HAL now.

Related

How to prevent c++ compiler optimising variable

tl;dr: In the code below, why does the Serial.println(value); statement change the behaviour of the code, and how can I make the code work properly without it ?
I need to save some values on a microcontroller that doesn't have any eeprom (an Arduino 33 BLE sense)
I have found a way to do that by declaring a const variable to reserve a region in the flash memory, and write to it using the microcontroller's NVMC (Non Volatile Memory Controller). This solution works fine but there is some weirdness I'd like to get rid of:
To read the values, I use the following function:
float getFloat(float *ptr) {
float value = *ptr;
Serial.println(value);
return value;
}
This works, but whenever I try to simplify this by
removing the Serial.println statement
not using the function at all and doing what it does inline: something.floatvalue = *ptr
...the code behaves as if the saved values were always 0 (A memory dump shows this is not the case)
I suspect the compiler is optimising stuff in a way I don't understand (I'm not too familiar with c++). How can I avoid that ?
Here ny current code:
#include <Arduino.h>
#include <stdbool.h>
// https://infocenter.nordicsemi.com/pdf/nRF52840_PS_v1.2.pdf
// nFR52 NVMC registers
#define NVMC_BASE (0x4001E000U)
#define NVMC_READY (NVMC_BASE + 0x400U)
#define NVMC_READYNEXT (NVMC_BASE + 0x408U)
#define NVMC_CONFIG (NVMC_BASE + 0x504U)
#define NVMC_ERASEPAGE (NVMC_BASE + 0x508U)
#define NVMC_ERASEALL (NVMC_BASE + 0x50CU)
#define NVMC_ERASEUICR (NVMC_BASE + 0x514U)
#define NVMC_ERASEPAGEPARTIAL (NVMC_BASE + 0X518U)
#define NVMC_ERASEPAGEPARTIALCFG (NVMC_BASE + 0X51CU)
#define NVMC_ICACHECNF (NVMC_BASE + 0x540U)
#define NVMC_IHIT (NVMC_BASE + 0x548U)
#define NVMC_IMISS (NMVC_BASE + 0x54cU)
// nFR52 MVMC values
#define MVMC_READ_MODE 0x00
#define MVMC_WRITE_MODE 0x01
#define MVMC_ERASE_MODE 0x02
typedef struct flash_mem {
float val_1;
float val_2;
// We want to fill a whole page of memory.
// A page is 4096 bytes, each float takes 4 bytes.
char filler[4096 - (4+4) ];
} flash_mem_t;
// This will reserve a space in flash memory for the values we need to save.
const flash_mem_t _values __attribute__((section("FLASH"), aligned(0x1000))) = {};
// A regular, in-memory instance of our values for easy manipulations.
// we'll use the load() and save() functions to move data between values and _values
flash_mem_t values;
void setup() {
// Initialize serial
Serial.begin(115200);
while (!Serial.ready()) { }
delay(500);
// Load and show saved values
load();
Serial.println(values.val_1);
Serial.println(values.val_2);
// Update and save values
values.val_1 += 1;
values.val_2 += 0.642;
save();
}
// Arduino expects this
void loop() {}
void load () {
// TODO: find a way to do this for all values in our struct automatically
values.val_1 = getFloat((float *)&_values.val_1);
values.val_2 = getFloat((float *)&_values.val_2);
}
void writeValues () {
// TODO: find a way to do this for all values in our struct automatically
*(float *)(&_values.val_1) = values.val_1;
*(float *)(&_values.val_2) = values.val_2;
}
bool save() {
// NVMC can only write on "deleted" bytes, so we delete the page _values sits on
deletePage((void *)&_values);
// make sure NVMC is ready
if (*(uint32_t *)NVMC_READY == false) return false;
// write values to flash
*(uint32_t *)NVMC_CONFIG = MVMC_WRITE_MODE;
writeValues();
while(*(uint32_t *)NVMC_READY == false) delayMicroseconds(50);
*(uint32_t *)NVMC_CONFIG = MVMC_READ_MODE;
return true;
}
bool deletePage(void *pageStart) {
if (*(uint32_t *)NVMC_READY == false) return false;
*(uint32_t *)NVMC_CONFIG = MVMC_ERASE_MODE;
*(uint32_t *)NVMC_ERASEPAGE = (uint32_t)pageStart;
while (*(uint32_t *)NVMC_READY == false) delay(85);
*(uint32_t *)NVMC_CONFIG = MVMC_READ_MODE;
return true;
}
float getFloat(float *ptr) {
float value = *ptr;
// This is the weird part: everything works properly whem the following line is here,
// but value is always 0 when it is commented out
Serial.println(value);
return value;
}
I didn't tag this question with the "arduino" tag because I feel like it's not really an arduino problem. If you disagree, I'll be happy to update the tags.
Thanks for any help!
Usually you can use volatile to inform the compiler that the variable can change (for example, in an interrupt), so it wouldn't optimize it away. See Why is volatile needed in C?.

How do I appropriately call constructors and pass objects around?

I have the following code, but something is wrong with it. It compiles, but nothing happens on the NeoPixel matrix. I've loaded up a strand test to verify that the hardware is working properly. Through some manual debugging, I've worked out that the line containing PixelArt art = PixelArt(matrix); triggers the issue. If I comment that out, I get a panel of orange light coming out of the light matrix as expected. Putting it back in simply results in darkness.
What about including that one line is causing issues? I'm new to C++ and this is driving me up the wall.
For some code context, I plan on including more modes, each with a series of submodes. PixelArt would show off different pixel art pictures as its sub modes. Other modes would include different animation patterns. The different modes should all share a reference to the same matrix object.
#include <Adafruit_NeoMatrix.h>
#include <gamma.h>
#define MODE_PIN 2
#define SUB_MODE_PIN 3
#define MATRIX_PIN 9
#define MATRIX_COLUMNS 16
#define MATRIX_ROWS 16
#define MATRIX_NUM_PIXELS (MATRIX_COLUMNS * MATRIX_ROWS)
class Mode {
public:
Mode(Adafruit_NeoMatrix &neomatrix) : matrix{&neomatrix} {}
virtual void toggleSubMode() = 0;
virtual void update(uint64_t timestamp) = 0;
protected:
Adafruit_NeoMatrix * const matrix;
};
class PixelArt : public Mode {
public:
PixelArt(Adafruit_NeoMatrix &neomatrix):Mode(neomatrix) {}
void toggleSubMode();
void update(uint64_t timestamp);
};
void PixelArt::toggleSubMode() {
// Stubbed.
}
// Green: 0x80c000
// Brown: 0xc04000
// Tan: 0xffa040
uint32_t link[16][16] = { [REDACTED FOR BREVITY] };
void PixelArt::update(uint64_t timestamp) {
matrix->clear();
for (uint16_t row = 0; row < 16; row++) {
for (uint16_t column = 0; column < 16; column++) {
matrix->setPassThruColor(link[row][column]);
matrix->drawPixel(column, row, 0);
matrix->setPassThruColor();
}
}
matrix->show();
}
//
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(
MATRIX_COLUMNS,
MATRIX_ROWS,
MATRIX_PIN,
NEO_MATRIX_TOP | NEO_MATRIX_LEFT | NEO_MATRIX_COLUMNS | NEO_MATRIX_ZIGZAG,
NEO_GRB | NEO_KHZ800
);
PixelArt art = PixelArt(matrix);
void setup() {
matrix.begin();
matrix.show();
matrix.setBrightness(30); // Max ~80
}
uint8_t mode = 0;
void loop() {
// art.update(millis());
matrix.fillScreen(matrix.Color(255, 127, 0));
matrix.show();
}
My suggestion would be that because the code runs on a microcontroller, it may run out of memory. Especially if the target board is an Arduino Uno or similar, with small dynamic memory. After compilation, the memory usage calculation is done for compile-time variables. The run-time variables and function calls may exceed the available memory.
Compiled for an Arduino Uno, the compiler calculates a 1204 bytes (58%) of dynamic memory usage with PixelArt object. Without it is 168 bytes (8%).
Try reducing the inheritance and function call levels.

Using Wire.onRequest by passing a class method?

I have an Arduino sketch that will be working on an Arduino UNO and I am trying to get uno to communicate over the i2c connection with a raspberry pi.
Problem is using wire.h library where method Wire.onRequest is working just fine when I use it like this.
#include <Wire.h>
#define COMM_DELAY 50
#define SLAVE_ADDRESS 0x04
int current_rule = 0;
void initI2c() {
// initialize i2c as slave
Wire.begin(SLAVE_ADDRESS);
// define callbacks for i2c communication
Wire.onReceive(receiveData);
}
// callback for received data
void receiveData(int byteCount) {
while (Wire.available()) {
current_rule = Wire.read();
}
}
but when I try to make this exact result with a class method, I get an error :
invalid use of non-static member function
(with Wire.onRequest(this->receiveData) line gets to be marked red)
Just like this:
void (*funptr)();
typedef void (*Callback)(byte);
class Comm{
public:
int callback_list_size = 0;
bool option_debug;
byte option_address;
int option_comm_delay;
void(*callback_list[256]);
byte *rules;
// function for receiving data. raspberry -> arduino
// Whenever the master sends new data, this method will call the appropriate callback.
void receiveData()
{
byte data;
Serial.println("[INFO] Received new data from master");
while (Wire.available())
{
data = Wire.read();
}
for (int i = 0; i < callback_list_size; i++)
{
if (rules[i] == data){
funptr = callback_list[i];
funptr();
}
}
}
// function for sending data. Called when raspberry request data. arduino -> raspberry
// Whenever the master requests data, this method will be called. For now we don't need this but anyway.
void sendData(int s)
{
if (option_debug)
Serial.println("[INFO] Master requests data!");
}
/* Constructor that takes 3 parameters at max. Only the adress is mandatory others are optional and will be filled with default values
:address - adress of slave(arduino) - Example 0x04
:delay - a delay is needed because I2C clock is quite slow compared to the CPU clock - 50
:debug - for debug purposes if true debug info will be sent to Serial interface - true/false
*/
Comm(byte address, int delay = 50, bool debug = false)
{
option_address = address;
option_comm_delay = delay;
option_debug = debug;
if (debug)
Serial.println("[INFO] Comm Object Created!");
}
// Function needs to be called to initialize the communication channel.
void initI2c()
{
Wire.begin(option_address);
Wire.onReceive(this->sendData);
Wire.onRequest(this->receiveData);
if (option_debug)
Serial.println("[INFO] I2C channel initialized");
}
// Function to add new callback for a rule.
// This function returns id of passed callback
int addCallback(Callback func, byte rule)
{
callback_list_size++;
// Enlarge rules array to keep 1 more byte
byte *temp = new byte[callback_list_size]; // create new bigger array.
for (int i = 0; i + 1 < callback_list_size; i++) // reason fo i+1 is if callback_list_size is 1 than this is the first initializition so we don't need any copying.
{
temp[i] = rules[i]; // copy rules to newer array.
}
delete[] rules; // free old array memory.
rules = temp; // now rules points to new array.
callback_list[callback_list_size - 1] = &func;
rules[callback_list_size - 1] = rule;
return callback_list_size;
}
};
Comm *i2c_comm;
void loop()
{
}
void setup()
{
Serial.begin(9600);
initI2C();
}
void initI2C()
{
i2c_comm = new Comm(0x04, 50, true);
i2c_comm->initI2c();
//Callback Definitions
i2c_comm->addCallback(&rule_1, 0x01);
i2c_comm->addCallback(&rule_2, 0x02);
i2c_comm->addCallback(&rule_3, 0x03);
i2c_comm->addCallback(&rule_4, 0x04);
}
I also tried to make the receiveData method to be static.
But in this case I have an error like this:
invalid use of member Com::callback_list_size in static member function
which makes sense to me as static method won't know which callback_list_size I am talking about.
so I am quite confused about how I can handle such a problem?
You're almost there. Generally speaking in C++ you need to pass a static class method for callback functions.
The error you received after changing your method to static is expected as you're trying to access a member of an instance of the class Comm which cannot be done in a static method in which there is no 'this'.
Here's one of many techniques to consider, but please read over the SO post Using a C++ class member function as a C callback function.
Anyway the approach here is to leverage a static pointer to an instance.
class Comm {
private:
static Comm* pSingletonInstance;
static void OnReceiveHandler() {
if (pSingletonInstance)
pSingletonInstance->receiveData();
}
static void OnSendHandler(int s) {
if (pSingletonInstance)
pSingletonInstance->sendData(s);
}
void initI2c() {
Comm::pSingletonInstance = this; // Assign the static singleton used in the static handlers.
Wire.onReceive(Comm::OnSendHandler);
Wire.onRequest(Comm::OnReceiveHandler);
Wire.begin(option_address);
}
}
// static initializer for the static member.
Comm* Comm::pSingletonInstance = 0;
Again there are many ways to get around this issue but above is an easy one and likely suitable for your project. If you need to manage multiple instances of Comm, you'll have to do something quite different.
Good luck!

Strange input of function in C++

What is done by (1L<<16u) in following code?
and What is the role of this pointer(&mode_absolute_pos_el.tracking) as an input to a function?
if (command_axis2.cif_mode_state == STATUS__DONE)
{
delta_position = absolute_position - turret_pos.elevation_16bit_pm180deg;
delta_position = MathRange_PlusMinusHalfRange32Bit(delta_position, 1L<<16u);
mode_absolute_pos_el.speed_setpoint = MathTracking_Main(&mode_absolute_pos_el.tracking, delta_position, 0L);
}
This is the complete function as one requested for the complete one:
static int16_t TwinX_AbsPos_Calc_El(int16_t absolute_position)
{
uint16_t position_reached = NO;
int32_t delta_position = 0L;
if (command_axis2.cif_mode_state == STATUS__DONE)
{
delta_position = absolute_position - turret_pos.elevation_16bit_pm180deg;
delta_position = MathRange_PlusMinusHalfRange32Bit(delta_position, 1L<<16u);
mode_absolute_pos_el.speed_setpoint = MathTracking_Main(&mode_absolute_pos_el.tracking, delta_position, 0L);
/* verify that absolute position is reached with tolerance: +/- CUSTOMER_ABSOLUTE_POS_ERROR
* for more than MODE_ABSOLUTE_POS_OK_DELAY_IN_MS:
* bai: That has to be here because otherwise position_reached is always "Yes" because delta_position == 0L */
if ((delta_position < TwinX_MODE_ABSOLUTE_POS_ERROR) && (delta_position > (-1 * TwinX_MODE_ABSOLUTE_POS_ERROR)))
{
position_reached = YES;
}
mode_absolute_pos_el.absolute_pos_reached = MathDebounce_Status(&mode_absolute_pos_el.debounce, position_reached);
}
else
{
mode_absolute_pos_el.speed_setpoint = 0;
MathTracking_SetStartCondition(&mode_absolute_pos_el.tracking, turret_speed.elev_speed_max16bit);
}
return mode_absolute_pos_el.speed_setpoint;
}
And below you can see the MATH_DEBOUNCE :
typedef struct
{
bool_t debounced_status; /* debounced status ZERO / ONE */
uint32_t debounce_counter; /* counter */
uint32_t threshold_for_zero; /* threshold to set debounced status to ZERO */
uint32_t threshold_for_one; /* threshold to set debounced status to ONE */
uint32_t step_down_size; /* step size to count down. used for underclock */
}MATH_DEBOUNCE_t;
void MathDebounce_Init(MATH_DEBOUNCE_t *const debounce_p,
bool_t initial_status,
uint16_t debounce_delay,
uint16_t underclock);
void MathDebounce_ResetStatus(MATH_DEBOUNCE_t *const debounce_p, bool_t reset_status);
bool_t MathDebounce_Status(MATH_DEBOUNCE_t *const debounce_p, bool_t status);
1 << 16 is 2^16. << is a left binary shift operator.
Lets look at how to get 2^3, imagine that you have 1 which in binary form looks like this 0b0001. One point about binary system is that powers of 2 have only one bit in their binary representation, so 2^3 = 8 looks like 0b1000. It's a very fast way to get a power of 2.
Passing pointer to function is usually a way to get more then one output from it. Like when return value is something like error code and actual output of the function is passed through the pointer you pass to it.
But it's hard to tell what exactly it does because you didn't provide the code of the function.

Create a function with unique function pointer in runtime

When calling WinAPI functions that take callbacks as arguments, there's usually a special parameter to pass some arbitrary data to the callback. In case there's no such thing (e.g. SetWinEventHook) the only way we can understand which of the API calls resulted in the call of the given callback is to have distinct callbacks. When we know all the cases in which the given API is called at compile-time, we can always create a class template with static method and instantiate it with different template arguments in different call sides. That's a hell of a work, and I don't like doing so.
How do I create callback functions at runtime so that they have different function pointers?
I saw a solution (sorry, in Russian) with runtime assembly generation, but it wasn't portable across x86/x64 archtectures.
You can use the closure API of libffi. It allows you to create trampolines each with a different address. I implemented a wrapping class here, though that's not finished yet (only supports int arguments and return type, you can specialize detail::type to support more than just int). A more heavyweight alternative is LLVM, though if you're dealing only with C types, libffi will do the job fine.
I've come up with this solution which should be portable (but I haven't tested it):
#define ID_PATTERN 0x11223344
#define SIZE_OF_BLUEPRINT 128 // needs to be adopted if uniqueCallbackBlueprint is complex...
typedef int (__cdecl * UNIQUE_CALLBACK)(int arg);
/* blueprint for unique callback function */
int uniqueCallbackBlueprint(int arg)
{
int id = ID_PATTERN;
printf("%x: Hello unique callback (arg=%d)...\n", id, arg);
return (id);
}
/* create a new unique callback */
UNIQUE_CALLBACK createUniqueCallback(int id)
{
UNIQUE_CALLBACK result = NULL;
char *pUniqueCallback;
char *pFunction;
int pattern = ID_PATTERN;
char *pPattern;
char *startOfId;
int i;
int patterns = 0;
pUniqueCallback = malloc(SIZE_OF_BLUEPRINT);
if (pUniqueCallback != NULL)
{
pFunction = (char *)uniqueCallbackBlueprint;
#if defined(_DEBUG)
pFunction += 0x256; // variable offset depending on debug information????
#endif /* _DEBUG */
memcpy(pUniqueCallback, pFunction, SIZE_OF_BLUEPRINT);
result = (UNIQUE_CALLBACK)pUniqueCallback;
/* replace ID_PATTERN with requested id */
pPattern = (char *)&pattern;
startOfId = NULL;
for (i = 0; i < SIZE_OF_BLUEPRINT; i++)
{
if (pUniqueCallback[i] == *pPattern)
{
if (pPattern == (char *)&pattern)
startOfId = &(pUniqueCallback[i]);
if (pPattern == ((char *)&pattern) + sizeof(int) - 1)
{
pPattern = (char *)&id;
for (i = 0; i < sizeof(int); i++)
{
*startOfId++ = *pPattern++;
}
patterns++;
break;
}
pPattern++;
}
else
{
pPattern = (char *)&pattern;
startOfId = NULL;
}
}
printf("%d pattern(s) replaced\n", patterns);
if (patterns == 0)
{
free(pUniqueCallback);
result = NULL;
}
}
return (result);
}
Usage is as follows:
int main(void)
{
UNIQUE_CALLBACK callback;
int id;
int i;
id = uniqueCallbackBlueprint(5);
printf(" -> id = %x\n", id);
callback = createUniqueCallback(0x4711);
if (callback != NULL)
{
id = callback(25);
printf(" -> id = %x\n", id);
}
id = uniqueCallbackBlueprint(15);
printf(" -> id = %x\n", id);
getch();
return (0);
}
I've noted an interresting behavior if compiling with debug information (Visual Studio). The address obtained by pFunction = (char *)uniqueCallbackBlueprint; is off by a variable number of bytes. The difference can be obtained using the debugger which displays the correct address. This offset changes from build to build and I assume it has something to do with the debug information? This is no problem for the release build. So maybe this should be put into a library which is build as "release".
Another thing to consider whould be byte alignment of pUniqueCallback which may be an issue. But an alignment of the beginning of the function to 64bit boundaries is not hard to add to this code.
Within pUniqueCallback you can implement anything you want (note to update SIZE_OF_BLUEPRINT so you don't miss the tail of your function). The function is compiled and the generated code is re-used during runtime. The initial value of id is replaced when creating the unique function so the blueprint function can process it.