Another potential solution is using "Shared"
passing, and writing a function that modifies its arguments. In other words: use pass-by-reference.
I am not quite certain that this is among the intended uses of "Shared"
argument passing, so I advise caution. I have not yet seriously used this method. Pass-by-reference isn't really compatible with how Mathematica works. To use this method successfully, one must have some familiarity of how Mathematica manages its expressions internally (copy-on-write paradigm).
Here's a demonstration using LTemplate. (LTemplate is not necessary at all for this. It just made it quicker for me to experiment.)
SetDirectory[$TemporaryDirectory];
<< LTemplate`
tem = LClass["Ret",
{LFun["incdec", {{Integer, _, "Shared"}, {Integer, _, "Shared"}},
"Void"]}
];
code = "
struct Ret {
void incdec(mma::IntTensorRef a, mma::IntTensorRef b) {
for (mint i=0; i < a.size(); ++i)
a[i] = i+1;
for (mint i=0; i < b.size(); ++i)
b[i] = b.size() - i;
a.disown();
b.disown();
}
};
";
Export["Ret.h", code, "String"];
CompileTemplate[tem]
LoadTemplate[tem]
obj = Make[Ret];
This function fills its first and second integer vector argument with increasing and decreasing numbers. It can be used like this:
a = ConstantArray[0, 10];
b = ConstantArray[0, 5];
{a, b}
(* {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}} *)
obj@"incdec"[a, b]
{a, b}
(* {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {5, 4, 3, 2, 1}} *)
Notice that both a
and b
were modified.
One should be very careful when doing such things because they violate the basic principles of how Mathematica is expected to work. You must be aware, for example, that Mathematica uses copy-on-write. Thus when setting c=b
, the contents of b
are not internally copied, only referenced. The actual copying only happens when modifying a sub-part of c
. However, modifications done by a LibraryLink library cannot be detected by the Mathematica kernel, so we may end up in a situation when more than one value appears to get modified.
a = ConstantArray[0, 10];
b = ConstantArray[0, 5];
c = b;
{a, b, c}
(* {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}} *)
obj@"incdec"[a, b]
{a, b, c}
(* {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {5, 4, 3, 2, 1}, {5, 4, 3, 2, 1}} *)
Notice that we only modified b
, but c
changed as well.
We must carefully ensure that the packed-array arguments we pass to this LibraryLink function are referenced by only a single Mathematica value. We must create these packed arrays ourselves, and never use a value that comes from a source we don't directly control.
We can create a deep copy of a packed array like this:
a = ConstantArray[0, 10];
b = ConstantArray[0, 5];
c = b[[ ;; ]];
{a, b, c}
(* {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}} *)
obj@"incdec"[a, b]
{a, b, c}
(* {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {5, 4, 3, 2, 1}, {0, 0, 0, 0, 0}} *)
There are other things to pay attention to. LibraryLink functions can only work with packed arrays. If we pass in a non-packed one, it will be packed, and therefore copied. The library function will modify the copy, not the original array.
a = ConstantArray[0, 10];
b = Developer`FromPackedArray@ConstantArray[0, 5];
{a, b}
(* {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}} *)
obj@"incdec"[a, b]
LibraryFunction::shrnopack: The argument {0,0,0,0,0} at position 3 was not a PackedArray but was successfully converted to a PackedArray.
{a, b}
(* {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 0, 0, 0, 0}} *)
Notice that b
was not modified.
Yet another thing that can go wrong is that the input array is packed, but not of the type expected by the library. In this case, it will be silently converted to the correct type (and thus copied again).
a = ConstantArray[0, 10];
b = N@ConstantArray[0, 5];
{a, b}
(* {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0., 0., 0., 0., 0.}} *)
obj@"incdec"[a, b]
{a, b}
(* {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0., 0., 0., 0., 0.}} *)
Again, b
was not modified.
As you can see, there are plenty of things that can go wrong ... Be very cautious if you decide to use this method to return multiple results.
TetGenExpression
is no longer referenced, it should be auto-freed on the library side too, similarly to how symbols with theTemporary
attribute ... $\endgroup$