logo MSJO.kr

C# .NET 8.0에서 LibraryImport 사용하기

2024-12-06
MsJ
 

닷넷 7.0 이전 버전에서는 대부분 외부 DLL 파일을 Import 하거나 P/Invoke1를 활용하여 Native DLL(Unmanaged DLL)에 있는 함수를 호출하기 위해 DllImport를 사용해 왔다. 8.0버전부터는 LibraryImport의 사용을 권장한다.

LibraryImport가 새로 생겨난 이유는 DllImport가 마샬링을 런타임에서 수행해서 IL 코드를 emit 한다고 하는데 NativeAOT 등 동적으로 IL 코드를 생성할 수 없는 환경에서 쓸 수 없으므로 LibraryImport의 소스 생성기 기능을 이용해 컴파일 시점에서 마샬링 코드를 삽입한다.2

기존에 작성했던 DllImport에서 LibraryImport 스타일로 변경할 때 몇 가지 변경 사항이 있는데 이 부분을 간단한 예제를 통하여 정리하였다. ~.csproj를 열어 <PropertyGroup>사이에<AllowUnsafeBlocks>true</AllowUnsafeBlocks>를 추가한다.

WPF에서 윈도우 이동(DragMove)
[LibraryImport("user32.DLL", EntryPoint = "ReleaseCapture", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool ReleaseCapture();

[LibraryImport("user32.DLL", EntryPoint = "SendMessageW", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
[return: MarshalAs(UnmanagedType.SysInt)]
private static partial IntPtr SendMessageW(IntPtr hWnd, uint wMsg, IntPtr wParam, IntPtr lParam);

[LibraryImport("USER32.DLL", EntryPoint = "FindWindowW", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
[return: MarshalAs(UnmanagedType.SysInt)]
private static partial IntPtr FindWindowW(string? lpClassName, string? lpWindowName);

public static void DragMoveWindow(string windowTitle)
{
    // MainWindow? mainWindow = Application.Current.MainWindow as MainWindow;
    // _ = ReleaseCapture();
    // _ = SendMessageW(mainWindow == null ? Process.GetCurrentProcess().MainWindowHandle : FindWindowW(null, mainWindow.Title),
    //     0x112, 0xf012, 0);

    _ = ReleaseCapture();
    _ = SendMessageW(FindWindowW(null, windowTitle), 0x112, 0xf012, 0);
}
INI Read/Write 예제
[LibraryImport("kernel32.dll", EntryPoint = "GetPrivateProfileStringW", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
private static partial void GetPrivateProfileStringW(string lpAppName, string lpKeyName, string lpDefault, [Out] char[] lpReturnedString, int nSize, string lpFileName);

[LibraryImport("kernel32.dll", EntryPoint = "WritePrivateProfileStringW", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool WritePrivateProfileStringW(string lpAppName, string lpKeyName, string lpString, string lpFileName);

private const string INI_FILE = "file_path";

public static string Read(string key)
{
    try
    {
        char[] _output = new char[1024];
        GetPrivateProfileStringW(SECTION, key, "", _output, output.length, INI_FILE);
        output = output.Where(c => c != '\0').ToArray();
        return new string(_output);
    }
    catch (Exception _ex)
    {
        LogHelper.Logger.Debug($"IniHelper Read ERROR : {_ex.Message}");
        return string.Empty;
    }
}

public static void Write(string key, string value)
{
    try
    {
        _ = WritePrivateProfileStringW(SECTION, key, value, INI_FILE);
    }
    catch (Exception _ex)
    {
        LogHelper.Logger.Debug($"IniHelper Write ERROR : {_ex.Message}");
    }
}
SendMessage IPC
 private const uint WM_SETTEXT = 0x000C;

[LibraryImport("user32.DLL", EntryPoint = "SendMessageW", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
[return: MarshalAs(UnmanagedType.SysInt)]
private static partial IntPtr SendMessageW(IntPtr hWnd, uint wMsg, IntPtr wParam, IntPtr lParam);

[LibraryImport("USER32.DLL", EntryPoint = "FindWindowW", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
[return: MarshalAs(UnmanagedType.SysInt)]
private static partial IntPtr FindWindowW(string? lpClassName, string? lpWindowName);

public static async Task SendMessage(string message)
{
    await Task.Run(() =>
    {
        try
        {
            IntPtr handle = FindWindowW(null, "TITLE_XXX");
            _ = SendMessageW(handle, WM_SETTEXT, IntPtr.Zero, Marshal.StringToHGlobalAuto(message));
        }
        catch (Exception ex)
        {
            LogHelper.Logger.Error($"SendMessage ERROR : {ex.Message}");
        }
    });
}

// 받는 부분
private static IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    try
    {
        switch (msg)
        {
            case 0x0011: // WM_QUERYENDSESSION
                _mainWindow.Close();
                handled = true;
                break;
            case 0x000C: // WM_SETTEXT
                ReceiveMessage(Marshal.PtrToStringAuto(lParam) ?? string.Empty);
                handled = true;
                break;
            // case 0x0400: // WM_USER
            //     ReceiveMessageUser(Marshal.PtrToStringAuto(lParam) ?? "");
            //     handled = true;
            //     break;
            // case 0x004A: // WM_COPYDATA
            //     ReceiveMessageCopyData(Marshal.PtrToStructure<CopyDataStruct>(lParam).LpData);
            //     handled = true;
            //     break;
        }

        return IntPtr.Zero; // 변경
    }
    catch (Exception ex)
    {
        LogHelper.Logger.Error($"WndProc Error : {ex.Message}");
        return IntPtr.Zero;
    }
}
AOT 활용
// AOT(Native) : TestAot.dll
[UnmanagedCallersOnly(EntryPoint = "GetTest")]
public static IntPtr GetTest(IntPtr inputString)
{
    string? tmpString = Marshal.PtrToStringAuto(inputString);
    return Marshal.StringToHGlobalAuto("문자열 : " + tmpString);
}

// Managed code
[LibraryImport("TestAot.dll", EntryPoint = "GetTest", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
[return: MarshalAs(UnmanagedType.SysInt)]
private static partial IntPtr GetTest([MarshalAs(UnmanagedType.LPWStr)] string inputStr);

private string? GetTestEx(string inputString)
{
    try
    {
        return Marshal.PtrToStringAuto(GetTest(inputString));
    }
    catch (Exception ex)
    {
        LogHelper.Logger.Error($"GetTestEx ERROR : {ex.Message}");
        return (false, ex.Message);
    }
}
Reference
  1. Microsoft, “Platform Invoke (P/Invoke)”
  2. forum.dotnetdev.kr, “DllImport 대신 LibraryImport 사용”

Prεv(Θld)   Nεxt(Nεw)
Content
Search     RSS Feed     BY-NC-ND