简体   繁体   中英

Passing Array of Structures from C#(.NET Core) to C++(unamnaged)

So I've read the documentation and countless examples online how to marshal array of structures. I've marshalled array of int's, I've marshalled structures, but now I'm completely stuck and can't get it to work no matter what I've try. Been stuck on it for over a day now.

Structure/class, tried as both

    [StructLayout(LayoutKind.Sequential,CharSet = CharSet.Unicode)]
public class SaveDetails
{
    [MarshalAs(UnmanagedType.LPWStr)]
    public string Log;
    public FILETIME FileTime;
    [MarshalAs(UnmanagedType.Bool)]
    public bool Saved;
}

Pinvoke and call delegate

public class LogSaveFiles : IDisposable
{
    [UnmanagedFunctionPointer(CallingConvention.Winapi,CharSet = CharSet.Unicode)]
    private delegate Status DLogSaveFiles([ In, Out] SaveDetails[] logsToSave, string destinationPath);
    private static DLogSaveFiles _dLogSaveFiles;

    private IntPtr PLogSaveFiles { get; set; }
    public bool LogSaveFilesAvailable => PLogSaveFiles != IntPtr.Zero;

    public LogSaveFiles(Importer importer)
    {
        if (importer.dllLibraryPtr!= IntPtr.Zero)
        {
            PLogSaveFiles = Importer.GetProcAddress(importer.dllLibrary, "LogSaveFiles");
        }
    }

    public Status SaveFiles(SaveDetails[] logsToSave,string destinationPath)
    {
        Status result = Status.FunctionNotAvailable;

        if (LogSaveFilesAvailable)
        {
            _dLogSaveFiles = (DLogSaveFiles)Marshal.GetDelegateForFunctionPointer(PLogSaveFiles, typeof(DLogSaveFiles));

            result = _dLogSaveFiles(logsToSave, destinationPath);
        }

        return result;
    }

    public void Dispose()
    {

    }
}

Call

      private void SaveLogs()
    {
        var logsToSave = new[]{
            new SaveDetails{
                FileTime = new FILETIME {dwHighDateTime = 3,dwLowDateTime = 5},
                Log = LogTypes.logDeviceLog,
                Saved = true},
            new SaveDetails{
                FileTime = new FILETIME {dwHighDateTime = 1,dwLowDateTime = 2},
                Log = LogTypes.logDeviceLog,
                Saved = false}
             };

        var pathToSave = "C:\\Logs";
        _logSaveFiles.SaveFiles(logsToSave, pathToSave);
    }

c++ exposed call

    typedef struct _LOG_SAVE_DETAILS
{
    LPTSTR      szLog;
    FILETIME    fromFileTime;
    BOOL        bSaved;
} LOG_SAVE_DETAILS, *PLOG_SAVE_DETAILS;


/* Function definitions */

ULY_STATUS _API LogSaveFiles (PLOG_SAVE_DETAILS   ppLogs [],
                                         LPCTSTR                szDestinationPath);

Path to destination gets passed properly, but array of structures never goes through resulting in access violation when trying to access it. At first I thought it was issue with LPTSTR not going through properly but I've implemented other calls with it on its own and succeeded marshalling it through.

I've read everything on https://docs.microsoft.com/en-us/dotnet/framework/interop/marshaling-data-with-platform-invoke , it all indicates that my approach is correct, but it doesn't work.

Any help is appreciated.

Simple solution: C side change PLOG_SAVE_DETAILS ppLogs [] to LOG_SAVE_DETAILS ppLogs [] , then C#-side change public class SaveDetails to public struct SaveDetails .

Marshaling array of objects seems to be difficult (I wasn't able to do it). Marshaling array of structs works. An alternative is to do the marshaling manually, but it is a pain.

The "pain" of manual marshaling (only modified lines of code):

[UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)]
private delegate Status DLogSaveFiles(IntPtr[] logsToSave, string destinationPath);

and then

public Status SaveFiles(SaveDetails[] logsToSave, string destinationPath)
{
    Status result = Status.FunctionNotAvailable;

    if (LogSaveFilesAvailable)
    {
        if (_dLogSaveFiles == null)
        {
            _dLogSaveFiles = (DLogSaveFiles)Marshal.GetDelegateForFunctionPointer(PLogSaveFiles, typeof(DLogSaveFiles));
        }

        int size = Marshal.SizeOf(typeof(SaveDetails));

        IntPtr basePtr = IntPtr.Zero;
        IntPtr[] ptrs = new IntPtr[logsToSave.Length + 1];

        try
        {
            basePtr = Marshal.AllocHGlobal(size * logsToSave.Length);

            for (int i = 0; i < logsToSave.Length; i++)
            {
                ptrs[i] = IntPtr.Add(basePtr, (i * size));
                Marshal.StructureToPtr(logsToSave[i], ptrs[i], false);
            }

            result = _dLogSaveFiles(ptrs, destinationPath);
        }
        finally
        {
            if (basePtr != IntPtr.Zero)
            {
                for (int i = 0; i < logsToSave.Length; i++)
                {
                    if (ptrs[i] != IntPtr.Zero)
                    {
                        Marshal.DestroyStructure(ptrs[i], typeof(SaveDetails));
                    }
                }

                Marshal.FreeHGlobal(basePtr);
            }
        }
    }

    return result;
}

Important: this is a marshaler C#->C++. The C++ mustn't modify the received array in any way or there will be a memory leak.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM