C# i C++ struktura ze stringiem

0

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?

0

Odniosę się do części C++. Kod zamieszczony w miejscu "Działa tak" i "Przypadek 4" (do którego nie jestem przekonany) zgłaszany AV nie bierze się znikąd. Zauważ, że w strukturze mamy wchar_t** description, przy utworzeniu tablicy struktur taki obiekt ma nieprzydzieloną pamięć. Później z tego powodu dereferencja *pFunction->description powoduje AV - to chyba oczywiste.

0

To byłoby oczywiste, gdybym pamięci nie próbował przydzielić po stronie C#. Próbowałem na dwa sposoby:

 
public IntPtr description = Marshal.AllocHGlobal(4); //4, bo dla testów system 32 bitowy
public IntPtr description = new IntPtr();

Poza tym bez takich fikołków działa wskaźnik na tablicę funkcji.

edit
To wygląda tak, jakby C++ w ogóle nie pracowało na pointerze otrzymanym z C#. Albo C#... tak jakby brakowało temu pointerowi określenia, że jest przez referencję. Ale nie można go tak zadeklarować w klasie.

2

OK, zagadka rozwiązana. Wystarczyło prześledzić pamięć po stronie C++ i wszystko stało się jasne. Struktura w C++ musi być też packed. Po jej spakowaniu wszystko śmiga. Do stringa wystarczy użyć wchar_t *, jak można się było spodziewać.

0

Takie rzeczy łatwiej się robi w C++/CLI (można na różne sposoby: zamiast natywnego C++, zamiast C#, albo jako pomost między C++ a C#).

1 użytkowników online, w tym zalogowanych: 0, gości: 1