简体   繁体   中英

pinvoke: How to free an array of structures containing LPWSTR

Simplifying the problem, here is the native methods that I try to invoke from my .NET class.

[NativeDll.dll]

Header

typedef struct _ADDRESS {
    LPWSTR  City;
} ADDRESS, *PADDRESS;

typedef struct _ADDRESS_SET {
    ULONG AddressCount;
    PADDRESS AddressList;
} ADDRESS_SET, *PADDRESS_SET;

DWORD WINAPI
GetAddressSet(_Outptr_ ADDRESS_SET **AddressSet);

VOID WINAPI
FreeAddressSet(__in ADDRESS_SET *AddressSet);

C++ Implementation

DWORD WINAPI
GetAddressSet(_Outptr_ ADDRESS_SET **AddressSet) {
    HRESULT hr = ERROR_SUCCESS;
    const int totalRecords = 2;
    LPCWSTR cities[totalRecords] = { L"City 1", L"City 2"};
    ADDRESS *addresses = (ADDRESS*)malloc(sizeof(ADDRESS) * totalRecords);

    for (int i = 0; i < totalRecords; i++) {
        addresses[i].City = (wchar_t*)malloc((wcslen(cities[i]) + 1) * sizeof(wchar_t));
        addresses[i].City = (LPWSTR)cities[i];
    }

    ADDRESS_SET *recordSet = (ADDRESS_SET*)malloc(sizeof(ADDRESS_SET));
    recordSet->AddressCount = totalRecords;
    recordSet->AddressList = addresses;
    *AddressSet = recordSet;

    return ERROR_SUCCESS;
}

VOID WINAPI
FreeAddressSet(__in ADDRESS_SET *AddressSet) {

    if (AddressSet != NULL) {

        if (AddressSet->AddressList != NULL) {

            for (int i = 0; i < AddressSet->AddressCount; i++) {

                if (AddressSet->AddressList[i].City != NULL) {
                    free(AddressSet->AddressList[i].City); // <-- This one AVs.
                    AddressSet->AddressList[i].City = NULL;
                }
            }

            free(AddressSet->AddressList);
            AddressSet->AddressList = NULL;
        }

        free(AddressSet);
        AddressSet = NULL;
    }
}

When i try to invoke these APIs from my native code, I am able to get the array of addresses. But AV when I try to free the City string (LPWSTR).

Here is my C# code.

[StructLayout(LayoutKind.Sequential)]
internal struct ADDRESS {
    internal IntPtr City;
}

[StructLayout(LayoutKind.Sequential)]
internal struct ADDRESS_SET {
    // ULONG is 4 bytes. 
    // ulong in .NET is 8 bytes.
    // Hence using uint.
    internal UInt32 AddressCount;

    internal IntPtr AddressList;
}

internal class NativeMethods {
    [DllImport("nativedll.dll", EntryPoint = "GetAddressSet")]
    internal static extern UInt32 GetAddressSet(ref IntPtr AddressSet);

    [DllImport("nativedll.dll", EntryPoint = "FreeAddressSet")]
    internal static extern UInt32 FreeAddressSet([In] IntPtr AddressSet);
}

class Program {
    static void Main(string[] args) {
        IntPtr pAddressSet = IntPtr.Zero;
        UInt32 returnStatus = NativeMethods.GetAddressSet(ref pAddressSet);

        if(returnStatus == 0 && pAddressSet != IntPtr.Zero) {
            ADDRESS_SET addressSet = Marshal.PtrToStructure<ADDRESS_SET>(pAddressSet);
            UInt32 addressCount = addressSet.AddressCount;
            IntPtr addressList = addressSet.AddressList;

            if (addressCount != 0 && addressList != IntPtr.Zero) {

                for (int i = 0; i < addressCount; i++) {
                    ADDRESS address = Marshal.PtrToStructure<ADDRESS>(addressList);
                    addressList += Marshal.SizeOf(typeof(ADDRESS));
                    Console.WriteLine($"City: {Marshal.PtrToStringUni(address.City)}");
                }
            }
        }

        NativeMethods.FreeAddressSet(pAddressSet); // <-- Call fails to free the City string.
    }
}

In this example i have the City field inside the Address structure as IntPtr. I have tried the City field to be [MarshalAs(UnmanagedType.LPWStr)] string as well but couldn't get to free successfully. I am not sure what is the mistake in my .NET code.

The problem is in your C++ code. You allocate memory for the string, but then never write to that memory.

addresses[i].City = (wchar_t*)malloc((wcslen(cities[i]) + 1) * sizeof(wchar_t));
addresses[i].City = (LPWSTR)cities[i];

If you enabled compiler hints and warnings the compiler will tell you that the value assigned in the first line is never used.

That second line is wrong. Instead you must copy the string content with wcscpy_s(). Now the FreeAddressSet() function can properly call free(), using the actual pointer value returned by malloc().

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