외부 함수
외부 함수 (External Functions)
이 문서에서는 F#으로 네이티브 코드의 함수를 호출하는 언어 차원의 지원 방식을 설명해요. .NET 외부의 C/C++ DLL 같은 네이티브 라이브러리를 F#에서 직접 호출해야 할 때 사용하는 기능이죠.
출처: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/functions/external-functions
본문
구문 (Syntax)
외부 함수는 extern 키워드와 DllImport 특성으로 선언해요.
[<DllImport( arguments )>]
extern declaration
설명 (Remarks)
위 구문에서 arguments는 System.Runtime.InteropServices.DllImportAttribute 특성에 넘겨주는 인자를 나타내요. 첫 번째 인자는 해당 함수가 들어 있는 DLL의 이름을 나타내는 문자열인데, .dll 확장자는 붙이지 않아요. 그 외에도 System.Runtime.InteropServices.DllImportAttribute 클래스의 공용 속성(예: 호출 규약 calling convention)에 해당하는 추가 인자를 넘길 수 있어요.
다음과 같은 내보낸 함수(exported function)를 가진 네이티브 C++ DLL이 있다고 가정해 볼게요.
#include <stdio.h>
extern "C" void __declspec(dllexport) HelloWorld()
{
printf("Hello world, invoked by F#!\n");
}
이 함수를 F#에서 호출하려면 다음과 같은 코드를 사용하면 돼요.
open System.Runtime.InteropServices
module InteropWithNative =
[<DllImport(@"C:\bin\nativedll", CallingConvention = CallingConvention.Cdecl)>]
extern void HelloWorld()
InteropWithNative.HelloWorld()
네이티브 코드와의 상호 운용(interoperability)을 플랫폼 호출(platform invoke)이라고 부르는데, 이는 CLR의 기능이에요. 자세한 내용은 Interoperating with Unmanaged Code를 참고하세요. 그 내용은 F#에도 그대로 적용돼요.
외부 함수에서 매개 변수 정의하기 (Defining Parameters in External Functions)
반환 값이나 매개 변수가 있는 외부 함수를 선언할 때는 C와 비슷한 구문을 사용해요. 두 가지 방식을 선택할 수 있는데, 하나는 관리되는 선언(managed declarations)이고요 — CLR이 네이티브 타입과 .NET 타입 사이를 자동으로 변환해 주는 방식이에요. 다른 하나는 관리되지 않는 선언(unmanaged declarations)인데, 상황에 따라 더 나은 성능을 제공할 수 있어요. 예를 들어 Windows 함수 GetBinaryTypeW는 다음 두 가지 방식으로 선언할 수 있어요.
// 관리되는 타입의 자동 마샬링 사용
[<DllImport("kernel32.dll",
CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Unicode,
ExactSpelling = true)>]
extern bool GetBinaryTypeW([<MarshalAs(UnmanagedType.LPWStr)>] string lpApplicationName, uint& lpBinaryType);
MarshalAs(UnmanagedType.LPWStr)는 함수가 호출될 때 CLR이 .NET 문자열과 Windows 네이티브 문자열 표현 사이를 자동으로 변환하도록 지시해요. uint&는 참조로 전달되는 uint, 즉 관리되는 포인터(managed pointer)를 선언하는 표기예요. 관리되는 포인터를 얻으려면 주소 연산자(address of &)를 사용해요.
반대로 데이터 타입의 마샬링을 직접 관리하고 싶다면, 관리되지 않는 타입(unmanaged types)만 사용해서 외부 함수를 선언할 수도 있어요.
// 관리되지 않는 타입 사용
[<DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, ExactSpelling = true)>]
extern int GetBinaryTypeW(nativeint lpApplicationName, uint* lpBinaryType);
.NET 문자열을 네이티브 형식으로 변환하고 싶다면 Marshal.StringToHGlobalUni를 사용해서 그 포인터(nativeint)를 받아 lpApplicationName에 넘겨줄 수 있어요.
정수에 대한 포인터를 얻으려면 && 연산자나 fixed 키워드를 사용하면 돼요.