2010-11-14 8 views
9

Muszę zaimportować niezarządzanego dll do mojej aplikacji C#, Chcę wiedzieć, co różni się między IntPtr i ref, a co polecił mi używać i dlaczego? Zauważ, że działają dla mnie obie metody. Na przykład:IntPtr vs ref C#

[DllImport("mydll.dll", CallingConvention=CallingConvention.Cdecl)] 
static extern Result Init(IntPtr versionInfo); 

[DllImport("mydll.dll", CallingConvention=CallingConvention.Cdecl)] 
public static extern Result Init(ref Version versionInfo); 

Odpowiedz

3

Edit: Na podstawie jakiejś krytyki zmuszając mnie do ponownego przemyślenia tego i badać ją dalej. Mogę swobodnie przyznać, że IntPtr jest podatny na błędy, myślę, że może toczyć się debata na temat tego, co "twardsze" oznacza, ale jeśli framework może zautomatyzować to i powstrzymać od konieczności przypinania rzeczy, itp. Jeśli nie używasz IntPtr to myślę Zgadzam się, że IntPtr byłby "trudniejszy", ale wydaje mi się, że po dalszych badaniach wydaje mi się, że będzie łatwiej (gdy zostaniesz zmuszony do P/Invoke i nie będziesz mógł utworzyć opakowania C++/CLI), aby zadeklarować swoją strukturę (w przypadku Version) w kodzie zarządzanym, a następnie przekazać je poprzez parametr ref.

[StructLayout(LayoutKind.Sequential)] 
struct Version 
{ 
    // Data members 
} 

[DllImport("mydll.dll", CallingConvention=CallingConvention.Cdecl)] 
public static extern Result Init(ref Version versionInfo); 

Moje przypuszczenie byłoby co chcesz wersję IntPtr. różnica polega na tym, że IntPtr jest klasa, która zarządza wskaźnik do pamięć, podczas gdy deklarowanie parametru jako odniesienia (słowa kluczowego ref) na liście argumentów oznacza, że ​​przechodzisz przez odniesienie. Generalnie podczas przekazywania danych do i od niezarządzanej biblioteki dll za pośrednictwem wywołania P/Invoke, chyba że przekazywane dane są typu waniliowego (int, double, string - z odpowiednią dekoracją Marshalla) przekazuję dane jako IntPtr. Następnie możesz przekazać Intrtr do zarządzanej deklaracji struktury niezarządzanej.

Należy sprawdzić ten link, aby uzyskać więcej informacji na temat P/Invoke wzywa: http://msdn.microsoft.com/en-us/magazine/cc164123.aspx

+0

To niefortunne, że robisz rzeczy w trudny, podatny na błędy sposób. Jeszcze bardziej niefortunne jest to, że uczysz innych ludzi również. –

+0

@Ben Z przyjemnością nauczę się łatwiejszego/mniej podatnego na błędy sposobu na zrobienie tego. Czy masz link lub sugerowany materiał do czytania.Nie spędzam zbyt wiele czasu na przywoływaniu rzeczy, przez większość czasu piszę wrapper C++/CLI, ale kiedy zszedłem do P/Invoke użyłem tylko parametrów ref dla takich rzeczy jak int, string (StringBuilder w przypadkach come), itp., A następnie używane IntPtr do innych rzeczy. – pstrjds

+1

C++/CLI dla wygranej :) Na te okazje, kiedy nie możesz go użyć (Windows CE lub nie możesz być pewien, że zostanie zainstalowane środowisko uruchomieniowe Visual C++), wtedy http://pinvoke.net ma okropne wiele przykładów. Ogólnie rzecz biorąc, nie tylko typy pierwotne, takie jak 'int' i' double', mogą być bezpiecznie używane w sygnaturach p/invoke, ale także typy zdefiniowane przez użytkownika, składające się z typów pierwotnych. Przykładami mogą być 'POINT',' POINTF', 'RECT',' FONTMETRIC', 'BITMAPFILEHEADER', itp. –

5

Jeśli Version to struktura, która jest zgodna z struct że extern Init funkcja oczekuje, nie ma istotnej różnicy między dwa, z tą różnicą, że wersja ref będzie łatwiejsza do użycia z C#, ponieważ środowisko wykonawcze będzie zarządzać wszystkimi operacjami buforowania i przypinania. Jeśli naprawdę nie chcesz wykonywać tej całej pracy, pozostanę przy opcji ref.

Oczywiście, nie widząc prototypu funkcji C, struktury Version w C# i struktury użytej w C dla tego parametru, naprawdę mogę tylko zgadywać.

+2

Istnieje jedna potencjalnie znacząca różnica: użycie IntPtr pozwala na przekazanie wartości IntPtr.Zero jako wartości, natomiast 'ref Version' nie. W związku z tym nie jest niezwykłe, aby wymagać przeciążenia IntPtr, w zależności od tego, czego wymaga natywny kod. – jonp

+2

@jonp: W takim przypadku potrzebujesz ** obydwu ** wersji (funkcja przeciążona), dzięki czemu możesz przekazać 'Version' przez referencję, gdy ją posiadasz, oraz' IntPtr.Zero', jeśli chcesz przekazać zero. Lub spraw, aby 'Version' była klasą zamiast struct, wtedy możesz przekazać wartość null (i nie potrzebujesz już słowa kluczowego" ref ", ale atrybuty adnotacji p/invoke są wtedy wymagane w klasie). –

-1

Lepiej używać IntPtr, ponieważ zwiększy to elastyczność twojego kodu, jeśli chodzi o kompilowanie kodu do x64. IntPtr automatycznie powiększy swój rozmiar.

IntPtr = 4 bytes on x86 
IntPtr = 8 bytes on x64 

Istnieją przypadki, kiedy pewne rodzaje w c Dwukrotnie they rozmiar gdy skompilowany dla x64, ale C# odpowiedniki (które są zadeklarowane dla PInvoke) nie. Wtedy wywołanie funkcji zakończy się niepowodzeniem. Jeśli klasa Version zawiera trochę takich pól, lepiej użyć wartości IntPtr.

+0

'ref' również przekaże parametr wielkości wskaźnika. Reszta twojej odpowiedzi brzmi, jakbyś mówił o wnętrznościach struktury "Version", a nie o samym parametrze. Chociaż możliwe jest, że niektóre pola 'Version' powinny mieć typ' IntPtr', nie ma powodu, aby nie używać 'ref Version' w deklaracji p/invoke. –

+0

@Ben Nie wpadłeś na pomysł, myślę, że wyraziłem się bardzo dobrze. Nie powiedziałem, że używanie ref nie zadziała, ale moim zdaniem użyłbym IntPtr. Ale dziękuję za głosowanie w dół, kiedy nie znasz odpowiedzi. –

+0

@Liviu: Cofnąłem (i oczywiście nie jestem jedynym), ponieważ twoja odpowiedź jest NIEPRAWIDŁOWA. Powiedziałeś, że "' IntPtr' sprawi, że twój kod stanie się bardziej elastyczny "rozmiar wskaźnika wrt, a to jest po prostu fałsz. 'ref' i' IntPtr' są tego samego rozmiaru na każdej platformie. –

2

IntPtr pozwoli ci przenieść wszystkich Twoich Zewnętrznych do jednej klasy, bez zależności od struktur używanych w połączeniach, które mogą być zdefiniowane gdzie indziej. Chciałbym, żeby adapter API oddzielał się od danych strukturalnych/klasowych, które wydają się bardziej naturalne, aby umieścić je w następnej warstwie.