8
$\begingroup$

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?

$\endgroup$
1
  • 2
    $\begingroup$ IMO, this is really a Windows/.Net question rather than a Mathematica issue per se. Relevant: blogs.msdn.com/b/oldnewthing/archive/2009/10/08/9904646.aspx, snipplr.com/view/27336. The answer would seem to be that System.Text.StringBuilder does not support this type of string and something else should be used instead. I personally have no experience with .Net, so I will let someone else make the suggestion as to what that should be. $\endgroup$ Nov 24, 2013 at 12:26

1 Answer 1

8
$\begingroup$

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..}]]
$\endgroup$
1
  • $\begingroup$ Thanks a lot, Todd, that pretty much looks like THE answer to the problem. For completeness I will also point out a solution using the DLL function "vensim_get_substring" which Ventana posted on the Vensim Forum: vensim.com/documentation/index.html?26210.htm $\endgroup$
    – gwr
    Nov 25, 2013 at 11:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.