Skip to content

Commit a8ad7c3

Browse files
committed
feat: implement custom memory management hooks and example usage
1 parent 6124c0d commit a8ad7c3

File tree

5 files changed

+274
-26
lines changed

5 files changed

+274
-26
lines changed

example/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ add_subdirectory(invoke_a_func)
66
add_subdirectory(notif_req)
77
add_subdirectory(rpc_with_params)
88
add_subdirectory(arg_to_function)
9+
add_subdirectory(memory_hooks)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
project(mjsonrpc-example-mem-hooks)
2+
3+
add_executable(${PROJECT_NAME} memory_hooks.c)
4+
5+
target_link_libraries(${PROJECT_NAME} mjsonrpc)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#include "mjsonrpc.h"
2+
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
#include <string.h>
6+
7+
// Statistics for tracking memory usage
8+
static size_t malloc_count = 0;
9+
static size_t free_count = 0;
10+
static size_t strdup_count = 0;
11+
12+
// Custom memory allocation functions with tracking
13+
void* custom_malloc(size_t size)
14+
{
15+
malloc_count++;
16+
printf("[CUSTOM MALLOC] Allocating %zu bytes (call #%zu)\n", size, malloc_count);
17+
return malloc(size);
18+
}
19+
20+
void custom_free(void* ptr)
21+
{
22+
if (ptr != NULL) {
23+
free_count++;
24+
printf("[CUSTOM FREE] Freeing memory (call #%zu)\n", free_count);
25+
}
26+
free(ptr);
27+
}
28+
29+
char* custom_strdup(const char* str)
30+
{
31+
strdup_count++;
32+
printf("[CUSTOM STRDUP] Duplicating string: '%.20s%s' (call #%zu)\n",
33+
str, strlen(str) > 20 ? "..." : "", strdup_count);
34+
35+
if (str == NULL) {
36+
return NULL;
37+
}
38+
39+
size_t len = strlen(str) + 1;
40+
char* dup = custom_malloc(len);
41+
if (dup != NULL) {
42+
memcpy(dup, str, len);
43+
}
44+
return dup;
45+
}
46+
47+
// Simple RPC method for testing
48+
cJSON* hello_method(mjrpc_func_ctx_t* context, cJSON* params, cJSON* id)
49+
{
50+
(void) context; // Unused parameter
51+
(void) id; // Unused parameter
52+
const char* name = "World";
53+
54+
if (params && cJSON_IsObject(params)) {
55+
cJSON* name_item = cJSON_GetObjectItem(params, "name");
56+
if (cJSON_IsString(name_item)) {
57+
name = name_item->valuestring;
58+
}
59+
}
60+
61+
// This will trigger memory allocation for the string
62+
char greeting[256];
63+
snprintf(greeting, sizeof(greeting), "Hello, %s!", name);
64+
65+
return cJSON_CreateString(greeting);
66+
}
67+
68+
int main(void)
69+
{
70+
printf("=== mjsonrpc Memory Hooks Example ===\n\n");
71+
72+
// Step 1: Set custom memory hooks
73+
printf("1. Setting custom memory hooks...\n");
74+
int result = mjrpc_set_memory_hooks(custom_malloc, custom_free, custom_strdup);
75+
if (result != MJRPC_RET_OK) {
76+
printf("Failed to set memory hooks!\n");
77+
return 1;
78+
}
79+
printf("Custom memory hooks set successfully!\n\n");
80+
81+
// Step 2: Create handle (this will use custom memory functions)
82+
printf("2. Creating mjsonrpc handle...\n");
83+
mjrpc_handle_t* handle = mjrpc_create_handle(0);
84+
if (!handle) {
85+
printf("Failed to create handle!\n");
86+
return 1;
87+
}
88+
printf("Handle created successfully!\n\n");
89+
90+
// Step 3: Add a method (this will use custom strdup)
91+
printf("3. Adding 'hello' method...\n");
92+
result = mjrpc_add_method(handle, hello_method, "hello", NULL);
93+
if (result != MJRPC_RET_OK) {
94+
printf("Failed to add method!\n");
95+
mjrpc_destroy_handle(handle);
96+
return 1;
97+
}
98+
printf("Method added successfully!\n\n");
99+
100+
// Step 4: Process a request (this may trigger more memory allocations)
101+
printf("4. Processing JSON-RPC request...\n");
102+
const char* request_str = "{\"jsonrpc\":\"2.0\",\"method\":\"hello\",\"params\":{\"name\":\"Alice\"},\"id\":1}";
103+
int ret_code;
104+
char* response_str = mjrpc_process_str(handle, request_str, &ret_code);
105+
106+
if (response_str) {
107+
printf("Response: %s\n", response_str);
108+
free(response_str); // This uses standard free since mjrpc_process_str uses standard memory allocation internally
109+
} else {
110+
printf("Failed to process request (return code: %d)\n", ret_code);
111+
}
112+
printf("\n");
113+
114+
// Step 5: Clean up (this will use custom free functions)
115+
printf("5. Cleaning up...\n");
116+
mjrpc_destroy_handle(handle);
117+
printf("Cleanup completed!\n\n");
118+
119+
// Step 6: Reset to default functions
120+
printf("6. Resetting to default memory functions...\n");
121+
result = mjrpc_set_memory_hooks(NULL, NULL, NULL);
122+
if (result != MJRPC_RET_OK) {
123+
printf("Failed to reset memory hooks!\n");
124+
return 1;
125+
}
126+
printf("Memory hooks reset to defaults!\n\n");
127+
128+
// Print statistics
129+
printf("=== Memory Usage Statistics ===\n");
130+
printf("Custom malloc calls: %zu\n", malloc_count);
131+
printf("Custom free calls: %zu\n", free_count);
132+
printf("Custom strdup calls: %zu\n", strdup_count);
133+
printf("\n");
134+
135+
printf("Example completed successfully!\n");
136+
return 0;
137+
}

src/mjsonrpc.c

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
#include <string.h>
2929
#include <stdbool.h>
3030

31+
/*--- memory management hooks ---*/
32+
33+
// Global memory function pointers, default to standard library functions
34+
static mjrpc_malloc_func g_mjrpc_malloc = malloc;
35+
static mjrpc_free_func g_mjrpc_free = free;
36+
static mjrpc_strdup_func g_mjrpc_strdup = strdup;
37+
3138
/*--- utility ---*/
3239

3340
enum method_state
@@ -54,7 +61,8 @@ static int resize(mjrpc_handle_t* handle)
5461

5562
handle->capacity *= 2;
5663
handle->size = 0;
57-
handle->methods = (struct mjrpc_method*) calloc(handle->capacity, sizeof(struct mjrpc_method));
64+
handle->methods =
65+
(struct mjrpc_method*) g_mjrpc_malloc(handle->capacity * sizeof(struct mjrpc_method));
5866
if (handle->methods == NULL)
5967
{
6068
handle->capacity = old_capacity;
@@ -67,10 +75,10 @@ static int resize(mjrpc_handle_t* handle)
6775
if (old_methods[i].state == OCCUPIED)
6876
{
6977
mjrpc_add_method(handle, old_methods[i].func, old_methods[i].name, old_methods[i].arg);
70-
free(old_methods[i].name);
78+
g_mjrpc_free(old_methods[i].name);
7179
}
7280
}
73-
free(old_methods);
81+
g_mjrpc_free(old_methods);
7482
return MJRPC_RET_OK;
7583
}
7684

@@ -107,8 +115,8 @@ static cJSON* invoke_callback(const mjrpc_handle_t* handle, const char* method_n
107115
ctx.error_message = NULL;
108116
if (!method_get((mjrpc_handle_t*) handle, method_name, &func, &arg) || !func)
109117
{
110-
return mjrpc_response_error(JSON_RPC_CODE_METHOD_NOT_FOUND, strdup("Method not found."),
111-
id);
118+
return mjrpc_response_error(JSON_RPC_CODE_METHOD_NOT_FOUND,
119+
g_mjrpc_strdup("Method not found."), id);
112120
}
113121
ctx.data = arg;
114122
returned = func(&ctx, params, id);
@@ -144,9 +152,9 @@ static cJSON* rpc_handle_obj_req(const mjrpc_handle_t* handle, const cJSON* requ
144152
const cJSON* version = cJSON_GetObjectItem(request, "jsonrpc");
145153
if (version == NULL || version->type != cJSON_String ||
146154
strcmp("2.0", version->valuestring) != 0)
147-
return mjrpc_response_error(JSON_RPC_CODE_INVALID_REQUEST,
148-
strdup("Invalid request received: JSONRPC version error."),
149-
id_copy);
155+
return mjrpc_response_error(
156+
JSON_RPC_CODE_INVALID_REQUEST,
157+
g_mjrpc_strdup("Invalid request received: JSONRPC version error."), id_copy);
150158

151159
const cJSON* method = cJSON_GetObjectItem(request, "method");
152160
if (method != NULL && method->type == cJSON_String)
@@ -156,12 +164,12 @@ static cJSON* rpc_handle_obj_req(const mjrpc_handle_t* handle, const cJSON* requ
156164
return invoke_callback(handle, method->valuestring, params, id_copy);
157165
}
158166
return mjrpc_response_error(JSON_RPC_CODE_INVALID_REQUEST,
159-
strdup("Invalid request received: No 'method' member."),
167+
g_mjrpc_strdup("Invalid request received: No 'method' member."),
160168
id_copy);
161169
}
162170
// Invalid id type
163171
return mjrpc_response_error(JSON_RPC_CODE_INVALID_REQUEST,
164-
strdup("Invalid request received: 'id' member type error."),
172+
g_mjrpc_strdup("Invalid request received: 'id' member type error."),
165173
cJSON_CreateNull());
166174
}
167175

@@ -243,7 +251,7 @@ cJSON* mjrpc_response_error(int code, char* message, cJSON* id)
243251
if (id == NULL)
244252
{
245253
if (message)
246-
free(message);
254+
g_mjrpc_free(message);
247255
return NULL;
248256
}
249257

@@ -266,7 +274,7 @@ cJSON* mjrpc_response_error(int code, char* message, cJSON* id)
266274
if (message)
267275
{
268276
cJSON_AddStringToObject(error_root, "message", message);
269-
free(message);
277+
g_mjrpc_free(message);
270278
}
271279
else
272280
{
@@ -286,15 +294,16 @@ mjrpc_handle_t* mjrpc_create_handle(size_t initial_capacity)
286294
{
287295
if (initial_capacity == 0)
288296
initial_capacity = DEFAULT_INITIAL_CAPACITY;
289-
mjrpc_handle_t* handle = malloc(sizeof(mjrpc_handle_t));
297+
mjrpc_handle_t* handle = g_mjrpc_malloc(sizeof(mjrpc_handle_t));
290298
if (handle == NULL)
291299
return NULL;
292300
handle->capacity = initial_capacity;
293301
handle->size = 0;
294-
handle->methods = (struct mjrpc_method*) calloc(handle->capacity, sizeof(struct mjrpc_method));
302+
handle->methods =
303+
(struct mjrpc_method*) g_mjrpc_malloc(handle->capacity * sizeof(struct mjrpc_method));
295304
if (handle->methods == NULL)
296305
{
297-
free(handle);
306+
g_mjrpc_free(handle);
298307
return NULL;
299308
}
300309
return handle;
@@ -308,13 +317,13 @@ int mjrpc_destroy_handle(mjrpc_handle_t* handle)
308317
{
309318
if (handle->methods[i].state == OCCUPIED)
310319
{
311-
free(handle->methods[i].name);
320+
g_mjrpc_free(handle->methods[i].name);
312321
if (handle->methods[i].arg != NULL)
313-
free(handle->methods[i].arg);
322+
g_mjrpc_free(handle->methods[i].arg);
314323
}
315324
}
316-
free(handle->methods);
317-
free(handle);
325+
g_mjrpc_free(handle->methods);
326+
g_mjrpc_free(handle);
318327
return MJRPC_RET_OK;
319328
}
320329

@@ -345,7 +354,7 @@ int mjrpc_add_method(mjrpc_handle_t* handle, mjrpc_func function_pointer, const
345354
index = (index + probe_count * probe_count) % handle->capacity;
346355
}
347356

348-
handle->methods[index].name = strdup(method_name);
357+
handle->methods[index].name = g_mjrpc_strdup(method_name);
349358
handle->methods[index].func = function_pointer;
350359
handle->methods[index].arg = arg2func;
351360
handle->methods[index].state = OCCUPIED;
@@ -365,9 +374,9 @@ int mjrpc_del_method(mjrpc_handle_t* handle, const char* name)
365374
if (handle->methods[index].state == OCCUPIED &&
366375
strcmp(handle->methods[index].name, name) == 0)
367376
{
368-
free(handle->methods[index].name);
377+
g_mjrpc_free(handle->methods[index].name);
369378
if (handle->methods[index].arg != NULL)
370-
free(handle->methods[index].arg);
379+
g_mjrpc_free(handle->methods[index].arg);
371380
handle->methods[index].state = DELETED;
372381
handle->size--;
373382
return MJRPC_RET_OK;
@@ -410,7 +419,8 @@ cJSON* mjrpc_process_cjson(mjrpc_handle_t* handle, const cJSON* request_cjson, i
410419
*ret_code = ret;
411420
return mjrpc_response_error(
412421
JSON_RPC_CODE_PARSE_ERROR,
413-
strdup("Invalid request received: Not a JSON formatted request."), cJSON_CreateNull());
422+
g_mjrpc_strdup("Invalid request received: Not a JSON formatted request."),
423+
cJSON_CreateNull());
414424
}
415425

416426
cJSON* cjson_return = NULL;
@@ -422,7 +432,7 @@ cJSON* mjrpc_process_cjson(mjrpc_handle_t* handle, const cJSON* request_cjson, i
422432
ret = MJRPC_RET_ERROR_EMPTY_REQUEST;
423433
cjson_return = mjrpc_response_error(
424434
JSON_RPC_CODE_PARSE_ERROR,
425-
strdup("Invalid request received: Empty JSON array."), cJSON_CreateNull());
435+
g_mjrpc_strdup("Invalid request received: Empty JSON array."), cJSON_CreateNull());
426436
}
427437
else
428438
{
@@ -441,7 +451,7 @@ cJSON* mjrpc_process_cjson(mjrpc_handle_t* handle, const cJSON* request_cjson, i
441451
ret = MJRPC_RET_ERROR_EMPTY_REQUEST;
442452
cjson_return = mjrpc_response_error(
443453
JSON_RPC_CODE_PARSE_ERROR,
444-
strdup("Invalid request received: Empty JSON object."), cJSON_CreateNull());
454+
g_mjrpc_strdup("Invalid request received: Empty JSON object."), cJSON_CreateNull());
445455
}
446456
else
447457
{
@@ -456,10 +466,37 @@ cJSON* mjrpc_process_cjson(mjrpc_handle_t* handle, const cJSON* request_cjson, i
456466
{
457467
cjson_return = mjrpc_response_error(
458468
JSON_RPC_CODE_PARSE_ERROR,
459-
strdup("Invalid request received: Not a JSON object or array."), cJSON_CreateNull());
469+
g_mjrpc_strdup("Invalid request received: Not a JSON object or array."),
470+
cJSON_CreateNull());
460471
ret = MJRPC_RET_ERROR_NOT_OBJ_ARY;
461472
}
462473
if (ret_code)
463474
*ret_code = ret;
464475
return cjson_return;
465476
}
477+
478+
int mjrpc_set_memory_hooks(mjrpc_malloc_func malloc_func, mjrpc_free_func free_func,
479+
mjrpc_strdup_func strdup_func)
480+
{
481+
// If all parameters are NULL, reset to default functions
482+
if (malloc_func == NULL && free_func == NULL && strdup_func == NULL)
483+
{
484+
g_mjrpc_malloc = malloc;
485+
g_mjrpc_free = free;
486+
g_mjrpc_strdup = strdup;
487+
return MJRPC_RET_OK;
488+
}
489+
490+
// If any parameter is not NULL, all must be provided
491+
if (malloc_func == NULL || free_func == NULL || strdup_func == NULL)
492+
{
493+
return MJRPC_RET_ERROR_INVALID_PARAM;
494+
}
495+
496+
// Set custom functions
497+
g_mjrpc_malloc = malloc_func;
498+
g_mjrpc_free = free_func;
499+
g_mjrpc_strdup = strdup_func;
500+
501+
return MJRPC_RET_OK;
502+
}

0 commit comments

Comments
 (0)