tags:

views:

192

answers:

4

Many windows APIs take a pointer to a buffer and a size element but the result needs to go into a c++ string. (I'm using windows unicode here so they are wstrings)

Here is an example :-

#include <iostream>
#include <string>
#include <vector>
#include <windows.h>

using namespace std;

// This is the method I'm interested in improving ...
wstring getComputerName()
{
    vector<wchar_t> buffer;
    buffer.resize(MAX_COMPUTERNAME_LENGTH+1);
    DWORD size = MAX_COMPUTERNAME_LENGTH;

    GetComputerNameW(&buffer[0], &size);

    return wstring(&buffer[0], size);
}

int main()
{
    wcout << getComputerName() << "\n";
}

My question really is, is this the best way to write the getComputerName function so that it fits into C++ better, or is there a better way? I don't see any way to use a string directly without going via a vector unless I missed something? It works fine, but somehow seems a little ugly. The question isn't about that particular API, it's just a convenient example.

+6  A: 

In this case, I don't see what std::vector brings to the party. MAX_COMPUTERNAME_LENGTH is not likely to be very large, so I would simply use a C-style array as the temporary buffer.

anon
Ok my example is a poor one, I used a vector because I might not know until run time how big I want the buffer to be. I agree completely it's not necessary for this example
John Burton
@JB Well, I don't think there are any general rules - you have to deal with each API you use in its own terms. This is partly because there really isn't an over-arching design idea behind the API as a whole.
anon
Ok thanks, really I just wanted to check I'd not missed any obvious better way to achieve this
John Burton
+2  A: 

I would say, since you are already at task of abstracting Windows API behind a more generic C++ interface, do away with vector altogether, and don't bother about wstring constructor:

wstring getComputerName()
{
    wchar_t name[MAX_COMPUTERNAME_LENGTH + 1];
    DWORD size = MAX_COMPUTERNAME_LENGTH;

    GetComputerNameW(name, &size);

    return name;
}

This function will return a valid wstring object.

amn
getComputerName() should return a LPCWSTR (a const wchar_t*) and let the caller construct a wstring from it if needed, and name can be static.
Alain Rist
I thought the OP wanted to abstract Windows API, and instead use a consistent C++ interface, in which case I believe returning a C++ string (possibly const as well) is the way to go, versus more naked "const wchar_t*". Or did you refer to something else entirely?
amn
I think Alain's point is that there's no great benefit to an automatic conversion at the return - the caller can write `wstring n = getComputerName();` or `wstring n; n = getComputerName();` regardless of whether the return type is `wstring` or `wchar_t*`. So he's saying do the thing with maximum flexibility. In this case I don't much like his `static` buffer, though: I don't think the change is worth making the function non-thread-safe, on the basis that YAGN the wchar_t* return. So in this case I think it is worth converting inside the function.
Steve Jessop
My question was really about how to manage the memory for the the buffer that's passed in I suppose, and if I was missing any obviously better way to do it. I think from the answers here it's clear that there are alternatives but it all depends a lot on the exact situation and what I posted it porobably as good as anything
John Burton
Yes, the underlying problem is that there is no good way to return a string in C. The StringBuffer solution that Ferruccio links to is pretty good, though. It saves having to wrap each Windows function individually, in the case where there's a known bound on the string size. Or even if you do wrap them all individually, it makes the wrappers very concise since they share all the buffer-handling code.
Steve Jessop
Actually, I should delete my answer, because it has been so long I've done any C++ or C for that matter. It suddenly struck me, that I have no idea what is the exact lifetime of the data pointed to by the local variable 'name'? I assume it remains on the thread stack even as getComputerName returns? Otherwise there is no point returning it, right? Sorry for the confusion.
amn
To clarify, my code will only work because returning "wchar_t[]" as "wstring" will make an implicit call to "wstring(const wchar_t*)" constructor, which WILL MAKE A COPY of the string pointed by "name". Because the string pointed by "name" is allocated on the stack and is thus considered unusable upon returning.
amn
This is a good, simple answer. But for safety you should set 'name[0] = 0' before calling GetComputerNameW(). You can never trust an api function to properly initialize your buffer if it fails.
Alan
+3  A: 

See this answer to another question. It provides the source to a StringBuffer class which handles this situation very cleanly.

Ferruccio
+1  A: 

I'd use the vector. In response to you saying you picked a bad example, pretend for a moment that we don't have a reasonable constant upper bound on the string length. Then it's not quite as easy:

#include <string>
#include <vector>
#include <windows.h>

using std::wstring;
using std::vector;

wstring getComputerName()
{
    DWORD size = 1; // or a bigger number if you like
    vector<wchar_t> buffer(size);
    while ((GetComputerNameW(&buffer[0], &size) == 0))
    {
        if (GetLastError() != ERROR_BUFFER_OVERFLOW) aargh(); // handle error
        buffer.resize(++size);
    };
    return wstring(&buffer[0], size);
}

In practice, you can probably get away with writing into a string, but I'm not entirely sure. You certainly need additional guarantees made by your implementation of std::wstring, beyond what's in the standard, but I expect MSVC's strings are probably OK.

I think that if wstring::reference is wchar_t& then you're sorted. 21.3.4 defines that non-const operator[] returns a reference, and that it returns data()[pos]. So if reference is just a plain wchar_t& then there's no scope for exciting copy-on-write behaviour through the reference, and the string must in fact be modifiable through the pointer &buffer[0]. I think. The basic problem here is that the standard allowed implementations more flexibility than turned out to be needed.

That's a lot of effort and commenting though, just to avoid copying a string, so I've never felt the need to avoid an intermediate array/vector.

Steve Jessop
Ah yes I suppose this does assume make assumptions about wstring, but as this code is inherently platform dependent anyway that's not a problem,
John Burton
Well, GetComputerNameW only requires you to be on Windows, whereas the necessary conditions on `wstring` could conceivably be satisfied by some Windows compilers / library implementations, but not others. So making assumptions does make the code "more" platform dependent. That is it works on a subset of the implementations it worked on with the vector, the subset may be strict, and even if it isn't strict it requires extra work per implementation to prove that it isn't. To put this in perspective, my computer name is all of 5 characters long ;-)
Steve Jessop