2

I've been mixing C# GUI with a C++ code for socket connection handling. The problem is that passing a string to a C++ char* DLL causes it to change its value in the C++ class(?).

I've got a class for handling sockets which was a bit changed to look around for a cause of error and some functions letting me get to the class itself:

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);
    };
}

The problem occurs somewhere in usage of mySock_Create.

Basically it is:

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

calling a constructor:

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

Later ipAddress is used with inet_addr and connects to the remote server. Anyway, the code above doesn't allow me to connect to server, but if I simply hardcode the IP address like this:

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

It works fine and connects to the local server.

So for checking I added CopyStr which you could see in DLL header code above, and it is supposed to get the IP address of mySock object and write it to C# textbox.

In case of a hardcoded IP address indeed it shows that IP address, but in case of passing an IP address from C# it returns only a not filled rectangle sign which I'm not even able to paste here.

Here's C# code for linking and using the DLL:

[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);
}

I've also tried using string instead of StringBuilder or adding CharSet=CharSet.Ansi in import options.

I've read quite a bunch of questions and solutions to passing string to char* in such cases, but I can't work out why value of ipAddress isn't passed correctly or changes somewhere else.

Hope I stated problem clearly enough as I'm already pretty much out-of-logic after fighting with above code.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Poziokat
  • 33
  • 1
  • 5

1 Answers1

0

The main problem is in your C++ constructor:

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

Here you make a copy of the pointer to character array. This requires that this pointer remains valid for as long as you need to use ipAddress. A string marshaled from C# is valid only for the duration of the p/invoke call.

Indeed, even if you were using this class only from C++ it's design would be unworkable. Your class needs to take a copy of the string rather than a copy of the pointer.

The solution is for your C++ code to use std::string exclusively. Let that class take care of memory management and copying. Only use C strings at the p/invoke interop boundary.

Change ipAddress to be of type std::string and have the constructor look like this:

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

You still need to use C strings in your interop wrapper function, but that can make use of the implicit conversion from const char* to std::string.

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

As far as the p/invoke goes, you misuse StringBuilder and char*. They are paired when you have a caller allocated buffer that is modified by the callee. For a string passed in to the C++ code use string and const char*.

[DllImport("...", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr mySock_Create(string IP, int prt);
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    Thanks a lot! Finally this is an answer I've been looking for (or I guess it is ATM) I do understand where lies the problem of my code. :) Do you propose to simply use string instead of char* and in place i need a char* use c_str() method on it? Right now I'm working on passing string from c# to that in c++, but I'm already marking this answer as most helpful – Poziokat Dec 29 '16 at 01:43
  • The strings pass from C# to the C++ so you never need to use c_str(). There's an implicit conversion from const char* to std::string so your wrapper function my socks create can receive const char* and then use that implicit conversion when calling the constructor. – David Heffernan Dec 29 '16 at 01:47