views:

248

answers:

6

in a C# file i have a

class Archiver {
    [DllImport("Archiver.dll")]
    public static extern void archive(string data, StringBuilder response);
}

string data is an input, and StringBuilder response is where the function writes something

the archive function prototype (written in C) looks like this:

void archive(char * dataChr, char * outChr);

and it receives a string in dataChr, and then does a

strcpy(outChr,"some big text");

from C# i call it something like this:

string message = "some text here";
StringBuilder response = new StringBuilder(10000);
Archiver.archive(message,response);

this works, but the problem, as you might see is that i give a value to the StringBuilder size, but the archive function might give back a (way) larger text than the size i've given to my StringBuilder. any way to fix this?

+4  A: 

You need to write a function that tells you how big the StringBuilder needs to be, then call that function to initialize the StringBuilder.

SLaks
i was just thinking about that. probably the best way. Damn, you guys are fast!
andrew
@andrew, you should really consider passing the size of your buffer into archive function as well, this will help prevent buffer overruns, archive should only fill the buffer upto the size of the buffer.
Chris Taylor
A: 

You don't need to give the response a size.

Raj
**WRONG**. You do.
SLaks
i tried not to give a size but it crashes
andrew
i'm thinking of running another function first that will return a int value containing the size to put in the stringbuilder (i think i've seen this practice in functions from windows api), but that doesn't sound very nice
andrew
@SLaks: **Why** would you have to? It has a parameterless constructor which creates an empty buffer and the buffer size will grow automatically when the buffer is full. You comment is no help without some explanation.
runrunraygun
@runrunraygun: Not in unmanaged code. @Andrew: That is your only option. See my anwser.
SLaks
So in unmanaged code the string builder doesn't do the one thing it is good for?? If you take away the auto-sizing aren't you left with just a byte buffer?
runrunraygun
@runrunraygun: For marshaling, a `StringBuilder` is just an encoding-aware byte array. It is not possible to have a raw array that automatically grows.
SLaks
@Alex: Raj is wrong. `StringBuilder` is used differently when marshalling.
SLaks
A: 

A waste of space, but gets the job done:

StringBuilder response = new StringBuilder(Int64.MaxValue);
Amber Shah
This will throw an `OutOfMemoryException`.
SLaks
Wow, that is some serious memory you have there. First StringBuilder does not accept an Int64 as an argument, so this will not compile. Second even Int32.MaxValue will throw an OutOfMemoryException.
Chris Taylor
@SLaks, no it won't, it has to first compile to throw an OutOfMememoryException :)
Chris Taylor
+2  A: 

Do you control the implementation of the archive function? If you do then you have a few options.

1- Have the archive function allocate the buffer and return it to the caller. The down side is that the caller wil need to use the right function to free the buffer otherwise risk either memory leak and even corrupting the heap.

2- Pass the size of the buffer you have to the archive function so that it will not exceed the buffer length when filling the buffer.

3- If you can have a function that can return the required buffer size then you can use that. This is a common approach in the windows API, passing null as the buffer and a pointer to a DWORD which receives the required buffer size, which you can then allocate and make a second call passing the allocated buffer.

Chris Taylor
A: 

I had a very similar problem once, but I had the dll's source code, so I added a function that would dump a file, the new call looked like this:

cr012062(query,filename)

then I would just read the file contents

File.ReadAllText(filename)
Enriquev
This is an extremely poor solution.
SLaks
+1  A: 

I would have the unmanaged code allocate the memory, then let the managed side copy it into a managed array via an IntPtr and release the allocation.

You need to first make your unmanaged function return the size of it's output array:

void archive(char * dataChr, char * outChr, int length);

Then the managed side needs to get it as an IntPtr:

class Archiver {
    public static byte[] Archive(string data) {
        IntPtr responsePtr = IntPtr.Zero;
        int length = 0;

        // Call the unmanaged function with our output params
        archive(data, responsePtr, length);

        byte[] responseArray;
        try {
            // Create an array for the response
            responseArray = new byte[length];
            // Copy from unmanaged into managed
            Marshal.Copy(responsePtr, responseArray, 0, length);
        } finally {
            // Free the unmanaged memory once copied
            Marshal.FreeHGlobal(responsePtr);
        }

        return responseArray;
    }

    [DllImport("Archiver.dll")]
    private static extern void archive(string data, [Out]IntPtr encoded, [Out]int length);
}

You didn't specify if your data was actually a string or if you were using a stringbuffer to hold opaque binary data. If the response data is a null-terminated string then you can easily use PtrToStringUni or PtrToStringAnsi to get a string instead of a simple array:

On the unmanaged side:

void archive(char * dataChr, char * outChr);

On the managed side:

class Archiver {
    public static string Archive(string data) {
        IntPtr responsePtr = IntPtr.Zero;

        // Call the unmanaged function with our output params
        archive(data, responsePtr);

        string responseString;
        try {
            // Marshal the string from unmanaged SZ String
            responseString = Marshal.PtrToStringAnsi(responsePtr);
        } finally {
            // Free the unmanaged memory once copied
            Marshal.FreeHGlobal(responsePtr);
        }

        return responseString;
    }

    [DllImport("Archiver.dll")]
    private static extern void archive(string data, [Out]IntPtr encoded);
}

NB: I didn't test any of this code, so there may be some small omissions or bugs...

joshperry