기본 콘텐츠로 건너뛰기

C# Interop – C#과 C API의 상호운영


C#은 매우 강력한 각종 기능과 Class를 제공하지만, Windows Application의 작성을 위해서는 C로 작성된 Library를 사용하여야 하는 경우가 많이 발생된다. C#은 기본적으로 C의 Pointer를 지원하지 않고, Managed Code의 메모리 관리 체계는 근본적으로 C(Unmanaged Code)와 차이가 많기 때문에 C와의 호환을 위해서는 특수한 기법을 사용하여야 가능하다. C#은 이를 위하여 PInvoke와 Marshaling 등의 기능을 제공한다.
?
1. Using C DLL (PInvoke : Plaform Invocation Service)
C#은 C DLL의 Unmanaged Function을 호출할 수 있도록 플랫폼 호출 서비스 (PInvoke)를 제공한다. 이는 일반적인 C로 작성된 DLL이나 Win32 API를 호출하는 용도로 사용된다.
?
1.1 DLL 함수의 정의
PInvoke를 이용하여 Win32 API를 정의하는 방법은 “DllImport” 속성을 사용한다. DllImport 속성은 “DllImportAttribute” Class를 사용하는데, 이 Class의 생성자는 아래와 같다.
public DllImportAttribute(string dllName)
즉, 사용하고자 하는 DLL의 파일명을 파라미터로 가지므로, DllImport 속성은 DLL의 파일명을 정의하고, 실제 사용하고자 하는 함수는 속성 하단의 함수선언에 의하여 정의된다.
?
[user32.dll의 MessageBox 함수의 정의]
using System.Runtime.InteropServices

[DllImport(“user32”)]
public static extern int MessageBox(int hWnd, String pText, String pCaption, int uType);
위와 같이 선언하면 C# Code내에서 “MessageBox”라는 이름으로 함수를 호출할 수 있다. 함수는 반드시 “static extern”으로 선언하여 사용하도록 한다.
?
1.2 DllImport 옵션
?
위에서 사용된 DllImport Attribute는 몇가지 옵션필드를 가질 수 있다. 이 필드 중 주요한 항목은 아래와 같다. 자세한 내용은 DllImportAttribute Class를 참조한다.
?
필드
설명
Calling Convention
DLL 내의 Export 함수에 대한 Calling Convention을 지정할 수 있다.
Cdecl, Winapi, StdCall 등을 포함하는 CallingConvention Enumerator를 지원하며, 기본값은 StdCall 이다.
CharSet
문자열에 사용할 Character Set을 설정한다.
None(자동), Unicode 값을 가질 수 있다.
Entry Point
DLL 내의 함수가 호출되는 이름을 나타낸다.
이를 이용하면 함수진입점을 지정하여, 선언시 다른 이름으로 별칭을 이용할 수도 있다.
?
Calling Convention
DLL은 VC++에서 제작시 Calling Convention을 지정할 수 있는데, 이 Calling Convention을 제작시 정의된 것과 일치시켜야 함수가 정상적으로 호출된다. Calling Convention 옵션은 DLL 내의 Export 함수에 대한 Calling Convention을 지정하는 옵션이며, 이는 Cdecl, Winapi, StdCall 등을 포함하는 CallingConvention Enumerator를 지원하며, 기본값은 StdCall 이다. 대부분의 DLL 함수는 StdCall 방식으로 제작되므로, 대부분 생략가능하다.
// Cdecl 방식의 함수 선언
[DllImport("msvcrt.dll", CharSet=CharSet.Unicode, CallingConvention=CallingConvention.Cdecl)]
public static extern int printf(String format, int i, double d); 

// StdCall 방식의 함수 선언
[DllImport("msvcrt.dll", CharSet=CharSet.Unicode, CallingConvention=CallingConvention.StdCall)]
public static extern int printf(String format, int i, String s);
CharSet
DLL 함수에서 사용되는 문자열에 사용할 Character Set을 설정한다. 이 역시 DLL 제작시 사용된 Character Set에 일치시켜야 한다. 이 속성은 CharSet Enumerator를 사용하며 None(자동), Unicode, Ansi 등의 값을 가질 수 있다.
// Unicode를 사용하도록 설정
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

EntryPoint
DLL 내의 함수가 호출되는 이름을 나타낸다. 정의한 함수명과 실제 DLL의 함수명이 일치하면 생략가능하지만, 만약 다른 이름으로 함수를 정의하고 싶다면, 이 옵션을 사용한다.
아래는 DllImport Attribute를 사용하여 함수에 별칭을 부여한 예이다.
[DllImport(“user32”, CharSet = CharSet.UniCode, EntryPoint = “MessageBoxW”)]
public static extern int MsgBox(int hWnd, String pText, String pCaption, int uType);

1.3 Data Type의 변환
DLL함수의 정의 시, 가장 많이 접하게 되는 문제는 C로 표현된 각 데이터 타입을 C#에서 어떻게 정의할것인가에 대한 문제이다. Unmanaged Code의 Data Type에 대한 C#(Managed Code)에서 정의는 아래의 표와 같이 1 대 1로 매핑이 되므로 아래 표를 참조하여 그대로 정의하면 된다.
?
C (Unmanaged Code)
C# (Managed Code)
HANDLE, void* 또는 일반 pointer
IntPtr
BYTE, unsigned char
Byte
short
Short
WORD, unsigned short
Ushort
int
int
UINT, unsigned int
uint
long
int
BOOL, long
int
DWORD, unsigned long
uint
char
char
LPSTR, char*
string 또는 StringBuilder
LPCSTR, const char*
string 또는 StringBuilder
BSTR
string
float
float
double
double
HRESULT
int
VARIANT
object
?
String Type의 변환
Unmanaged Code를 사용하는 경우, char*는 상황에 따라 다르게 표현되며, String 또는 StringBuilder를 사용할 수 있다. 각 상황에 따른 String의 처리는 아래와 같다.
1. Call by value
String을 Value에 의하여 전달하는 경우는 string Type으로 사용한다. 즉, 단순히 String을 DLL함수로 전달하는 경우는 string으로 정의하면 된다.
C
int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
C#
[DllImport(“user32”)]
public static extern int MessageBox(int hWnd, String pText, String pCaption, int uType);
?
2. Call by Reference(Pointer)
Parameter를 Pointer로 In/Out으로 사용할 경우에는 StringBuilder를 사용한다. 즉, DLL함수로부터 String값을 전달받는 경우, StringBuilder를 사용한다.
C
UINT GetSystemDirectory(LPTSTR lpBuffer, UINT uSize);
C#
[DllImport( "Kernel32.dll)]
public static extern int GetSystemDirectory(StringBuilder sysDirBuffer, int size);
?
String Type 또는 Call By Reference 방식을 사용하는 경우, 주의를 기울여야 한다. 통상, DLL내부에서 생성한 메모리를 리턴받아서 .NET에서 사용하게 되는 경우, 시스템이 죽는 현상이 발생할 수 있다. 즉, 아래와 같이 char*를 DLL 내부에서 생성하고, .NET에서 메모리를 리턴받아 참조하는 경우, 프로그램이 비정상적으로 종료될 수 있다.
C
char* GetLastError();
C#
[DllImport("somedll.dll"]
public static extern string GetLastError();
string s = GetLastError();
?
이와 같은 현상이 발생되는 경우, DLL 함수 자체를 변경하거나, 함수를 사용하지 않는 쪽으로 검토하여야 한다. 위의 코드에서는 DLL을 Call By Reference로 수정하면 문제가 해결된다.
C
int GetLastError(char* sError);
C#
[DllImport("somedll.dll"]
public static extern int GetLastError(StringBuilder error);
StringBuilder sb = new StringBuilder();
GetLastError(sb);
?
2. Marshalling
앞에서 일반적인 데이터형을 사용하는 DLL 함수의 사용법을 배웠다. 하지만 C로 제작된 DLL의 대다수는 아래와 같이 struct 형의 파라미터를 사용하는 경우가 많다. 이러한 경우 어떻게 C#에서 함수를 정의하여야 할까?
Struct MyStruct
{
   int n;
   char* s;
}

void SomeFn(MyStruct st);
2.1 StructLayoutAttribute
StructLayout 속성은 StructLayoutAttribute Class를 사용한다. 이 StructLayout은 생성자에서 LayoutKind Type을 파라미터로 받는다. 즉, StructLayout 하단에 정의된 struct가 메모리상에 어떻게 저장되는지를 설정하여야 한다. LayoutKind형은 Sequential, Explicit, Auto의 값을 가지는데, 대부분의 경우 Sequential 한 Struct를 위하여 이 속성을 사용하므로 항상 “LayoutKind.Sequential”을 사용하면 된다.
[C]
struct MyStruct
{
   int n;
   char* s;
}

void SomeFn(MyStruct st);
[C#]
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct MyStruct
{
   int n;
   string s;
}

[DllImport("somedll.dll"]
public static extern void SomeFn(MyStruct st);

Pack 옵션
VC++에서 DLL을 제작할 때, Byte Align을 설정할 수 있다. 이는 구조체가 연속된 메모리에 저장될 때, 성능향상을 위하여 몇바이트를 기준으로 정렬되느냐에 대한 설정인다. 이는 DLL 사용시, DLL이 어떤 설정으로 되어 있는지를 알면 그에 따라 설정하면 된다. 만약 DLL이 시스템의 디폴트값을 사용하고 있다면, 생략하면 된다.
[StructLayout(LayoutKind.Sequential, Pack=8)]
public struct MyStruct
{
   int n;
   string s;
}
Struct 내에서의 문자열 배열
Struct 내에서 고정된 길이의 문자배열을 사용하기 위해서는 “MarshalAs” Attribute를 사용하여 Data의 Type과 길이를 지정하여야 한다.
[C]
Struct tpstart_t
{
   char usrname[18];
   char cltname[18];
   char dompwd[18];
   char usrpwd[18];
   int flags;
}
[C#]
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct tpstart_t
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=18)] public string usrname;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=18)] public string cltname;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=18)] public string dompwd;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=18)] public string usrpwd;
    public int flags;
}

2.2 Array Marshaling
함수에 배열을 Parameter로 사용하기 위해서 “In/Out” Attributes 또는 ref를 사용한다.
[C]
int TestArrayOfInts(int* pArray, int pSize);

int TestRefArrayOfInts(int** ppArray, int* pSize);

int TestMatrixOfInts(int pMatrix[][COL_DIM], int row);

int TestArrayOfStrings(char** ppStrArray, int size);

int TestArrayOfStructs(MYPOINT* pPointArray, int size);

int TestArrayOfStructs2 (MYPERSON* pPersonArray, int size);
[C#]
[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern int TestArrayOfInts([In, Out] int[] array, int size );

[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern int TestRefArrayOfInts( ref IntPtr array, ref int size );

[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern int TestMatrixOfInts([In, Out] int[,] pMatrix, int row );   

[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern int TestArrayOfStrings( [In, Out] String[] stringArray, int size );

[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern int TestArrayOfStructs([In, Out] MyPoint[] pointArray, int size ); 

[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern int TestArrayOfStructs2( [In, Out] MyPerson[] personArray, int size );

3. Using Pointer
C와 C#의 가장 큰 차이점 중 하나는 Pointer의 존재여부이다. Pointer는 C에서 워낙 광범위하게 사용되고 있으므로, C#에서 C 함수를 사용하기 위해서는 Pointer의 처리가 필수적이다.
.NET에서는 이러한 C언어와의 상호운영을 위하여 Marshal Class를 제공하는데, Marshal Class는 관리되지 않는 메모리를 할당하고, 관리되지 않는 메모리 블록을 복사하고, 관리되는 형식을 관리되지 않는 형식으로 변환하는 메서드의 컬렉션 및 관리되지 않는 코드와 상호 작용할 때 사용되는 기타 메서드의 컬렉션을 제공한다.
즉, Managed Code의 메모리와 Unmanaged Code의 메모리를 상호 변환 또는 관리하기 위한 일련의 Method를 제공하므로, DLL 함수내에서 만나게되는 각종 Pointer관련 파라미터의 처리에 활용한다.
?
? IntPtr & Marshal Class
.NET에서 C의 Pointer를 표현하기 위한 데이터타입이다. Pointer라는 것은 실제로 주소값이므로, 시스템에 따라 32bit 또는 64bit 정수형으로 표기되는데, 이러한 Pointer형을 지원하기 위한 데이터타입이다. C 함수는 그 특성상, 메모리를 할당하기 위한 alloc 계열의 함수와 할당된 메모리의 Pointer를 파라미터로 받는 함수가 다수 존재한다. 이러한 메모리 주소 위주의 함수들을 .NET에서 사용하는 경우, 거의 필수적으로 IntPtr이 사용되며, 이는 Marshal Class와 함께 주로 사용된다.
?
아래의 예는 국내 미들웨어인 TMAX를 C#에서 사용한 예인데, 이와 같이 C-DLL에서 메모리를 할당(관리되지 않는 메모리)하고 이 메모리에 특정 Struct를 복사하는 구조로 작성되는 경우, 일반적인 C#의 함수로는 구현이 불가능하다. 따라서 이와 같은 C의 Unmanaged Memory에 대한 각종 처리를 담당하는 역할을 수행한다.
// 접속을 위한 메모리 할당 : DLL 함수 호출 
int nAddr = TMaxLib.tpalloc("TPSTART", "", 0);

// C Style 메모리주소를 IntPtr로 변환
IntPtr pMem = new IntPtr(nAddr);

// TMax 접속을 위한 start_t struct 생성 : StructLayout으로 정의됨
TMaxLib.tpstart_t tpinfop;
tpinfop.cltname = ClientName + '\0';
tpinfop.usrname = UserName + '\0';
tpinfop.dompwd = "\0";
tpinfop.usrpwd = "\0";
tpinfop.flags = TMaxLib.TPU_DIP;

// 마샬링을 이용하여 start_t 구조체를 할당한 Buffer로 복사
Marshal.StructureToPtr(tpinfop, pMem, true);

// T-Max 접속
if(TMaxLib.tpstart(pMem.ToInt32()) == -1)
{
   m_sLastError = "연결에 실패하였습니다.";
   return false;
}

// Buffer 메모리 해제
TMaxLib.tpfree(nAddr);

Marshal Class Methods
Marshal Class는 Pointer 관련 처리를 위하여 매우 자주 사용하게 되는데, 자주 사용하게 되는 함수는 아래와 같다.
AllocHGlobal
Unmanaged Memory 영역에 특정 바이트 만큼의 메모리를 할당한다.
함수에 할당된 메모리주소를 파라미터로 넘겨야 하는 경우 사용할 수 있다.
FreeHGlobal
AllocHGlobal로 할당된 메모리를 해제한다.
AllocHGlobal은 Unmanaged Memory를 할당하므로 C와 마찬가지로 직접 메모리를 Delete해야 한다.
Copy
Managed Type에서 Unmanaged type의 Pointer로 데이터를 복사한다.
ReadByte
Unmanaged Memory로부터 데이터를 바이트 단위로 Read한다.
WriteByte
Unmanaged Memory에 데이터를 바이트 단위로 Write한다.
SizeOf
Unmanaged Type의 크기를 리턴한다.
PtrToStructure
Unmanaged Type의 Memory Pointer로부터 Managed Object에 복사한다.
StructureToPtr
Managed Type의 오브젝트를 Unmanaged Memory의 Pointer에 복사한다.
?
Function Pointer
C는 Callback Function 등과 같이 함수 자체의 Pointer를 파라미터로 많이 사용한다. 이 Function Pointer를 C#에서 정의하기 위해서는 Delegate를 선언하여 처리한다. 이에 대한 Marshaling 방법은 아래와 같다.
[C]
void TestCallBack(FPTR pf, int value);
void TestCallBack2(FPTR2 pf2, char* value);
[C#]
public delegate bool FPtr( int value );
public delegate bool FPtr2( String value );

[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern void TestCallBack( FPtr cb, int value );   

[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern void TestCallBack2( FPtr2 cb2, String value ); 
...
public class App
{
   public static void Main()
   {
      FPtr cb = new FPtr( App.DoSomething );
      LibWrap.TestCallBack( cb, 99 );
      FPtr2 cb2 = new FPtr2( App.DoSomething2 );
      LibWrap.TestCallBack2( cb2, "abc" );
   }

   public static bool DoSomething( int value )
   {
      Console.WriteLine( "\nCallback called with param: {0}", value );
}
   public static bool DoSomething2( String value )
   {
      Console.WriteLine( "\nCallback called with param: {0}", value );
   }
}
Void Pointer
void pointer는 기본적으로 UnmanagedType.AsAny Type의 object 형식으로 변환하며, void*의 type이 미리 예상될 때, 임의의 Data Type으로 Overload 하여 사용할 수 있다.
[C]
void SetData(DataType typ, void* object)
[C#]
[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern void SetData( DataType t, [ MarshalAs( UnmanagedType.AsAny )] Object o );

[ DllImport( "..\\LIB\\PinvokeLib.dll", EntryPoint="SetData" )]
public static extern void SetData2( DataType t, ref double i );
[ DllImport( "..\\LIB\\PinvokeLib.dll", EntryPoint="SetData" )]
public static extern void SetData2( DataType t, String s );
4. Using MFC
본인은 Visual C++을 전문적으로 개발하다가 .NET의 발표와 함께 C#으로 개발하기 시작하였다. C#으로 개발을 하면서 “기존에 만들어두었던 MFC 기반의 많은 Class를 C#에서 그대로 활용할 수 있을까?” 라는 문제와 위에서 설명한 바와 같이 대부분의 C API를 C#에서 사용가능 하지만, 수많은 Pointer 위주의 함수를 일일이 PInvoke/Mashal을 이용하여 변환해서 사용하는 것은 상당한 귀찮은 일이라 “C로 만든 로직을 그대로 .NET에서 사용할 수 있는 방법은 없을까?” 라고 하는 문제에 항상 관심을 가져왔다.
사실 답은 간단한데, “C와 .NET 라이브러리 모두를 사용가능한 언어를 사용”하면 해결이 된다. 즉, C를 이용하여 기존의 C 라이브러리를 그대로 사용하고, .NET을 이용하여 .NET에서 사용가능한 Class를 만들면 되는 것이다.
이러한 능력은 현재 Visual C++을 사용하면 가능하다. 예전에는 Managed C++이라고 불리었고, 현재는 C++/CLI라고 불리는 기능을 사용하면 된다. 물론 Visual C++을 이미 사용할 수 있다면, 쉽게 가능하지만, VC++에 익숙하지 않더라도, 단지 C 라이브러리를 .NET에서 사용가능한 Class를 만드는데 목적을 둔다면 충분히 약간의 학습으로 가능하다.
?
4.1 C++/CLI
Visual C++은 Windows SDK, MFC와 .Net Framework를 동시에 사용하여 개발가능한 능력을 갖고 있다. C++/CLI(이전 Managed Extensions for C++) 불리우는 .Net Framework를 위한 언어규격을 사용하여 .NET Framework를 위한 코드를 개발할 수 있다.
이를 이용하여 .Net과 C API의 중간자로서, .Net에서 직접적으로 사용하기 힘든 각종 C Library를 .Net에서 직접사용할 수 있도록 하는 DLL을 만들 수 있다. 즉, 내부적으로는 C API/MFC를 사용하고 외부로의 기능노출을 .Net으로 개발하여 중간자 역할을 하는 DLL을 개발가능하다.
?
?특징
-????? 내부적으로 C API, MFC 등 Visual C++의 모든 기능을 사용할 수 있다.
-????? C++/CLI를 사용하여 .Net의 모든 Class를 사용할 수 있다.
-????? C++/CLI를 사용하여 .Net에서 참조만으로 사용가능한 Class를 만들 수 있다.
?
? API Proxy Class
.Net에서 MFC 기능을 사용하고 싶다거나, 복잡한 Pointer 구조 등을 사용하는 C API를 .Net에서 사용하고 싶은 경우, 기존의 Visual C#이나 Visual Basic에서는 사용이 불가하거나, 그 사용법의 복잡함으로 인하여 개발이 용이하지 않은 경우가 많다.
이러한 경우, Visual C++을 이용하여 C Library를 .Net과 호환시켜주는 Proxy Class를 만들어, .Net에서 사용할 수 있도록 할 수 있다.
image
이렇게 하는 경우 .Net에서 사용할 수 없는 C++ Class를 사용할 수 있게 하여 C++ 개발자와 C# 개발자의 협력이 가능하다.
?
?
4.2 C++/CLI 구문
?
?Class 선언
C++/CLI에서 관리되는 형식의 선언방법은 아래와 같다.
ref class Block {};                // reference class
value class Vector {};             // value class
interface class I {};        // interface class
ref class Shape abstract {};       // abstract class
ref class Shape2D sealed: Shape{}; // derived class

?개체 선언
C++/CLI에서 관리되는 형식의 개체선언은 아래와 같이 “^”를 이용하여 선언한다. “^”는 관리되는 개체형식의 Pointer를 의미한다.

public ref class Form1 : System::Windows::Forms::Form {
System::ComponentModel::Container^ components;
System::Windows::Forms::Button^ button1;
   System::Windows::Forms::DataGrid^ myDataGrid;
   System::Data::DataSet^ myDataSet;
};
?관리되는 Object 생성
C++/CLI에서 Object의 생성은 “gcnew”를 사용한다.
Button^ button1 = gcnew Button;        // managed heap
int * pi1 = new int;                   // native heap
Int32^ pi2 = gcnew Int32;              // managed heap
?CLR 배열
“array” keyword 사용
array^ myArr
array<int,3>^ myArr
?Property (속성)
“property” keyword 사용
public ref class Vector sealed { 
   double _x;

public:
   property double x 
   {
      double get()             { return _x; }
      void   set( double newx ){ _x = newx; }
   } 
};
4.3 Making C++/CLI DLL
간단한 DLL을 만들기 위하여 아래와 같은 기능을 가지는 Class를 DLL로 만들기로 한다.
?
-????? MFC의 CFtpConnection Class를 .NET에서 사용할 수 있는 CLR Class를 제작
-????? 동작의 시험을 위하여 Connect/Disconnect 기능만을 구현한다.
?
아래와 같이 새 프로젝트를 선택하고 “Visual C++” 카테고리의 “클래스 라이브러리”를 선택하여 프로젝트를 생성한다.
image
프로젝트 속성 페이지에서 “구성속성 ? 일반” 카테고리를 선택하여 “MFC사용” 항목에서 “공유 DLL에서 MFC사용”을 선택한다.
image

MFC를 사용하기 위하여 Stdafx.h에 아래와 같이 MFC Header 파일을 포함시킨다.
[Stdafx.h]
#include  
#include 
.Net에서 사용할 CLR Class를 작성한다. 실제 내부적으로 사용할 MFC의 CFTPConnection Class를 위한 변수를 멤버로 추가한다. Class는 Resource 반환을 위하여 IDisposable Interface를 구현한다. 또한 서버의 접속/접속해제를 위한 함수를 추가한다. 함수의 파라미터는 .NET에서 사용할 것이므로 CLR Type을 사용한다.
[FTPProxySample.h]
namespace FTPProxySample {

    public ref class FtpConnection : public IDisposable
    {
    public:
        FtpConnection();
        ~FtpConnection();

        void Open(String^ serverURL, int Port, String^ ClientName, String^ UserID, String^ Pwd);
        void Close();

    private:
        CInternetSession* m_pSession;
        CFtpConnection*  m_pFtpConn;
    };
}
실제 Connect와 Close의 구현을 추가한다. 각 함수는 실제 MFC Class의 오브젝트를 이용하여 처리를 하면된다. 이렇게 하는 경우, .NET에서 함수를 호출하면, 함수에서 MFC를 접근하여 각 처리를 수행하게 된다.
[FTPProxySample.cpp]
namespace FTPProxySample
{
    FtpConnection::FtpConnection()
    {
        m_pSession = NULL;
        m_pFtpConn = NULL;
    }

    FtpConnection::~FtpConnection()
    {
        Close();
        System::GC::SuppressFinalize(this);
    }

    void FtpConnection::Open(String^ ServerURL, int Port, String^ ClientName, String^ UserID, String^ Pwd)
    {
        CString serverURL(ServerURL);
        CString userID(UserID);
        CString password(Pwd);
        CString clientName(ClientName);

        m_pSession = new CInternetSession(clientName);

        // FTP 접속
        try
        {
            m_pFtpConn = m_pSession->GetFtpConnection(serverURL, userID, password, Port);
        }
        catch(CInternetException* pe)
        {
            // Exception 처리
        }
    }

    void FtpConnection::Close()
    {
        if(m_pSession != NULL)
        {
            m_pSession->Close();
            delete m_pSession;
            m_pSession = NULL;
            m_pFtpConn = NULL;
        }
    }
}
컴파일을 하면, .NET에서 참조가능한 FTPProxySample.dll이 만들어진다.
?
4.4 Using C++/CLI DLL
만들어진 DLL은 .NET용 DLL이므로, .NET 프로젝트에서 “참조추가”를 하면, 바로 DLL내의 Class를 사용할 수 있다.
image
아래와 같이 참조한 DLL의 Class의 객체를 생성하여 사용한다.
private void button1_Click(object sender, EventArgs e)
{
    using (var ftp = new FTPProxySample.FtpConnection())
    {
        try
        {
            ftp.Open("server-url", 21, "Test", "id", "password");
            MessageBox.Show("Success");
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }            
    }
}

댓글

이 블로그의 인기 게시물

80040154 오류로 인해 CLSID가 {xxxx-...}인 구성 요소의 COM 클래스 팩터리를 검색하지 못했습니다.

원문보기 .NET 으로 만든 응용프로그램에서 com 객체를 호출한 경우 Windows7 64bit 에서 제목과 같은 에러가 발생했다. Win32 COM 과 .NET 프로그램간의 호환성 때문에 생긴 문제였다. 원인은 .NET 실행시 JIT 컴파일러에 의해 최적화된 기계어로 변환되기 때문.. Win32 COM은 컴파일시.. Win32 COM에 맞춰 빌드 속성에서 하위버전으로 맞춰 컴파일을 다시하는 방법도 있지만 메인 프로젝트가 .NET이라면 참조되는 모든 프로젝트를 다 바꿔야할 노릇.. 또 다른 방법은 COM+를 이용하여 독립적으로 만드는 것이다. 분리시키는 방법은 아래 주소해서 확인할 수 있다. http://support.microsoft.com/kb/281335 나의 경우는 Win32 COM DLL을 64비트 .NET 프로그램에서 참조하니 COM 객체를 제대로 호출하지 못하였습니다. 그래서 .NET 프로그램의 Target Machine을 x86으로 설정하니 제대로 COM 객체를 호출하였습니다.

[Pyinstaller] 실행 파일 관리자 권한 획득하기

고객사에서 일부 사용자에게서 프로그램 오류가 발생한다며 아래와 같이 에러 캡처를 보내왔습니다. 프로그램에서 로그를 남기기 위해 로그 파일을 생성하는데 권한의 문제로 로그 파일을 생성하지 못해 프로그램 오류가 발생한 것 같습니다. 처음에는 Python 코드에서 관리자 권한을 요청하는 코드를 넣으려고 했는데, 실제로 Stackoverflow를 찾아보면 이런 내용이 나옵니다. 프로그램이 관리자 권한으로 실행되지 않았다면 관리자 권한으로 다시 프로그램을 실행시키는 코드입니다. import os import sys import win32com.shell.shell as shell ASADMIN = 'asadmin' if sys.argv[-1] != ASADMIN: script = os.path.abspath(sys.argv[0]) params = ' '.join([script] + sys.argv[1:] + [ASADMIN]) shell.ShellExecuteEx(lpVerb='runas', lpFile=sys.executable, lpParameters=params) sys.exit(0) 하지만 개인적으로 이런 방식은 마음에 들지 않았고 조금 더 찾아보니 Pyinstaller로 exe 파일을 만들 때 옵션을 설정하여 관리자 권한을 요청하도록 할 수 있다고 합니다. --uac-admin을 옵션에 추가하면 프로그램 실행 시 관리자 권한을 요청할 수 있습니다. pyinstaller.exe --uac-admin sample.py 하지만 안타깝게도 이 방식은 원하는 대로 동작하지 않았습니다. 마지막으로 manifest 파일을 이용하여 시도해보았습니다. spec 파일을 이용하여 pyinstaller로 빌드하면 <실행 파일 이름>.manifest 라는 파일이 생성됩니다. 파일에서 아랫부분을 찾아볼 수 있습니다. <security> <re

초간단 프로그램 락 걸기

프로그램에 락을 걸 일이 생겨났다. 하드웨어 락을 걸면 쉬울텐데 그 정도는 아니고 프로그램의 실행 날짜를 제한 해 달라고 한다. 그래서 파일(license.lic)을 가지고 락을 걸리고 결정을 했다. 요구 사항은 아래와 같다. 1. license.lic 파일이 없으면 프로그램을 실행 할수 없게 한다. 2. 지정한 날짜를 넘어서는 프로그램을 실행 할수 없게 한다. 3. 사용자가 시스템 날짜를 되돌렸을때 인식하여 프로그램을 실행 할수 없게 한다. 음.... 1.번 문제는 사용자가 프로그램을 실행하기 위해서 license.lic 파일을 받아야만 한다. license.lic 파일에는 최근 실행 날짜/종료날짜 이런식으로 적도록 한다.(물론 내용은 암호화 한다.) 최근 실행날짜는 프로그램이 실행때마다 업데이트 하도록 하고 시스템 날짜와 비교하여 시스템 날짜가 최근 실행 날짜보다 이전의 날짜면 시스템 날짜를 되돌렸다고 인식하도록 한다.(3.번 문제 해결) 시스템 날짜와 종료 날짜를 비교하여 시스템 날짜가 종료 날짜를 넘으면 프로그램을 실행 할수 없도록 한다.(2.번 문제 해결)