C#에서 win32 dll을 로드하여 함수를 호출할때 다음과 같은 에러가 발생한다면

AccessViolation Exception was unhandled-Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

함수 호출시 포인터  인자의 in, out 속성이 잘못 되었을 가능성이 있다...

AND

출처: http://ljh131.tistory.com/45


C#에서 DLL의 Unmanaged function을 호출하려면 다음과 같은 방법으로 함수를 만들어야 합니다. 아래 글의 출처는 msdn입니다.

To consume exported DLL functions

1. Identify functions in DLLs.
Minimally, you must specify the name of the function and name of the DLL that contains it.

2. Create a class to hold DLL functions.
You can use an existing class, create an individual class for each unmanaged function, or create one class that contains a set of related unmanaged functions.

3. Create prototypes in managed code.
[C#] Use the DllImportAttribute to identify the DLL and function. Mark the method with the static and extern modifiers.

4. Call a DLL function.
Call the method on your managed class as you would any other managed method. Passing structures and implementing callback functions are special cases.

가장 일반적인 방법이죠. 좀 복잡할테니 예를 들어서 설명하겠습니다.

우리가 필요한 함수는 Win32API함수인 EnumDisplayDevice()라는 함수입니다. 함수의 프로토타입은 다음과 같습니다.

BOOL EnumDisplayDevices(
  LPCTSTR lpDevice,                // device name
  DWORD iDevNum,                   // display device
  PDISPLAY_DEVICE lpDisplayDevice, // device information
  DWORD dwFlags                    // reserved
);
이제 이 함수 프로토타입을 C#용으로 고쳐야 합니다.

가장 먼저 할 일은 함수가 있는 DLL파일입니다. 이건 MSDN을 통해 쉽게 찾을 수 있죠. User32.dll에 있다는 것이 바로 확인됩니다.

두번째는 본격적으로 함수의 프로토타입을 C#으로 만드는 과정입니다.

우선 첫번째 인자를 볼까요? 문자열을 나타내는 LPCTSTR입니다. long pointer to const string이죠. 이 타입을 어떻게 바꿔야 할까요?

여기서 반드시 참고해야 할 것이 MSDN의 Platform Invoke Data Types 표입니다.

http://msdn2.microsoft.com/en-us/library/ac7ay120(vs.80).aspx

이 표를 보면 Win32API나 C스타일 함수에서 사용되는 데이터 타입들에 대응되는 Managed class type들이 나와있습니다. 표를 보면서 C#에서의 파라매터 타입을 정해주면 됩니다. 표에 따르면 - LPCTSTR은 직접 나와있지 않지만 - 얼추 string에 대응됩니다.

마찬가지로 DWORD는 UInt32입니다. 문제는 세번째 인자인 PDISPLAY_DEVICE인데요, 구조체의 포인터죠.

아쉽게도 이 경우에 대해서는 MSDN에서 명확한 해답을 찾을 수 없었습니다. 하지만 제가 알아본 바로는 대략 구조체를 클래스로 변환한 뒤, In, Out Attribute을 결정해주면 됩니다.

구조체인 DISPLAY_DEVICE에 대응되는 클래스를 만들기 전에 프로토타입 부터 설정해봅시다. MSDN을 보면 이 파라매터는 out 타입입니다. 따라서 In과 Out Attribute를 넣어줍니다. 완성된 프로토타입은 다음과 같습니다.

  [DllImport("user32.dll")]
  public static extern bool EnumDisplayDevices(string lpDevice, UInt32 iDevNum,
             [In, Out] DisplayDevice lpDisplayDevice, UInt32 dwFlags);
이제 할일은 구조체 DISPLAY_DEVICE를 클래스로 변환하는 작업입니다. 구조체의 프로토타입을 봅시다.

typedef struct _DISPLAY_DEVICE {
  DWORD cb;
  TCHAR DeviceName[32];
  TCHAR DeviceString[128];
  DWORD StateFlags;
  TCHAR DeviceID[128];
  TCHAR DeviceKey[128];
} DISPLAY_DEVICE, *PDISPLAY_DEVICE;
여기서 문제는 배열이죠. C#의 배열은 C의 배열과 메모리 할당 방식이 다릅니다. 이 문제는 MarshalAs Attribute를 이용해서 해결할 수 있습니다.

완성된 클래스는 아래와 같습니다.

 [StructLayout(LayoutKind.Sequential)]
 public class DisplayDevice
 {
  public UInt32 cb;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
  public string DeviceName;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceString;  
  public UInt32 StateFlags;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceID;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceKey;
 }
구조체가 선언된 순서대로 할당되는 것처럼 클래스 Attribute도

 [StructLayout(LayoutKind.Sequential)]

로 만들어서 구조체와 마찬가지로 순차적으로 변수를 할당한다고 선언을 해주면 됩니다.

드디어 프로토타입이 완성되었습니다!

하지만 실제로 함수를 호출하려면 아직 남은 것이 한가지 있습니다.

'Before calling EnumDisplayDevices, you must initialize the cb member of DISPLAY_DEVICE to the size, in bytes, of DISPLAY_DEVICE.'

함수를 호출하기 전에 구조체(여기서는 클래스)의 cb멤버를 구조체의 크기로 설정해야 한다는 것입니다. C에선 sizeof(DISPLAY_DEVICE)로 가능합니다. 하지만 C#에서는 어떻게 해야할까요?

물론 C#에도 sizeof키워드가 있습니다만 여기서는 동작하지 않습니다. 이 경우에도 마샬을 이용해야 하는데, 코드는 아래와 같습니다.

   DisplayDevice dd = new DisplayDevice();
   dd.cb = (UInt32)Marshal.SizeOf(dd);
이제 EnumDisplayDevices함수를 C#에서 호출할 수 있게 되었습니다.

기타 함수들에서 사용되는 상수들은 직접 헤더 파일에서 찾아서 넣어주면 됩니다.

자세한 설명이 필요하면 아래 링크를 참고하세요

http://www.canaware.com/default.aspx?page=ed51cde3-d979-4daf-afae-fa6192562ea9&article=cfd2e407-d20f-4dfc-8c34-e7d29c771b05

AND

출처: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2350703&SiteID=1

//////////////////////////////////////////////////////////////////////
질문자: 누군가 DLL 호출중 ESP 오류가 난다고 했나보다...
//////////////////////////////////////////////////////////////////////


Hello.

I'm trying to wrap a C++ dll using C#. I don't have access to the source used to create the C++ dll so I cannot alter the header files to declare the C++ callback with __stdcall. All I have is the API information.

Trouble arises when I try to wrap a function that has a callback. I get the following error:

"The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention"

This is the prototype of the C++ callback function:
typedef unsigned long (*QS_CALLBACK)(const QS_MSG * pMsg);

QS_MSG is defined in C++ as:
typedef struct s_QS_MSG
 {
unsigned long     Type;           
QS_RESULT         CompletionCode; 
unsigned long     nContextID;    
QS_RequesterType  eRequesterType; 
QS_DeviceAddress  DevAddr;       
QS_SessionHandle  hSessionID;  
const void *      hHandle;
const void *      MoreInfo;  
} QS_MSG;

My C# prototype is:
public delegate UInt32 QS_CALLBACK(ref QS_MSG pMsg);

  
The arguments match (sort of, with the exception of the const). So maybe it's a calling convention problem?

Here's more of the code:

// QS_RESULT, QS_RequesterType are a enum
// QS_DeviceAddress is a struct
[StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct QS_MSG
        {
            public UInt32 Type;            /* Which message is being sent to the UserCallback */
            public QS_RESULT CompletionCode;  /* Result of request */
            public UInt32 nContextID;      /* Context that caused this request */
            public QS_RequesterType eRequesterType;    /* Context that caused this request */
            public QS_DeviceAddress DevAddr;         /* The device address */
            public IntPtr hSessionID;
            public object hHandle;
            public object MoreInfo;        
        }

// QS_ApplicationType is an enum
[DllImport("ViconNet.dll")]
public static extern IntPtr QS_Initialize(QS_ApplicationType eApplType,
                                                         byte[] ipMe,
       QS_CALLBACK pUserCallback,
                                                                   UInt32 nContextID);

private static QS_CALLBACK cb = new QS_CALLBACK(callbackConductor);
private static IntPtr sessionConductor;

// function which is calling the C++ DLL

private void callingFunction( )
{
// ip is defined here
byte [] ip = getIP( );

sessionConductor = QS_Initialize(QS_ApplicationType.QS_APPLTYPE_CONDUCTOR,
                                                  ip, cb, 0)
}


 
//callback function
public static UInt32 callbackConductor(ref QS_MSG pMsg)
{
// do stuff with pMsg
return 1;
}


The error occurs when returning from the callback. Before the return, I inserted a breakpoint to inspect pMsg. It seems to have all the correct information.  If I change the prototype of the callback to have no parameters, there is no error. How can I resolve this problem without changing the C++ code? Thanks for any feedback.

-Neal







QS_SessionHandle QS_Initialize (QS_ApplicationType eApplType,
                                                const unsigned char ipMe[],
                                                QS_CALLBACK pUserCallback,
                                                unsigned long nContextID);



//////////////////////////////////////////////////////////////////////
별다섯개 답글~: 함수 호출 방식을 바꾸는 방법~~
//////////////////////////////////////////////////////////////////////


 

Hi Neal,

Like you may guessed, the problem is that the function pointer that the unmanaged function accepts should respect the C calling convention but the delegate instance you pass to it does not (it respects the standard calling convention (__stdcall).

If you’re using .net 2.0, you can use UnmanagedFunctionPointer Attribute to Control the marshaling behavior of a delegate signature passed as an unmanaged function pointer to or from unmanaged code. Check out the following example:

Code Block

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]

public delegate UInt32 QS_CALLBACK(ref QS_MSG pMsg);


 

If you’re using .net 1.1 then you have to check out the solution here: Using C calling convention callback functions in C# and VB - the easy way.

Hope this helps!

Thanks!








AND

출처: http://whiteblank.tistory.com/tag/dll


C#에서 DLL을 Access하기 위해 P/Invoke라는것을 이용한다.

1. DLL을 만든다. / C++
   -> DLL 이름을 Iocp.dll로 정의한다.

       int _declspec(dllexport) Iocp(int a)
      {

       }

2. DLL holder class를 만든다. 그리고 함수를 선언한다. / C#
   ->
       class Holer
       {
             [DllImport!("Iocp.dll")]
             public export static int Iocp(int);
       }

3. Call 한다.
   ->
        Holder.Iocp(10);
       
        이렇게 기본적으로 하는것인데.. Callback함수나 Array는 어떻게 전달 될까라는 점이다.

        Callback 함수 전달법을 보면은
        (1) Callback 함수 원형을 정의한다. (in DLL)

             // __stdcall을 안하면 뻑난다. 대부분 CALLBACK이나 WINAPI로 재정의 되어있다.
             typedef BOOL __stdcall Callback(int a, int b);
             
        (2) Callback 함수를 등록하는 루틴을 만든다. (in DLL)
             
             Callback *_cb;
           
             void RegCallback(PVOID *pcb)
             {
                  _cb = pcb;
             }

        (3) Callback 함수를 호출하는 루틴을 만든다. (in DLL)
             if(_cb != NULL)
             {
                  (_cb)(10);
             }

        (4) DLL holder class를 만든다. 그리고 함수를 선언한다. (in C#)
         
             class Holder
             {
                   [DllImport("Iocp.dll")]
                   public export static int RegCallback(Callback cb);
             }

         (5) 실제 수행할 Callback 함수 Body를 만든다. (in C#)
           
             unit CBIopc(int a, int b)
             {
                     .....
              }

         (6) Call 한다. (in C#)
           
             Holder.RegCallback(new Callback(CBIocp));

      Array는 그냥 array[]를 넘기면 된다.
      참고로 C++ 함수에 인자로 boo을 쓰면 C#에서 깨지는것 처럼 보인다.
             
            

AND

될수 있으면 void * 형은 쓰시지 마시고 ^^:

일단은 IntPtr로 하시면 됩니다 ^^;

라고합니다~~

AND

출처: http://chovo.net/tag/c%23


레퍼런스 타입의 변수는 파라미터로 넘겨진 레퍼런스를 통해 직접 해당 오브젝트의 내용을 변경할 수 있다. 하지만, 밸류 타입은 파라미터가 함수 안의 변수로 선언되고 값이 복사되어 들어가기 때문에 함수 내부에서 값을 변경하면 함수 안의 파라미터 변수의 값만 변하고, 함수 호출이 끝나면 사라져 버리기 때문에 원래 변수의 값은 그대로 남게 된다. 즉, ref와 out은 파라미터로 넘겨진 밸류 탑입의 값을 변경할 때 사용한다.
이것은 어찌보면 C/C++ 에서 함수에 파라미터로 넘겨줄때 해당 변수의 주소값을 넘겨주어서 변수의 값을 변경하는 것과 비슷한 방식이라 할 수 있다. C#에서도 포인터가 있지만 포인터에 대해서는 나중에 살펴보도록하고, 우선 refout에 대해 알아보도록 하자.

아래 소스에서는 Main에서 Point 클래스를 이용하여 myPoint 라는 이름의 객체를 생성하고, 이 인스턴스의 맵버함수인 GetPoint를 호출하여 x와 y의 값을 얻어오는 코드이다.

less..

class Program
{
        static void Main(string[] args)
        {
            Point myPoint = new Point(10, 15);
            int x;
            int y;

            myPoint.GetPoint(ref x, ref y);

            Console.WriteLine("myPoint({0}, {1})", x, y);
        }
}

class Point
{
        public Point(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
        public void GetPoint(ref int x, ref int y)
        {
            x = this.x;
            y = this.y;
        }
        int x;
        int y;
}

less..


우선 이 코드를 컴파일하면 컴파일러는 할당되지 않은 지역 변수를 사용했다는 메시지와 함께 에러를 발생한다. 이런 상황에서 두 가지 방법을 사용할 수 있는데, 첫 번째는 변수를 선언할 때 초기화를 하는 것이다. 아래는 변경된 예제이다.

less..

class Program
{
        static void Main(string[] args)
        {
            Point myPoint = new Point(10, 15);
            int x=0;
            int y=0;


            myPoint.GetPoint(ref x, ref y);

            Console.WriteLine("myPoint({0}, {1})", x, y);
        }
}

class Point
{
        public Point(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
        public void GetPoint(ref int x, ref int y)
        {
            x = this.x;
            y = this.y;
        }
        int x;
        int y;
}

less..

이와같이 파라미터로 넘겨지는 변수 x, y에 대해서 변수 선언시 초기화를 해주면 코드는 정상정으로 컴파일 되며 동작 역시 잘 돌아간다. 하지만, 0으로 초기화된 다음에 GetPoint() 함수에서 값이 다시 쓰여진다. 이것은 무언가 조금 지저분하다고나할까, 의미없는 동작을 한다고나할까...아무튼 뭔가 마음에 들지 않는다.
C#에서는 ref 파라미터 대신에 out 파라미터를 사용해서 GetPoint() 함수의 정의를 바꿀 수 있는 옵션을 제공한다. 아래 코드는 ref 대신 out을 사용한 예제이다.

less..

class Program
{
        static void Main(string[] args)
        {
            Point myPoint = new Point(10, 15);
            int x;
            int y;


            myPoint.GetPoint(out x, out y);

            Console.WriteLine("myPoint({0}, {1})", x, y);
        }
}

class Point
{
        public Point(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
        public void GetPoint(out int x, out int y)
        {
            x = this.x;
            y = this.y;
        }
        int x;
        int y;
}

less..

out 파라미터는 초기화되지 않은 변수를 넘겨서 사용할 수 있다는 점을 빼면 ref 파라미터와 똑같다. 따라서, ref 보다는 out을 이용해서 함수를 호출하는 것이 좋다. .Net 언어라는 관점에서 보면, ref와 out 파라미터는 아무런 차이가 없다. C# 프로그램에서 out 파라미터로 함수를 호출하더라도, 다른 언어에서 보기에는 ref 파라미터를 이용한 것과 같다.

AND

출처: http://blog.naver.com/process3?Redirect=Log&logNo=20030533885

요즈음 시간 나는대로 C#을 조금식 삽질하고 있습니다. 솔직히 웹2.0시대에 Visual C++로 삽질(개발)하는것은 생각만해도 ㅠ,.ㅜ

 오늘은 기존에 Visual C++ 만든 DLL을 C#에서 사용할 일이 많을것 같아서 자료를 인터넷에서 찾아 보았았습니다. 그 내용을 간단하게 정리해 보겠습니다.


using System;
using System.Runtime.InteropServices;

namespace CUnit
{
 /// <summary>Native methods</summary>
    public class NativeMethods
    {
        /// <summary>Windows Message</summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct Message
        {
            public IntPtr hWnd;
            public uint msg;
            public IntPtr wParam;
            public IntPtr lParam;
            public uint time;
            public System.Drawing.Point p;
        }

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("User32.dll", CharSet=CharSet.Auto)]
        public static extern bool PeekMessage( out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags );
    }
}


코드는 위와 같은 형식을 따라 주면 됩니다.  다만 조심해야 하는 것이 파라미터의 typecasting이죠


예를 들어 C++에서 HWND -> IntPtr(C#) 로 바꾸어 주는것과 같은것입니다.


실제로 작동하는 것을 보고 싶다면 첨부된 파일을 실행해 보시면됩니다.


출처 : 최근의 삽질(경험)과 http://www.c-unit.com으로부터


AND

Gosu.Net 아티클 퍼옵@


Low-Level 키보드 입력 후킹의 이해를 돕기 위한 아티클입니다. unsafe 키워드를 사용해야 할 일이 그렇게 많지는 않겠지만, WinAPI를 다뤄야 한다면 피해갈 수 없는 부분이므로, 아직 C#에 그렇게까지 익숙하지는 않지만 한번 소개해 보겠습니다 : )



우리는 포인터가 필요하다


Kenial은 처음으로 c/c++을 공부하는 사람이 가장 이해하기 어려운 부분이 포인터가 아닐까. 라고 생각한다. 시스템에 접근할 수 있는 가장 저수준의 도구이면서, '기계의 입장에서' 생각하지 않으면 도저히 이해할 수 없는 부분이 아닐까.


사실 포인터 없이도 좋은 프로그램을 만들 수는 있다. 하지만 우리는 아직도 WinAPI를 벗어날 수 없고, WinAPI와 상호작용할 수 있는 코드를 만들고 싶다면, 포인터는 필요하다. 물론 포인터 없이도 가능한 일이기는 하지만, 그만큼 코드가 늘어난다거나 지저분한 코드를 얻게 되는 등의 댓가를 치뤄야 하므로...



unsafe 키워드


msdn의 c# programmer's reference를 참고하면, unsafe를 이렇게 설명하고 있다 :


    The unsafe keyword denotes an unsafe context, which is required for any operation involving pointers.


말 그대로, unsafe한 내용을 표시할 때 사용되는 키워드이다. 일단 간단한 다음 코드를 보자 :


int a;

int *b = &a;    


a를 참조하는 포인터 변수 b를 선언하는 코드이다. c#에서 &과 *이 횡행하는 코드를 봐서 당황스러울 수는 있겠으나, 막상 컴파일을 해 보면 문법 에러는 발생하지 않는다. 다음과 같은 에러가 발생할 것이다 : 포인터는 안전하지 않은 컨텍스트 안에서만 사용할 수 있습니다.


여기서 말하는 '안전하지 않은 컨텍스트'를 표시하는 기능을 가진 키워드가 바로 unsafe인 것이다. 다음과 같이 코드를 고치고 다시 컴파일 해 보도록 하자 :


unsafe {

    int a;

    int *b = &a;    

}


잘 컴파일되는가? 천만의 말씀이다. 이번에는 이런 에러가 발생할 것이다 : 안전하지 않은 코드는 /unsafe를 사용하여 컴파일하는 경우에만 나타날 수 있습니다.


기본적으로 마이크로소프트에서는 '안전하지 않은 코드'를 사용하는 것을 무척이나 싫어하는게 아닐까 하는 생각이 든다. (물론 당연한 생각이긴 하지만..) unsafe 키워드를 사용한 프로그램은 컴파일러에서 /unsafe 옵션을 주어야 하며, 이는 프로젝트 속성을 바꿔주면 된다.


'안전하지 않은 코드 블록 허용' 항목을 true로 세팅한다.


이와 같이 설정을 바꾸면 코드가 제대로 컴파일되는 것을 확인할 수 있을 것이다.



unsafe 선언의 다른 형태


짐작했겠지만, unsafe는 위와 같이 코드 블록에만 사용할 수 있는 것은 아니다.


unsafe private void ThisIsUnsafeFunction( void* pvVoid )

{

    ...

}

unsafe class UnsafeClass

{

        //...

}

unsafe delegate void* UnsafeEventHandler ( int* i );

unsafe event UnsafeEventHandler UnsafeEvent;


보시다시피, 함수나 클래스, 딜리게이트, 이벤트까지 사용 가능하다. 딜리게이트의 경우, 이벤트 핸들러의 파라메터나 리턴 값이 포인터일 경우에 unsafe 키워드를 사용해야 한다.


event의 경우 unsafe를 선언할 수는 있지만, 어떤 경우에 사용해야 하는지는 Kenial도 아직 모른다. 혹시 아는 분은 부디 Kenial에게 깨우침을 주시길...



정리


포인터를 사용하기 위한 키워드인 unsafe에 대하여 간단히 알아보았습니다. 하지만 예전 c/c++ 처럼 포인터를 사용해서 객체를 전달하는 것 같은 일을 간단하게 수행할 수는 없고, 여러가지 제약이 있습니다. 물론 이것도 피해가는 방법이 있습니다만, 일단은 천천히 소개하기로 하죠.

AND

출처: http://blog.naver.com/kieuns?Redirect=Log&logNo=80019436298

C#으로 공부겸 해서 겸사겸사  뭔가 ( 흠 -_-; ) 만들어보고

있는 중에 씨#에서 Win32Api를 쓸 일이 생겼다.

이번에도 구글에서 검색, 대략 "어떻게 씨#에서 Win32Api를 쓰지?" 라고 쳤다.

그리고 결과가 주루룩...


씨에서는 함수 선언부에 __declspec(import,export) 따위를 붙여서 내보내거나

얻어와야 했던 반면 씨#에서는 세련된게 함수 프로퍼티로 가져올 함수를

명세할 수 있었다.


씨에서는 LoadLibrary() -> GetProcAddress() 해야했었는데,

( 나는 일반함수를 가져다 쓸때도 lib을 붙여 컴파일하는걸 별로 안좋아한다.

   DirectX 함수들도 GetProcAddress()로 가져다 쓰는걸 좋아한다. )

인데 씨#에서는


[DllImport( "mydll.dll", CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall )]
public static extern int GetLastErrorCode();


만으로 한곳에서 모든 명세가 완료되어 바로 사용가능했다. (간편하다..고 느꼈다.)

나는 이 코드가 멋지다고  생각한다. 작업하다보면 자바와 씨#을 자꾸 비교하게

되는데 자바도 씨#처럼 가상머신을 벗어나서 플랫폼에 종속되는 부분을

건드릴 수 있게 넣어줬더라면 지금 짜고 있던걸 분명히 자바로 만들려고 했을거다.

한가지 더 생각해보는데, 다른 os 였으면 자바를 썼을까?

아마 리눅스에서 쓸려고 생각했다면 파이썬으로 짜고 있었을것 같다.

리눅스 환경에서 자바에 한글을 쓰려고 하려고 삽질했던것 생각하면... 부르부르부르..


씨# 책에서 프로퍼티는 자세히 안읽어봤었는데, 아무래도 함수마다 속성이 각각

붙는다는 개념은 생소하기도하고 이상하기도 해서, 저 프로퍼티가 씨#

프로그래밍의 주요 키워드 일지도 모른다는 생각이 들어서 꼼꼼히 정독해야

할 것 같다.


또 한참 진행하다가 재밌는데서 막혔다. 씨로 dll을 한개 만들고 씨#에서 그걸 사용 중인데,

뭐하고 있는지 알지도 모르겠다. 지금 난 후킹기능을 씨#에서 쓰려고 한다,

테스트를 위해서 dll 쪽에 함수 포인터를 선언했고 씨#의 함수를 그 변수에 할당해

보려고 했다. 즉, 씨#의 함수주소를 dll 쪽으로 넘겨서 호출해보려는 것였다.

잠시고민.. 하다가 delegate 키워드를 함 써보기로 했다.

내가 본바로는 이녀석이 씨#에서 함수포인터를 선언할때 쓰는 넘이다.

public delegate void HookProc( Int32 wParam, IntPtr lParam );


위 함수는 씨로


typedef void __stdcall SendWindowInfo( WPARAM, LPARAM );


라고 디파인 되어 있다.

이렇게 프로토타입을 선언하고, 씨 함수에서 함수 포인터를 전달하는 부분에서


StartHooking( new HookProc(ShellProc) );


라고 실행해봤다. StartingHooking()mydll.dll 에서 가져온 함수다.

전통적인 씨 개발자로서는 이미 인스턴스화 되어 있는 함수를

다시 new 로 할당해서 쓰는게 어색하기만 하다. 하지만..

실행은 한방에 되었다~! 오호라.. 이렇게도 쓰는군. (간단하고 멋지걸..)


언어 구현에 대한 고민이 간단히 해결되니 뭔가 굉장한 횡재를 한 기분이었다.

( 예전에 씨++언어 초보시절에서 이랬을까? )


가만.. 히 생각해보니 이런 방식의 코드를 어디선가 본 듯한 생각이 들었다.

그래서 msdn을 다시 키워드 검색해보니,

이런 비스므리한 코드가 비베에서도 쓰였다..기 보다 비베에서 가져온 방식이었다.

흠.. 뭔가 설명하기 힘든 애매한 기분.

하지만 비베를 다시 보게 됐다. 기능이 좀 막강한걸..라고 흘.


다음엔 윈도우메시지를 받을 방법이 없을까? 같은 고민이 생겼다.

자꾸 씨#으로 씨++ 프로그래밍을 짜려고 한다. 영어를 한국말 하듯이 

해버리려고 하는 것 같은 느낌이다.


각 언어는 그 특성에 맞게 느낌을 살려줘야한다.고 생각한다. 어셈블리어로

CRT 함수를 가져다 쓰면서 프로그램을 프로시져로 잘게 쪼개면서 만드는건

어셈블리어답지 못하다. 레지스터와 스택을 최대한 돌려 사용하면서 코드

사이를 점핑해 다녀야 어셈블리어 답다.


씨#도 이런 특징이 있을 것 같다. 아직 모르겠지만 말야..

애시당초 씨++로 만들어야 했나? 하는 생각도 드는데..


-_-; 일단 해보지 뭐.


AND

 

C#에서 Win32 API 사용하기

 

개요

Win32 API를 불러올 때, 함수의 명칭, 인자, 리턴 값을 가지고 불러오게 되어 있다. 하지만, C#에서 타입들이 모두 객체(Object)의 형식이며, 일반적인 C 의 데이터 형과 상이한 모양을 가진다. 이러한 문제들을 해결할 수 있는 것이 PInvoke 기능이다.

 

PInvoke( Platform Invocation Service)는 관리화 코드에서 비관리화 코드를 호출할 방법을 제공한다. 일반적인 용도는 Win32 API의 호출을 위해 사용한다.

 

namespace PinvokeExample

{

using System;

 

             using System.Runtime.InteropServices; // 반드시 입력해야 한다.

 

             public class Win32

             {

                           [DllImport(“user32.dll”)]

                           public static extern int FindWindow(string a, string b);

                          

             }

}

 

위 예제는 FindWindow라는 user32.dll C함수를 사용하는 모습을 보여주고 있다. 실제 FindWindow의 선언은 다음과 같다.

 

             HWND FindWindow(LPCSTR swClassName, LPCSTR swTitle);

 

HWND는 윈도우 핸들을 표현하는 32비트 정수 이므로, int형으로 치환되고 LPCSTR 형은 NULL로 끝나는 문자열을 표현한다. 이때 PInvoke string을 자동으로 LPCSTR로 치환해 주는 역할을 하게 된다.

이 문서에서는 이처럼 Win32 API 함수의 여러 유형들을 어떻게 C#에서 사용 할 것인지에 대하여 알아보자.

 

WIN32 데이터형의 치환

Win32 API에서 일반적으로 사용하고 있는 데이터형은 모두 C#의 데이터 형으로 치환될 수 있다.

 

Win32 API TYPE

C#

BOOL, BOOLEAN

bool

BYTE

byte

CALLBACK

delegate

COLORREF

int

DWORD

int

DWORD_PTR

long

DWORD32

uint

DWORD64

ulong

FLOAT

float

HACCEL

int

HANDLE

int

HBITMAP

int

HBRUSH

int

HCONV

int

(모든 HANDLE 타입) Hxxxx

int

LPARAM

long

LPCSTR

[in] string [out] StringBuilder

LPBOOL

ref bool

이외 LP*

ref 형식

UINT

uint

Uxxxx

unsigned 타입들..

WORD

Short

WPARAM

Uint

 

 

Structure 의 전달

예를 들어 POINT 형의 경우,

typedef struct t_Point {

             int x;

             int y;

} POINT;

 

이것은 기본적으로 다음과 같이 선언될 수 있다.

[순차적]

[StructLayout(LayoutKind.Sequential)]
public struct Point {
      public int x;
      public int y;
}

 

[명시적]

[StructLayout(LayoutKind.Explicit)]
public struct Point {
      [FieldOffset(0)] public int x;
      [FieldOffset(4)] public int y;
}

 

일차적으로 할당되는 메모리 레이아웃이 동일하다면, C#에서 바로 받아 들이 수 있다.

 

// BOOL SetWindowPos(POINT pos); 이런 함수가 있다고 가정하면… ^^

 

[DllImport (“user32.dll”)]

public static extern bool SetWindowPos(Point pos);

 

사용할 함수 이름 바꾸기

여기서 함수의 이름을 바꿔서 사용하고 싶다면 다음과 같이 변경하면 된다.

 

// BOOL SetWindowPos(POINT pos);

 

[DllImport (“user32.dll”, EntryPoint = “SetWindowPos”)]

public static extern bool ShowAt(Point pos);

레퍼런스형 전달하기

 

LPPOINT형은 POINT의 포인터 형이므로 ref Point와 같이 사용 할 수 있다. 실제 사용하는 형식은 다음과 같다.

C 언어의 포인터의 경우 레퍼런스로 사용하려고 하면, ref 키워드를 사용하는 방법이 있다.

// BOOL SetWindowPos(HWND hWnd, LPRECT lpRect);

[DllImport(“user32.dll”)]

public static extern bool SetWindowPos(int hWnd, ref Rect lpRect);

 

Out형 함수 인자 사용하기

MSDN 같은 곳에서 함수의 선언을 살펴보면 다음과 같은 형식의 함수를 볼 수 있을 것이다. 이러한 형식은 레퍼런스 형으로 결과를 함수의 인자에 보내겠다는 말이다. 이러한 형식은 Win32 API에서 많이 쓰이고 있고, 포인터를 사용하므로, 많은 주의를 기울여야 한다.

 

BOOL GetWindowRect(
  HWND hWnd,      // handle to window
  LPRECT lpRect   // window coordinates
);

Parameters

hWnd

[in] Handle to the window.

lpRect

[out] Pointer to a RECT structure that receives the screen coordinates of the upper-left and lower-right corners of the window.

 

여기서 LPRECT는 앞 절에서 설명한 Structure의 전달을 참고하여 치환 될 수 있다.

여기서 lpRect RECT의 포인터이며, GetWindowRect 함수 내에서 이 포인터에 직접 값을 쓰게 되어 있다. 즉 이 포인터는 값을 기록하기 위한 인자이지, 값을 전달하기 위한 인자는 아닌 것이다. 이것은 또 다른 C# 레퍼런스 연산자인 out 키워드를 사용하여 쉽게 해결 할 수 있다.

public static extern bool GetwindowRect(int hWnd, out Rect lpRect);

 

실제 사용하는 모습은 다음과 같다.

public static extern bool GetWindowRect(int hWnd, out Rect lpRect);

public static void UseFunction() {

        Rect _rect; // 값을 대입하지 않아도 된다.

        Win32.GetWindowRect(hwnd, out _rect);

}

 

참고로 ref 키워드는 입력과 출력 둘 다 사용 할 수 있다. 그러나 ref를 사용하는 변수가 값이 설정되어 있다는 가정을 하고 있으므로, 이전에 반드시 어떠한 값을 입력해야 한다.

실제 사용 예는 다음과 같다.

public static extern bool GetWindowRect(int hWnd, ref Rect lpRect);

public static void UseFunction() {

        Rect _rect = new Rect(); // 꼭 값을 대입해야 한다.

       

        _rect.top = 20; _rect.left = 30;

        _rect.bottom = 50; _rect.right = 60;

 

        Win32.GetWindowRect(hwnd, ref _rect);

}

 

여기서 잠깐

대중없이 Rect라는 구조체가 나오는데 이는 API에서 RECT형을 C#으로 바꾸어 사용하는 structure이다. 앞의 예제들은 다음과 같은 선언을 하였다고 가정한다.

[StructLayout(LayoutKind.Explicit)]
public struct Point {
      [FieldOffset(0)] public int top;
[FieldOffset(4)] public int left;
[FieldOffset(8)] public int bottom;
[FieldOffset(12)] public int right;

}

 

 

CALLBACK 함수의 선언

C 언어에서 콜백 함수는 함수 포인터로 존재하게 된다. 이것은 함수 인스턴스의 포인터로, 함수 자체를 전달하게 되는 방식이다. 대표적으로 사용되는 부분은 EnumWindows 함수이다.

// BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam)

이 함수는 현재 열려 있는 모든 윈도우 핸들을 열거하기 위한 함수로 실제 처리하는 부분은 함수 포인터, 즉 콜백함수인 lpEnumFunc에서 처리하게 되어 있다. WNDENUMPROC 타입의 선언은 다음과 같다.

// typedef BOOL (CALLBACK* WNDENUMPROC)(HWND, LPARAM);

public delegate bool Callback(int hWnd, long lParam);

이러한 콜백 함수 역할을 하는 C#의 프리미티브는 delegate이다. CALLBACK delegate로 지환된다.

 

결과적으로 다음과 같이 사용하게 된다.

namespace ada.appshare

{

             public delegate bool Callback(int hwnd, int lParam);

            

             internal class Win32

             {

               

                           internal static extern int EnumWindows(CallBack x, int y);

                           [DllImport("user32.dll")]

 

                public static bool EnumWindowsCallback(int hWnd, int lParam)
                {

                        System.Console.WriteLine(“” + hWnd);

                        return true;

                }

                

             }

 

        public static void Main(String []args)

        {

                Win32.Callback call
= new Win32.Callback(Win32.EnumWindowsCallback);

                Win32.EnumWindows(call, 0);

        }

}

 

 


 출처 : 데브피아~~~~

AND