C# Language
상호 운용성
수색…
비고
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;
}