views:

426

answers:

3

So I have a function in unmanaged c++ that gets called when some text happens to have "arrived":

#using <MyParser.dll>
...
void dump_body(const unsigned char *Body, int BodyLen) 
{
  // Need to pass the body to DumpBody, but as what type?
     ...
  MyParser::Parser::DumpBody(???);
}

DumpBody is a static function defined in a C# DLL that should take one parameter of type?

Body holds an array of characters (text) of length BodyLen.

There's obviously some marshalling to be done here but I have no idea how.

Please help.

+2  A: 

The follwing code describes the situation of communication between managed C# code and a unmanaged C/C++ DLL via PInvoke. Essentially the idea is, that you can pass your C code a C# delegate. The const unsigned char* gets converted to a string with Marshal.PtrToStringAnsi.

C++:

typedef void FncFoo (const char*);

FncFoo callback = 0;    

extern "C" {
  // this function is called from C# to pass the delegate
  void TakeFooCallback(FncFoo f) {
    callback = f;
  }
}

// C function calling C#
void dump_body(const unsigned char *Body, int BodyLen) {
  if(callback) {
    callback(Body);
  }
}

C#:

class StringC2CS {

  delegate void DlgFoo(IntPtr a);

  [DllImport("myclib.dll")] // name of the dll
  void TakeFooCallback(DlgFoo callback);

  // this function gets called by C
  void FooImpl(IntPtr a) {
    string str = Marshal.PtrToStringAnsi(a);
    // use string
  }

  public StringC2CS() {
    // passes a callback to C
    TakeFooCallback(new DlgFoo(FooImpl));
  }
}
Danvil
This looks like a viable solution but I have yet to try it. However, I chose the one liner solution because that also works. +1 and thanks for the input.
tzup
If you are using managed C++, it is easier. I had to use my approach when communicating with "pure" C/C++ libraries.
Danvil
In this case it was managed C++, but your solution is noted and archived for future use :)
tzup
+1  A: 
void dump_body(const unsigned char *body, int bodyLen)
{
    // you might want a different encoding...
    String ^str = gcnew String((sbyte*)body, 0, bodyLen, gcnew ASCIIEncoding);
    MyParser::Parser::DumpBody(str);
}

DumpBody will take a string.

plinth
I think you nailed it! However, "sbyte" is a C# keyword so I had to change that to "const char" and it worked. But, the function is getting "unsigned char" (values 0 to 255) and I am creating a System::String that expects a signed 8 bit integer which makes sense for ASCII encoding but maybe not for this case (although the text I'm receiving looks like regular ASCII text). I hope it is not some custom encoding.
tzup
It turns out the encoding was Latin-1 so here is that line updated:String ^str = gcnew String((const char*)body, 0, bodyLen, Encoding::GetEncoding("ISO-8859-1"))
tzup
A: 

I ended up using Danvil's approach. I wrapped the C code in a C++ dll and then I made another C# dll that referenced the C++ dll and exposed the functionality that I wanted in managed code. So here it is:

C++ dll:

.h File

 // Definition of a callback function to be called when some data arrives
 typedef void (*FncDlg) (const unsigned char*, int len);

 // Declaration of the function that will be exposed in managed code. 
 // The managed code will use this function to pass in a delegate
 extern "C" __declspec(dllexport) void AttachCallback(FncDlg f);

 // Declaration of a function that receives data and passes it to managed code
 void PassData(const unsigned char *Body, int BodyLen);

.cpp File

 // Instantiate a global function pointer to nothing
 void (*callback)(const unsigned char*, int len) = 0; 

 // This is called by C# to pass in a delegate
 void AttachCallback(FncDlg f) 
 {
    // Save delegate globally (to be triggered later when we receive data)
    callback = f;
 }

 // This is the function called when data is read from the socket
 void PassData(const unsigned char *Body, int BodyLen)
 {
    if(callback) {
    callback(Body, BodyLen);
    }
 }

C# dll:

 public static class Parser
 {
    public delegate void DlgDumpTipData(IntPtr a, int len);

    [DllImport("C++ dll name here", EntryPoint = "AttachCallback", ExactSpelling = true)]
    public static extern void AttachCallback(DlgDumpTipData callback); 

    public static int ConnectAndStartReceive(...)
    {
       // Attach a callback function (called when a message is received from data feed)
       AttachCallback(DumpDataCalback);
       ...
    }

    public delegate void MsgHandler(string msg);
    // When data arrives from the C library, this event passes it on to C# clients 
    public static event MsgHandler OnMessageArrived = delegate { };

    public static void DumpDataCalback(IntPtr ptr, int len)
    {
       //string str = Marshal.PtrToStringAnsi(ptr); 
       string dumpData;
       sbyte[] byteArr = new sbyte[len];
       for (int i = 0; i < len; i++)
       {
          // WARNING: if byte > 127 we have a problem when casting from Byte to SByte
          // In this case there is no problem, tested with values over 127 and it
          // converts fine to Latin-1 using the String overload 
          byteArr[i] = (sbyte)Marshal.ReadByte(ptr, i); 
       }

       unsafe
       {
         fixed (sbyte* pbyteArr = byteArr) // Instruct the GC not to move the memory
         {
             dumpData = new String(pbyteArr, 0, len, Encoding.GetEncoding("ISO-8859-1"));
         }
       }

       // Send data to whoever subscribes to it
       OnMessageArrived(dumpData);
       GC.Collect(); // This slows things down but keeps the memory usage low
    }        
 }
tzup