Я добавлю несколько примеров кода, который я написал сегодня утром.В общем, говоря о возвращаемых объектах (в широком смысле, когда даже строка char*
является объектом), в C / C ++ возникают большие вопросы:
- Кто выделяет память
- Сколько нужно элементов
- Как распределяется память (какой распределитель используется)
- И как следствие, как память должна быть освобождена
Последний необязательный вопрос: действительно ли память должна быть освобождена: метод может вернуть указатель на внутренний объект, у которого время жизни равно времени жизни программы и которое не должно быть освобождено.Например:
const char* Message()
{
return "OK";
}
Вы не должны освобождать память, возвращаемую Message()
!
Эти вопросы становятся еще более сложными, когда вы пишете библиотеку (dll), который будет использоваться другими программами: malloc
и new
, используемые в dll, могут отличаться / отличаться от malloc
и new
, используемых основной программой (или другойdll), так что вы не должны free
с вашей (основной программой) освободить память, которая malloc
(ed) dll.
Существует три возможных решения этой конкретной проблемы:
- Используйте общий распределитель, например, тот, который предоставляется ОС.Windows дает
LocalAlloc
и CoTaskMemAlloc
.Они даже доступны из .NET (Marshal.AllocHGlobal
и Marshal.AllocCoTaskMem
).Таким образом, основное приложение может освободить память, выделенную dll - API вашего dll имеет метод
Free()
, который должен использоваться для освобождения памяти, выделенной dll - .API вашей dll имеет несколько методов, таких как
SetAllocator(void *(*allocator)(size_t))
и SetFree(void (*free)(void*))
, так что методы, которые хранят указатель на функцию, которые основное приложение может использовать для установки распределителя и освобождают для использования dll, так что они совместно используютсяОсновное приложение и длл.DLL будет использовать эти распределители.Обратите внимание, что SetAllocator(malloc); SetFree(free)
, если это сделано основным приложением, совершенно законно: теперь DLL будет использовать malloc
основного приложения, а не malloc
! - ярлык, используемый в некоторых примерах.: метод имеет в качестве параметра распределитель (указатель на функцию), который затем будет использоваться
В качестве важной характеристики: мы находимся в 2018 году. По крайней мере, 15 лет вы должны были забыть оchar*
для строк в C для Windows.Используйте wchar_t
.Всегда.
И, наконец, некоторый код: -)
Сейчас ... задано (код C #):
int WriteToInstrument(string command, ref string response, int stage)
{
response = "The quick brown fox jumps over the lazy dog";
return 0;
}
Простой метод, который вызывает WriteToInstrument
а затем копирует результат response
в строку ANSI (char*
).Буфер выделяется вызывающей стороной и имеет размер length
.После выполнения метода length
содержит количество используемых символов (включая завершающий \0
).response
всегда \0
завершается.Проблема здесь в том, что response
может быть усечен и / или вызывающая сторона может выделить буфер слишком большой (что не защитит его от проблемы усечения, если ему не повезло :-)).Я повторюсь здесь: использование char*
для строк в 2018 году - древняя технология.
// Utility method to copy a unicode string to a fixed size buffer
size_t Utf16ToAnsi(const wchar_t *wstr, char *str, size_t length)
{
if (length == 0)
{
return 0;
}
// This whole piece of code can be moved to a method
size_t length2 = WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, (int)length, nullptr, nullptr);
// WideCharToMultiByte will try to write up to *length characters, but
// if the buffer is too much small, it will return 0,
// **and the tring won't be 0-terminated**
if (length2 != 0)
{
return length2;
}
// Buffer too much small
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// We add a terminating 0
str[length - 1] = 0;
return length;
}
// Big bad error, shouldn't happen. Return 0 but terminate the string
str[0] = 0;
return 0;
}
Пример использования:
char response[16];
size_t length = sizeof(response) / sizeof(char); // useless / sizeof(char) == / 1 by definition
WriteToInstrumentWrap1("cmd1", response, &length, 1);
std::cout << "fixed buffer char[]: " << response << ", used length: " << length << std::endl;
или (использование std::vector<>
/ std::array<>
)
//Alternative: std::array<char, 16> response;
std::vector<char> response(16);
size_t length = response.size();
WriteToInstrumentWrap1("cmd1", response.data(), &length, 1);
std::cout << "fixed buffer vector<char>: " << response.data() << ", used length: " << length << std::endl;
Простой метод, который вызывает WriteToInstrument
, а затем копирует результат response
в строку Unicode (wchar_t*
).Буфер выделяется вызывающей стороной и имеет размер length
.После выполнения метода length
содержит количество используемых символов (включая завершающий \0
).response
всегда \0
завершается.
// in input length is the size of response, in output the number of characters (not bytes!) written to response
// (INCLUDING THE \0!). The string is always correctly terminated.
int WriteToInstrumentWrap2(const wchar_t *command, wchar_t *response, size_t *length, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
*length = (size_t)str2->Length < *length ? str2->Length : *length - 1;
memcpy(response, pch, *length * sizeof(wchar_t));
response[*length] = '\0';
*length++;
return res;
}
Пример использования:
wchar_t response[16];
size_t length = sizeof(response) / sizeof(wchar_t);
WriteToInstrumentWrap2(L"cmd1", response, &length, 1);
std::wcout << L"fixed buffer wchar_t[]: " << response << L", used length: " << length << std::endl;
или (с использованием std::vector<>
/ std::array<char, 16>
)
//Alternative: std::array<wchar_t, 16> response;
std::vector<wchar_t> response(16);
size_t length = response.size();
WriteToInstrumentWrap2(L"cmd1", response.data(), &length, 1);
std::wcout << L"fixed buffer vector<wchar_t>: " << response.data() << ", used length: " << length << std::endl;
Во всех следующих примерах будет использоваться char
вместо wchar_t
.Их довольно легко конвертировать.Я повторюсь здесь: использование char*
для строк в 2018 году - древняя технология.Это похоже на использование ArrayList
вместо List<>
Простой метод, который вызывает WriteToInstrument
, выделяет буфер response
с использованием CoTaskMemAlloc
и копирует результат в строку ANSI (char*
).Вызывающая сторона должна CoTaskMemFree
выделенной памяти.response
всегда \0
завершается.
// Memory allocated with CoTaskMemAlloc. Remember to CoTaskMemFree!
int WriteToInstrumentWrap3(const char *command, char **response, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = (char*)CoTaskMemAlloc(length * sizeof(char));
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Пример использования:
char *response;
WriteToInstrumentWrap3("cmd1", &response, 1);
std::cout << "CoTaskMemFree char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with CoTaskMemFree!
CoTaskMemFree(response);
Простой метод, который вызывает WriteToInstrument
, выделяет буфер response
используя «частный» «библиотечный» распределитель и копирует результат в строку ANSI (char*
).Вызывающая сторона должна использовать библиотеку deallocator MyLibraryFree
, чтобы освободить выделенную память.response
всегда \0
завершается.
// Free method used by users of the library
void MyLibraryFree(void *p)
{
free(p);
}
// The memory is allocated through a proprietary allocator of the library. Use MyLibraryFree() to free it.
int WriteToInstrumentWrap4(const char *command, char **response, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = (char*)malloc(length);
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Пример использования:
char *response;
WriteToInstrumentWrap4("cmd1", &response, 1);
std::cout << "Simple MyLibraryFree char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with the MyLibraryFree() method
MyLibraryFree(response);
Простой метод, который вызывает WriteToInstrument
, выделяет буфер response
используя устанавливаемый (с помощью методов SetLibraryAllocator
/ SetLibraryFree
) распределитель (по умолчанию используется, если не выбран специальный распределитель), и копирует результат в строку ANSI (char*
).Вызывающая сторона должна использовать библиотеку deallocator LibraryFree
(которая использует распределитель, выбранный SetLibraryFree
), чтобы освободить выделенную память, или, если она установила другой распределитель, она может напрямую использовать этот освобождающий модуль.response
всегда \0
завершается.
void *(*libraryAllocator)(size_t length) = malloc;
void (*libraryFree)(void *p) = free;
// Free method used by library
void SetLibraryAllocator(void *(*allocator)(size_t length))
{
libraryAllocator = allocator;
}
// Free method used by library
void SetLibraryFree(void (*free)(void *p))
{
libraryFree = free;
}
// Free method used by library
void LibraryFree(void *p)
{
libraryFree(p);
}
// The memory is allocated through the allocator specified by SetLibraryAllocator (default the malloc of the dll)
// You can use LibraryFree to free it, or change the SetLibraryAllocator and the SetLibraryFree with an allocator
// of your choosing and then use your free.
int WriteToInstrumentWrap5(const char *command, char **response, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = (char*)libraryAllocator(length);
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Пример использования:
void* MyLocalAlloc(size_t size)
{
return LocalAlloc(0, size);
}
void MyLocalFree(void *p)
{
LocalFree(p);
}
, а затем:
// Using the main program malloc/free
SetLibraryAllocator(malloc);
SetLibraryFree(free);
char *response;
WriteToInstrumentWrap5("cmd1", &response, 1);
std::cout << "SetLibraryAllocator(malloc) char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Here I'm using the main program free, because the allocator has been set to malloc
free(response);
или
// Using the Windows LocalAlloc/LocalFree. Note that we need to use an intermediate method to call them because
// they have a different signature (stdcall instead of cdecl and an additional parameter for LocalAlloc)
SetLibraryAllocator(MyLocalAlloc);
SetLibraryFree(MyLocalFree);
char *response;
WriteToInstrumentWrap5("cmd1", &response, 1);
std::cout << "SetLibraryAllocator(LocalAlloc) char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Here I'm using diretly the Windows API LocalFree
LocalFree(response);
Более сложный метод, который вызывает WriteToInstrument
, но имеет в качестве параметра allocator
, который будет использоваться для выделения буфера response
.Существует дополнительный параметр par
, который будет передан в allocator
.Затем метод скопирует результат в виде строки ANSI (char*
).Вызывающая сторона должна освободить память с помощью специального освобождающего средства на основе используемого allocator
.response
всегда \0
завершается.
// allocator is a function that will be used for allocating the memory. par will be passed as a parameter to allocator(length, par)
// the length of allocator is in number of elements, *not in bytes!*
int WriteToInstrumentWrap6(const char *command, char **response, char *(*allocator)(size_t length, void *par), void *par, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = allocator(length, par);
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Примеры использования (показано несколько распределителей: vector<>
, malloc
, new[]
, unique_ptr<>
):
Обратите внимание на использование параметра par
.
template<typename T>
T* vector_allocator(size_t length, void *par)
{
std::vector<T> *pvector = static_cast<std::vector<T>*>(par);
pvector->resize(length);
return pvector->data();
}
template<typename T>
T* malloc_allocator(size_t length, void *par)
{
return (T*)malloc(length * sizeof(T));
}
template<typename T>
T* new_allocator(size_t length, void *par)
{
return new T[length];
}
template<typename T>
T* uniqueptr_allocator(size_t length, void *par)
{
std::unique_ptr<T[]> *pp = static_cast<std::unique_ptr<T[]>*>(par);
pp->reset(new T[length]);
return pp->get();
}
и затем (обратите внимание на тот факт, что иногда один из параметров, передаваемых в WriteToInstrumentWrap6
, равен useless
, поскольку у нас уже есть указатель на буфер):
{
std::vector<char> response;
char *useless;
WriteToInstrumentWrap6("cmd1", &useless, vector_allocator<char>, &response, 1);
std::cout << "vector char: " << response.data() << ", used length: " << response.size() << std::endl;
// The memory is automatically freed by std::vector<>
}
{
char *response;
WriteToInstrumentWrap6("cmd1", &response, malloc_allocator<char>, nullptr, 1);
std::cout << "malloc char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with free
free(response);
}
{
char *response;
WriteToInstrumentWrap6("cmd1", &response, new_allocator<char>, nullptr, 1);
std::cout << "new[] char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with delete[]
delete[] response;
}
{
std::unique_ptr<char[]> response;
char *useless;
WriteToInstrumentWrap6("cmd1", &useless, uniqueptr_allocator<char>, &response, 1);
std::cout << "unique_ptr<> char: " << response.get() << ", used length: " << strlen(response.get()) + 1 << std::endl;
// The memory is automatically freed by std::unique_ptr<>
}