0

I call a C routine from C#, with some doubles, ints, a large string and some arrays as argument. Some arrays return correctly, with the correct values, only one array does not.The array is corrupted on returning to C#, NOT during execution of the C-routine.

C#-side:

[DllImport(@"Critias.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "Cr", 
            CharSet = CharSet.Ansi, BestFitMapping =false,ThrowOnUnmappableChar =true)]
public static extern int Cr([In] StringBuilder isc, 
            int nrModes, 
            double[] eigenfrequencies, 
            ref double error, 
            double[,] defl,
            double[] reactions,
            double[,] force);

public double[,] Deflection;
public double[,] MemberEndforces;       
public double[] Reactions;
public double error;

    ....

var eigenFreq = new double[nrModes + 1]; // +1 for C-sides pseudo FORTRAN arrays
MemberEndforces = new double[mbCount,12];                
Deflection = new double[mbCount + 1,6];               
Reactions = new double[6];       
info = NativeMethods.Cr(
            isc, nrModes, eigenFreq, ref error,  Deflection, Reactions, MemberEndforces);

now, all arguments come back OK, except MemberEndforces. Also when I swap the order of Reactions and MemberEndforces on the caller AND callee-side, Reactions come back from Cr OK, but not MemberEndforces. I get "Unable to read memory" for MemberEndforces, in the VS watch pannel . Also, if I do NOT fill MemberEndforces C-side, so do nothing with it, it makes no difference: Still the same error message

And C-side:

extern "C"
{
    __declspec(dllexport) int __cdecl Cr(
        char *_isc,
        int nM_calc,
        double* f,
        double* error,
        double* deflection,     
        double* reactions ,
        double* endforces ) // this is the annoying one
     {
      double** Q; // malloc-ed somewhere.
      double* D;
      double *R;

     .....
     for (int ii = 0; ii < DoF; ii++)
            deflection[ii ] = D[ii+1];
    for (int ii = 0; ii < nE; ii++)
        for (int jj = 0; jj < 12; jj++)
        {
            endforces[ii * 12 + jj] = Q[ii+1][jj + 1];// values OK,but...
        }
    for (int ii = 0; ii < 6; ii++)
        reactions[ii] = R[ii+1];

            ......

        }

The question, of course, is: What do I do wrong (and why, maybe?)?

Erik
  • 894
  • 1
  • 8
  • 25
  • Why are you `Marshaling` `double*` to `double[,]`? `double*` is a pointer to one dimensional array? I think it should be `double[]` – Rickless Dec 17 '17 at 16:23
  • 1
    It is exceedingly risky pinvoke, the C code is writing directly to the GC heap and has no way to ensure that the arrays are large enough. There is no way for us to tell if DoF and nE have proper values that match mbCount. Especially the deflection array access looks fishy. The +1 hacks look like an off-by-one bug waiting to happen. Coloring outside of the lines causes memory corruption and a program that starts producing garbage and random crashes. Pass mbCount as an argument as well. – Hans Passant Dec 17 '17 at 16:33

2 Answers2

1

There seem to be some concept issues here. As to why the last parameter might be corrupted. You define it as a two-dimensional array (c#), then use it as a one dimensional array in C.

In C an array is a pointer to a block of memory, of some particular type (example double). Typically, you allocated it as *typeof(double) * arraysize*. A two dimensional array is a pointer to a block of memory of pointers to some type. To create a 2D array you have to first allocate the array of pointers, then set each pointer to an array. You might look at something like this: Multidimensional array initialization in C

C# has different options here, see: What are the differences between a multidimensional array and an array of arrays in C#? Plus, C# is a managed memory system, so passing pointers out of C# (to say C) is a problem, because at any moment the GC (Garbage Collector) might kick in and change the memory address of a variable (pulling the rug out from under your C running C code). C# has a way to fix memory (using 'unsafe' code), which allows you to 'lock' and address (prevent the GC from moving it) for a period of time (like during your call to unmanaged code).

In the end you are just using addresses to blocks of memory. Makes sure those blocks don't move during calls and make sure the format of the blocks interpreted the same way in both coding languages.

Dweeberly
  • 4,668
  • 2
  • 22
  • 41
  • The arrays in question are made C#-side with "new". They are not jagged (array of pointers to arrays of pointer), but "real" 2D. (Q is jagged, but that is not relevant here:it's copied). They are passed-by-ref to C, I guess? So picking up a pointer at C-side would make the arrays accessible C-side somehow. So all values I would store in them C-side would have to be visible C#-side, I guess. – Erik Dec 17 '17 at 20:09
  • That's true and yes the arrays are exchanged by reference. Interop between languages can be trick. For example a StringBuilder or String in C# is the same as a char* in C, so I'm assuming you get away with that because it isn't used. You can pass C# created arrays into C; IF you don't exceed array bounds AND you stop the GC from moving memory during your C call. You can allocated unmanaged memory in C# using Marshal.AllocHGlobal (just remember to free it) or inhibit the GC using a 'fixed' statement. You should check the values of nE and mbCount. – Dweeberly Dec 17 '17 at 21:47
  • If I do NOTHING to MemberEndforces C-side, except passing it in as a pointer, I have still the same problem/error message. – Erik Dec 17 '17 at 22:23
0

It was the eigenfrequencies/f array and the nrModes argument that caused the trouble. The problem was 100% C-side. The calling/receiving was OK. I just handled eigenfrequencies wrong inside the C function Cr, overwriting stuff. Thnx for thinking with me.

Erik
  • 894
  • 1
  • 8
  • 25