gpt4 book ai didi

c# - 如何编写以引用字符串作为参数的托管C#dll的C++包装器

转载 作者:行者123 更新时间:2023-11-28 01:33:27 25 4
gpt4 key购买 nike

我正在使用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#中的“引用字符串”到C++中的char *的转换?或者我该如何处理需要从C#中获取可在C / C++中使用的ref字符串的情况?提前谢谢了。

最佳答案

我将添加一些今天上午编写的代码示例。通常,在谈到返回对象时(从广义上讲,甚至char*字符串都是对象),C / C++中的主要问题是:

  • 谁分配内存
  • 需要多少个元素
  • 如何分配内存(使用哪个分配器)
  • 作为推论,必须如何释放内存
  • 最后一个可选问题是是否必须真正释放内存:方法可以返回一个指向内部对象的指针,该对象的生存期等于程序的生存期,并且不能释放。例如:
    const char* Message()
    {
    return "OK";
    }

    您一定不能释放Message()返回的内存!

  • 当您编写将由其他程序使用的库(dll)时,此问题会变得更加复杂:dll中使用的 mallocnew与主程序使用的 mallocnew可以不同/不同。程序(或另一个dll),以便您不应该在主程序中使用 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的malloc!
  • 我将在某些示例中使用快捷方式:该方法具有分配器(函数指针)作为参数,然后将其用作

  • 作为重要的旁注:我们已经到了2018年。至少有15年的时间了,您应该忘记了Windows C语言中字符串的 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的简单方法,使用“私有(private)”“库”分配器分配 response缓冲区,并将结果复制到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的简单方法,使用可设置的分配器(通过 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<>
    }

    关于c# - 如何编写以引用字符串作为参数的托管C#dll的C++包装器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50531733/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com