3

I'm currently working on a project with a few other people, who are sadly no longer available for reference material. This project uses C# for the GUI, and C++ for updating our archaic database. At the moment, I have this function in C#:

[DllImport("Synch.DLL", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    //Synch bool Synch(void * voidPtr, const TCHAR * databaseDir, const TCHAR * truckDir)
    static extern Int32 Synch(IntPtr pSync, string databaseDir, string destDir, ref bool[] wholeFiles);

wholeFiles is an array of booleans used to determine if certain files should be synced or not, based on user input.

The C++ is supposed to take in this array, and be able to flip some of the bools to true or false as certain conditions are met.

if (numSyncRecords > 0){
        Log::WriteVerbose(_T("   %d recs, %d differences found"), (int) numRecsUsed, (int) numSynRecords);
        if(wholeFiles[sourceFileID]){
            CString tempPath = _T("");
            tempPath += destDir;
            tempPath += fileName;
            DeleteFile(tempPath);
        }
        else
            wholeFiles[sourceFileID] = false;
    }
    else
        wholeFiles[sourceFileID] = false;

In the case above, I pass in Trues, and if there are no changes to the file, set it to false to avoid processing a file that doesn't need it.

Now, my issue is, when the C++ code is finished, there are still True marks when they should be set to false. If I send it in by ref, then I get a stack overflow. Without ref, the array doesn't seem to change.

Edit: This was requested.

SYNC_API Int32 Synch(void * voidPtr, const TCHAR * databaseDir, const TCHAR * truckDir, bool wholeFiles[])

Edit2: I have, in the C++ code, a log statement that loops over the array and outputs true/false once per line per position in the wholeFiles array, and I end up with the appropriate log output, but when I look at the Array on the C# code after that is done, it's unchanged.

user3169698
  • 143
  • 5
  • 23
  • You don't want to use `ref` - `wholeFiles` is an array, it's already passed by reference. You'd use `ref` for `TCHAR **`, i.e. returning a *different* array. You might want to use `[In, Out]` attributes on the parameter, seeing as it's input/output, but I don't think it's going to help. However, the comment you have for the C-declaration doesn't fit the P/Invoke - did the signature change, or is that an error? You're passing the `destDir` string instead of the `wholeFiles` array the way it looks now. If the signature changed, please post the relevant section of the header files. – Luaan Jul 14 '15 at 15:27
  • I forgot to update the comment, sorry about that. – user3169698 Jul 14 '15 at 15:40

2 Answers2

6

If the comment below your DllImport is the exact function declaration on the C++ side, then your wholeFiles array will never be seen on the C++ side because it's not expecting a 5th argument. Make sure to fix that issue first.

In C# there's only one size of bool, and that is 1 byte. In C++ there are two types, the C-style 1-byte bool and the Win32 4-byte BOOL. By default P/Invoke will marshal bools as BOOL.

So if that first bit isn't your entire issue and it's not working, then you are using the C-style bool and need to tell C# that with [MarshalAs] like this:

[DllImport("Synch.DLL", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
static extern Int32 Synch(
    IntPtr pSync,
    string databaseDir,
    string destDir,
    [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I1)]
    bool[] wholeFiles);

You also don't need the ref because an array is a reference type in itself. A ref array would only be useful if the function you are calling wants to swap the array with another one.

Also note that you don't have to make it multiline, the attribute will work if the function is all in one line, I just did that for readability here.

Robert Rouhani
  • 14,512
  • 6
  • 44
  • 59
1

It's hard to tell exactly what#s going on without the full C++ function, but one thing that you could try is getting the C++ function to return the modified array (rather than relying on the in-place modification).

[DllImport("Synch.DLL", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
// Now return an IntPtr
static extern IntPtr Synch(IntPtr pSync, string databaseDir, string destDir, ref bool[] wholeFiles);

wholeFiles = Synch(..., wholeFiles);

Option 2

Another option is to declare a buffer in C#, copy the wholeFiles array to that buffer, and then pass the address of the buffer through to C# (this is what I've done in the past). The C++ code then modifies the buffer and you can then copy this back into wholeFiles after the call to the dll completes.

Option 3

The best option (in my opinion) is to use SWIG. SWIG makes it very easy to wrap C++ functions for C# use (and actually many other languages). This will give you a C# function called Synch that calls the dll behind the scenes, dealing with any memory issues for you.

Example (simple) synch.i file assuming C++ header is called synch.h:

%module SynchSWIG
%{
#include "synch.h"
%}

%include "synch.h

Generate wrappers with command (not tested):

swig -c++ -csharp -dllimport Synch.DLL synch.i

This will give you C# files to compile into your C# project, and a C++ file to compile into the C++ dll project.

Tim B
  • 3,033
  • 1
  • 23
  • 28