0

I am calling a function (named cbFunction) from a c++ dll using c# code. I have to pass array of strings (named strArray) ,from c# code, as arguments to c++ 'cbFunction'. Inside c++ function, I have to change the array's values. The newly changed values should be read back in c# code.

Problems I face:

  1. The base address of strArray in c# and the one received in arguments in c++ dll are totally different.
  2. I can read the array of strings in c++ dll. But, I have to modify the array's values inside c++ function. When I change those values in c++ function, the change is not reflected in c# code. `

C# code

public static class Call_API
{
   [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
   private delegate bool CallBackFunction(string[] argv, int argc);

   private static  string[] strArray;

   public static void ChangeValue()
   {
      IntPtr pDll = NativeMethods.LoadLibrary("DllFunctions.dll");
      IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "cbFunction");

      string args = "Hello Disney World";
      strArray = args.Split(' ');
      CallBackFunction cbFunction = ((CallBackFunction)Marshal.GetDelegateForFunctionPointer(
                                                  pAddressOfFunctionToCall, typeof(CallBackFunction));
      if(false == cbFunction(strArray, strArray.Count()))
      {
          //some task
      }
   }
}

c++ dll code (DllFunctions.dll) :

bool cbFunction(char** argv, int argc)
{
   //Some task

   char VarValue[256] = { 0 };
   std::string s = "Jetix";
   sprintf_s(VarValue, s.c_str());

   strcpy_s(argv[1], sizeof(VarValue), VarValue);
   return false;
}

Based on this code, my expected values at the end in strArray (in c#) are {"Hello", "Jetix", "World"}.

Thanks in advance!

Jane
  • 1
  • 1
  • `argv[1] = VarValue;`??? This is not correct. String is not the same as char array. You need to copy `VarValue` to `argv[1]`. – γηράσκω δ' αεί πολλά διδασκόμε Oct 03 '18 at 16:50
  • That the address changes is a feature and not a bug, the strings needs to be converted from wchar_t* to char* so the C# array cannot be directly passed to the C function. If you change the strings (quite risky) then you have to explicitly tell the pinvoke marshaller that it needs to copy them back into the C# array, that requires using [In, Out] on the argument. It is quite risky because the memory allocated for the new string needs to be released. Right now you have a dangling pointer to the VarValue local variable that becomes invalid when the function returns, that can't work at all. – Hans Passant Oct 03 '18 at 17:24
  • Consider using BSTR in your C++ code, the marshaller knows how to release it. – Hans Passant Oct 03 '18 at 17:25
  • modified argv[1] = VarValue to strcpy_s. But still it is not working. – Jane Oct 04 '18 at 06:01

1 Answers1

0

First of all a char in c# is not the same as char in c++. You need an sbyte in c# which is the same as char in c++. We will use a byte for convenience. It works fine.

Code:

string args = "Hello Disney World";
string[] strArray = args.Split( ' ' );

byte[][] byteArray = new byte[ strArray.Length ][ ];

//convert string to byte array
for( int i = 0; i < strArray.Length; i++ ) {
    byteArray[ i ] = Encoding.ASCII.GetBytes( strArray[ i ] );
}

To get a byte**, you need a byte*[] ( More info Converting from a jagged array to double pointer in C# ):

unsafe
{
    GCHandle[] pinnedArray = new GCHandle[ byteArray.Length ];
    byte*[] ptrArray = new byte*[ byteArray.Length ];

    for( int i = 0; i < byteArray.Length; i++ ) {
        pinnedArray[ i ] = GCHandle.Alloc( byteArray[ i ], GCHandleType.Pinned );
    }

    for( int i = 0; i < byteArray.Length; i++ ) {
        ptrArray[ i ] = (byte *)pinnedArray[ i ].AddrOfPinnedObject();
    }

    fixed ( byte **ptr = &ptrArray[ 0 ] ) {
        IntPtr mptr = (IntPtr)ptr;

        if( false == cbFunction( mptr, strArray.Count() ) ) {
            //some task
        }

    }

    for( int i = 0; i < pinnedArray.Length; i++ )
        pinnedArray[ i ].Free();

}

Your declaration should be:

[UnmanagedFunctionPointer( CallingConvention.Cdecl )]
private delegate bool CallBackFunction( IntPtr argv, int argc );

EDIT

For completion there is an easier way to to do it ( Marshaling Data with Platform Invoke, Marshaling Different Types of Arrays ):

bool cbFunction(char *argv[], int argc)
{
    //Some task
    STRSAFE_LPSTR temp;
    const size_t alloc_size = sizeof(char) * 5; //"Jetix" num of characters

    temp = (STRSAFE_LPSTR)CoTaskMemAlloc( alloc_size );
    StringCchCopyA( temp, alloc_size, (STRSAFE_LPSTR)"Jetix" );

    CoTaskMemFree( argv[1] );
    argv[1] = (char *) temp;

    return false;
}

Call it:

string args = "Hello Disney World";
strArray = args.Split(' ');
CallBackFunction cbFunction = ((CallBackFunction)Marshal.GetDelegateForFunctionPointer(
                                pAddressOfFunctionToCall, typeof(CallBackFunction));

if( false == cbFunction(strArray, strArray.Count()) )
{
    //some task
}

And declaration:

[UnmanagedFunctionPointer( CallingConvention.Cdecl )]
public delegate bool CallBackFunction( [In, Out] String[] argv, int argc );
  • Your comment and post will get the OP into big trouble. He'll corrupt the GC heap when he copies the string back. Very hard to diagnose, such corruption doesn't crash the CLR quick enough. – Hans Passant Oct 04 '18 at 14:45