출처: 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

출처: http://khsong33.egloos.com/341967/

C# 프로그램은 자바나 C++과 같이 객체 지향 개념의 프로그래밍이다.

 하나의 C# 소스 파일에는 using statement와 namespace의 구문을 작성하는 프롤로그 부분 / 프로그램의 몸체에 해당하는 클래스로 이루어진다.

프롤로그는 없어도 소스는 작동되나 클래스는 반드시 하나이상 있어야만 한다.
=========다음은 모든 프로그래밍의 기초인 Hello World의 출력 소스이다.


위의 소스를 보면 using문과 namespace문의 프롤로그 부분과 Hello라고 이름 붙여진 클래스와 프로그램의 시작점을 나타내는 Main()메소드로 구성되어 있는것을 알수 있다.

C++과는 다르게 C#에서는 메인 시작 메소드도 하나의 클래스로 구성되어 있다.
이 Main()메소드를 정의할때 주의해야 될 두가지 사항이 있다.
=====================Main() 메소드===============================
 1) Main() 메소드의 M은 반드시 대문자!!!
 2) Main() 메소드를 선언할 때는 항상 public static void로 선언!!!
================================================================


2)번과 같은 이유가 붙은 이유는 public으로 접근에 제한을 두지 말아야 되며 static으로 한번 이상이 접근을 허용해서는 안된다는 의미인듯 하다.

====================프롤로그 부분 : using statement / namespace===========
닷넷 프레임 워크 안엔 유용하게 사용되는 클래스가 많은데, 이런 클래스를 유틸리티 클래스라 한다.
이 유틸리티 클래스는 네임스페이스 단위로 그룹지어져 있다.

이중 가장 중요한것은 System 네임 스페이스이다. 이는 주로 운영체제 시스템과의 상호 작용을 관리하는 클래스로 이루어져 있고, C++의 using namespace std; 와 같이 표준 입출력(I/O)를 담당하고 있다.

위의 소스와 같이 System을 사용하지 않고 WriteLine을 사용시에는 (.)으로 구분해 사용할 수 있다.
  ex) System.Console.WriteLine("Hello World");




AND