Cześć, już kilka dni próbuję ogarnąć ten problem, ale nie mogę. W necie też nie mogę znaleźć niczego akurat z takim przypadkiem związanego, co by działało.
Mam sobie po stronie C++ funkcję API, która ma za zadanie zwrócić mi tablicę struktur. Struktura po stronie C++ wygląda tak:
struct SupportFunctionInfo
{
__int32 type;
bool active;
**MA BYĆ STRING** description;
};
przy czym pole description próbowałem dać jako wchar_t *
i wchar_t **
. Wyjaśnienie poniżej.
Deklaracja funkcji API po stronie C++ wygląda w taki sposób:
_API bool __stdcall loadFile(SupportFunctionInfo ** ppFunctions, __int32 & fCount);
(pominąłem nieistotne rzeczy)
Działa tak:
ASFFile f; //wczytanie pliku w C++
if(!f.LoadFromFile(pFileName))
return false;
*ppFunctions = new SupportFunctionInfo[fCount]; //tworzenie tablicy struktur (fCount mam z innego miejsca)
SupportFunctionInfo * pFunction = *ppFunctions; //pobranie pierwszego elementu z tablicy
for(size_t i = 0; i < fCount; ++i, pFunction++)
{
//uzupełnianie pól struktury
//no i ten string
const std::wstring & desc = functions[i]->GetDescription(); //functions to vector trzymający mi obiekty, które potem tłumaczę na tą strukturę
//w tym przypadku description jest zdefiniowane jako wchar_t**
//i dostaję Access Violation przy new
*pFunction->description = new wchar_t[desc.size() + 2];
ZeroMemory(*pFunction->description, desc.size() * sizeof(wchar_t) + 2);
wcscpy_s(*pFunction->description, desc.size() * sizeof(wchar_t), desc.c_str());
}
return true;
OK i teraz tak. Jeśli chodzi o przekazywanie tablicy struktur jeśli nie ma tego stringa, to wszystko jest w porządku. Więc skupmy się teraz tylko na tym stringu.
Klasa w C# wygląda z grubsza tak:
[StructLayout(LayoutKind.Explicit, Pack=1)]
public class _APISupportFunctionInfo
{
[FieldOffset(0)]
public Int32 type;
[FieldOffset(4)]
public bool active;
[FieldOffset(5)]
public IntPtr description;
}
Deklaracja funkcji z API:
[DllImport("my.dll", CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.I1)]
static extern bool loadSupportFile(ref IntPtr infoArray, ref Int32 arrayLen);
Wywołanie funkcji:
IntPtr infoArray = IntPtr.Zero;
Int32 arrayLen = 0;
bool result = loadSupportFile(ref infoArray, ref arrayLen);
_APISupportFunctionInfo[] infos = new _APISupportFunctionInfo[arrayLen];
for (int i = 0; i < arrayLen; i++)
{
int classSize = Marshal.SizeOf(typeof(_APISupportFunctionInfo));
IntPtr itemPtr = new IntPtr(infoArray.ToInt32() + (i * classSize));
infos[i] = (_APISupportFunctionInfo)Marshal.PtrToStructure(itemPtr, typeof(_APISupportFunctionInfo));
}
I teraz rozpatrzmy kilka przypadków.
Przypadek 1.
String w C# zdefiniowany jako:
[FieldOffset(5)]
[MarshalAs(UnmanagedType.LPWStr)]
public string description = IntPtr.Zero;
Rezultatem jest błąd:
Nie można załadować typu '_APISupportFunctionInfo' (...) ponieważ zawiera on pole obiektu o przesunięciu '5', które jest nieprawidłowo wyrównane lub częściowo pokrywa się z polem niebędącym polem obiektu.
Błąd wyskakuje w momencie wywołania powyższej metody (tej, która wywołuje funkcję API).
Przypadek 2.
String w C# zdefiniowany jako:
[FieldOffset(5)]
[MarshalAs(UnmanagedType.LPWStr)]
public IntPtr description = IntPtr.Zero;
Tutaj dostaję błąd przy Marshal.SizeOf:
nie może zostać zorganizowany jako struktura niezarządzana; nie można obliczyć akceptowalnego rozmiaru ani przesunięcia.
Przypadek 3.
String w C# zdefiniowany tak:
[FieldOffset(5)]
public IntPtr description = IntPtr.Zero;
String w strukturze w C++:
wchar_t * description;
Utworzenie tego pointera:
pFunction->description = new wchar_t[desc.size() + 2];
Rezultat jest taki, że po stronie C# ten description ma nieprawidłowy adres (0xCDCDCD). No i oczywiście przy próbie zmiany tego na string w C# jest AccessViolation.
Przypadek 4.
String w C# zdefiniowany na jeden z niżej podanych sposobów:
[FieldOffset(5)]
public IntPtr description = Marshal.AllocHGlobal(4); //4, bo dla testów system 32 bitowy
public IntPtr description = new IntPtr();
public IntPtr description;
String w C++:
wchar_t ** description;
Tworzenie stringa w C++:
*pFunction->description = new wchar_t[desc.size() + 2];
I tutaj dostaję AccessViolation.
Pomysły mi się skończyły. Wypróbowałem już chyba wszystkie możliwości oprócz tej poprawnej :)
Dochodzę do wniosku, że żeby to zrobić w taki sposób, konieczne jest użycie w C# unsafe i wskaźnika.
Ktoś ma pomysł, jak to zrobić dobrze?