diff --git a/gtwrap/matlab_wrapper/mixins.py b/gtwrap/matlab_wrapper/mixins.py index f2e63b8..910e8ca 100644 --- a/gtwrap/matlab_wrapper/mixins.py +++ b/gtwrap/matlab_wrapper/mixins.py @@ -16,6 +16,7 @@ class CheckMixin: "char", "unsigned char", "size_t", + "uint64_t", "Key", # This is an alias for a uint64_t ) # Ignore the namespace for these datatypes diff --git a/gtwrap/matlab_wrapper/wrapper.py b/gtwrap/matlab_wrapper/wrapper.py index 90804c7..50b271c 100755 --- a/gtwrap/matlab_wrapper/wrapper.py +++ b/gtwrap/matlab_wrapper/wrapper.py @@ -54,6 +54,7 @@ def __init__(self, 'ConstMatrixView': 'double', 'int': 'numeric', 'size_t': 'numeric', + 'uint64_t': 'numeric', 'Key': 'numeric', 'bool': 'logical' } @@ -64,6 +65,7 @@ def __init__(self, 'char': 'char', 'unsigned char': 'unsigned char', 'size_t': 'int', + 'uint64_t': 'numeric', 'int': 'int', 'double': 'double', 'Point2': 'double', diff --git a/matlab.h b/matlab.h index 0547ceb..ae559a9 100644 --- a/matlab.h +++ b/matlab.h @@ -37,12 +37,14 @@ extern "C" { #include } +#include #include #include #include #include #include #include +#include #include using namespace std; @@ -122,13 +124,33 @@ void checkArguments(const string& name, int nargout, int nargin, int expected) { // wrapping C++ basic types in MATLAB arrays //***************************************************************************** -// default wrapping throws an error: only basic types are allowed in wrap +template +struct IsMatlabSizeOrKeyScalar + : std::integral_constant::value || + std::is_same::value> {}; + +// size_t and uint64_t can be the same C++ type, so handle both through the +// primary template rather than potentially duplicate explicit specializations. template -mxArray* wrap(const Class& value) { +mxArray* wrapDefault(const Class& value, std::true_type) { + mxArray *result = scalar(mxUINT32OR64_CLASS); + *static_cast(mxGetData(result)) = value; + return result; +} + +// Unsupported types retain the generic runtime error. +template +mxArray* wrapDefault(const Class&, std::false_type) { error("wrap internal error: attempted wrap of invalid type"); return 0; } +// Default wrapping supports size/key scalars and rejects all other types. +template +mxArray* wrap(const Class& value) { + return wrapDefault(value, IsMatlabSizeOrKeyScalar()); +} + // specialization to string // wraps into a character array template<> @@ -160,16 +182,6 @@ mxArray* wrap(const bool& value) { return result; } -// specialization to size_t but skip Win64 size check & CUDACC check -#if (!defined(_WIN64) && !defined(__LP64__)) || defined(__CUDACC__) -template<> -mxArray* wrap(const size_t& value) { - mxArray *result = scalar(mxUINT32OR64_CLASS); - *(size_t*)mxGetData(result) = value; - return result; -} -#endif - // specialization to int template<> mxArray* wrap(const int& value) { @@ -178,14 +190,6 @@ mxArray* wrap(const int& value) { return result; } -// specialization to gtsam::Key which is an alias for uint64_t -template<> -mxArray* wrap(const uint64_t& value) { - mxArray *result = scalar(mxUINT32OR64_CLASS); - *(uint64_t*)mxGetData(result) = value; - return result; -} - // specialization to double -> just double template<> mxArray* wrap(const double& value) { @@ -261,14 +265,40 @@ mxArray* wrap_enum(const T x, const std::string& classname) { // unwrapping MATLAB arrays into C++ basic types //***************************************************************************** -// default unwrapping throws an error -// as wrap only supports passing a reference or one of the basic types +// Check for 64-bit, as Mathworks says mxGetScalar only good for 32 bit template -T unwrap(const mxArray* array) { +T myGetScalar(const mxArray* array) { + switch (mxGetClassID(array)) { + case mxINT64_CLASS: + return (T) *(std::int64_t*) mxGetData(array); + case mxUINT64_CLASS: + return (T) *(std::uint64_t*) mxGetData(array); + default: + // hope for the best! + return (T) mxGetScalar(array); + } +} + +// size_t and uint64_t share this path whether they are aliases or distinct. +template +T unwrapDefault(const mxArray* array, std::true_type) { + checkScalar(array, "unwrap"); + return myGetScalar(array); +} + +// Unsupported types retain the generic runtime error. +template +T unwrapDefault(const mxArray*, std::false_type) { error("wrap internal error: attempted unwrap of invalid type"); return T(); } +// Default unwrapping supports size/key scalars and rejects all other types. +template +T unwrap(const mxArray* array) { + return unwrapDefault(array, IsMatlabSizeOrKeyScalar()); +} + /// @brief Unwrap from matlab array to C++ enum type /// @tparam T The C++ enum type /// @param array Matlab mxArray @@ -299,20 +329,6 @@ string unwrap(const mxArray* array) { return str; } -// Check for 64-bit, as Mathworks says mxGetScalar only good for 32 bit -template -T myGetScalar(const mxArray* array) { - switch (mxGetClassID(array)) { - case mxINT64_CLASS: - return (T) *(std::int64_t*) mxGetData(array); - case mxUINT64_CLASS: - return (T) *(std::uint64_t*) mxGetData(array); - default: - // hope for the best! - return (T) mxGetScalar(array); - } -} - // specialization to bool template<> bool unwrap(const mxArray* array) { @@ -341,24 +357,6 @@ int unwrap(const mxArray* array) { return myGetScalar(array); } -// specialization to gtsam::Key which is an alias for uint64_t -template<> -uint64_t unwrap(const mxArray* array) { - checkScalar(array,"unwrap"); - return myGetScalar(array); -} - -// specialization to size_t; omit it on Win64 because size_t is uint64_t there -// and would duplicate unwrap. The __CUDACC__ case intentionally -// bypasses the Win64 guard when compiling with CUDA. -#if (!defined(_WIN64) && !defined(__LP64__)) || defined(__CUDACC__) -template<> -size_t unwrap(const mxArray* array) { - checkScalar(array, "unwrap"); - return myGetScalar(array); -} -#endif - // specialization to double template<> double unwrap(const mxArray* array) { diff --git a/tests/fixtures/matlab_integer_aliases.i b/tests/fixtures/matlab_integer_aliases.i new file mode 100644 index 0000000..d67a8bf --- /dev/null +++ b/tests/fixtures/matlab_integer_aliases.i @@ -0,0 +1,2 @@ +size_t roundTripSizeT(size_t value); +uint64_t roundTripUint64(uint64_t value); diff --git a/tests/test_matlab_wrapper.py b/tests/test_matlab_wrapper.py index 202cb3b..30974e1 100644 --- a/tests/test_matlab_wrapper.py +++ b/tests/test_matlab_wrapper.py @@ -268,6 +268,52 @@ def test_class(self): actual = osp.join(self.MATLAB_ACTUAL_DIR, file) self.compare_and_diff(file, actual) + def test_size_t_round_trip(self): + """Generated size_t wrappers use alias-safe scalar conversions.""" + file = osp.join(self.INTERFACE_DIR, 'matlab_integer_aliases.i') + + wrapper = MatlabWrapper( + module_name='matlab_integer_aliases', + top_module_namespace=['gtsam'], + ignore_classes=[''], + ) + wrapper.wrap([file], path=self.MATLAB_ACTUAL_DIR) + + cpp_file = osp.join(self.MATLAB_ACTUAL_DIR, + 'matlab_integer_aliases_wrapper.cpp') + with open(cpp_file, 'r', encoding='UTF-8') as generated_file: + cpp_content = generated_file.read() + + self.assertIn('size_t value = unwrap< size_t >(in[0]);', cpp_content) + self.assertIn( + 'out[0] = wrap< size_t >(roundTripSizeT(value));', + cpp_content) + self.assertIn('uint64_t value = unwrap< uint64_t >(in[0]);', + cpp_content) + self.assertIn( + 'out[0] = wrap< uint64_t >(roundTripUint64(value));', + cpp_content) + self.assertNotIn('unwrap_shared_ptr< uint64_t >', cpp_content) + self.assertNotIn('wrap_shared_ptr(std::make_shared', + cpp_content) + + uint64_file = osp.join(self.MATLAB_ACTUAL_DIR, 'roundTripUint64.m') + with open(uint64_file, 'r', encoding='UTF-8') as generated_file: + uint64_content = generated_file.read() + self.assertIn("isa(varargin{1},'numeric')", uint64_content) + + matlab_header = osp.join(self.TEST_DIR, '..', 'matlab.h') + with open(matlab_header, 'r', encoding='UTF-8') as header_file: + header_content = header_file.read() + + self.assertIn('struct IsMatlabSizeOrKeyScalar', header_content) + self.assertIn('std::is_same::value', header_content) + self.assertIn('std::is_same::value', header_content) + self.assertNotIn('mxArray* wrap', header_content) + self.assertNotIn('size_t unwrap', header_content) + self.assertIn('attempted wrap of invalid type', header_content) + self.assertIn('attempted unwrap of invalid type', header_content) + def test_enum(self): """Test interface file with only enum info.""" file = osp.join(self.INTERFACE_DIR, 'enum.i')