开发者

P/Invoke a Function Passed a StringBuilder

开发者 https://www.devze.com 2022-12-30 23:51 出处:网络
in a C# file i have a class Archiver { [DllImport(\"Archiver.dll\")] public static extern void archive(string data, StringBuilder response);

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 respons开发者_Go百科e 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?


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


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.


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...


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)


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

0

精彩评论

暂无评论...
验证码 换一张
取 消