I am trying to improve the usability of an open source C# API that wraps a C library. The underlying library pulls multiplexed 2D data from a server over a network connection. In C, the samples come out as a pointer to the data (many types are supported), e.g. float*
. The pull
function returns the number of data points (frames * channels, but channels is known and never changes) so that the client knows how much new data is being passed. It is up to the client to allocate enough memory behind these pointers. For example, if one wants to pull floats
the function signature is something like:
long pull_floats(float *floatbuf);
and floatbuf
better have sizeof(float)*nChannels*nMoreFramesThanIWillEverGet
bytes behind it.
In order to accommodate this, the C# wrapper currently uses 2D arrays, e.g. float[,]
. The way it is meant to be used is a literal mirror to the C method---to allocate more memory than one ever expects to these arrays and return the number of data points so that the client knows how many frames of data have just come in. The underlying dll handler has a signature like:
[DllImport(libname, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
public static extern uint pull_floats(IntPtr obj, float[,] data_buffer);
And the C# wrapper itself has a definition like:
int PullFloats(float[,] floatbuf)
{
// DllHandler has the DllImport code
// Obj is the class with the handle to the C library
uint res = DllHandler.pull_floats(Obj, floatbuf);
return res/floatbuf.GetLength(1);
}
The C++ wrapper for this library is idiomatic. There, the client supplies a vector<vector<T>>&
to the call and in a loop, each frame gets pushed into the multiplexed data container. Something like:
void pull_floats_cpp(std::vector<std::vector<float>>& floatbuf)
{
std::vector<float> frame;
floatbuf.clear();
while(pull_float_cpp(frame)) // C++ function to pull only one frame at a time
{
floatbuf.push_back(frame); // (memory may be allocated here)
}
}
This works because in C++ you can pun a reference to a std::vector
to a primitive type like float*
. That is, the vector frame
from above goes into a wrapper like:
void pull_float_cpp(std:vector<float>& frame)
{
frame.resize(channel_count); // memory may be allocated here as well...
pull_float_c(&frame[0]);
}
where pull_float_c
has a signature like:
void pull_float_c(float* frame);
I would like to do something similar in the C# API. Ideally the wrapper method would have a signature like:
void PullFloats(List<List<float>> floatbuf);
instead of
int PullFloats(float[,] floatbuf);
so that clients don't have work with 2D arrays and (more importantly) don't have to keep track of the number of frames they get. That should be inherent to the dimensions of the containing object so that clients can use enumeration patterns and foreach
. But, unlike C++ std::vector, you can't pun a List to an array. As far as I know ToArray
allocates memory and does a copy so that not only is there memory being allocated, but the new data doesn't go into the List of Lists that the array was built from.
I hope that the psuedocode + explanation of this problem is clear. Any suggestions for how to tackle it in an elegant C# way is much appreciated. Or, if someone can assure me that this is simply a rift between C and C# that may not be breached without imitating C-style memory management, at least I would know not to think about this any more.
Could a MemoryStream
or a Span
help here?