Is P/Invoke Dead?

A reader recently asked whether P/Invoke is dead since you can use C++ Interop and avoid re-declaring all the functions and structures that you might already have in system header files.

Firstly, P/Invoke is still extremely useful for languages like C# that can’t consume header files, but then that should be obvious. Secondly, P/Invoke can also be useful for calling functions for which you don’t readily have header and lib files for and avoids resorting to calling LoadLibrary followed by GetProcAddress.

What may not be as obvious is that P/Invoke can still be very useful from C++ for other reasons. Although you can avoid P/Invoke entirely in your C++ projects there are times when the code can actually be simpler if you just use P/Invoke instead. Let’s look at a few examples.

Lets say you’re mixing native (HWND) and managed (Windows.Forms.Control and derived classes) windows in a project. You might be using the Form class as the main window for your application and HWNDs (window handles) to represent windows in another process. So the challenge is to get the window text from an HWND and display it in a Windows.Forms control. Essentially the problem boils down to implementing the following function:

String^ GetWindowText(HWND windowHandle);

Given a window handle, we would like to get the window text as a managed string. As a C++ programmer you might naturally write the following implementation:

String^ GetWindowText(HWND windowHandle)
{
    String^ result = String::Empty;
    int textLength = ::GetWindowTextLength(windowHandle);
 
    if (0 < textLength)
    {
        std::vector<wchar_t> buffer(textLength + 1);
 
        if (0 == ::GetWindowText(windowHandle,
                                 &buffer[0],
                                 textLength + 1))
        {
            throw gcnew ComponentModel::Win32Exception(::GetLastError(),
                                                       "The GetWindowText User32 function failed.");
        }
 
        result = Runtime::InteropServices::Marshal::PtrToStringUni(IntPtr(&buffer[0]));
    }
 
    return result;
}

We start by getting the text length with a call to the GetWindowTextLength function. If the window has any text, we use the indispensable vector class to manage the buffer that the GetWindowText function will use to write the text to. 1 is added to the length of the buffer as the GetWindowText function null-terminates the text written to the buffer. If you neglect this step the text will be truncated. Finally, the PtrToStringUni helper method from the Marshal class is used to convert the null-terminated string into a managed string.

Another solution is to use the marshalling services provided by the CLR for P/Invoke to avoid the native string management and conversion. Consider the following solution:

using namespace Runtime::InteropServices;
 
[DllImport("User32.dll", CharSet=CharSet::Unicode, SetLastError=true)]
int GetWindowText(HWND windowHandle,
                  Text::StringBuilder% text,
                  int count);
 
String^ GetWindowText(HWND windowHandle)
{
    String^ result = String::Empty;
    int textLength = ::GetWindowTextLength(windowHandle);
 
    if (0 < textLength)
    {
        Text::StringBuilder buffer(textLength + 1);
 
        if (0 == GetWindowText(windowHandle,
                               buffer,
                               buffer.Capacity))
        {
            throw gcnew ComponentModel::Win32Exception(::GetLastError(),
                                                       "The GetWindowText User32 function failed.");
        }
 
        result = buffer.ToString();
    }
 
    return result;
}

Here we use the DllImport attribute to indicate to the CLR that the GetWindowText function is implemented by the User32 DLL. The function is prototyped with the .NET Framework’s StringBuilder class to represent the text buffer and a tracking reference is used so we can use stack semantics for the StringBuilder object when calling the function. As you can see, the GetWindowText implementation is very similar to the original. We still use the GetWindowTextLength directly as there is no benefit in using P/Invoke for it. Of course the solution using P/Invoke is 7 lines longer than the first solution and requires that you translate the GetWindowText function into a P/Invoke declaration for the CLR. About the only thing good about this solution is that you’re not using native memory which could become a performance problem in some rare cases. Of course you’d want to profile this to actually make such a conclusion.

Here’s a more compelling example. For a project I was working on a few months back I needed to allow the user to pick computers from the network. I decided to use the Active Directory Object Picker dialog box. If you’ve never had the pleasure of using this little gem, its exposed through the IDsObjectPicker COM interface and can be a challenge to get just right even from C++. Needless to say using P/Invoke from C# would just be nightmare. To make life simpler I wrapped the functionally we needed in a DLL written in native C++ and exposed it with the following function:

HRESULT __stdcall BrowseForComputers(HWND parentWindow,
                                     bool multiSelect,
                                     SAFEARRAY** computers,
                                     bool* dialogResult);

I’ll spare you the details of its implementation. For this function it would be very useful to be able to use P/Invoke since there is quite a bit of overhead involved in calling this function. Firstly we need to deal with COM safe arrays which are really not fun to work with. Secondly we need to check the HRESULT error code for failure. And finally we need to check the dialogResult pointer (if you remember MIDL then just think [out,retval]) to check whether the user pressed OK or Cancel. This is where P/Invoke and managed code really makes me smile. Consider the following example of using this function:

[DllImport("Native.dll", PreserveSig=false, CharSet=CharSet::Unicode)]
bool BrowseForComputers(IntPtr parentWindow,
                        bool multiSelect,
                        [MarshalAs(UnmanagedType::SafeArray, SafeArraySubType=VarEnum::VT_VARIANT)]
                        array<String^>^% computers);
 
void main()
{
    array<String^>^ computers = nullptr;
 
    if (BrowseForComputers(IntPtr::Zero, // parent
                           true, // multi-select
                           computers))
    {
        for each (String^ computer in computers)
        {
            Console::WriteLine(computer);
        }
    }
}

Although the declaration of the BrowseForComputers function can take a few moments to get just right, using it is very simple and elegant. The CLR takes care of all the marshalling and error translation.


© 2005 Kenny Kerr

3 Comments

  • OK, I see that you can marshal string builders and managed arrays into calls to native APIs, which is nice. But still missing is the kind of situation I was asking about, where the APIs _and struct types for arguments to the APIs_ are declared in &lt;windows.h&gt; or other such headers.



    Your previous example showed how a C++/CLI program could declare managed types and initialize aggregate variables, your present example shows how a C++/CLI program can declare managed types and marshal them to APIs, but neither of them says how a C++/CLI program can define and initialize a variable whose type is declared in &lt;windows.h&gt; or other such headers.



    I finally did it by declaring a static const variable outside of all classes, which makes the variable global to that source file but invisible to clients of the class. It was good enough for my experiment. But it's not good enough in general. The static const variable belonged in the class that really used it, it should not be visible to random code in that source file outside the class, and it should be visible to clients as classname::variable.



    Based on your examples and my reading, the only general solution I can see is for someone to write declarations for a ton of managed types that can all map onto the native types in &lt;windows.h&gt; etc. Then programmers can declare managed variables inside their classes where they should be. The compiler doesn't let us declare variables of structure types from &lt;windows.h&gt; etc. inside classes. So P/Invoke isn't really dead, but users lose half the benefit from it and have to redesign all our structure variable declarations.

  • Norman, please keep in mind that this blog’s reason for being is not exclusively to answer your questions and concerns. I may blog about whatever I think is interesting and will try to answer questions as I have time. This is just something I do in my spare time. Your last few comments have had a number of subtle points, questions and complaints, some of which are interesting to many readers and some I would imagine are not. I will get to them when I can. Just remember that I blog because I enjoy writing and blogging, but you cannot *expect* me to answer every one of your questions directly.

  • &gt; Norman, please keep in mind that this

    &gt; blog’s reason for being is not exclusively

    &gt; to answer your questions



    Of course. From the title &quot;Is P/Invoke Dead?&quot; I thought it looked like you were intending to answer the question that I asked in your other thread. If that wasn't your intention, then there was no need for me to repeat and further explain the question. Sorry.



    (Though if you do have time someday, I really don't think I'm the only person who needs the answer to that. Structure type declarations were put in &lt;windows.h&gt; for other people besides me...)

Comments have been disabled for this content.