21#include <freerdp/config.h>
26#include <winpr/image.h>
27#include <winpr/stream.h>
28#include <winpr/clipboard.h>
30#include <freerdp/log.h>
31#include <freerdp/client/cliprdr.h>
32#include <freerdp/channels/channels.h>
33#include <freerdp/channels/cliprdr.h>
35#include <freerdp/client/client_cliprdr_file.h>
37#include "wlf_cliprdr.h"
39#define TAG CLIENT_TAG("wayland.cliprdr")
41#define mime_text_plain "text/plain"
43static const char mime_text_utf8[] = mime_text_plain
";charset=utf-8";
46static const char* mime_text[] = { mime_text_plain, mime_text_utf8,
"UTF8_STRING",
47 "COMPOUND_TEXT",
"TEXT",
"STRING" };
49static const char mime_png[] =
"image/png";
50static const char mime_webp[] =
"image/webp";
51static const char mime_jpg[] =
"image/jpeg";
52static const char mime_tiff[] =
"image/tiff";
53static const char mime_uri_list[] =
"text/uri-list";
54static const char mime_html[] =
"text/html";
56#define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
57static const char* mime_bitmap[] = { BMP_MIME_LIST };
58static const char* mime_image[] = { mime_png, mime_webp, mime_jpg, mime_tiff, BMP_MIME_LIST };
60static const char mime_gnome_copied_files[] =
"x-special/gnome-copied-files";
61static const char mime_mate_copied_files[] =
"x-special/mate-copied-files";
63static const char type_FileGroupDescriptorW[] =
"FileGroupDescriptorW";
64static const char type_HtmlFormat[] =
"HTML Format";
69 UINT32 responseFormat;
75 const FILE* responseFile;
76 UINT32 responseFormat;
77 const char* responseMime;
83 rdpChannels* channels;
84 CliprdrClientContext* context;
90 size_t numClientFormats;
93 size_t numServerFormats;
99 CliprdrFileContext* file;
101 wQueue* request_queue;
104static void wlf_request_free(
void* rq)
106 wlf_request* request = rq;
109 free(request->responseMime);
110 if (request->responseFile)
111 (void)fclose(request->responseFile);
116static wlf_request* wlf_request_new(
void)
118 return calloc(1,
sizeof(wlf_request));
121static void* wlf_request_clone(
const void* oth)
123 const wlf_request* other = (
const wlf_request*)oth;
124 wlf_request* copy = wlf_request_new();
128 if (other->responseMime)
130 copy->responseMime = _strdup(other->responseMime);
131 if (!copy->responseMime)
136 wlf_request_free(copy);
140static BOOL wlf_mime_is_file(
const char* mime)
142 if (strncmp(mime_uri_list, mime,
sizeof(mime_uri_list)) == 0)
144 if (strncmp(mime_gnome_copied_files, mime,
sizeof(mime_gnome_copied_files)) == 0)
146 if (strncmp(mime_mate_copied_files, mime,
sizeof(mime_mate_copied_files)) == 0)
151static BOOL wlf_mime_is_text(
const char* mime)
153 for (
size_t x = 0; x < ARRAYSIZE(mime_text); x++)
155 if (strcmp(mime, mime_text[x]) == 0)
162static BOOL wlf_mime_is_image(
const char* mime)
164 for (
size_t x = 0; x < ARRAYSIZE(mime_image); x++)
166 if (strcmp(mime, mime_image[x]) == 0)
173static BOOL wlf_mime_is_html(
const char* mime)
175 return strcmp(mime, mime_html) == 0;
178static void wlf_cliprdr_free_server_formats(wfClipboard* clipboard)
180 if (clipboard && clipboard->serverFormats)
182 for (
size_t j = 0; j < clipboard->numServerFormats; j++)
185 free(format->formatName);
188 free(clipboard->serverFormats);
189 clipboard->serverFormats =
nullptr;
190 clipboard->numServerFormats = 0;
194 UwacClipboardOfferDestroy(clipboard->seat);
197static void wlf_cliprdr_free_client_formats(wfClipboard* clipboard)
199 if (clipboard && clipboard->numClientFormats)
201 for (
size_t j = 0; j < clipboard->numClientFormats; j++)
204 free(format->formatName);
207 free(clipboard->clientFormats);
208 clipboard->clientFormats =
nullptr;
209 clipboard->numClientFormats = 0;
213 UwacClipboardOfferDestroy(clipboard->seat);
221static UINT wlf_cliprdr_send_client_format_list(wfClipboard* clipboard)
223 WINPR_ASSERT(clipboard);
226 .numFormats = (UINT32)clipboard->numClientFormats,
227 .formats = clipboard->clientFormats,
228 .common.msgType = CB_FORMAT_LIST };
230 if (!cliprdr_file_context_clear(clipboard->file))
231 return ERROR_INTERNAL_ERROR;
233 WLog_VRB(TAG,
"-------------- client format list [%" PRIu32
"] ------------------",
234 formatList.numFormats);
235 for (UINT32 x = 0; x < formatList.numFormats; x++)
238 WLog_VRB(TAG,
"client announces %" PRIu32
" [%s][%s]", format->formatId,
239 ClipboardGetFormatIdString(format->formatId), format->formatName);
241 WINPR_ASSERT(clipboard->context);
242 WINPR_ASSERT(clipboard->context->ClientFormatList);
243 return clipboard->context->ClientFormatList(clipboard->context, &formatList);
246static void wfl_cliprdr_add_client_format_id(wfClipboard* clipboard, UINT32 formatId)
249 const char* name = ClipboardGetFormatName(clipboard->system, formatId);
251 for (
size_t x = 0; x < clipboard->numClientFormats; x++)
253 format = &clipboard->clientFormats[x];
255 if (format->formatId == formatId)
259 format = realloc(clipboard->clientFormats,
265 clipboard->clientFormats = format;
266 format = &clipboard->clientFormats[clipboard->numClientFormats++];
267 format->formatId = formatId;
268 format->formatName =
nullptr;
270 if (name && (formatId >= CF_MAX))
271 format->formatName = _strdup(name);
274static BOOL wlf_cliprdr_add_client_format(wfClipboard* clipboard,
const char* mime)
277 ClipboardLock(clipboard->system);
278 if (wlf_mime_is_html(mime))
280 UINT32 formatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
281 wfl_cliprdr_add_client_format_id(clipboard, formatId);
283 else if (wlf_mime_is_text(mime))
285 wfl_cliprdr_add_client_format_id(clipboard, CF_TEXT);
286 wfl_cliprdr_add_client_format_id(clipboard, CF_OEMTEXT);
287 wfl_cliprdr_add_client_format_id(clipboard, CF_UNICODETEXT);
289 else if (wlf_mime_is_image(mime))
291 for (
size_t x = 0; x < ARRAYSIZE(mime_image); x++)
293 const char* mime_bmp = mime_image[x];
294 UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_bmp);
296 wfl_cliprdr_add_client_format_id(clipboard, formatId);
298 wfl_cliprdr_add_client_format_id(clipboard, CF_DIB);
299 wfl_cliprdr_add_client_format_id(clipboard, CF_TIFF);
301 else if (wlf_mime_is_file(mime))
303 const UINT32 fileFormatId =
304 ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
305 wfl_cliprdr_add_client_format_id(clipboard, fileFormatId);
308 ClipboardUnlock(clipboard->system);
309 return (wlf_cliprdr_send_client_format_list(clipboard) == CHANNEL_RC_OK);
317static UINT wlf_cliprdr_send_data_request(wfClipboard* clipboard,
const wlf_const_request* rq)
323 if (!Queue_Enqueue(clipboard->request_queue, rq))
324 return ERROR_INTERNAL_ERROR;
326 WINPR_ASSERT(clipboard);
327 WINPR_ASSERT(clipboard->context);
328 WINPR_ASSERT(clipboard->context->ClientFormatDataRequest);
329 return clipboard->context->ClientFormatDataRequest(clipboard->context, &request);
337static UINT wlf_cliprdr_send_data_response(wfClipboard* clipboard,
const BYTE* data,
size_t size)
341 if (size > UINT32_MAX)
342 return ERROR_INVALID_PARAMETER;
344 response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
345 response.common.dataLen = (UINT32)size;
346 response.requestedFormatData = data;
348 WINPR_ASSERT(clipboard);
349 WINPR_ASSERT(clipboard->context);
350 WINPR_ASSERT(clipboard->context->ClientFormatDataResponse);
351 return clipboard->context->ClientFormatDataResponse(clipboard->context, &response);
354BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard,
const UwacClipboardEvent* event)
356 if (!clipboard || !event)
359 if (!clipboard->context)
364 case UWAC_EVENT_CLIPBOARD_AVAILABLE:
365 clipboard->seat =
event->seat;
368 case UWAC_EVENT_CLIPBOARD_OFFER:
369 WLog_Print(clipboard->log, WLOG_DEBUG,
"client announces mime %s", event->mime);
370 return wlf_cliprdr_add_client_format(clipboard, event->mime);
372 case UWAC_EVENT_CLIPBOARD_SELECT:
373 WLog_Print(clipboard->log, WLOG_DEBUG,
"client announces new data");
374 wlf_cliprdr_free_client_formats(clipboard);
387static UINT wlf_cliprdr_send_client_capabilities(wfClipboard* clipboard)
389 WINPR_ASSERT(clipboard);
392 .capabilitySetType = CB_CAPSTYPE_GENERAL,
393 .capabilitySetLength = 12,
394 .version = CB_CAPS_VERSION_2,
396 CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(clipboard->file)
402 WINPR_ASSERT(clipboard);
403 WINPR_ASSERT(clipboard->context);
404 WINPR_ASSERT(clipboard->context->ClientCapabilities);
405 return clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
413static UINT wlf_cliprdr_send_client_format_list_response(wfClipboard* clipboard, BOOL status)
416 .common.msgType = CB_FORMAT_LIST_RESPONSE,
417 .common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL,
420 WINPR_ASSERT(clipboard);
421 WINPR_ASSERT(clipboard->context);
422 WINPR_ASSERT(clipboard->context->ClientFormatListResponse);
423 return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
431static UINT wlf_cliprdr_monitor_ready(CliprdrClientContext* context,
436 WINPR_UNUSED(monitorReady);
437 WINPR_ASSERT(context);
438 WINPR_ASSERT(monitorReady);
440 wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
441 WINPR_ASSERT(clipboard);
443 if ((ret = wlf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK)
446 if ((ret = wlf_cliprdr_send_client_format_list(clipboard)) != CHANNEL_RC_OK)
449 clipboard->sync = TRUE;
450 return CHANNEL_RC_OK;
458static UINT wlf_cliprdr_server_capabilities(CliprdrClientContext* context,
461 WINPR_ASSERT(context);
462 WINPR_ASSERT(capabilities);
464 const BYTE* capsPtr = (
const BYTE*)capabilities->capabilitySets;
465 WINPR_ASSERT(capsPtr);
467 wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
468 WINPR_ASSERT(clipboard);
470 if (!cliprdr_file_context_remote_set_flags(clipboard->file, 0))
471 return ERROR_INTERNAL_ERROR;
473 for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
477 if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
482 if (!cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags))
483 return ERROR_INTERNAL_ERROR;
486 capsPtr += caps->capabilitySetLength;
489 return CHANNEL_RC_OK;
492static UINT32 wlf_get_server_format_id(
const wfClipboard* clipboard,
const char* name)
494 WINPR_ASSERT(clipboard);
497 for (UINT32 x = 0; x < clipboard->numServerFormats; x++)
500 if (!format->formatName)
502 if (strcmp(name, format->formatName) == 0)
503 return format->formatId;
508static const char* wlf_get_server_format_name(
const wfClipboard* clipboard, UINT32 formatId)
510 WINPR_ASSERT(clipboard);
512 for (UINT32 x = 0; x < clipboard->numServerFormats; x++)
515 if (format->formatId == formatId)
516 return format->formatName;
521static void wlf_cliprdr_transfer_data(UwacSeat* seat,
void* context,
const char* mime,
int fd)
523 wfClipboard* clipboard = (wfClipboard*)context;
526 EnterCriticalSection(&clipboard->lock);
528 wlf_const_request request = WINPR_C_ARRAY_INIT;
529 if (wlf_mime_is_html(mime))
531 request.responseMime = mime_html;
532 request.responseFormat = wlf_get_server_format_id(clipboard, type_HtmlFormat);
534 else if (wlf_mime_is_file(mime))
536 request.responseMime = mime;
537 request.responseFormat = wlf_get_server_format_id(clipboard, type_FileGroupDescriptorW);
539 else if (wlf_mime_is_text(mime))
541 request.responseMime = mime_text_plain;
542 request.responseFormat = CF_UNICODETEXT;
544 else if (wlf_mime_is_image(mime))
546 request.responseMime = mime;
547 if (strcmp(mime, mime_tiff) == 0)
548 request.responseFormat = CF_TIFF;
550 request.responseFormat = CF_DIB;
553 if (request.responseMime !=
nullptr)
555 request.responseFile = fdopen(fd,
"w");
557 if (request.responseFile)
558 wlf_cliprdr_send_data_request(clipboard, &request);
560 WLog_Print(clipboard->log, WLOG_ERROR,
561 "failed to open clipboard file descriptor for MIME %s",
562 request.responseMime);
565 LeaveCriticalSection(&clipboard->lock);
568static void wlf_cliprdr_cancel_data(UwacSeat* seat,
void* context)
570 wfClipboard* clipboard = (wfClipboard*)context;
573 WINPR_ASSERT(clipboard);
574 cliprdr_file_context_clear(clipboard->file);
585static UINT wlf_cliprdr_server_format_list(CliprdrClientContext* context,
593 if (!context || !context->custom)
594 return ERROR_INVALID_PARAMETER;
596 wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
597 WINPR_ASSERT(clipboard);
599 wlf_cliprdr_free_server_formats(clipboard);
600 if (!cliprdr_file_context_clear(clipboard->file))
601 return ERROR_INTERNAL_ERROR;
603 if (!(clipboard->serverFormats =
606 WLog_Print(clipboard->log, WLOG_ERROR,
607 "failed to allocate %" PRIuz
" CLIPRDR_FORMAT structs",
608 clipboard->numServerFormats);
609 return CHANNEL_RC_NO_MEMORY;
612 clipboard->numServerFormats = formatList->numFormats;
614 if (!clipboard->seat)
616 WLog_Print(clipboard->log, WLOG_ERROR,
617 "clipboard->seat=nullptr, check your client implementation");
618 return ERROR_INTERNAL_ERROR;
621 for (UINT32 i = 0; i < formatList->numFormats; i++)
625 srvFormat->formatId = format->formatId;
627 if (format->formatName)
629 srvFormat->formatName = _strdup(format->formatName);
631 if (!srvFormat->formatName)
633 wlf_cliprdr_free_server_formats(clipboard);
634 return CHANNEL_RC_NO_MEMORY;
638 if (format->formatName)
640 if (strcmp(format->formatName, type_HtmlFormat) == 0)
645 else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
653 switch (format->formatId)
673 UwacClipboardOfferCreate(clipboard->seat, mime_html);
676 if (file && cliprdr_file_context_has_local_support(clipboard->file))
678 UwacClipboardOfferCreate(clipboard->seat, mime_uri_list);
679 UwacClipboardOfferCreate(clipboard->seat, mime_gnome_copied_files);
680 UwacClipboardOfferCreate(clipboard->seat, mime_mate_copied_files);
685 for (
size_t x = 0; x < ARRAYSIZE(mime_text); x++)
686 UwacClipboardOfferCreate(clipboard->seat, mime_text[x]);
691 for (
size_t x = 0; x < ARRAYSIZE(mime_image); x++)
692 UwacClipboardOfferCreate(clipboard->seat, mime_image[x]);
695 UwacClipboardOfferAnnounce(clipboard->seat, clipboard, wlf_cliprdr_transfer_data,
696 wlf_cliprdr_cancel_data);
697 return wlf_cliprdr_send_client_format_list_response(clipboard, TRUE);
706wlf_cliprdr_server_format_list_response(WINPR_ATTR_UNUSED CliprdrClientContext* context,
709 WINPR_ASSERT(context);
710 WINPR_ASSERT(formatListResponse);
712 if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
713 WLog_WARN(TAG,
"format list update failed");
714 return CHANNEL_RC_OK;
723wlf_cliprdr_server_format_data_request(CliprdrClientContext* context,
726 UINT rc = CHANNEL_RC_OK;
727 char* data =
nullptr;
729 const char* mime =
nullptr;
731 UINT32 localFormatId = 0;
732 wfClipboard* clipboard =
nullptr;
735 BYTE* ddata =
nullptr;
737 WINPR_ASSERT(context);
738 WINPR_ASSERT(formatDataRequest);
740 localFormatId = formatId = formatDataRequest->requestedFormatId;
741 clipboard = cliprdr_file_context_get_context(context->custom);
742 WINPR_ASSERT(clipboard);
744 ClipboardLock(clipboard->system);
745 const UINT32 fileFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
746 const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
753 localFormatId = ClipboardGetFormatId(clipboard->system, mime_text_plain);
754 mime = mime_text_utf8;
759 mime = mime_bitmap[0];
767 if (formatId == fileFormatId)
769 localFormatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
770 mime = mime_uri_list;
772 else if (formatId == htmlFormatId)
774 localFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
782 data = UwacClipboardDataGet(clipboard->seat, mime, &size);
784 if (!data || (size > UINT32_MAX))
787 if (fileFormatId == formatId)
789 if (!cliprdr_file_context_update_client_data(clipboard->file, data, size))
794 const BOOL res = ClipboardSetData(clipboard->system, localFormatId, data, (UINT32)size);
800 data = ClipboardGetData(clipboard->system, formatId, &len);
805 if (fileFormatId == formatId)
807 const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file);
808 const UINT32 error = cliprdr_serialize_file_list_ex(
815 ClipboardUnlock(clipboard->system);
816 rc = wlf_cliprdr_send_data_response(clipboard, ddata, dsize);
827wlf_cliprdr_server_format_data_response(CliprdrClientContext* context,
830 UINT rc = ERROR_INTERNAL_ERROR;
832 WINPR_ASSERT(context);
833 WINPR_ASSERT(formatDataResponse);
835 const UINT32 size = formatDataResponse->common.dataLen;
836 const BYTE* data = formatDataResponse->requestedFormatData;
838 wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
839 WINPR_ASSERT(clipboard);
841 wlf_request* request = Queue_Dequeue(clipboard->request_queue);
846 if (formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL)
848 WLog_WARN(TAG,
"clipboard data request for format %" PRIu32
" [%s], mime %s failed",
849 request->responseFormat, ClipboardGetFormatIdString(request->responseFormat),
850 request->responseMime);
853 rc = ERROR_INTERNAL_ERROR;
855 ClipboardLock(clipboard->system);
856 EnterCriticalSection(&clipboard->lock);
858 BYTE* cdata =
nullptr;
859 UINT32 srcFormatId = 0;
860 UINT32 dstFormatId = 0;
861 switch (request->responseFormat)
866 srcFormatId = request->responseFormat;
867 dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
872 srcFormatId = request->responseFormat;
873 dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
878 const char* name = wlf_get_server_format_name(clipboard, request->responseFormat);
881 if (strcmp(type_FileGroupDescriptorW, name) == 0)
884 ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
886 ClipboardGetFormatId(clipboard->system, request->responseMime);
888 if (!cliprdr_file_context_update_server_data(clipboard->file,
889 clipboard->system, data, size))
892 else if (strcmp(type_HtmlFormat, name) == 0)
894 srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
896 ClipboardGetFormatId(clipboard->system, request->responseMime);
906 const BOOL sres = ClipboardSetData(clipboard->system, srcFormatId, data, size);
908 cdata = ClipboardGetData(clipboard->system, dstFormatId, &len);
914 if (request->responseFile)
916 const size_t res = fwrite(cdata, 1, len, request->responseFile);
927 ClipboardUnlock(clipboard->system);
928 LeaveCriticalSection(&clipboard->lock);
930 wlf_request_free(request);
934wfClipboard* wlf_clipboard_new(
wlfContext* wfc)
936 rdpChannels* channels =
nullptr;
937 wfClipboard* clipboard =
nullptr;
941 clipboard = (wfClipboard*)calloc(1,
sizeof(wfClipboard));
946 InitializeCriticalSection(&clipboard->lock);
947 clipboard->wfc = wfc;
948 channels = wfc->common.context.channels;
949 clipboard->log = WLog_Get(TAG);
950 clipboard->channels = channels;
951 clipboard->system = ClipboardCreate();
952 if (!clipboard->system)
955 clipboard->file = cliprdr_file_context_new(clipboard);
956 if (!clipboard->file)
959 if (!cliprdr_file_context_set_locally_available(clipboard->file, TRUE))
962 clipboard->request_queue = Queue_New(TRUE, -1, -1);
963 if (!clipboard->request_queue)
967 wObject* obj = Queue_Object(clipboard->request_queue);
976 wlf_clipboard_free(clipboard);
980void wlf_clipboard_free(wfClipboard* clipboard)
985 cliprdr_file_context_free(clipboard->file);
987 wlf_cliprdr_free_server_formats(clipboard);
988 wlf_cliprdr_free_client_formats(clipboard);
989 ClipboardDestroy(clipboard->system);
991 EnterCriticalSection(&clipboard->lock);
993 Queue_Free(clipboard->request_queue);
994 LeaveCriticalSection(&clipboard->lock);
995 DeleteCriticalSection(&clipboard->lock);
999BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr)
1001 WINPR_ASSERT(clipboard);
1002 WINPR_ASSERT(cliprdr);
1004 clipboard->context = cliprdr;
1005 cliprdr->MonitorReady = wlf_cliprdr_monitor_ready;
1006 cliprdr->ServerCapabilities = wlf_cliprdr_server_capabilities;
1007 cliprdr->ServerFormatList = wlf_cliprdr_server_format_list;
1008 cliprdr->ServerFormatListResponse = wlf_cliprdr_server_format_list_response;
1009 cliprdr->ServerFormatDataRequest = wlf_cliprdr_server_format_data_request;
1010 cliprdr->ServerFormatDataResponse = wlf_cliprdr_server_format_data_response;
1012 return cliprdr_file_context_init(clipboard->file, cliprdr);
1015BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr)
1017 WINPR_ASSERT(clipboard);
1018 if (!cliprdr_file_context_uninit(clipboard->file, cliprdr))
1022 cliprdr->custom =
nullptr;
This struct contains function pointer to initialize/free objects.
OBJECT_FREE_FN fnObjectFree
OBJECT_NEW_FN fnObjectNew