如何编写具有ref字符串作为参数的托管C#dll的C ++包装器

时间:2018-05-25 14:40:58

标签: c# c++ clr

我正在使用C ++ / CLR编写包装器。托管的C#类具有如下函数签名

//C#
int WriteToInstrument(string command, ref string response, int stage);

我必须在这个函数中写一个C ++包装器,如下面的签名

//C++
int WriteToInstrumentWrap(const char * command, char * response, int stage);

我的问题是:如何处理从C#中的“ref string”到C ++中的char *的转换?或者我如何处理需要从C#中获取可以在C / C ++中使用的ref字符串的情况?提前谢谢了。

3 个答案:

答案 0 :(得分:1)

在高级溢出中,您需要C ++ / CLI(使用托管.NET代码的C ++)

好的,你可以使用System :: String(.NET)类型的句柄并获取它的长度属性。使用该值分配一个大小为+ 2个字符的新缓冲区,使用malloc和memset将其清零。锁定字符串,复制其内容并再次解锁。

如果有帮助,有一个转换运算符可以从System :: String ^转到MFC的CString。它将使代码成为单个衬垫

答案 1 :(得分:1)

我将添加一些我今天早上写的代码示例。一般来说,当谈到返回对象时(即使char*字符串是一个对象的广义),C / C ++中的大问题是:

  • 谁分配内存
  • 需要多少元素
  • 如何分配内存(使用哪个分配器)
  • 作为必然结果,必须如何释放记忆
  • 最后一个可选问题是,是否必须真正释放内存:一个方法可以返回一个指向内部对象的指针,该内部对象的生命周期等于程序的生命周期并且不能被释放。例如:

    const char* Message()
    {
        return "OK";
    }
    

    你不能释放Message()返回的内存!

当您编写将由其他程序使用的库(dll)时,此问题会变得更加复杂:dll中使用的mallocnew可能不同/与主程序(或其他dll)使用的mallocnew不同,因此您不应该使用 free(主程序)用dll释放malloc(ed)的内存。

这个特殊问题有三种可能的解决方案:

  • 使用共享分配器,例如OS提供的分配器。 Windows提供LocalAllocCoTaskMemAlloc。甚至可以从.NET(Marshal.AllocHGlobalMarshal.AllocCoTaskMem)访问它们。通过这种方式,主应用程序可以释放由dll
  • 分配的内存
  • 你的dll的API有一个Free()方法,必须用来释放dll分配的内存
  • 你的dll的API有一些方法,比如SetAllocator(void *(*allocator)(size_t))SetFree(void (*free)(void*)),所以存储函数指针的方法,主应用程序可以使用它来设置分配器并由dll自由使用,以便它们在主应用程序和DLL之间共享。该DLL将使用这些分配器。请注意,SetAllocator(malloc); SetFree(free)如果由主应用程序完成,则完全合法:现在dll将使用主应用程序malloc,而不是dll&#39; malloc!< / LI>
  • 在一些示例中使用的快捷方式我将给出:该方法具有将被使用的分配器(函数指针)作为参数

作为一个重要的旁注:我们是在2018年。至少15年你应该忘记{C} for Windows中的char*字符串。使用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可能会被截断和/或调用者可能会分配一个太大的缓冲区(如果不幸的话,它不会真正保护它免受截断问题:-))。我在这里重复一遍:2018年使用char*字符串是古老的技术。

// 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。转换它们非常容易。我在这里重复一遍:在2018年使用char*字符串是古老的技术。这就像使用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的简单方法,使用&#34; private&#34;分配response缓冲区。 &#34;文库&#34; allocator并将结果复制到ansi字符串(char*)。调用者必须使用库解除分配器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的简单方法,使用settable(通过response / SetLibraryAllocator方法)分配器分配SetLibraryFree缓冲区(如果使用了默认值,则使用没有选择特殊的分配器)并将结果复制到ansi字符串(char*)。调用者必须使用库解除分配器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<>mallocnew[]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<>
}

答案 2 :(得分:0)

是。但是,再次,

CString unmanaged = CString(System :: String ^)为你完成所有这些。