수색…


비고

C #을 사용하여 Win32 API로 작업하기

Windows는 Win32 API 형태로 많은 기능을 제공합니다. 이 API를 사용하면 창에서 직접 작업을 수행 할 수 있으므로 응용 프로그램의 성능이 향상됩니다. 소스 여기를 클릭하십시오.

Windows는 광범위한 API를 제공합니다. 다양한 API에 대한 정보를 얻으려면 pinvoke 와 같은 사이트를 확인할 수 있습니다.

비 관리 C ++ DLL에서 가져 오기 기능

다음은 비 관리 C ++ DLL에 정의 된 함수를 가져 오는 방법의 예입니다. "myDLL.dll"에 대한 C ++ 소스 코드에서 add 함수가 정의됩니다.

extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
    return a + b;
}

그런 다음 다음과 같이 C # 프로그램에 포함될 수 있습니다.

class Program
{
    // This line will import the C++ method.
    // The name specified in the DllImport attribute must be the DLL name.
    // The names of parameters are unimportant, but the types must be correct.
    [DllImport("myDLL.dll")]
    private static extern int add(int left, int right);

    static void Main(string[] args)
    {
        //The extern method can be called just as any other C# method.
        Console.WriteLine(add(1, 2));
    }
}

extern "C"__stdcall 이 필요한 이유에 대한 설명은 호출 규칙C ++ 이름 맹 글링 을 참조하십시오.

동적 라이브러리 찾기

extern 메서드가 처음 호출 될 때 C # 프로그램은 적절한 DLL을 검색하고로드합니다. DLL을 찾기 위해 어디에서 검색 할 것인지, 검색 위치에 어떻게 영향을 줄 수 있는지에 대한 자세한 내용은 이 stackoverflow 질문을 참조하십시오.

com에 대한 클래스를 노출하는 간단한 코드

using System;
using System.Runtime.InteropServices;
 
namespace ComLibrary
{
    [ComVisible(true)]
    public interface IMainType
    {
        int GetInt();
 
        void StartTime();
 
        int StopTime();
    }
 
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class MainType : IMainType
    {
        private Stopwatch stopWatch;
 
        public int GetInt()
        {
            return 0;
        }
 
        public void StartTime()
        {
            stopWatch= new Stopwatch();
            stopWatch.Start();
        }
 
        public int StopTime()
        {
            return (int)stopWatch.ElapsedMilliseconds;
        }
    }
}

C ++ 이름 맹 글링

C ++ 컴파일러는 다른 인수로 오버로드를 가능하게하기 위해 인수 유형과 같은 내 보낸 함수의 이름에 추가 정보를 인코딩합니다. 이 프로세스를 이름 변환 이라고합니다. int add(int a, int b) 함수의 이름이 더 이상 add 되지 않으므로 C #에서 함수를 가져 오는 데 문제가 발생합니다 (일반적으로 다른 언어와 상호 작용 int add(int a, int b) ?add@@YAHHH@Z , _add@8 또는 컴파일러 및 호출 규칙에 따라 다르기 때문입니다.

이름 맹 글링 문제를 해결하는 몇 가지 방법이 있습니다.

  • extern "C" 를 사용하여 C 이름 변환을 사용하는 C 외부 링크로 전환하는 기능 내보내기 :

    extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    

    함수 이름은 계속해서 맹 글링되지만 ( _add@8 ), StdCall + extern "C" 이름 맹 글링은 C # 컴파일러에서 인식됩니다.

  • myDLL.def 모듈 정의 파일에 내 보낸 함수 이름 지정 :

    EXPORTS
      add
    
    int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    

    이 경우 함수 이름은 순수 add 됩니다.

  • 맹 글링 된 이름 가져 오기. 맹 글링 된 이름을 보려면 일부 DLL 뷰어가 필요하며 명시 적으로 지정할 수 있습니다.

    __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
    

호출 규칙

함수 호출에는 여러 가지 규칙이 있습니다. 호출자 또는 호출 수신자가 스택에서 인수를 가져 오는 방법, 인수가 전달되는 순서 및 순서를 지정하는 것입니다. C ++은 기본적으로 Cdecl 호출 규칙을 사용하지만 C #에서는 Windows API에서 일반적으로 사용되는 StdCall 이 필요합니다. 둘 중 하나를 변경해야합니다.

  • C ++에서 StdCall 로 호출 규칙 변경 :

    extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    
  • 또는 C #의 Cdecl 에 대한 호출 규칙 변경 :

    extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b)
    
    [DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
    

Cdecl 호출 규칙 및 Cdecl 이름이있는 함수를 사용하려는 경우 코드는 다음과 같습니다.

__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
           EntryPoint = "?add@@YAHHH@Z")]
  • thiscall ( __thiscall )은 주로 클래스의 멤버 인 함수에서 사용됩니다.

  • 함수가 thiscall ( __thiscall )을 사용할 때 클래스에 대한 포인터가 첫 번째 매개 변수로 전달됩니다.

관리되지 않는 DLL의 동적로드 및 언로드

DllImport 특성을 사용할 때 컴파일시 올바른 dll과 메서드 이름을 알아야합니다. 좀 더 유연하고 런타임 에 dll과로드 할 메소드를 결정하려면 LoadLibrary() , GetProcAddress()FreeLibrary() 와 같은 Windows API 메소드를 사용할 수 있습니다. 사용하려는 라이브러리가 런타임 조건에 따라 다르면이 방법이 유용 할 수 있습니다.

GetProcAddress() 의해 반환 된 포인터는 Marshal.GetDelegateForFunctionPointer() 사용하여 대리자로 캐스팅 될 수 있습니다.

다음 코드 샘플은 이전 예제의 myDLL.dll 을 사용하여 myDLL.dll 보여줍니다.

class Program
{
    // import necessary API as shown in other examples
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr LoadLibrary(string lib);
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern void FreeLibrary(IntPtr module);
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GetProcAddress(IntPtr module, string proc);

    // declare a delegate with the required signature
    private delegate int AddDelegate(int a, int b);

    private static void Main()
    {
        // load the dll
        IntPtr module = LoadLibrary("myDLL.dll");
        if (module == IntPtr.Zero) // error handling
        {
            Console.WriteLine($"Could not load library: {Marshal.GetLastWin32Error()}");
            return;
        }

        // get a "pointer" to the method
        IntPtr method = GetProcAddress(module, "add");
        if (method == IntPtr.Zero) // error handling
        {
            Console.WriteLine($"Could not load method: {Marshal.GetLastWin32Error()}");
            FreeLibrary(module);  // unload library
            return;
        }
            
        // convert "pointer" to delegate
        AddDelegate add = (AddDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(AddDelegate));
    
        // use function    
        int result = add(750, 300);
        
        // unload library   
        FreeLibrary(module);
    }
}

Win32 오류 다루기

interop 메서드를 사용할 때 GetLastError API를 사용하여 API 호출에 대한 추가 정보를 얻을 수 있습니다.

DllImport 특성 SetLastError 특성

SetLastError = true

호출 수신자가 SetLastError (Win32 API 함수)를 호출 함을 나타냅니다.

SetLastError = false

호출 수신자 SetLastError (Win32 API 함수)를 호출 하지 않음을 나타내므로 오류 정보를 얻지 못합니다.

  • SetLastError가 설정되지 않으면 false로 설정됩니다 (기본값).

  • Marshal.GetLastWin32Error 메서드를 사용하여 오류 코드를 얻을 수 있습니다.

예:

[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);

존재하지 않는 뮤텍스를 열려고하면 GetLastError는 ERROR_FILE_NOT_FOUND 를 반환합니다.

var lastErrorCode = Marshal.GetLastWin32Error();

if (lastErrorCode == (uint)ERROR_FILE_NOT_FOUND)
{
    //Deal with error         
}

시스템 오류 코드는 다음에서 찾을 수 있습니다.

https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx

GetLastError API

다음과 같이 사용할 수있는 네이티브 GetLastError API가 있습니다.

[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
  • 관리되는 코드에서 Win32 API를 호출 할 때는 항상 Marshal.GetLastWin32Error를 사용해야합니다.

이유는 다음과 같습니다.

SetLastError를 호출하는 Win32 호출 사이에서 CLR은 SetLastError 를 호출 할 수있는 다른 Win32 호출을 호출 할 수 있습니다.이 동작은 오류 값을 무시할 수 있습니다. 이 시나리오에서 GetLastError 를 호출하면 잘못된 오류를 얻을 수 있습니다.

SetLastError = true로 설정하면 다른 Win32 호출을 실행하기 전에 CLR이 오류 코드를 검색하는지 확인합니다.

고정 된 개체

GC (Garbage Collector)는 쓰레기를 청소합니다.

GC 는 가비지를 정리하는 동안 힙 조각화를 일으키는 관리되지 않은 개체를 관리되는 힙에서 제거합니다. GC 는 제거와 함께 완료되면 힙에서 오브젝트를 이동시키는 힙 압축 (defragmintation)을 수행합니다.

GC 는 결정적이 아니므로 관리 객체 참조 / 포인터를 원시 코드에 전달할 때 GC 는 언제든지 Inerop 호출 직후에 발생하면 어떤 객체 (참조를 네이티브로 전달했는지)가 관리되는 힙에서 이동할 수 있습니다. 결과적으로 관리되는 쪽에서 잘못된 참조가 발생합니다.

이 시나리오에서는 객체를 원시 코드로 전달하기 전에 객체를 고정 해야합니다.

고정 된 개체

고정 된 개체는 GC로 이동할 수없는 개체입니다.

Gc 고정 손잡이

Gc.Alloc 메서드를 사용하여 핀 객체를 만들 수 있습니다.

GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned); 
  • 관리 객체에 고정 된 GCHandle 을 가져 오면 핸들을 해제 할 때까지 특정 객체를 GC 로 이동할 수없는 객체로 표시합니다.

예:

[DllImport("kernel32.dll", SetLastError = true)]
public static extern void EnterCriticalSection(IntPtr ptr);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern void LeaveCriticalSection(IntPtr ptr);
       
public void EnterCriticalSection(CRITICAL_SECTION section)
{
    try
    {
        GCHandle handle = GCHandle.Alloc(section, GCHandleType.Pinned); 
        EnterCriticalSection(handle.AddrOfPinnedObject());
        //Do Some Critical Work
        LeaveCriticalSection(handle.AddrOfPinnedObject());
    }
    finaly
    {
        handle.Free()
    }
}

예방 조치

  • 피닝 (특히 커다란 피칭 ) 객체는 힙 조각 모음을 인터럽트하므로 가능한 빨리 피닝 된 GcHandle 을 해제하려고 시도합니다.
  • GcHandle 을 무료로 잊어 버리면 아무 것도하지 않습니다. 안전한 코드 섹션에서 수행하십시오 (예 : finaly).

마샬 러와 구조 읽기

Marshal 클래스에는 PtrToStructure 라는 함수가 포함되어 있으며이 함수는 관리되지 않는 포인터로 구조를 읽을 수있는 기능을 제공합니다.

PtrToStructure 함수는 많은 오버로드를 가지지 만 모두 동일한 의도를 가지고 있습니다.

일반 PtrToStructure :

public static T PtrToStructure<T>(IntPtr ptr);

T - 구조체 유형.

ptr - 관리되지 않는 메모리 블록을 가리키는 포인터입니다.

예:

NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);       
  • 기본 구조를 읽는 동안 관리 객체를 다루는 경우 객체를 고정하는 것을 잊지 마십시오.
 T Read<T>(byte[] buffer)
    {
        T result = default(T);
        
        var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);
    
        try
        {
            result = Marshal.PtrToStructure<T>(gch.AddrOfPinnedObject());
        }
        finally
        {
            gch.Free();
        }
        
        return result;
    }


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow