c++pointerswchar-t

Return wchar_t* from a C function that allocates memory for the string


After allocating memory for a wchar_t* parameter and copying a string into the buffer, when returning from the function the buffer can not be read. Per the debugger:

machineName 0xcccccccccccccccc <Error reading characters of string.>    wchar_t *

given the following code

wchar_t *machineName;
EXPECT_TRUE(c_GetMachineName(machineName));

file test.cpp ...

#include "sysCertStore.h"
TEST(sysCertStoreC, c_GetMachineName)
{
    wchar_t *machineName;
    EXPECT_TRUE(c_GetMachineName(machineName));
    setlocale(LC_ALL, "");
    printf("%ls", machineName);
    free(machineName);
}

file sysCertStore.h ...

#ifdef __cplusplus
    extern "C" 
    {  
        // only need to export C interface if
        // used by C++ source code
    BOOL c_GetMachineName(wchar_t* machineName);
    };
#endif

file sysCertStore.cpp ...

BOOL c_GetMachineName(wchar_t* machineName) {
  wchar_t Name[MAX_COMPUTERNAME_LENGTH + 1];
  int i = 0;


  LPWSTR infoBuf = new wchar_t[MAX_COMPUTERNAME_LENGTH + 1];  // = {'\0'};
  DWORD bufCharCount = MAX_COMPUTERNAME_LENGTH + 1;
  memset(Name, 0, MAX_COMPUTERNAME_LENGTH + 1);
  if (GetComputerNameW(infoBuf, &bufCharCount)) 
  {
    for (i = 0; i < MAX_COMPUTERNAME_LENGTH + 1; i++) 
    {
      Name[i] = infoBuf[i];
    }
    delete[] infoBuf;
    INT strln= wcslen(Name)+1;
    machineName = (wchar_t*)malloc(strln * sizeof(wchar_t));  // new wchar_t[strln];
    wcscpy_s(machineName, strln, Name);
    return TRUE;
  } else 
  {
    wcscpy_s(Name,7+1, L"Unknown");
    return FALSE;
  }
  return FALSE;
 }

I'm expecting the test to print the machine name and free the memory of the allocated string/buffer, allocated in the c_GetMachineName function.

TEST(sysCertStoreC, c_GetMachineName)
{
    wchar_t *machineName;
    EXPECT_TRUE(c_GetMachineName(machineName));
    setlocale(LC_ALL, "");
    printf("%ls", machineName);
    free(machineName);
}

Solution

  • The machineName parameter of c_GetMachineName() is being passed in by value, so the caller's variable gets copied and c_GetMachineName() modifies the copy instead.

    The local machineName variable declared inside of your test is uninitialized (and thus set to 0xcccccccccccccccc in debug mode), and c_GetMachineName() cannot change it to point at a valid address. That is why the test crashes trying to read from an invalid address 0xcccccccccccccccc.

    To do what you are attempting, c_GetMachineName() needs to take in its machineName parameter by pointer instead, so it can modify the caller's variable to point at the memory returned by malloc().

    Also, your infoBuf variable is redundant and can be eliminated. So can your use of wcslen(), since GetComputerNameW() tells you how many chars it writes to the buffer.

    Try this instead:

    #include "sysCertStore.h"
    TEST(sysCertStoreC, c_GetMachineName)
    {
        wchar_t *machineName;
        EXPECT_TRUE(c_GetMachineName(&machineName));
        setlocale(LC_ALL, "");
        printf("%ls", machineName);
        free(machineName);
    }
    
    BOOL c_GetMachineName(wchar_t** machineName) {
    
      if (!machineName)
        return FALSE;
    
      wchar_t Name[MAX_COMPUTERNAME_LENGTH + 1] = {};
      DWORD bufCharCount = MAX_COMPUTERNAME_LENGTH + 1;
    
      if (!GetComputerNameW(Name, &bufCharCount))
        return FALSE;
    
      ++bufCharCount;
      *machineName = (wchar_t*) malloc(bufCharCount * sizeof(wchar_t));  // new wchar_t[bufCharCount];
      if (!*machineName)
        return FALSE;
    
      wcscpy_s(*machineName, bufCharCount, Name);
      return TRUE;
    
      /* or simpler:
      *machineName = _wcsdup(Name);
      return (*machineName != NULL);
      */
    }
    

    Alternatively, you can simply return the malloc'd pointer instead, eg:

    #include "sysCertStore.h"
    TEST(sysCertStoreC, c_GetMachineName)
    {
        wchar_t *machineName = c_GetMachineName();
        EXPECT_NE(machineName, nullptr);
        setlocale(LC_ALL, "");
        printf("%ls", machineName);
        free(machineName);
    }
    
    wchar_t* c_GetMachineName() {
    
      wchar_t Name[MAX_COMPUTERNAME_LENGTH + 1] = {};
      DWORD bufCharCount = MAX_COMPUTERNAME_LENGTH + 1;
    
      if (!GetComputerNameW(Name, &bufCharCount))
        return NULL;
    
      ++bufCharCount;
      wchar_t *machineName = (wchar_t*) malloc(bufCharCount * sizeof(wchar_t));  // new wchar_t[bufCharCount];
      if (!machineName)
        return NULL;
    
      wcscpy_s(machineName, bufCharCount, Name);
      return machineName;
    
      /* or simpler:
      return _wcsdup(Name);
      */
    }
    

    Or, simply have the caller allocate the memory, and let c_GetMachineName() merely fill it, eg:

    #include "sysCertStore.h"
    TEST(sysCertStoreC, c_GetMachineName)
    {
        wchar_t machineName[256];
        EXPECT_TRUE(c_GetMachineName(machineName, 256));
        setlocale(LC_ALL, "");
        printf("%ls", machineName);
    }
    
    BOOL c_GetMachineName(wchar_t* machineName, DWORD maxLength) {
      return GetComputerNameW(machineName, &maxLength))
    }