使用 DLL 将 C# StringBuilder / string 传递给 C++ char*

Passing C# StringBuilder / string to C++ char* using DLL

我一直在混合使用 C# GUI 和 C++ 代码来处理套接字连接。 问题是将字符串传递给 C++ char* DLL 会导致它在 C++ class(?).

中更改其值

我有一个 class 用于处理套接字,它被稍微改变以查找错误原因和一些让我到达 class 本身的函数:

extern "C" { __declspec(dllexport)

    class mySock
    {

        int port;
        SOCKET littleSock;

        public:
            char* ipAddress;
            mySock() {};
            mySock(char* inIP, int inPort);
            ~mySock();
            int initialize();
            int sendMsg(char* Msg);
            //int addition(int a, int b);

    };
}

extern "C" { __declspec(dllexport) mySock* mySock_Create(char* IP, int prt);}
extern "C" { __declspec(dllexport) mySock* mySock_Create1(); }
extern "C" { __declspec(dllexport) int mySock_initialize(mySock* inSocket); }
extern "C" { __declspec(dllexport) int mySock_sendMsg(mySock* inSocket,char* Msg); }
extern "C" { __declspec(dllexport) void mySock_delete(mySock* inSocket); }
extern "C" { __declspec(dllexport) void CopyStr(char* str1, mySock* inSocket)
    {
        strcpy_s(str1, strlen(str1), inSocket->ipAddress);
    };
}

问题出现在mySock_Create的使用中。

基本上是:

mySock* mySock_Create(char* IP, int prt)
{
    return new mySock(IP, prt);
}

调用构造函数:

mySock::mySock(char* inIP, int inPort)
{
    ipAddress = inIP;
    port = inPort;
}

稍后ipAddress与inet_addr一起使用并连接到远程服务器。 无论如何,上面的代码不允许我连接到服务器,但如果我只是像这样硬编码 IP 地址:

mySock* mySock_Create(char* IP, int prt)
{
    return new mySock("192.168.1.164", prt);
}

它工作正常并连接到本地服务器。

所以为了检查,我添加了 CopyStr,您可以在上面的 DLL header 代码中看到它,它应该获取 mySock object 的 IP 地址并将其写入 C# 文本框。

如果是硬编码的 IP 地址,它确实会显示该 IP 地址,但如果从 C# 传递 IP 地址,它 returns 只是一个未填充的矩形符号,我什至无法粘贴这里。

这是链接和使用 DLL 的 C# 代码:

[DllImport("G:\socketstraning\klikacz\MyFirstDLLyay\Debug\MyFirstDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr mySock_Create(StringBuilder IP, int prt);
[DllImport("G:\socketstraning\klikacz\MyFirstDLLyay\Debug\MyFirstDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int mySock_initialize(IntPtr value);
[DllImport("G:\socketstraning\klikacz\MyFirstDLLyay\Debug\MyFirstDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int mySock_sendMsg(IntPtr value, string Msg);
[DllImport("G:\socketstraning\klikacz\MyFirstDLLyay\Debug\MyFirstDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void mySock_delete(IntPtr value);
[DllImport("G:\socketstraning\klikacz\MyFirstDLLyay\Debug\MyFirstDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr mySock_Create1();
[DllImport("G:\socketstraning\klikacz\MyFirstDLLyay\Debug\MyFirstDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void CopyStr(StringBuilder str1, IntPtr value);

private void button3_Click(object sender, EventArgs e)
{
    StringBuilder str = new StringBuilder("192.168.1.164", 50);
    IntPtr we = mySock_Create(str, 16999);
    StringBuilder str2 = new StringBuilder("255.255.255.255", 25);
    CopyStr(str2, we);
    textBox1.Text = str2.ToString();
    if (mySock_initialize(we) == -2)
         button3.Text = "Ups";

    mySock_delete(we);
}

我也尝试过使用字符串代替 StringBuilder 或在导入选项中添加 CharSet=CharSet.Ansi。

我已经阅读了很多关于在这种情况下将字符串传递给 char* 的问题和解决方案,但我无法弄清楚为什么 ipAddress 的值没有正确传递或在其他地方更改。

希望我把问题说得足够清楚,因为在与上面的代码打架后我已经差不多 out-of-logic 了。

主要问题出在您的 C++ 构造函数中:

mySock::mySock(char* inIP, int inPort)
{
    ipAddress = inIP;
    port = inPort;
}

这里你复制了一个指向字符数组的指针。这就要求只要你需要使用ipAddress,这个指针就一直有效。从 C# 编组的字符串仅在 p/invoke 调用期间有效。

确实,即使您仅在 C++ 中使用此 class,它的设计也是行不通的。您的 class 需要获取字符串的副本而不是指针的副本。

解决方案是让您的 C++ 代码独占使用 std::string。让 class 负责内存管理和复制。仅在 p/invoke 互操作边界使用 C 字符串。

ipAddress 更改为 std::string 类型并使构造函数如下所示:

mySock::mySock(const std::string inIP, const int inPort)
{
    ipAddress = inIP;
    port = inPort;
}

您仍然需要在互操作包装函数中使用 C 字符串,但这可以利用从 const char*std::string 的隐式转换。

mySock* mySock_Create(const char* IP, const int prt)
{
    return new mySock(IP, prt);
}

就 p/invoke 而言,您误用了 StringBuilderchar*。当您有一个由调用者修改的调用者分配的缓冲区时,它们会配对。对于传递到 C++ 代码的字符串,使用 stringconst char*

[DllImport("...", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr mySock_Create(string IP, int prt);