diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f7a7b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.asv +*~ +*.mex* +.DS_Store diff --git a/class_handle.hpp b/class_handle.hpp new file mode 100755 index 0000000..ac5cde2 --- /dev/null +++ b/class_handle.hpp @@ -0,0 +1,53 @@ +#ifndef __CLASS_HANDLE_HPP__ +#define __CLASS_HANDLE_HPP__ +#include "mex.h" +#include +#include +#include +#include + +#define CLASS_HANDLE_SIGNATURE 0xFF00F0A5 +template class class_handle +{ +public: + class_handle(base *ptr) : signature_m(CLASS_HANDLE_SIGNATURE), name_m(typeid(base).name()), ptr_m(ptr) {} + ~class_handle() { signature_m = 0; delete ptr_m; } + bool isValid() { return ((signature_m == CLASS_HANDLE_SIGNATURE) && !strcmp(name_m.c_str(), typeid(base).name())); } + base* ptr() { return ptr_m; } + +private: + uint32_t signature_m; + const std::string name_m; + base* const ptr_m; +}; + +template inline mxArray *convertPtr2Mat(base *ptr) +{ + mexLock(); + mxArray *out = mxCreateNumericMatrix(1, 1, mxUINT64_CLASS, mxREAL); + *((uint64_t *)mxGetData(out)) = reinterpret_cast(new class_handle(ptr)); + return out; +} + +template inline class_handle *convertMat2HandlePtr(const mxArray *in) +{ + if (mxGetNumberOfElements(in) != 1 || mxGetClassID(in) != mxUINT64_CLASS || mxIsComplex(in)) + mexErrMsgTxt("Input must be a real uint64 scalar."); + class_handle *ptr = reinterpret_cast *>(*((uint64_t *)mxGetData(in))); + if (!ptr->isValid()) + mexErrMsgTxt("Handle not valid."); + return ptr; +} + +template inline base *convertMat2Ptr(const mxArray *in) +{ + return convertMat2HandlePtr(in)->ptr(); +} + +template inline void destroyObject(const mxArray *in) +{ + delete convertMat2HandlePtr(in); + mexUnlock(); +} + +#endif // __CLASS_HANDLE_HPP__ diff --git a/example_interface.m b/example_interface.m new file mode 100755 index 0000000..890be2f --- /dev/null +++ b/example_interface.m @@ -0,0 +1,27 @@ +%EXAMPLE_INTERFACE Example MATLAB class wrapper to an underlying C++ class +classdef example_interface < handle + properties (SetAccess = private, Hidden = true) + objectHandle; % Handle to the underlying C++ class instance + end + methods + %% Constructor - Create a new C++ class instance + function this = example_interface(varargin) + this.objectHandle = example_mex('new', varargin{:}); + end + + %% Destructor - Destroy the C++ class instance + function delete(this) + example_mex('delete', this.objectHandle); + end + + %% Train - an example class method call + function varargout = train(this, varargin) + [varargout{1:nargout}] = example_mex('train', this.objectHandle, varargin{:}); + end + + %% Test - another example class method call + function varargout = test(this, varargin) + [varargout{1:nargout}] = example_mex('test', this.objectHandle, varargin{:}); + end + end +end \ No newline at end of file diff --git a/example_mex.cpp b/example_mex.cpp new file mode 100755 index 0000000..703da54 --- /dev/null +++ b/example_mex.cpp @@ -0,0 +1,71 @@ +#include "mex.h" +#include "class_handle.hpp" + +// The class that we are interfacing to +class example +{ +public: + example() { mexPrintf("Calling constructor\n"); } + ~example() { mexPrintf("Calling destructor\n"); } + void train() { mexPrintf("Calling train\n"); }; + void test() { mexPrintf("Calling test\n"); }; +private: +}; + +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) +{ + // Get the command string + char cmd[64]; + if (nrhs < 1 || mxGetString(prhs[0], cmd, sizeof(cmd))) + mexErrMsgTxt("First input should be a command string less than 64 characters long."); + + // New + if (!strcmp("new", cmd)) { + // Check parameters + if (nlhs != 1) + mexErrMsgTxt("New: One output expected."); + // Return a handle to a new C++ instance + plhs[0] = convertPtr2Mat(new example); + return; + } + + // Check there is a second input, which should be the class instance handle + if (nrhs < 2) + mexErrMsgTxt("Second input should be a class instance handle."); + + // Delete + if (!strcmp("delete", cmd)) { + // Destroy the C++ object + destroyObject(prhs[1]); + // Warn if other commands were ignored + if (nlhs != 0 || nrhs != 2) + mexWarnMsgTxt("Delete: Unexpected arguments ignored."); + return; + } + + // Get the class instance pointer from the second input + example* example_instance = convertMat2Ptr(prhs[1]); + + // Call the various class methods + // Train + if (!strcmp("train", cmd)) { + // Check parameters + if (nlhs < 0 || nrhs < 2) + mexErrMsgTxt("Train: Unexpected arguments."); + // Call the method + example_instance->train(); + return; + } + // Test + if (!strcmp("test", cmd)) { + // Check parameters + if (nlhs < 0 || nrhs < 2) + mexErrMsgTxt("Test: Unexpected arguments."); + // Call the method + example_instance->test(); + return; + } + + // Got here, so command not recognized + mexErrMsgTxt("Command not recognized."); +} diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..fed2261 --- /dev/null +++ b/license.txt @@ -0,0 +1,24 @@ +Copyright (c) 2018, Oliver Woodford +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/mex_interface.m b/mex_interface.m new file mode 100755 index 0000000..55ec5d9 --- /dev/null +++ b/mex_interface.m @@ -0,0 +1,49 @@ +%MEX_INTERFACE MATLAB wrapper to an underlying C++ class +% +% This interface assumes that the mex function uses the following standard +% interface: +% Construction - obj = mexfun('new', ...) +% Destruction - mexfun('delete', obj) +% Other methods - [...] = mexfun('method', obj, ...) +classdef mex_interface < handle + properties (Access = private, Hidden = true) + objectHandle; % Handle to the underlying C++ class instance + mexHandle; % Handle to the mex function + end + methods + %% Constructor - Create a new C++ class instance + % Inputs: + % mexfun - handle to the C++ class interface mex. + % varargin - arguments passed to the mex when calling 'new'. + function this = mex_interface(mexfun, varargin) + this.mexHandle = mexfun; + this.objectHandle = this.mexHandle('new', varargin{:}); + end + + %% Destructor - Destroy the C++ class instance + function delete(this) + if ~isempty(this.objectHandle) + this.mexHandle('delete', this.objectHandle); + end + this.objectHandle = []; + end + + %% Disp - get the function name + function disp(this, var_name) + if nargin > 1 + fprintf('%s is an object instance of %s\n', var_name, func2str(this.mexHandle)); + else + fprintf('Object instance of %s\n', func2str(this.mexHandle)); + end + end + + %% All other methods + function varargout = subsref(this, s) + if numel(s) < 2 || ~isequal(s(1).type, '.') || ~isequal(s(2).type, '()') + error('Not a valid indexing expression') + end + assert(~isempty(this.objectHandle), 'Object not initialized correctly'); + [varargout{1:nargout}] = this.mexHandle(s(1).subs, this.objectHandle, s(2).subs{:}); + end + end +end \ No newline at end of file diff --git a/run_example.m b/run_example.m new file mode 100644 index 0000000..5111a99 --- /dev/null +++ b/run_example.m @@ -0,0 +1,33 @@ +function run_example() +% Check if the mex exists +dir = fileparts(mfilename('fullpath')); +if ~isequal(fileparts(which('example_mex')), dir) + % Compile the mex + cwd = cd(dir); + cleanup_obj = onCleanup(@() cd(cwd)); + fprintf('Compiling example_mex\n'); + mex example_mex.cpp +end + +% Use the example interface +% This is the interface written specifically for the example class +fprintf('Using the example interface\n'); +obj = example_interface(); +train(obj); +test(obj); +clear obj % Clear calls the delete method + +% Use the standard interface +% This interface can be used for any mex interface function using the +% pattern: +% Construction - obj = mexfun('new', ...) +% Destruction - mexfun('delete', obj) +% Other methods - [...] = mexfun('method', obj, ...) +% The standard interface avoids the need to write a specific interface +% class for each mex file. +fprintf('Using the standard interface\n'); +obj = mex_interface(str2fun([dir '/example_mex'])); % str2fun allows us to use the full path, so the mex need not be on our path +obj.train(); +obj.test(); +clear obj % Clear calls the delete method +end \ No newline at end of file diff --git a/str2fun.m b/str2fun.m new file mode 100644 index 0000000..9d0ccc7 --- /dev/null +++ b/str2fun.m @@ -0,0 +1,41 @@ +%STR2FUN Construct a function_handle from a function name or path. +% FUNHANDLE = STR2FUN(S) constructs a function_handle FUNHANDLE to the +% function named in the character vector S. The S input must be a +% character vector. The S input cannot be a character array with +% multiple rows or a cell array of character vectors. +% +% You can create a function handle using either the @function syntax or +% the STR2FUN command. You can create an array of function handles from +% character vectors by creating the handles individually with STR2FUN, +% and then storing these handles in a cell array. +% +% Examples: +% +% To create a function handle from the function name, 'humps': +% +% fhandle = str2func('humps') +% fhandle = +% @humps +% +% To call STR2FUNC on a cell array of character vectors, use the +% CELLFUN function. This returns a cell array of function handles: +% +% fh_array = cellfun(@str2func, {'sin' 'cos' 'tan'}, ... +% 'UniformOutput', false); +% fh_array{2}(5) +% ans = +% 0.2837 +% +% See also STR2FUNC, FUNCTION_HANDLE, FUNC2STR, FUNCTIONS. + +function fun = str2fun(str) +assert(ischar(str)); +if str(1) ~= '@' + [p, str] = fileparts(str); + if ~isempty(p) + cwd = cd(p); + cleanup_obj = onCleanup(@() cd(cwd)); + end +end +fun = str2func(str); +end \ No newline at end of file