views:

1923

answers:

5

Hi all,

I have a very painful library which, at the moment, is accepting a C# string as a way to get arrays of data; apparently, this makes marshalling for pinvokes easier.

So how do I make a ushort array into a string by bytes? I've tried:

int i;
String theOutData = "";
ushort[] theImageData = inImageData.DataArray;
 //this is as slow like molasses in January
 for (i = 0; i < theImageData.Length; i++) {
     byte[] theBytes = System.BitConverter.GetBytes(theImageData[i]);
     theOutData += String.Format("{0:d}{1:d}", theBytes[0], theBytes[1]);
 }

I can do it this way, but it doesn't finish in anything remotely close to a sane amount of time.

What should I do here? Go unsafe? Go through some kind of IntPtr intermediate?

If it were a char* in C++, this would be significantly easier...

edit: the function call is

DataElement.SetByteValue(string inArray, VL Length);

where VL is a 'Value Length', a DICOM type, and the function itself is generated as a wrapper to a C++ library by SWIG. It seems that the representation chosen is string, because that can cross managed/unmanaged boundaries relatively easily, but throughout the C++ code in the project (this is GDCM), the char* is simply used as a byte buffer. So, when you want to set your image buffer pointer, in C++ it's fairly simple, but in C#, I'm stuck with this weird problem.

This is hackeration, and I know that probably the best thing is to make the SWIG library work right. I really don't know how to do that, and would rather a quick workaround on the C# side, if such exists.

+1  A: 

One thing you can do is switch from using a string to a stringBuilder it will help performance tremendously.

If you are willing to use unsafe code you can use pointers and implement the your c# code just like your c++. Or you could write a small c++\cli dll that implements this functionality.

Aaron Fischer
+4  A: 

P/Invoke can actually handle what you're after most of the time using StringBuilder to create writable buffers, for example see pinvoke.net on GetWindowText and related functions.

However, that aside, with data as ushort, I assume that it is encoded in UTF-16LE. If that is the case you can use Encoding.Unicode.GetString(), but that will exepect a byte array rather than a ushort array. To turn your ushorts into bytes, you can allocate a separate byte array and use Buffer.BlockCopy, something like this:

ushort[] data = new ushort[10];
for (int i = 0; i < data.Length; ++i)
    data[i] = (char) ('A' + i);

string asString;
byte[] asBytes = new byte[data.Length * sizeof(ushort)];
Buffer.BlockCopy(data, 0, asBytes, 0, asBytes.Length);
asString = Encoding.Unicode.GetString(asBytes);

However, if unsafe code is OK, you have another option. Get the start of the array as a ushort*, and hard-cast it to char*, and then pass it to the string constructor, like so:

string asString;
unsafe
{
    fixed (ushort *dataPtr = &data[0])
        asString = new string((char *) dataPtr, 0, data.Length);
}
Barry Kelly
that got it! Thanks! (the first part appears to be fast enough, even with the extra copy).
mmr
A: 

I don't like this much, but it seems to work given the following assumptions:

1. Each ushort is an ASCII char between 0 and 127

2. (Ok, I guess there is just one assumption)

  ushort[] data = inData; // The ushort array source

  Byte[] bytes = new Byte[data.Length];  // Assumption - only need one byte per ushort

  int i = 0;
  foreach(ushort x in data) {
   byte[] tmp = System.BitConverter.GetBytes(x);
   bytes[i++] = tmp[0];
   // Note: not using tmp[1] as all characters in 0 < x < 127 use one byte.
  }

  String str = Encoding.ASCII.GetString(bytes);

I'm sure there are better ways to do this, but it's all I could come up with quickly.

Dan Shield
sorry, the ushorts are across the entire dynamic range, 0 to 65535.
mmr
So I misunderstood the earlier comment about 8-bit chars? Sorry.
Dan Shield
A: 

Look into the Buffer class:

ushort[] theImageData = inImageData.DataArray;

byte[] buf = new byte[Buffer.ByteLength(theImageData)]; // 2 bytes per short
Buffer.BlockCopy(theImageData, 0, buf, 0, Buffer.ByteLength(theImageData));

string theOutData = System.Text.Encoding.ASCII.GetString(buf);
Mark Brackett
+1  A: 

Just FYI, this has been fixed in later revision (gdcm 2.0.10). Look here:

http://gdcm.sourceforge.net/

-> http://apps.sourceforge.net/mediawiki/gdcm/index.php?title=GDCM_Release_2.0