35

I am using Pinvoke for Interoperability between Native(C++) code and Managed(C#) code. What i want to achieve is get some text from native code into my managed code. For this i try lot lot of things,e.g passing string/stringbuilder by ref, using [IN] and [OUT], Marshaling to LPSTR, returning string from function etc. but nothing works in my case. Any help with some small code would be highly appreciated.

5 Answers 5

49

I'd do it with a BSTR since it means you don't have to call into native twice per string, once to get the length and then once to get the contents.

With a BSTR the marshaller will take care of deallocating the BSTR with the right memory manager so you can safely pass it out of your C++ code.

C++

#include <comutil.h>
BSTR GetSomeText()
{
    return ::SysAllocString(L"Greetings from the native world!");
}

C#

[DllImport(@"test.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string GetSomeText();

There is one minor drawback of the BSTR, namely that it carries a UTF-16 payload but your source data may well be char*.

To overcome this you can wrap up the conversion from char* to BSTR like this:

BSTR ANSItoBSTR(const char* input)
{
    BSTR result = NULL;
    int lenA = lstrlenA(input);
    int lenW = ::MultiByteToWideChar(CP_ACP, 0, input, lenA, NULL, 0);
    if (lenW > 0)
    {
        result = ::SysAllocStringLen(0, lenW);
        ::MultiByteToWideChar(CP_ACP, 0, input, lenA, result, lenW);
    } 
    return result;
}

That's the hardest one out of the way, and now it's easy to add other wrappers to convert to BSTR from LPWSTR, std::string, std::wstring etc.

Sign up to request clarification or add additional context in comments.

5 Comments

@rturrado Many thanks indeed for your corrections in the edit.
the answer is very helpful, so it deserves it. BTW, just realising that 'may well char*' is missing a 'be'.
You can pass -1 as the fourth argument to MultiByteToWideChar to have the function calculate the length of the string automatically; avoids the need to call lstrlenA yourself.
The source data type char* says nothing about the encoding of the string; if it's an UTF-8 encoded string, consider using CP_UTF8 instead of CP_ACP.
@Frerich That is a good point. My answer was written under the assumption that text on Windows is almost always encoded using the local ANSI codepage, or UTF-16. UTF-8 is quite rarely seen in Windows.
5

Here is a topic where string marshaling has been discussed.

It's needed to mark parameter with attribute

[MarshalAs(UnmanagedType.LPSTR)]

2 Comments

Worked for me on Unity for Android.
This type of marshaling also goes by the name "memory leak".
4

Here is an example of doing this through C#. I am calling Native function GetWindowText through C# by pInvoking. GetWindowText returns the caption of the window whose handle is passed to it.

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern int GetWindowTextLength(IntPtr hWnd);

    public static string GetText(IntPtr hWnd)
    {
        // Allocate correct string length first
        int length = GetWindowTextLength(hWnd);
        StringBuilder sb = new StringBuilder(length + 1);
        GetWindowText(hWnd, sb, sb.Capacity);
        return sb.ToString();
    }        

    private void button1_Click(object sender, EventArgs e)
    {
        string str = GetText(this.Handle);
    }

Comments

0

If you're returning a char *, and don't want to have to modify the C/C++ code to allocate memory for your return value (or you can't modify that code), then you can change your C# extern function-prototype to return an IntPtr, and do the marshaling yourself.

For instance, here's a snippet of the interop I wrote for Pocketsphinx:

[DllImport("sphinxbase.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr /* char const * */ jsgf_grammar_name(IntPtr /* jsgf_t * */ jsgf);

And here's the get-accessor for the C# JsgfGrammar class. m_pU is an IntPtr that points to the raw jsgf_t object.

public string Name
{
    get
    {
        IntPtr pU = jsgf_grammar_name(m_pU);
        if (pU == IntPtr.Zero)
            strName = null;
        else
            strName = Marshal.PtrToStringAnsi(pU);
        return strName;
    }
}

Modifying this example for other string formats (e.g. Unicode) should be trivial.

10 Comments

Who's going to clean up the resources (potentially) allocated in native code now?
@IInspectable: The native code owns the resources referred to by the IntPtr. Any cleanup would have to be done there, regardless of whether C# interop is involved.
"Any cleanup would have to be done there" - Obviously. The more difficult question would then be: When? The marshaler isn't going to call back into native code to let it know: "That pointer you handed out earlier? I've made a copy of the pointee. Feel free to release those resources on your end. Just letting you know." Who's going to inform the native code to let go?
@IInspectable: Marshal.PtrToStringAnsi() makes a copy of the C-string referred to by the IntPtr, converting it to a C# string, so your concern isn't applicable here.
"Marshal.PtrToStringAnsi() makes a copy of the C-string referred to by the IntPtr" - Which is exactly what I said. PtrToStringAnsi() then forgets the IntPtr, leaving the native code as the sole owner. Said native code never gets informed that it can release the resource. This is called a "memory leak".
|
-2

why not building your own text struct :

struct mystr { mystr(const char *_) : _text((_ == 0) 
                                           ? 0 
                                           : ::strdup(_)
                                          ) 
                                      {}
              ~mystr() { if(this->_text != 0) delete [] this->_text; }
               operator char * & () { return this->_text; }

              private : char *_text;
             };

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.