I am trying to connect a classic System Dynamics tool (Vensim DSS) with Mathematica using a DLL that is included in Vensim DSS. Reading though the instructions on how to define DLL functions on how to declare arguments - here strings - I found that if there are [out] strings one should use the .NET class System.Text.StringBuilder.
So applying the advice given will look like this:
(* definitions *)
$maxBufLen = 1000;
strOutput = NETNew["System.Text.StringBuilder", $maxBufLen];
vensimGetInfo = DefineDLLFunction["vensim_get_info", vensimDLL,
"int", {"int", "System.Text.StringBuilder", "int"}];
(* usage *)
infoWanted = 3; bufLen = $maxBufLen;
vensimGetInfo[infoWanted, strOutput, bufLen];
strOuput@ToString[]
Unfortunately this only works properly for a single string and some information will be returned by the DLL function as a series of null terminated strings that is finally ended by a double null. In such a case the above procedure will only return the first string omitting the rest.
What can be done?
Answer
First off, I should mention that Vensim also has a Java interface that you could call using J/Link. The Java signature of this particular function is
static String[] get_info(int infowanted);
so you can see that the different strings are conveniently returned in an array for you.
But you can also do what you want using .NET/Link's DefineDLLFunction
. You have to drop down to low-level memory allocations. Declare the buffer that will be filled with the strings as an IntPtr
:
vensimGetInfo = DefineDLLFunction["vensim_get_info", vensimDLL, "int", {"int", "IntPtr", "int"}];
Then allocate a buffer of a size large enough to hold all the data:
LoadNETType["System.Runtime.InteropServices.Marshal"];
buf = Marshal`AllocHGlobal[$maxBufLen];
Call the DLL function:
infoWanted = 3;
numBytes = vensimGetInfo[infoWanted, buf, $maxBufLen];
One way to get the data from buf
is to copy it into a .NET array, and then use NETObjectToExpression
to convert it into a list of character values.
managedArray = NETNew["System.Byte[]", numBytes];
Marshal`Copy[buf, managedArray, 0, numBytes];
bytes = NETObjectToExpression[managedArray]
bytes will look something like {100, 105, ..., 0, 97, 100, ..., 0, 105, 102, ... 0, 0}
. Use whatever method you like to convert these character values into separate strings, such as
FromCharacterCode[DeleteCases[SplitBy[bytes, # != 0 &], {0..}]]
Comments
Post a Comment