Skip to content

Commit b90fb87

Browse files
committed
Mex example: need to map into stdlib.is_char_device
1 parent d039acd commit b90fb87

File tree

4 files changed

+143
-1
lines changed

4 files changed

+143
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
Filesystem.class
22
code-coverage/
33
test-results/
4+
.buildtool/
45
*.asv
56
docs/
7+
*.mex*

buildfile.m

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,40 @@
11
function plan = buildfile
22
plan = buildplan(localfunctions);
3+
34
plan.DefaultTasks = "test";
5+
6+
if ~isMATLABReleaseOlderThan("R2024a")
7+
root = plan.RootFolder;
8+
bin = fullfile(root, "bin");
9+
if ~isfolder(bin), mkdir(bin), end
10+
addpath(bin)
11+
12+
plan("clean") = matlab.buildtool.tasks.CleanTask;
13+
14+
if ispc
15+
compiler_opt = "COMPFLAGS=/EHsc /std:c++17";
16+
else
17+
compiler_opt = "CXXFLAGS=-std=c++17";
18+
end
19+
20+
plan("mex:is_char_device") = matlab.buildtool.tasks.MexTask("is_char_device.cpp", bin, ...
21+
Options=compiler_opt);
22+
23+
plan("test").Dependencies = "mex";
24+
end
25+
426
end
527

28+
629
function checkTask(~)
730
% Identify code issues (recursively all Matlab .m files)
831
issues = codeIssues;
932
assert(isempty(issues.Issues), formattedDisplayText(issues.Issues))
1033
end
1134

1235
function testTask(~)
13-
addpath(fileparts(mfilename("fullpath")))
36+
t = fileparts(mfilename("fullpath"));
37+
addpath(t)
1438

1539
r = runtests(IncludeSubfolders=true, strict=true, UseParallel=true, OutputDetail="Concise");
1640

is_char_device.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// C++ Matlab MEX function using C++17 <filesystem>
2+
// build examples:
3+
// * MSVC: mex COMPFLAGS="/EHsc /std:c++17" is_char_device.cpp
4+
// * GCC: mex CXXFLAGS="-std=c++17" is_char_device.cpp
5+
6+
// https://www.mathworks.com/help/matlab/matlab_external/data-types-for-passing-mex-function-data-1.html
7+
8+
#include "mex.hpp"
9+
#include "mexAdapter.hpp"
10+
11+
#include <string>
12+
// note: <string_view> causes compile failures with MSVC at least
13+
14+
#include <vector>
15+
#include <memory>
16+
17+
#if defined(_MSC_VER)
18+
# define WIN32_LEAN_AND_MEAN
19+
# include <windows.h>
20+
#elif __has_include(<filesystem>)
21+
# include <filesystem>
22+
# include <system_error>
23+
#endif
24+
25+
#include <sys/types.h>
26+
#include <sys/stat.h>
27+
28+
static int fs_st_mode(std::string path)
29+
{
30+
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/stat-functions
31+
struct stat s;
32+
33+
return stat(path.data(), &s) == 0 ? s.st_mode : 0;
34+
}
35+
36+
37+
static bool fs_is_char_device(std::string path)
38+
{
39+
// character device like /dev/null or CONIN$
40+
#if defined(_MSC_VER)
41+
// currently broken in MSVC STL for <filesystem>
42+
HANDLE h =
43+
CreateFileA(path.data(), GENERIC_READ, FILE_SHARE_READ,
44+
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
45+
if (h == INVALID_HANDLE_VALUE)
46+
return false;
47+
48+
const DWORD type = GetFileType(h);
49+
CloseHandle(h);
50+
return type == FILE_TYPE_CHAR;
51+
#elif defined(__cpp_lib_filesystem)
52+
std::error_code ec;
53+
return std::filesystem::is_character_file(path, ec) && !ec;
54+
#else
55+
// Windows: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fstat-fstat32-fstat64-fstati64-fstat32i64-fstat64i32
56+
return S_ISCHR(fs_st_mode(path));
57+
#endif
58+
}
59+
60+
61+
class MexFunction : public matlab::mex::Function {
62+
public:
63+
void operator()(matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) {
64+
// boilerplate engine & ArrayFactory setup
65+
std::shared_ptr<matlab::engine::MATLABEngine> matlabPtr = getEngine();
66+
67+
matlab::data::ArrayFactory factory;
68+
// boilerplate input checks
69+
if (inputs.size() != 1) {
70+
matlabPtr->feval(u"error", 0,
71+
std::vector<matlab::data::Array>({ factory.createScalar("One input required") }));
72+
}
73+
if (inputs[0].getType() != matlab::data::ArrayType::MATLAB_STRING) {
74+
matlabPtr->feval(u"error", 0,
75+
std::vector<matlab::data::Array>({ factory.createScalar("Input must be a string") }));
76+
}
77+
if (inputs[0].getNumberOfElements() != 1) {
78+
matlabPtr->feval(u"error", 0,
79+
std::vector<matlab::data::Array>({ factory.createScalar("Input must be a scalar string") }));
80+
}
81+
82+
// Matlab strings are an array, so we use [0][0] to get the first element
83+
std::string inputStr = inputs[0][0];
84+
85+
// actual function algorithm / computation
86+
bool y = fs_is_char_device(inputStr);
87+
88+
// convert to Matlab output -- even scalars are arrays in Matlab
89+
// https://www.mathworks.com/help/matlab/matlab_external/create-matlab-array-with-matlab-data-cpp-api.html
90+
// https://www.mathworks.com/help/matlab/apiref/matlab.data.arrayfactory.html
91+
92+
outputs[0] = factory.createArray<bool>({1,1}, {y});
93+
}
94+
};

test/TestMex.m

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
classdef TestMex < matlab.unittest.TestCase
2+
3+
methods (Test)
4+
5+
function test_is_char_device(tc)
6+
tc.assumeFalse(isMATLABReleaseOlderThan("R2024a"), "mex:is_char_device requires Matlab >= R2024a")
7+
8+
% /dev/stdin may not be available on CI systems
9+
10+
if ispc
11+
b = is_char_device("NUL");
12+
else
13+
b = is_char_device("/dev/null");
14+
end
15+
16+
tc.verifyTrue(b)
17+
18+
end
19+
20+
end
21+
22+
end

0 commit comments

Comments
 (0)