Platform Çağırma (P/Invoke)

P/Invoke yönetilen kodunuzdan yönetilmeyen kitaplıklardaki yapılara, geri çağırmalara ve işlevlere erişmenizi sağlayan bir teknolojidir. P/Invoke API'sinin çoğu iki ad alanında yer alır: System ve System.Runtime.InteropServices. Bu iki ad alanını kullanmak, yerel bileşenle nasıl iletişim kurmak istediğinizi açıklayan araçlar sağlar.

En yaygın örnekten başlayalım ve yönetilen kodunuzda yönetilmeyen işlevleri çağıralım. Şimdi komut satırı uygulamasından bir ileti kutusu gösterelim:

using System;
using System.Runtime.InteropServices;

public partial class Program
{
    // Import user32.dll (containing the function we need) and define
    // the method corresponding to the native function.
    [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)]
    private static partial int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);

    public static void Main(string[] args)
    {
        // Invoke the function as a regular managed method.
        MessageBox(IntPtr.Zero, "Command-line message box", "Attention!", 0);
    }
}

Önceki örnek basittir, ancak yönetilen koddan yönetilmeyen işlevleri çağırmak için gerekenleri gösterir. Şimdi örnekte ilerleyelim:

  • 2. satır, using gerekli tüm öğeleri barındıran ad alanının yönergesini System.Runtime.InteropServices gösterir.
  • 8. satır özniteliğini tanıtır LibraryImportAttribute . Bu öznitelik çalışma zamanına yönetilmeyen ikiliyi yüklemesi gerektiğini bildirir. geçirilen dize, hedef işlevi içeren yönetilmeyen ikili dosyadır. Ayrıca, dizeleri sıralamak için kullanılacak kodlamayı belirtir. Son olarak, bu işlevin SetLastError'ı çağırdığını ve kullanıcının aracılığıyla Marshal.GetLastPInvokeError()alabilmesi için çalışma zamanının bu hata kodunu yakalaması gerektiğini belirtir.
  • 9. satır, P/Invoke çalışmasının crux'larıdır. Yönetilmeyen ile tam olarak aynı imzaya sahip yönetilen bir yöntem tanımlar. bildirimi, bir derleyici uzantısına LibraryImportpartial yönetilmeyen kitaplığa çağrı yapmak üzere kod oluşturmasını bildirmek için özniteliğini ve anahtar sözcüğünü kullanır.
    • Oluşturulan kod içinde ve .NET 7'nin DllImport öncesinde kullanılır. Bu bildirim, çalışma zamanına bunun bir dış yöntem olduğunu ve çağırdığınızda çalışma zamanının özniteliğinde belirtilen yönetilmeyen ikili dosyada bulması gerektiğini belirtmek için anahtar sözcüğünü DllImport kullanırextern.

Örneğin geri kalanı, diğer yönetilen yöntemler gibi yöntemini çağırmaktır.

Örnek, macOS için benzerdir. macOS' un dinamik kitaplıkları adlandırmak için farklı bir şeması olduğundan, özniteliğindeki LibraryImport kitaplığın adının değişmesi gerekir. Aşağıdaki örnek, uygulamanın işlem kimliğini almak ve konsola yazdırmak için işlevini kullanır getpid(2) :

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        // Import the libSystem shared library and define the method
        // corresponding to the native function.
        [LibraryImport("libSystem.dylib")]
        private static partial int getpid();

        public static void Main(string[] args)
        {
            // Invoke the function and get the process ID.
            int pid = getpid();
            Console.WriteLine(pid);
        }
    }
}

Linux'ta da benzerdir. İşlev adı aynıdır, çünkü getpid(2) standart bir POSIX sistem çağrısıdır.

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        // Import the libc shared library and define the method
        // corresponding to the native function.
        [LibraryImport("libc.so.6")]
        private static partial int getpid();

        public static void Main(string[] args)
        {
            // Invoke the function and get the process ID.
            int pid = getpid();
            Console.WriteLine(pid);
        }
    }
}

Yönetilmeyen koddan yönetilen kodu çağırma

Çalışma zamanı, iletişimin her iki yönde de akmasına olanak tanıyarak işlev işaretçilerini kullanarak yerel işlevlerden yönetilen koda geri dönmenizi sağlar. Yönetilen koddaki işlev işaretçisine en yakın şey bir temsilcidir, bu nedenle yerel koddan yönetilen koda geri çağırmalara izin vermek için bu kullanılır.

Bu özelliği kullanmanın yolu, daha önce açıklanan yönetilenden yerel işleme benzer. Belirli bir geri arama için imzayla eşleşen bir temsilci tanımlar ve bunu dış yönteme geçirirsiniz. Çalışma zamanı diğer her şeyi halleder.

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    public static partial class Program
    {
        // Define a delegate that corresponds to the unmanaged function.
        private delegate bool EnumWC(IntPtr hwnd, IntPtr lParam);

        // Import user32.dll (containing the function we need) and define
        // the method corresponding to the native function.
        [LibraryImport("user32.dll")]
        private static partial int EnumWindows(EnumWC lpEnumFunc, IntPtr lParam);

        // Define the implementation of the delegate; here, we simply output the window handle.
        private static bool OutputWindow(IntPtr hwnd, IntPtr lParam)
        {
            Console.WriteLine(hwnd.ToInt64());
            return true;
        }

        public static void Main(string[] args)
        {
            // Invoke the method; note the delegate as a first parameter.
            EnumWindows(OutputWindow, IntPtr.Zero);
        }
    }
}

Örnekte gezinmeden önce, üzerinde çalışmanız gereken yönetilmeyen işlevlerin imzalarını gözden geçirmek iyi olur. Tüm pencereleri listelemek için çağrılacak işlev aşağıdaki imzaya sahiptir: BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);

İlk parametre bir geri çağırmadır. Söz edilen geri çağırma aşağıdaki imzaya sahiptir: BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);

Şimdi örneği inceleyelim:

  • Örnekteki 9. satır, yönetilmeyen koddan gelen geri çağırmanın imzası ile eşleşen bir temsilci tanımlar. LPARAM ve HWND türlerinin yönetilen kodda kullanılarak IntPtr nasıl temsil edildiklerine dikkat edin.
  • Satır 13 ve #14, user32.dll kitaplığındaki işlevi tanıtır EnumWindows .
  • Satırlar #17 - 20 temsilciyi uygular. Bu basit örnek için yalnızca tanıtıcıyı konsola çıkarmak istiyoruz.
  • Son olarak, 24. satırda dış yöntem çağrılır ve temsilciye geçirilir.

Linux ve macOS örnekleri aşağıda gösterilmiştir. Bunlar için, C kitaplığında libcbulunabilen işlevini kullanırızftw. Bu işlev, dizin hiyerarşileri arasında geçiş yapmak için kullanılır ve bir işlevin işaretçisini parametrelerinden biri olarak alır. Söz edilen işlev şu imzaya sahiptir: int (*fn) (const char *fpath, const struct stat *sb, int typeflag).

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        // Define a delegate that has the same signature as the native function.
        private delegate int DirClbk(string fName, ref Stat stat, int typeFlag);

        // Import the libc and define the method to represent the native function.
        [LibraryImport("libc.so.6", StringMarshalling = StringMarshalling.Utf16)]
        private static partial int ftw(string dirpath, DirClbk cl, int descriptors);

        // Implement the above DirClbk delegate;
        // this one just prints out the filename that is passed to it.
        private static int DisplayEntry(string fName, ref Stat stat, int typeFlag)
        {
            Console.WriteLine(fName);
            return 0;
        }

        public static void Main(string[] args)
        {
            // Call the native function.
            // Note the second parameter which represents the delegate (callback).
            ftw(".", DisplayEntry, 10);
        }
    }

    // The native callback takes a pointer to a struct. This type
    // represents that struct in managed code.
    [StructLayout(LayoutKind.Sequential)]
    public struct Stat
    {
        public uint DeviceID;
        public uint InodeNumber;
        public uint Mode;
        public uint HardLinks;
        public uint UserID;
        public uint GroupID;
        public uint SpecialDeviceID;
        public ulong Size;
        public ulong BlockSize;
        public uint Blocks;
        public long TimeLastAccess;
        public long TimeLastModification;
        public long TimeLastStatusChange;
    }
}

macOS örneği aynı işlevi kullanır ve tek fark özniteliğin LibraryImport bağımsız değişkenidir, macOS farklı bir yerde tutar libc .

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        // Define a delegate that has the same signature as the native function.
        private delegate int DirClbk(string fName, ref Stat stat, int typeFlag);

        // Import the libc and define the method to represent the native function.
        [LibraryImport("libSystem.dylib", StringMarshalling = StringMarshalling.Utf16)]
        private static partial int ftw(string dirpath, DirClbk cl, int descriptors);

        // Implement the above DirClbk delegate;
        // this one just prints out the filename that is passed to it.
        private static int DisplayEntry(string fName, ref Stat stat, int typeFlag)
        {
            Console.WriteLine(fName);
            return 0;
        }

        public static void Main(string[] args)
        {
            // Call the native function.
            // Note the second parameter which represents the delegate (callback).
            ftw(".", DisplayEntry, 10);
        }
    }

    // The native callback takes a pointer to a struct. This type
    // represents that struct in managed code.
    [StructLayout(LayoutKind.Sequential)]
    public struct Stat
    {
        public uint DeviceID;
        public uint InodeNumber;
        public uint Mode;
        public uint HardLinks;
        public uint UserID;
        public uint GroupID;
        public uint SpecialDeviceID;
        public ulong Size;
        public ulong BlockSize;
        public uint Blocks;
        public long TimeLastAccess;
        public long TimeLastModification;
        public long TimeLastStatusChange;
    }
}

Önceki örneklerin her ikisi de parametrelere bağlıdır ve her iki durumda da parametreler yönetilen türler olarak verilir. Çalışma zamanı "doğru şeyi" yapar ve bunları diğer taraftaki eşdeğerlerine işler. Tür sıralama sayfasındaki sayfamızda türlerin nasıl yerel koda göre sıraya alınıp düzenleneceğinizi öğrenin.

Diğer kaynaklar