@@ -11,6 +11,7 @@ from rosidl_parser.definition import Array
1111from rosidl_parser .definition import BasicType
1212from rosidl_parser .definition import EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME
1313from rosidl_parser .definition import NamespacedType
14+ from rosidl_parser .definition import UnboundedSequence
1415from rosidl_parser .definition import SERVICE_RESPONSE_MESSAGE_SUFFIX
1516from rosidl_parser .definition import SERVICE_REQUEST_MESSAGE_SUFFIX
1617
@@ -28,13 +29,25 @@ def primitive_msg_type_to_c(type_):
2829 return BASIC_IDL_TYPES_TO_C[type_ .typename ]
2930
3031
32+ # Check if this message has any uint8[] buffer fields
33+ has_buffer_fields = False
34+ for member in message .structure .members :
35+ if isinstance (member .type , UnboundedSequence) and isinstance (member .type .value_type , BasicType) and member .type .value_type .typename == ' uint8' :
36+ has_buffer_fields = True
37+ break
38+
39+
3140include_parts = [package_name] + list (interface_path .parents [0 ].parts ) + [
3241 ' detail' , convert_camel_case_to_lower_case_underscore (interface_path .stem )]
3342include_base = ' /' .join (include_parts)
3443
3544header_files = [
3645 ' Python.h' ,
3746 ' stdbool.h' ,
47+ ]
48+ if has_buffer_fields :
49+ header_files .append (' stdint.h' )
50+ header_files += [
3851 ' numpy/ndarrayobject.h' ,
3952 ' rosidl_runtime_c/visibility_control.h' ,
4053 include_base + ' __struct.h' ,
@@ -68,6 +81,7 @@ repeated_header_file = header_file in include_directives
6881# endif
6982@ [ end if ]@
7083@ [end for ]@
84+ @ # Buffer-backed uint8[] fields use the is_rosidl_buffer flag on the sequence struct.
7185
7286@ {
7387have_not_included_primitive_arrays = True
@@ -250,6 +264,43 @@ nested_type = '__'.join(type_.namespaced_name())
250264@ [ end if ]@
251265@ [ elif isinstance (member .type , AbstractNestedType)]@
252266@ [ if isinstance (member .type , AbstractSequence) and isinstance (member .type .value_type , BasicType)]@
267+ @ [ if isinstance (member .type , UnboundedSequence) and member .type .value_type .typename == ' uint8' ]@
268+ // Check if the field is an rosidl_buffer .Buffer with a non- CPU backend
269+ {
270+ PyObject * backend_attr = PyObject_GetAttrString (field, " backend_type" );
271+ if (backend_attr != NULL) {
272+ const char * backend_str = PyUnicode_AsUTF8 (backend_attr);
273+ if (backend_str != NULL && strcmp (backend_str, " cpu" ) != 0 ) {
274+ // Non- CPU backend : set is_rosidl_buffer flag instead of copying data
275+ PyObject * rosidl_buffer_mod = PyImport_ImportModule (" rosidl_buffer" );
276+ if (rosidl_buffer_mod != NULL) {
277+ PyObject * get_ptr_func = PyObject_GetAttrString (rosidl_buffer_mod, " _get_buffer_ptr" );
278+ if (get_ptr_func != NULL) {
279+ PyObject * ptr_result = PyObject_CallFunctionObjArgs (get_ptr_func, field, NULL);
280+ if (ptr_result != NULL) {
281+ uintptr_t buffer_ptr = (uintptr_t)PyLong_AsUnsignedLongLong (ptr_result);
282+ ros_message-> @ (member .name ).data = (uint8_t * )buffer_ptr;
283+ ros_message-> @ (member .name ).size = 0 ;
284+ ros_message-> @ (member .name ).capacity = 0 ;
285+ ros_message-> @ (member .name ).is_rosidl_buffer = true ;
286+ ros_message-> @ (member .name ).owns_rosidl_buffer = false ;
287+ Py_DECREF (ptr_result);
288+ }
289+ Py_DECREF (get_ptr_func);
290+ }
291+ Py_DECREF (rosidl_buffer_mod);
292+ }
293+ Py_DECREF (backend_attr);
294+ Py_DECREF (field);
295+ // Sentinel is set, skip normal conversion for this field
296+ goto @ (member .name )__done;
297+ }
298+ Py_DECREF (backend_attr);
299+ } else {
300+ PyErr_Clear ();
301+ }
302+ }
303+ @ [ end if ]@
253304 if (PyObject_CheckBuffer (field)) {
254305 // Optimization for converting arrays of primitives
255306 Py_buffer view;
@@ -513,6 +564,10 @@ nested_type = '__'.join(type_.namespaced_name())
513564@ [ end if ]@
514565 Py_DECREF (field);
515566 }
567+ @ [ if isinstance (member .type , UnboundedSequence) and isinstance (member .type .value_type , BasicType) and member .type .value_type .typename == ' uint8' ]@
568+ @ (member .name )__done :
569+ ;
570+ @ [ end if ]@
516571@ [end for ]@
517572
518573 return true ;
@@ -569,60 +624,100 @@ if isinstance(type_, AbstractNestedType):
569624 memcpy (dst, src, @ (member .type .size ) * sizeof (@ primitive_msg_type_to_c (member .type .value_type )));
570625 Py_DECREF (field);
571626@ [ elif isinstance (member .type , AbstractSequence)]@
572- field = PyObject_GetAttrString (_pymessage, " @(member.name)" );
573- if (! field) {
574- return NULL;
575- }
576- assert (field-> ob_type != NULL);
577- assert (field-> ob_type-> tp_name != NULL);
578- assert (strcmp (field-> ob_type-> tp_name, " array.array" ) == 0 );
579- // ensure that itemsize matches the sizeof of the ROS message field
580- PyObject * itemsize_attr = PyObject_GetAttrString (field, " itemsize" );
581- assert (itemsize_attr != NULL);
582- size_t itemsize = PyLong_AsSize_t (itemsize_attr);
583- Py_DECREF (itemsize_attr);
584- if (itemsize != sizeof (@ primitive_msg_type_to_c (member .type .value_type ))) {
585- PyErr_SetString (PyExc_RuntimeError, " itemsize doesn't match expectation" );
586- Py_DECREF (field);
587- return NULL;
588- }
589- // clear the array, poor approach to remove potential default values
590- Py_ssize_t length = PyObject_Length (field);
591- if (- 1 == length) {
592- Py_DECREF (field);
593- return NULL;
594- }
595- if (length > 0 ) {
596- PyObject * pop = PyObject_GetAttrString (field, " pop" );
597- assert (pop != NULL);
598- for (Py_ssize_t i = 0 ; i < length; ++ i) {
599- PyObject * ret = PyObject_CallFunctionObjArgs (pop, NULL);
600- if (! ret) {
601- Py_DECREF (pop);
602- Py_DECREF (field);
603- return NULL;
627+ @ [ if isinstance (member .type , UnboundedSequence) and member .type .value_type .typename == ' uint8' ]@
628+ if (ros_message-> @ (member .name ).is_rosidl_buffer ) {
629+ // The RMW deserialized into a vendor- backed buffer — wrap it in a Python Buffer.
630+ // All C++ operations go through the rosidl_buffer ._rosidl_buffer_py module since this
631+ // file is compiled as C.
632+ PyObject * rosidl_buffer_internal = PyImport_ImportModule (" rosidl_buffer" );
633+ if (rosidl_buffer_internal != NULL) {
634+ PyObject * take_func = PyObject_GetAttrString (rosidl_buffer_internal, " _take_buffer_from_ptr" );
635+ if (take_func != NULL) {
636+ PyObject * ptr_arg = PyLong_FromUnsignedLongLong (
637+ (uint64_t)(uintptr_t)ros_message-> @ (member .name ).data );
638+ field = PyObject_CallFunctionObjArgs (take_func, ptr_arg, NULL);
639+ Py_XDECREF (ptr_arg);
640+ Py_DECREF (take_func);
641+ if (field != NULL) {
642+ // Ownership transferred to Python — clear the C sequence immediately
643+ ros_message-> @ (member .name ).data = NULL;
644+ ros_message-> @ (member .name ).size = 0 ;
645+ ros_message-> @ (member .name ).capacity = 0 ;
646+ ros_message-> @ (member .name ).is_rosidl_buffer = false ;
647+ ros_message-> @ (member .name ).owns_rosidl_buffer = false ;
648+ }
604649 }
605- Py_DECREF (ret );
650+ Py_DECREF (rosidl_buffer_internal );
606651 }
607- Py_DECREF (pop);
608- }
609- if (ros_message-> @ (member .name ).size > 0 ) {
610- // populating the array .array using the frombytes method
611- PyObject * frombytes = PyObject_GetAttrString (field, " frombytes" );
612- assert (frombytes != NULL);
613- @ primitive_msg_type_to_c (member .type .value_type ) * src = & (ros_message-> @ (member .name ).data [0 ]);
614- PyObject * data = PyBytes_FromStringAndSize ((const char * )src, ros_message-> @ (member .name ).size * sizeof (@ primitive_msg_type_to_c (member .type .value_type )));
615- assert (data != NULL);
616- PyObject * ret = PyObject_CallFunctionObjArgs (frombytes, data, NULL);
617- Py_DECREF (data);
618- Py_DECREF (frombytes);
619- if (! ret) {
652+ if (field == NULL) {
653+ return NULL;
654+ }
655+ // Set the Buffer on the Python message object
656+ if (PyObject_SetAttrString (_pymessage, " @(member.name)" , field) == - 1 ) {
620657 Py_DECREF (field);
621658 return NULL;
622659 }
623- Py_DECREF (ret);
624- }
625- Py_DECREF (field);
660+ Py_DECREF (field);
661+ } else {
662+ @ [ end if ]@
663+ @ {bi = ' ' if (isinstance (member .type , UnboundedSequence) and member .type .value_type .typename == ' uint8' ) else ' ' }@
664+ @ (bi) field = PyObject_GetAttrString (_pymessage, " @(member.name)" );
665+ @ (bi) if (! field) {
666+ @ (bi) return NULL;
667+ @ (bi) }
668+ @ (bi) assert (field-> ob_type != NULL);
669+ @ (bi) assert (field-> ob_type-> tp_name != NULL);
670+ @ (bi) assert (strcmp (field-> ob_type-> tp_name, " array.array" ) == 0 );
671+ @ (bi) // ensure that itemsize matches the sizeof of the ROS message field
672+ @ (bi) PyObject * itemsize_attr = PyObject_GetAttrString (field, " itemsize" );
673+ @ (bi) assert (itemsize_attr != NULL);
674+ @ (bi) size_t itemsize = PyLong_AsSize_t (itemsize_attr);
675+ @ (bi) Py_DECREF (itemsize_attr);
676+ @ (bi) if (itemsize != sizeof (@ primitive_msg_type_to_c (member .type .value_type ))) {
677+ @ (bi) PyErr_SetString (PyExc_RuntimeError, " itemsize doesn't match expectation" );
678+ @ (bi) Py_DECREF (field);
679+ @ (bi) return NULL;
680+ @ (bi) }
681+ @ (bi) // clear the array, poor approach to remove potential default values
682+ @ (bi) Py_ssize_t length = PyObject_Length (field);
683+ @ (bi) if (- 1 == length) {
684+ @ (bi) Py_DECREF (field);
685+ @ (bi) return NULL;
686+ @ (bi) }
687+ @ (bi) if (length > 0 ) {
688+ @ (bi) PyObject * pop = PyObject_GetAttrString (field, " pop" );
689+ @ (bi) assert (pop != NULL);
690+ @ (bi) for (Py_ssize_t i = 0 ; i < length; ++ i) {
691+ @ (bi) PyObject * ret = PyObject_CallFunctionObjArgs (pop, NULL);
692+ @ (bi) if (! ret) {
693+ @ (bi) Py_DECREF (pop);
694+ @ (bi) Py_DECREF (field);
695+ @ (bi) return NULL;
696+ @ (bi) }
697+ @ (bi) Py_DECREF (ret);
698+ @ (bi) }
699+ @ (bi) Py_DECREF (pop);
700+ @ (bi) }
701+ @ (bi) if (ros_message-> @ (member .name ).size > 0 ) {
702+ @ (bi) // populating the array .array using the frombytes method
703+ @ (bi) PyObject * frombytes = PyObject_GetAttrString (field, " frombytes" );
704+ @ (bi) assert (frombytes != NULL);
705+ @ (bi) @ primitive_msg_type_to_c (member .type .value_type ) * src = & (ros_message-> @ (member .name ).data [0 ]);
706+ @ (bi) PyObject * data = PyBytes_FromStringAndSize ((const char * )src, ros_message-> @ (member .name ).size * sizeof (@ primitive_msg_type_to_c (member .type .value_type )));
707+ @ (bi) assert (data != NULL);
708+ @ (bi) PyObject * ret = PyObject_CallFunctionObjArgs (frombytes, data, NULL);
709+ @ (bi) Py_DECREF (data);
710+ @ (bi) Py_DECREF (frombytes);
711+ @ (bi) if (! ret) {
712+ @ (bi) Py_DECREF (field);
713+ @ (bi) return NULL;
714+ @ (bi) }
715+ @ (bi) Py_DECREF (ret);
716+ @ (bi) }
717+ @ (bi) Py_DECREF (field);
718+ @ [ if isinstance (member .type , UnboundedSequence) and member .type .value_type .typename == ' uint8' ]@
719+ } // end else (non- buffer path)
720+ @ [ end if ]@
626721@ [ end if ]@
627722@ [ else ]@
628723@ [ if isinstance (type_, NamespacedType)]@
0 commit comments