6
\$\begingroup\$

I have a C# function that calls a C++ function to fill the array with pixel data from a camera. This is better than allocating memory on the C++ side and marshaling it on C# side each time the function is called.

I thought about running into a scenario where a camera that outputs 720p can receive a command to output 1080p image. When this happens the array size allocated from C# may not be enough to hold the pixel information. That current function call will fail. I do not want to throw an exception and do not want it to fail. It must continue and should not discard that frame.

My current solution is to check if the size of the pixels to write to the array is < the size of the array allocated from C# before writing to that array from C++ side.

If the size of the pixels to write is bigger than the array, allocate new memory then return it to C#. After that resize the size of the array from C# then use Marshal.Copy to copy the data returned from C++ to that newly resized array in C#. Finally call a function from C++ side to free the memory allocated there.

Bascially, when this problem happens, I will allocate memory on both sides (C++ and C#).

How can this be improved?

Note that I had to use unsafe and fixed keywords to pin the array for maximum performance after days of testing this with other methods. Anything faster than this is also acceptable.

C++:

int* test6FillArrayWithAutoResize(int* data, int count, int* outValue) {
    //For testing Purposes. Used to determine how much size to write to the data variable
    const int requiredMemory = 20;

    //Create new and Resize if memory is not enough
    if (requiredMemory > count) {
        int* newVar = new int[requiredMemory];

        //Do something to the variable then return it
        for (int i = 0; i < requiredMemory; i++)
        {
            newVar[i] = i;
        }

        //Tell caller memory size
        *outValue = requiredMemory;

        //Return the new created variable
        return newVar;
    }

    //Memory is enough. Write Directly to it the array that is passed in
    for (int i = 0; i < count; i++)
    {
        data[i] = i;
    }
    //Tell caller memory size
    *outValue = requiredMemory;

    //Return null since the memory fits. No need to return pointer
    return nullptr;
}

//Free memory allocated in the test6FillArrayWithAutoResize function above
void freeMem(int* data) {
    if (data != nullptr)
        delete[] data;
}

C#:

[DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr test6FillArrayWithAutoResize(IntPtr data, int count, ref int outValue);

public unsafe int getTest6FillArrayWithAutoResize(ref int[] outArray, int count, ref int outValue)
{
    IntPtr returnedPtr;

    //Pin Memory
    fixed (int* p = outArray)
    {
        IntPtr dataParam = (IntPtr)p;
        returnedPtr = test6FillArrayWithAutoResize(dataParam, count, ref outValue);
    }

    //Resize outArray if outValue > count
    if (outValue > count)
    {
        outArray = new int[outValue];

        //Copy from result pointer to the C# variable
        Marshal.Copy(returnedPtr, outArray, 0, 20);

        //Free memory on the native sied
        freeMem(returnedPtr);
    }

    return outValue;
}

[DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern void freeMem(IntPtr data);

Test 1:

const int arraySize = 44500;
int[] arrayToFill = new int[arraySize];

void Start()
{
    int length = arrayToFill.Length;
    int outVal6 = 0;
    int returnedSize = 0;
    returnedSize = getTest6FillArrayWithAutoResize(ref arrayToFill, length, ref outVal6);
}

Result: Array is not resized because the required size is 20 but array size is 44500.

Test 2:

const int arraySize = 19;
int[] arrayToFill = new int[arraySize];

void Start()
{
    int length = arrayToFill.Length;
    int outVal6 = 0;
    int returnedSize = 0;
    returnedSize = getTest6FillArrayWithAutoResize(ref arrayToFill, length, ref outVal6);
}

Result: Array is resized because the required size is 20 but array size is 19.

Note:

The required size is the requiredMemory variable defined in the C++ code. It is only there to emulate the behavior of the camera and for testing purposes. Also, forget about me using for loop to iterate through the array. This is only for testing purposes too. I plan to use std::copy for that so pretend that std::copy is used there.

The thing I want to improve is how memory is allocated twice on both ends when size is not enough. Any other improvement is welcome.

\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

Your situation immediately reminded me of the "PC loser-ing" problem: a classic case study contrasting the design philosophies of MIT's ITS operating system and the UNIX operating system developed at Bell Labs.

Two famous people, one from MIT and another from Berkeley (but working on Unix) once met to discuss operating system issues. The person from MIT was knowledgeable about ITS (the MIT AI Lab operating system) and had been reading the Unix sources. He was interested in how Unix solved the PC loser-ing problem. The PC loser-ing problem occurs when a user program invokes a system routine to perform a lengthy operation that might have significant state, such as IO buffers. If an interrupt occurs during the operation, the state of the user program must be saved. Because the invocation of the system routine is usually a single instruction, the PC of the user program does not adequately capture the state of the process. The system routine must either back out or press forward. The right thing is to back out and restore the user program PC to the instruction that invoked the system routine so that resumption of the user program after the interrupt, for example, re-enters the system routine. It is called "PC loser-ing" because the PC is being coerced into "loser mode," where "loser" is the affectionate name for "user" at MIT.

The MIT guy did not see any code that handled this case and asked the New Jersey guy how the problem was handled. The New Jersey guy said that the Unix folks were aware of the problem, but the solution was for the system routine to always finish, but sometimes an error code would be returned that signaled that the system routine had failed to complete its action. A correct user program, then, had to check the error code to determine whether to simply try the system routine again. The MIT guy did not like this solution because it was not the right thing.

The New Jersey guy said that the Unix solution was right because the design philosophy of Unix was simplicity and that the right thing was too complex. Besides, programmers could easily insert this extra test and loop. The MIT guy pointed out that the implementation was simple but the interface to the functionality was complex. The New Jersey guy said that the right tradeoff has been selected in Unix-namely, implementation simplicity was more important than interface simplicity.

Based on that lesson, I would seriously reconsider your design goal:

I do not want to throw an exception and do not want it to fail. It must continue and should not discard that frame.

Consider the consequences of that goal:

  • You do the memory allocation in two ways: usually on the C# side, but sometimes on the C++ side. That's two codepaths to maintain and test.
  • If the C++ allocation mechanism worked well (reliably, maintainably, and fast), why wouldn't you use it as the sole solution? As you stated, having C# be responsible for array allocation is simply better, except when there is insufficient capacity.

I would also challenge your fear of discarding a frame. You would have the same problem even if you were coding purely in C++.

Here's a design that does all of the buffer allocation in C#. The C++ function returns the size of the required buffer. If your camera's API allows you to query the required frame size before retrieving the frame data, then the code could be even simpler than the solution below.

C++:

int testFillArray(int* data, int offset, int count) {
    //For testing Purposes. Used to determine how much size to write to the data variable
    const int requiredMemory = 20;

    if (requiredMemory < count) {
        count = requiredMemory;
    }
    for (int i = offset; i < offset + count; i++) {
        data[i] = i;
    }
    return requiredMemory;
}

C#:

public unsafe int getTestFillArray(ref int[] array)
{
    int offset = 0, frameSize;
    while (true)
    {
        fixed (int* p = array) // Pin memory
        {
            frameSize = offset + testFillArray((IntPtr)p, offset, array.Length - offset);
        }
        if (frameSize <= array.Length)
        {
            return frameSize;
        }
        // Insufficient buffer space.  Reallocate and retry.
        var newArray = new int[frameSize];
        Array.Copy(array, newArray, offset = array.Length);
        array = newArray;
    }
}

Nitpicks

In your C++ code, in your //Memory is enough case, what happens if the camera produces less data than the array size? You would write junk data at the end of the array.

Use braces consistently. In your C++ code, you used a different brace style for your if than for your loops. You should also not omit "optional" braces, and if you must omit them, write the condition and statement on the same line.

\$\endgroup\$
6
  • \$\begingroup\$ Great advises at the end. "what happens if the camera produces less data than the array size? You would write junk data at the end of the array." That's totally fine and that' s what I expect to happen all the time. Actually if you look very close at the code you will realize that int* outValue is where C++ writes the number bytes written to. Since this is a ref in C#, I will be able to see that value. That's why getTest6FillArrayWithAutoResize returns int because it returns the number of bytes written into. \$\endgroup\$
    – Programmer
    Commented Apr 29, 2017 at 23:31
  • \$\begingroup\$ I can then use that number and upload the image to the screen or easily send it to the network without sending all the bytes. Question... Why did you put the function is a while loop? \$\endgroup\$
    – Programmer
    Commented Apr 29, 2017 at 23:33
  • \$\begingroup\$ I understand that, which is why I called it a nitpick and not a bug. I just wouldn't expect the function to write beyond the point where it claims to stop. \$\endgroup\$ Commented Apr 29, 2017 at 23:34
  • \$\begingroup\$ I put it in a while loop because I have no idea what your camera's API looks like, and I don't know whether I can trust it to be consistent about the required frame size. \$\endgroup\$ Commented Apr 29, 2017 at 23:35
  • \$\begingroup\$ Ok. I get it. It is a retry technique and I actually like it. \$\endgroup\$
    – Programmer
    Commented Apr 29, 2017 at 23:40
4
\$\begingroup\$

I believe a much simpler solution would be to simply ask the unmanaged code how much memory it requires, without doing any other operation in the unmananged code if the buffer passed it is not large enough.

With this information, the caller can simply allocate a big enough array and pass it in, thus avoiding having to remember to free memory allocated by the unmanaged code. By writing to a big enough buffer the first time, you also avoid unneeded copy operations.

A lot of C APIs act this way, returning the required size of a buffer when they are passed a buffer size of 0.

Another simple solution, although Windows only, involves using C++/CLI to return a .NET array from a mixed native/managed method. This however is not optimal as perhaps you would want to reuse such a buffer for multiple calls, avoiding allocations.

Edit, concerning the comments:

Should the buffer requirement change between your call to determine the size and the actual call to fill it, simply loop:

bool done = false;
int[] buffer;
do 
{
   int sizeRequired = getRequiredSize();
   buffer = new int[sizeRequired];
   done = doWork(buffer, buffer.Length); //returns false if buffer is not large enough
} while (! done);

As the resolution won't change n times a second, the loop will rarely loop more than once.

\$\endgroup\$
3
  • \$\begingroup\$ "I believe a much simpler solution would be to simply ask the unmanaged code how much memory it requires"....We don't know how much memory is required since it comes from the camera and can change if the camera resolution is changed which can happen anytime. I don't want to allocate really large memory since I will have array pool and this will not be good on mobile devices. I think I am looking for a way to allocate on C# when memory is not enough instead of both C++ and C#. \$\endgroup\$
    – Programmer
    Commented Apr 29, 2017 at 12:25
  • \$\begingroup\$ I just came up with an idea of using a callback function to trigger re-allocation on the C# side but since I am pinning that array with the fixed keyword, I don't think it is a good idea to re-allocate it. \$\endgroup\$
    – Programmer
    Commented Apr 29, 2017 at 12:27
  • \$\begingroup\$ Added sample code for addressing changes to camera resolution. \$\endgroup\$
    – D. Jurcau
    Commented Apr 29, 2017 at 12:37

Your Answer

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

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