푸터 콘텐츠로 바로가기
동영상

C#에서 PDF 파일의 바코드를 읽는 방법

BarcodeScanning.Native.Maui에서 IronBarcode로의 마이그레이션

이 가이드는 BarcodeScanning.Native.Maui에서IronBarcode로의 완벽한 마이그레이션 경로를 제공하며, 카메라 이벤트 패턴 교체, 네임스페이스 변경, 코드 마이그레이션 예제, 그리고 BarcodeScanning.Native.Maui에서 처리할 수 없는 시나리오(Windows MAUI, 파일 및 PDF 입력, 서버 측 처리, 바코드 생성) 처리 방법을 다룹니다.

BarcodeScanning.MAUI에서 마이그레이션해야 하는 이유

BarcodeScanning.Native.Maui에서 마이그레이션하는 팀에서 다음과 같은 문제가 발생한다고 보고합니다.

Windows MAUI 타겟 필요: BarcodeScanning.Native.Maui는 iOS 및 Android 네이티브 API를 래핑합니다. Windows 구현은 없으며 향후 계획도 없습니다. MAUI 앱이 iOS 및 Android 외에도 Windows를 대상으로 하는 경우, 플랫폼별 분기 없이 세 가지 대상 모두에서 작동하는 라이브러리가 필요합니다.

파일 또는 PDF 입력이 요구 사항에 추가되었습니다: BarcodeScanning.Native.Maui는 실시간 카메라 프레임만 허용합니다. 사용자가 갤러리에서 이미지를 업로드해야 하거나 서버 측 엔드포인트에서 PDF에서 바코드를 추출해야 하는 경우, 해당 라이브러리는 제공할 수 있는 코드 경로가 없습니다. 파일이나 PDF 바코드 시나리오에 따라 필요한 도구가 다릅니다.

iOS UPC-A 데이터 오류 발생: Apple의 Vision 프레임워크가 UPC-A 바코드(EAN-13 인코딩)에 대해 13자리 숫자를 반환합니다. BarcodeScanning.Native.Maui는 이를 수정 없이 그대로 전달합니다. UPC-A 코드가 앞에 0이 붙어서 저장될 경우, 재고 기록, 판매 시점 조회 또는 공급망 통합에 있어 아무런 오류 없이 문제가 발생할 수 있습니다.IronBarcode수동 정규화 없이 정확한 12자리 UPC-A 값을 반환합니다.

PDF417 스캔 기능이 불안정했습니다. 도서관 자체 GitHub 이슈 문서에도 PDF417이 "매우 문제가 많으며 대부분의 스캔이 이루어지지 않는다"고 기록되어 있습니다. 배송 라벨, 운전면허증, 탑승권 등의 경우 이는 직접적인 걸림돌이 됩니다.

바코드 생성이 필요합니다: BarcodeScanning.Native.Maui는 바코드를 생성할 수 없습니다.IronBarcode Code128, QR, DataMatrix 및 기타 형식을 이미지 파일 또는 바이트 배열로 생성합니다.

서버 측 처리 도입: BarcodeScanning.Native.Maui는 카메라 UI 컨트롤이므로 서버 프로세스에서 실행될 수 없습니다. 모바일 스캔과 함께 서버 측 바코드 판독이 필요한 경우,IronBarcode동일한 패키지와 API로 양쪽 모두를 지원합니다.

근본적인 문제

BarcodeScanning.Native.Maui는 바코드 판독 기능을 실시간 카메라 이벤트 모델에 완벽하게 연동합니다. 요구사항 중 하나라도 해당 모델에서 벗어나는 순간, 라이브러리는 아무것도 제공하지 않습니다.

private void OnBarcodeDetected(object sender, OnDetectionFinishedEventArg e)
{
    var barcode = e.BarcodeResults.FirstOrDefault();
    if (barcode != null)
        ResultLabel.Text = barcode.DisplayValue;
}
private void OnBarcodeDetected(object sender, OnDetectionFinishedEventArg e)
{
    var barcode = e.BarcodeResults.FirstOrDefault();
    if (barcode != null)
        ResultLabel.Text = barcode.DisplayValue;
}
Private Sub OnBarcodeDetected(sender As Object, e As OnDetectionFinishedEventArg)
    Dim barcode = e.BarcodeResults.FirstOrDefault()
    If barcode IsNot Nothing Then
        ResultLabel.Text = barcode.DisplayValue
    End If
End Sub
$vbLabelText   $csharpLabel

IronBarcode 카메라 캡처, 파일, PDF, 바이트 배열 등 모든 종류의 데이터 입력을 허용하며 모든 플랫폼에서 실행됩니다.

using IronBarCode;

private async void ScanBarcodeButton_Clicked(object sender, EventArgs e)
{
    var photo = await MediaPicker.CapturePhotoAsync();
    if (photo == null) return;

    using var stream = await photo.OpenReadAsync();
    using var ms = new MemoryStream();
    await stream.CopyToAsync(ms);

    var results = BarcodeReader.Read(ms.ToArray());
    ResultLabel.Text = results.FirstOrDefault()?.Value ?? "No barcode found";
}
using IronBarCode;

private async void ScanBarcodeButton_Clicked(object sender, EventArgs e)
{
    var photo = await MediaPicker.CapturePhotoAsync();
    if (photo == null) return;

    using var stream = await photo.OpenReadAsync();
    using var ms = new MemoryStream();
    await stream.CopyToAsync(ms);

    var results = BarcodeReader.Read(ms.ToArray());
    ResultLabel.Text = results.FirstOrDefault()?.Value ?? "No barcode found";
}
Imports IronBarCode

Private Async Sub ScanBarcodeButton_Clicked(sender As Object, e As EventArgs)
    Dim photo = Await MediaPicker.CapturePhotoAsync()
    If photo Is Nothing Then Return

    Using stream = Await photo.OpenReadAsync()
        Using ms As New MemoryStream()
            Await stream.CopyToAsync(ms)

            Dim results = BarcodeReader.Read(ms.ToArray())
            ResultLabel.Text = If(results.FirstOrDefault()?.Value, "No barcode found")
        End Using
    End Using
End Sub
$vbLabelText   $csharpLabel

IronBarcode와 BarcodeScanning.MAUI의 기능 비교

기능 BarcodeScanning.MAUI IronBarcode
실시간 카메라 프레임 읽기 예 — CameraView 제어 아니요 (미디어피커를 사용하여 캡처한 다음 읽으세요)
앱 내 카메라 뷰파인더 예 — 실시간 연속 아니요 — MediaPicker를 통해 시스템 카메라 UI를 사용합니다.
이미지 파일에서 읽어옴 아니요 예 — BarcodeReader.Read(path)
바이트 배열에서 읽기 아니요 예 — BarcodeReader.Read(bytes)
스트림에서 읽어오기 아니요 예 — BarcodeReader.Read(stream)
PDF에서 읽기 아니요 예 — BarcodeReader.Read(pdf)
바코드 생성 아니요 예 — BarcodeWriter + QRCodeWriter
Windows MAUI 지원 아니요
iOS MAUI 지원
안드로이드 MAUI 지원
macOS MAUI 지원 문서화되지 않음
서버 측 / ASP.NET 아니요
Docker / Azure / AWS Lambda 아니요
iOS UPC-A 정확도 13자리 숫자를 반환하는 버그가 있어 수동으로 정규화해야 합니다. 정확한 12자리 UPC-A 코드를 반환합니다.
PDF417 신뢰성 "대부분의 스캔은 발생하지 않습니다." (GitHub 이슈) 지원됨
다중 바코드 감지 예 (매 프레임당 여러 번 e.BarcodeResults으로) 예 (ExpectMultipleBarcodes 옵션)
읽기 속도 제어 None ReadingSpeed.Faster / Balanced / Detailed / ExtremeDetail
라이선스 MIT (오픈 소스, 무료) 상업용 - Lite $749, Plus $1,499, Professional $2,999, 무제한 $5,999
.NET Framework 지원 아니요 (MAUI 섬에만 해당) 예 — .NET Framework 4.6.2 이상

빠른 시작: BarcodeScanning.MAUI에서IronBarcode로 마이그레이션

1단계: NuGet 패키지 교체

BarcodeScanning.Native.Maui를 제거하세요:

dotnet remove package BarcodeScanning.Native.Maui
dotnet remove package BarcodeScanning.Native.Maui
SHELL

IronBarcode 설치하세요:

dotnet add package IronBarcode
dotnet add package IronBarcode
SHELL

단계 2: 네임스페이스 업데이트

모든 파일에서 BarcodeScanning 네임스페이스를 제거하세요.

// Remove
using BarcodeScanning;
// Remove
using BarcodeScanning;
Imports BarcodeScanning
$vbLabelText   $csharpLabel

IronBarcode 네임스페이스를 추가하세요:

// Add
using IronBarCode;
// Add
using IronBarCode;
Imports IronBarCode
$vbLabelText   $csharpLabel

XAML 파일에서, scanner: XML 네임스페이스 선언을 제거하십시오:


xmlns:scanner="clr-namespace:BarcodeScanning;assembly=BarcodeScanning.Native.Maui"

xmlns:scanner="clr-namespace:BarcodeScanning;assembly=BarcodeScanning.Native.Maui"
XML

단계 3: 라이선스 초기화

애플리케이션 시작 시 라이선스 초기화를 추가하십시오 — MauiProgram.cs 또는 App.xaml.cs 에서:

IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY";
Imports IronBarCode

IronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY"
$vbLabelText   $csharpLabel

코드 마이그레이션 예제

카메라 스캐닝: CameraView에서 MediaPicker로

CameraView 컨트롤은 연속 프레임 감지를 통해 실시간 뷰 파인더를 제공했습니다.IronBarcode대체는 MAUI의 MediaPicker 을 사용하여 시스템 카메라를 열고 사진을 캡처한 후 결과 이미지를 처리합니다.

바코드 스캐닝.MAUI 접근 방식 — XAML:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:scanner="clr-namespace:BarcodeScanning;assembly=BarcodeScanning.Native.Maui">
    <StackLayout>
        <scanner:CameraView x:Name="CameraView"
                            OnDetectionFinished="OnBarcodeDetected"
                            CameraEnabled="True"
                            BarcodeFormats="All"
                            VerticalOptions="FillAndExpand" />
        <Label x:Name="ResultLabel" Text="Waiting for scan..." />
    </StackLayout>
</ContentPage>
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:scanner="clr-namespace:BarcodeScanning;assembly=BarcodeScanning.Native.Maui">
    <StackLayout>
        <scanner:CameraView x:Name="CameraView"
                            OnDetectionFinished="OnBarcodeDetected"
                            CameraEnabled="True"
                            BarcodeFormats="All"
                            VerticalOptions="FillAndExpand" />
        <Label x:Name="ResultLabel" Text="Waiting for scan..." />
    </StackLayout>
</ContentPage>
XML

바코드 스캐닝.MAUI 접근 방식 — 코드 비하인드:

using BarcodeScanning;

private void OnBarcodeDetected(object sender, OnDetectionFinishedEventArg e)
{
    var barcode = e.BarcodeResults.FirstOrDefault();
    if (barcode != null)
        MainThread.BeginInvokeOnMainThread(() =>
            ResultLabel.Text = barcode.DisplayValue);
}
using BarcodeScanning;

private void OnBarcodeDetected(object sender, OnDetectionFinishedEventArg e)
{
    var barcode = e.BarcodeResults.FirstOrDefault();
    if (barcode != null)
        MainThread.BeginInvokeOnMainThread(() =>
            ResultLabel.Text = barcode.DisplayValue);
}
Imports BarcodeScanning

Private Sub OnBarcodeDetected(sender As Object, e As OnDetectionFinishedEventArg)
    Dim barcode = e.BarcodeResults.FirstOrDefault()
    If barcode IsNot Nothing Then
        MainThread.BeginInvokeOnMainThread(Sub()
                                               ResultLabel.Text = barcode.DisplayValue
                                           End Sub)
    End If
End Sub
$vbLabelText   $csharpLabel

IronBarcode 접근 방식 — XAML:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui">
    <StackLayout>
        <Button Text="Scan Barcode" Clicked="ScanButton_Clicked" />
        <Label x:Name="ResultLabel" Text="Tap to scan..." />
    </StackLayout>
</ContentPage>
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui">
    <StackLayout>
        <Button Text="Scan Barcode" Clicked="ScanButton_Clicked" />
        <Label x:Name="ResultLabel" Text="Tap to scan..." />
    </StackLayout>
</ContentPage>
XML

IronBarcode 접근 방식 — 코드 비하인드:

using IronBarCode;

private async void ScanButton_Clicked(object sender, EventArgs e)
{
    var photo = await MediaPicker.CapturePhotoAsync();
    if (photo == null) return;

    using var stream = await photo.OpenReadAsync();
    using var ms = new MemoryStream();
    await stream.CopyToAsync(ms);

    var results = BarcodeReader.Read(ms.ToArray());
    var first = results.FirstOrDefault();
    ResultLabel.Text = first?.Value ?? "No barcode found";
}
using IronBarCode;

private async void ScanButton_Clicked(object sender, EventArgs e)
{
    var photo = await MediaPicker.CapturePhotoAsync();
    if (photo == null) return;

    using var stream = await photo.OpenReadAsync();
    using var ms = new MemoryStream();
    await stream.CopyToAsync(ms);

    var results = BarcodeReader.Read(ms.ToArray());
    var first = results.FirstOrDefault();
    ResultLabel.Text = first?.Value ?? "No barcode found";
}
Imports IronBarCode
Imports System.IO
Imports System.Linq

Private Async Sub ScanButton_Clicked(sender As Object, e As EventArgs)
    Dim photo = Await MediaPicker.CapturePhotoAsync()
    If photo Is Nothing Then Return

    Using stream = Await photo.OpenReadAsync()
        Using ms As New MemoryStream()
            Await stream.CopyToAsync(ms)

            Dim results = BarcodeReader.Read(ms.ToArray())
            Dim first = results.FirstOrDefault()
            ResultLabel.Text = If(first?.Value, "No barcode found")
        End Using
    End Using
End Sub
$vbLabelText   $csharpLabel

이 코드는 플랫폼별 분기 없이 iOS, Android 및 Windows MAUI에서 실행됩니다. 사용자 경험은 앱 내 실시간 뷰파인더에서 플랫폼의 기본 카메라 화면으로 변경되며, 이는 대부분의 비즈니스 애플리케이션에 적합합니다. IronBarcode MAUI 사용 설명서에는 추가 구성 옵션이 설명되어 있습니다.

한 번의 스캔으로 여러 개의 바코드 처리

바코드 스캐닝.MAUI 접근 방식:

private void OnBarcodeDetected(object sender, OnDetectionFinishedEventArg e)
{
    foreach (var barcode in e.BarcodeResults)
    {
        MainThread.BeginInvokeOnMainThread(() =>
            Console.WriteLine($"Found: {barcode.DisplayValue}"));
    }
}
private void OnBarcodeDetected(object sender, OnDetectionFinishedEventArg e)
{
    foreach (var barcode in e.BarcodeResults)
    {
        MainThread.BeginInvokeOnMainThread(() =>
            Console.WriteLine($"Found: {barcode.DisplayValue}"));
    }
}
Private Sub OnBarcodeDetected(sender As Object, e As OnDetectionFinishedEventArg)
    For Each barcode In e.BarcodeResults
        MainThread.BeginInvokeOnMainThread(Sub()
                                               Console.WriteLine($"Found: {barcode.DisplayValue}")
                                           End Sub)
    Next
End Sub
$vbLabelText   $csharpLabel

IronBarcode 접근 방식:

using IronBarCode;

private async void ScanButton_Clicked(object sender, EventArgs e)
{
    var photo = await MediaPicker.CapturePhotoAsync();
    if (photo == null) return;

    using var stream = await photo.OpenReadAsync();
    using var ms = new MemoryStream();
    await stream.CopyToAsync(ms);

    var options = new BarcodeReaderOptions
    {
        Speed = ReadingSpeed.Balanced,
        ExpectMultipleBarcodes = true
    };

    var results = BarcodeReader.Read(ms.ToArray(), options);
    foreach (var result in results)
        Console.WriteLine($"{result.Format}: {result.Value}");
}
using IronBarCode;

private async void ScanButton_Clicked(object sender, EventArgs e)
{
    var photo = await MediaPicker.CapturePhotoAsync();
    if (photo == null) return;

    using var stream = await photo.OpenReadAsync();
    using var ms = new MemoryStream();
    await stream.CopyToAsync(ms);

    var options = new BarcodeReaderOptions
    {
        Speed = ReadingSpeed.Balanced,
        ExpectMultipleBarcodes = true
    };

    var results = BarcodeReader.Read(ms.ToArray(), options);
    foreach (var result in results)
        Console.WriteLine($"{result.Format}: {result.Value}");
}
Imports IronBarCode
Imports System.IO

Private Async Sub ScanButton_Clicked(sender As Object, e As EventArgs)
    Dim photo = Await MediaPicker.CapturePhotoAsync()
    If photo Is Nothing Then Return

    Using stream = Await photo.OpenReadAsync()
        Using ms As New MemoryStream()
            Await stream.CopyToAsync(ms)

            Dim options As New BarcodeReaderOptions With {
                .Speed = ReadingSpeed.Balanced,
                .ExpectMultipleBarcodes = True
            }

            Dim results = BarcodeReader.Read(ms.ToArray(), options)
            For Each result In results
                Console.WriteLine($"{result.Format}: {result.Value}")
            Next
        End Using
    End Using
End Sub
$vbLabelText   $csharpLabel

ExpectMultipleBarcodes = true 은 독자가 첫 번째 바코드를 찾은 후 계속 스캔하도록 지시합니다. 이 옵션을 사용하지 않으면 첫 번째 일치 항목에서 호출이 반환되므로 단일 바코드 시나리오에서는 더 빠릅니다.

iOS UPC-A 수정: 정규화 임시 해결 방법 제거

코드베이스에 UPC-A 앞에 0을 붙이는 임시 해결책이 있다면, 해당 부분을 완전히 제거하십시오.IronBarcode수동 조작 없이 정확한 12자리 값을 반환합니다.

바코드 스캐닝.MAUI 접근 방식 - 임시 해결책 마련됨:

private void OnBarcodeDetected(object sender, OnDetectionFinishedEventArg e)
{
    var barcode = e.BarcodeResults.FirstOrDefault();
    if (barcode == null) return;

    var value = barcode.DisplayValue;

    // Workaround: Apple Vision returns 13 digits for UPC-A
    if (barcode.BarcodeFormat == BarcodeFormats.Upca && value.Length == 13)
        value = value.Substring(1);

    ProcessBarcode(value, barcode.BarcodeFormat.ToString());
}
private void OnBarcodeDetected(object sender, OnDetectionFinishedEventArg e)
{
    var barcode = e.BarcodeResults.FirstOrDefault();
    if (barcode == null) return;

    var value = barcode.DisplayValue;

    // Workaround: Apple Vision returns 13 digits for UPC-A
    if (barcode.BarcodeFormat == BarcodeFormats.Upca && value.Length == 13)
        value = value.Substring(1);

    ProcessBarcode(value, barcode.BarcodeFormat.ToString());
}
Private Sub OnBarcodeDetected(sender As Object, e As OnDetectionFinishedEventArg)
    Dim barcode = e.BarcodeResults.FirstOrDefault()
    If barcode Is Nothing Then Return

    Dim value = barcode.DisplayValue

    ' Workaround: Apple Vision returns 13 digits for UPC-A
    If barcode.BarcodeFormat = BarcodeFormats.Upca AndAlso value.Length = 13 Then
        value = value.Substring(1)
    End If

    ProcessBarcode(value, barcode.BarcodeFormat.ToString())
End Sub
$vbLabelText   $csharpLabel

IronBarcode 방식 — 별도의 해결책이 필요 없습니다:

using IronBarCode;

private async void ScanButton_Clicked(object sender, EventArgs e)
{
    var photo = await MediaPicker.CapturePhotoAsync();
    if (photo == null) return;

    using var stream = await photo.OpenReadAsync();
    using var ms = new MemoryStream();
    await stream.CopyToAsync(ms);

    var results = BarcodeReader.Read(ms.ToArray());
    var first = results.FirstOrDefault();
    if (first == null) return;

    // result.Value is the correct 12-digit UPC-A — no normalization needed
    ProcessBarcode(first.Value, first.Format.ToString());
}
using IronBarCode;

private async void ScanButton_Clicked(object sender, EventArgs e)
{
    var photo = await MediaPicker.CapturePhotoAsync();
    if (photo == null) return;

    using var stream = await photo.OpenReadAsync();
    using var ms = new MemoryStream();
    await stream.CopyToAsync(ms);

    var results = BarcodeReader.Read(ms.ToArray());
    var first = results.FirstOrDefault();
    if (first == null) return;

    // result.Value is the correct 12-digit UPC-A — no normalization needed
    ProcessBarcode(first.Value, first.Format.ToString());
}
Imports IronBarCode
Imports System.IO
Imports System.Linq
Imports System.Threading.Tasks

Private Async Sub ScanButton_Clicked(sender As Object, e As EventArgs)
    Dim photo = Await MediaPicker.CapturePhotoAsync()
    If photo Is Nothing Then Return

    Using stream = Await photo.OpenReadAsync()
        Using ms As New MemoryStream()
            Await stream.CopyToAsync(ms)

            Dim results = BarcodeReader.Read(ms.ToArray())
            Dim first = results.FirstOrDefault()
            If first Is Nothing Then Return

            ' result.Value is the correct 12-digit UPC-A — no normalization needed
            ProcessBarcode(first.Value, first.Format.ToString())
        End Using
    End Using
End Sub
$vbLabelText   $csharpLabel

BarcodeFormats.Upca 과(와) Substring(1) 이(가) 쌍으로 일치하는 항목을 삭제하십시오 — 마이그레이션 후 해당 코드는 사용되지 않습니다.

파일 및 PDF 지원 추가

BarcodeScanning.Native.Maui에는 파일 또는 PDF 입력에 해당하는 기능이 없습니다. 이것이 마이그레이션 시점에 충족해야 하는 새로운 요구 사항이라면:

바코드 스캐닝.MAUI 접근 방식:

// 아니요 equivalent exists — BarcodeScanning.Native.Maui cannot read from files or PDFs
// 아니요 equivalent exists — BarcodeScanning.Native.Maui cannot read from files or PDFs
' 아니요 equivalent exists — BarcodeScanning.Native.Maui cannot read from files or PDFs
$vbLabelText   $csharpLabel

IronBarcode 접근 방식:

using IronBarCode;

// Read from a file the user picked with FilePicker
private async void ReadFileButton_Clicked(object sender, EventArgs e)
{
    var file = await FilePicker.PickAsync(new PickOptions
    {
        PickerTitle = "Select image or PDF"
    });
    if (file == null) return;

    var results = BarcodeReader.Read(file.FullPath);
    foreach (var result in results)
        ResultLabel.Text += $"\n{result.Format}: {result.Value}";
}

// Read barcodes from a PDF directly — no intermediate image step
private void ReadPdfBarcodes()
{
    var results = BarcodeReader.Read("shipment-manifest.pdf");
    foreach (var result in results)
        Console.WriteLine($"{result.Format}: {result.Value}");
}
using IronBarCode;

// Read from a file the user picked with FilePicker
private async void ReadFileButton_Clicked(object sender, EventArgs e)
{
    var file = await FilePicker.PickAsync(new PickOptions
    {
        PickerTitle = "Select image or PDF"
    });
    if (file == null) return;

    var results = BarcodeReader.Read(file.FullPath);
    foreach (var result in results)
        ResultLabel.Text += $"\n{result.Format}: {result.Value}";
}

// Read barcodes from a PDF directly — no intermediate image step
private void ReadPdfBarcodes()
{
    var results = BarcodeReader.Read("shipment-manifest.pdf");
    foreach (var result in results)
        Console.WriteLine($"{result.Format}: {result.Value}");
}
Imports IronBarCode

' Read from a file the user picked with FilePicker
Private Async Sub ReadFileButton_Clicked(sender As Object, e As EventArgs)
    Dim file = Await FilePicker.PickAsync(New PickOptions With {
        .PickerTitle = "Select image or PDF"
    })
    If file Is Nothing Then Return

    Dim results = BarcodeReader.Read(file.FullPath)
    For Each result In results
        ResultLabel.Text &= vbCrLf & $"{result.Format}: {result.Value}"
    Next
End Sub

' Read barcodes from a PDF directly — no intermediate image step
Private Sub ReadPdfBarcodes()
    Dim results = BarcodeReader.Read("shipment-manifest.pdf")
    For Each result In results
        Console.WriteLine($"{result.Format}: {result.Value}")
    Next
End Sub
$vbLabelText   $csharpLabel

IronBarcode PDF 읽기 설명서에는 여러 페이지로 구성된 PDF 지원 및 페이지 범위 선택에 대한 내용이 포함되어 있습니다.

서버 측 바코드 처리

백엔드 ASP.NET API도 바코드 처리가 필요한 경우, 동일한 BarcodeReader.Read() 호출이 수정 없이 실행됩니다. BarcodeScanning.Native.Maui에는 서버 측에 해당하는 기능이 없습니다.

바코드 스캐닝.MAUI 접근 방식:

// 아니요 equivalent exists — BarcodeScanning.Native.Maui is a camera UI control
// and cannot run in a server process
// 아니요 equivalent exists — BarcodeScanning.Native.Maui is a camera UI control
// and cannot run in a server process
' 아니요 equivalent exists — BarcodeScanning.Native.Maui is a camera UI control
' and cannot run in a server process
$vbLabelText   $csharpLabel

IronBarcode 접근 방식:

using IronBarCode;

// ASP.NET endpoint — reads barcodes from an uploaded file
[HttpPost("scan")]
public async Task<IActionResult> ScanBarcode(IFormFile file)
{
    using var ms = new MemoryStream();
    await file.CopyToAsync(ms);

    var results = BarcodeReader.Read(ms.ToArray());
    var values = results.Select(r => new { r.Value, Format = r.Format.ToString() });
    return Ok(values);
}
using IronBarCode;

// ASP.NET endpoint — reads barcodes from an uploaded file
[HttpPost("scan")]
public async Task<IActionResult> ScanBarcode(IFormFile file)
{
    using var ms = new MemoryStream();
    await file.CopyToAsync(ms);

    var results = BarcodeReader.Read(ms.ToArray());
    var values = results.Select(r => new { r.Value, Format = r.Format.ToString() });
    return Ok(values);
}
Imports IronBarCode
Imports Microsoft.AspNetCore.Mvc
Imports System.IO
Imports System.Threading.Tasks

' ASP.NET endpoint — reads barcodes from an uploaded file
<HttpPost("scan")>
Public Async Function ScanBarcode(file As IFormFile) As Task(Of IActionResult)
    Using ms As New MemoryStream()
        Await file.CopyToAsync(ms)

        Dim results = BarcodeReader.Read(ms.ToArray())
        Dim values = results.Select(Function(r) New With {Key .Value = r.Value, Key .Format = r.Format.ToString()})
        Return Ok(values)
    End Using
End Function
$vbLabelText   $csharpLabel

모바일과 서버에서 동일한 패키지, 동일한 API, 동일한 동작을 제공합니다.

바코드 생성

BarcodeScanning.Native.Maui에는 생성 API가 없습니다.IronBarcode다양한 형식을 생성합니다.

바코드 스캐닝.MAUI 접근 방식:

// 아니요 equivalent exists — BarcodeScanning.Native.Maui cannot generate barcodes
// 아니요 equivalent exists — BarcodeScanning.Native.Maui cannot generate barcodes
' 아니요 equivalent exists — BarcodeScanning.Native.Maui cannot generate barcodes
$vbLabelText   $csharpLabel

IronBarcode 접근 방식:

using IronBarCode;

// QR code
QRCodeWriter.CreateQrCode("https://example.com/product/12345", 500)
    .SaveAsPng("qr.png");

// Code128 barcode sized to specific dimensions
BarcodeWriter.CreateBarcode("ITEM-98765", BarcodeEncoding.Code128)
    .ResizeTo(400, 100)
    .SaveAsPng("label.png");

// Get bytes for returning from an API or storing in a database
byte[] barcodeBytes = BarcodeWriter.CreateBarcode("ITEM-98765", BarcodeEncoding.Code128)
    .ToPngBinaryData();
using IronBarCode;

// QR code
QRCodeWriter.CreateQrCode("https://example.com/product/12345", 500)
    .SaveAsPng("qr.png");

// Code128 barcode sized to specific dimensions
BarcodeWriter.CreateBarcode("ITEM-98765", BarcodeEncoding.Code128)
    .ResizeTo(400, 100)
    .SaveAsPng("label.png");

// Get bytes for returning from an API or storing in a database
byte[] barcodeBytes = BarcodeWriter.CreateBarcode("ITEM-98765", BarcodeEncoding.Code128)
    .ToPngBinaryData();
Imports IronBarCode

' QR code
QRCodeWriter.CreateQrCode("https://example.com/product/12345", 500) _
    .SaveAsPng("qr.png")

' Code128 barcode sized to specific dimensions
BarcodeWriter.CreateBarcode("ITEM-98765", BarcodeEncoding.Code128) _
    .ResizeTo(400, 100) _
    .SaveAsPng("label.png")

' Get bytes for returning from an API or storing in a database
Dim barcodeBytes As Byte() = BarcodeWriter.CreateBarcode("ITEM-98765", BarcodeEncoding.Code128) _
    .ToPngBinaryData()
$vbLabelText   $csharpLabel

IronBarcode 생성 문서에는 지원되는 모든 형식과 스타일 옵션이 설명되어 있습니다.

BarcodeScanning.MAUI API와IronBarcode매핑 참조

BarcodeScanning.Native.Maui IronBarcode
CameraView XAML 컨트롤 제거 — Button + MediaPicker.CapturePhotoAsync() 사용
OnDetectionFinished 이벤트 BarcodeReader.Read(imageBytes) 반환 값
OnDetectionFinishedEventArg e BarcodeReader.Read() 로부터의 IEnumerable 결과
e.BarcodeResults BarcodeReader.Read() 의 반환 값
e.BarcodeResults.FirstOrDefault() results.FirstOrDefault()
barcode.DisplayValue result.Value
barcode.BarcodeFormat result.Format
BarcodeFormats="All" 자동 감지됨 - 별도의 설정이 필요 없습니다
CameraEnabled="True" await MediaPicker.CapturePhotoAsync()
iOS 및 Android 전용 iOS, 안드로이드, 윈도우, macOS MAUI
파일 입력 없음 BarcodeReader.Read(filePath)
PDF 입력 없음 BarcodeReader.Read("document.pdf")
어떤 세대도 BarcodeWriter.CreateBarcode() / QRCodeWriter.CreateQrCode()
iOS UPC-A는 13자리 숫자를 반환합니다. 정확한 12자리 숫자를 반환합니다. 정규화는 필요하지 않습니다.
PDF417 신뢰할 수 없음 지원됨

일반적인 마이그레이션 문제와 해결책

문제 1: 라이브 뷰파인더 경험 상실

BarcodeScanning.MAUI: CameraView 컨트롤은 실시간 카메라 미리 보기를 직접 MAUI 페이지에 내장했습니다. 사용자는 카메라 영상을 보고 바코드를 조준할 수 있으며, 버튼을 누르지 않아도 자동으로 바코드가 감지됩니다.

솔루션: 대신 MediaPicker.CapturePhotoAsync() 이 플랫폼 카메라 화면을 나타냅니다. 대부분의 업무 흐름에서는 이것이 허용됩니다. 연속적인 실시간 미리 보기가 필요한 소비자 앱의 경우, 카메라 프레임을 BarcodeReader.Read() 으로 직접 전달할 수 있습니다.

using IronBarCode;

// Continuous scanning: pass camera frame bytes to BarcodeReader.Read()
// (frame capture depends on your MAUI camera frame source)
private void ProcessCameraFrame(byte[] frameBytes)
{
    var results = BarcodeReader.Read(frameBytes);
    if (results.Any())
    {
        var first = results.First();
        MainThread.BeginInvokeOnMainThread(() =>
            ResultLabel.Text = first.Value);
    }
}
using IronBarCode;

// Continuous scanning: pass camera frame bytes to BarcodeReader.Read()
// (frame capture depends on your MAUI camera frame source)
private void ProcessCameraFrame(byte[] frameBytes)
{
    var results = BarcodeReader.Read(frameBytes);
    if (results.Any())
    {
        var first = results.First();
        MainThread.BeginInvokeOnMainThread(() =>
            ResultLabel.Text = first.Value);
    }
}
Imports IronBarCode

' Continuous scanning: pass camera frame bytes to BarcodeReader.Read()
' (frame capture depends on your MAUI camera frame source)
Private Sub ProcessCameraFrame(frameBytes As Byte())
    Dim results = BarcodeReader.Read(frameBytes)
    If results.Any() Then
        Dim first = results.First()
        MainThread.BeginInvokeOnMainThread(Sub()
                                               ResultLabel.Text = first.Value
                                           End Sub)
    End If
End Sub
$vbLabelText   $csharpLabel

이를 위해서는 카메라 프레임 소스를IronBarcode와 별도로 연결해야 합니다. 이 방법을 선택하기 전에 실시간 미리보기가 정말 필요한지, 아니면 시스템 카메라 UI로 충분한지 평가해 보세요.

문제 2: e.BarcodeResults 속성 이름 및 열거형 변경

BarcodeScanning.MAUI: barcode.DisplayValue 이 디코딩된 문자열을 반환합니다; barcode.BarcodeFormat 은 BarcodeScanning 라이브러리 열거형 값을 반환합니다.

솔루션: DisplayValue 을(를) result.Value 로 대체하고 barcode.BarcodeFormat 을(를) result.Format 로 대체하십시오. 반복 패턴은 동일합니다.

// Before
foreach (var barcode in e.BarcodeResults)
    Console.WriteLine(barcode.DisplayValue);

// After
var results = BarcodeReader.Read(imageBytes);
foreach (var result in results)
    Console.WriteLine(result.Value);
// Before
foreach (var barcode in e.BarcodeResults)
    Console.WriteLine(barcode.DisplayValue);

// After
var results = BarcodeReader.Read(imageBytes);
foreach (var result in results)
    Console.WriteLine(result.Value);
' Before
For Each barcode In e.BarcodeResults
    Console.WriteLine(barcode.DisplayValue)
Next

' After
Dim results = BarcodeReader.Read(imageBytes)
For Each result In results
    Console.WriteLine(result.Value)
Next
$vbLabelText   $csharpLabel

문제 3: UI 업데이트를 위한 스레드 마샬링

BarcodeScanning.MAUI: OnDetectionFinished 은 백그라운드 스레드에서 실행되므로 모든 UI 업데이트가 MainThread.BeginInvokeOnMainThread() 을(를) 필요로 합니다.

솔루션: MediaPicker + async 패턴을 사용하여 await 이후의 지속은 호출 컨텍스트에서 반환됩니다 — 일반적으로 메인 스레드. 결과 표시 주위의 MainThread.BeginInvokeOnMainThread() 래퍼는 일반적으로 제거할 수 있어 핸들러 코드를 간소화합니다.

4번째 문제: MAUI 카메라 권한

BarcodeScanning.MAUI: 패키지는 설정의 일부로써 AndroidManifest.xmlInfo.plist 에 카메라 권한을 자동으로 추가합니다.

솔루션: IronBarcode가 MediaPicker 을(를) 사용함에 따라, 표준 MAUI 카메라 권한은 수동으로 필요합니다. 이들은 MediaPicker.CapturePhotoAsync() 에 필요한 MAUI 앱의 동일한 권한이며 일반적으로 이미 설정되어 있습니다. 장치에서 테스트하기 전에 android.permission.CAMERA 이(가) AndroidManifest.xml 에 선언되어 있고 NSCameraUsageDescription 이(가) Info.plist 에 설정되어 있는지 확인하십시오.

바코드스캐닝.MAUI 마이그레이션 체크리스트

이동 전 작업

변경하기 전에 다음 검색을 실행하여 BarcodeScanning.Native.Maui 사용 내역을 모두 찾으세요.

grep -rn "using BarcodeScanning" --include="*.cs" .
grep -rn "using BarcodeScanning" --include="*.xaml" .
grep -rn "CameraView" --include="*.cs" .
grep -rn "CameraView" --include="*.xaml" .
grep -rn "OnDetectionFinished" --include="*.cs" .
grep -rn "OnDetectionFinishedEventArg" --include="*.cs" .
grep -rn "e\.BarcodeResults" --include="*.cs" .
grep -rn "DisplayValue" --include="*.cs" .
grep -rn "BarcodeFormats\.Upca" --include="*.cs" .
grep -rn "scanner:" --include="*.xaml" .
grep -rn "using BarcodeScanning" --include="*.cs" .
grep -rn "using BarcodeScanning" --include="*.xaml" .
grep -rn "CameraView" --include="*.cs" .
grep -rn "CameraView" --include="*.xaml" .
grep -rn "OnDetectionFinished" --include="*.cs" .
grep -rn "OnDetectionFinishedEventArg" --include="*.cs" .
grep -rn "e\.BarcodeResults" --include="*.cs" .
grep -rn "DisplayValue" --include="*.cs" .
grep -rn "BarcodeFormats\.Upca" --include="*.cs" .
grep -rn "scanner:" --include="*.xaml" .
SHELL

각 공격 기록을 문서화하십시오. XAML 사용(은 XAML 변경이 필요함)을 포함하는 파일과 코드 비하인드 변경만 포함하는 파일을 구분하십시오. 마이그레이션 후 삭제해야 하는 UPC-A 정규화 관련 임시 해결책을 모두 확인하십시오.

코드 업데이트 작업

  1. BarcodeScanning.Native.Maui NuGet 패키지를 제거하십시오
  2. IronBarcode NuGet 패키지를 설치하십시오
  3. MauiProgram.cs 또는 App.xaml.csIronBarCode.License.LicenseKey = "YOUR-LICENSE-KEY"; 을 추가하십시오
  4. 모든 .cs 파일에서 using BarcodeScanning; 을(를) using IronBarCode; 로 대체하십시오
  5. 모든 XAML 파일에서 xmlns:scanner="..." 네임스페이스 선언을 제거하십시오
  6. XAML에서 scanner:CameraView 컨트롤을 MediaPicker.CapturePhotoAsync() 을 발동하는 Button 으로 교체하십시오
  7. XAML에서 OnDetectionFinished="..." 이벤트 연결을 제거하십시오
  8. BarcodeReader.Read() 을 사용하여 OnDetectionFinished 이벤트 핸들러를 async 버튼 클릭 핸들러로 교체하십시오
  9. 전체에서 barcode.DisplayValue 을(를) result.Value 으로 대체하십시오
  10. 전체에서 barcode.BarcodeFormat 을(를) result.Format 로 대체하십시오
  11. 모든 BarcodeFormats.Upca + Substring(1) 정규화 해결 방법을 삭제하십시오
  12. 다중 바코드 감지가 이전에 e.BarcodeResults 이 여러 항목을 반환하는 것에 의존하던 곳에 ExpectMultipleBarcodes = trueBarcodeReaderOptions 에 추가하십시오
  13. 비동기 패턴으로 불필요해진 곳에서 결과 표시 코드의 MainThread.BeginInvokeOnMainThread() 래퍼를 제거하십시오
  14. AndroidManifest.xmlInfo.plist 카메라 권한이 존재하는지 확인하십시오

마이그레이션 후 테스트

  • iOS 바코드 스캔 기능이 정상적으로 작동하고 UPC-A 값이 앞에 0이 없는 12자리 문자열로 반환되는지 확인합니다.
  • 안드로이드 바코드 스캔이 애플리케이션에서 사용되는 모든 형식에 대해 올바른 값을 생성하는지 확인합니다.
  • Windows가 빌드 대상인 경우 Windows MAUI 바코드 스캔 기능이 제대로 작동하는지 확인합니다.
  • 실제 배송 라벨, 운전면허증 또는 탑승권 등을 사용하는 경우, PDF417 스캔 기능을 실제 문서와 비교하여 테스트하십시오.
  • ExpectMultipleBarcodes = true 으로 다중 바코드 시나리오를 테스트하고 이미지의 모든 바코드가 반환되는지 확인하십시오
  • 모든 MAUI 대상에서 파일 선택기 스캔(BarcodeReader.Read(filePath))이 작동하는지 확인하십시오
  • 마이그레이션과 함께 추가된 새로운 기능이라면 PDF 바코드 읽기 기능을 확인하십시오.
  • 백엔드 컴포넌트가 추가되었을 때 서버 측 BarcodeReader.Read() 이 올바른 결과를 생성하는지 확인하십시오
  • 기존 자동화 테스트를 실행하고 바코드 값 출력값을 마이그레이션 전 기준선과 비교합니다.

IronBarcode로 마이그레이션할 때의 주요 이점

완벽한 Windows MAUI 지원:IronBarcode동일한 코드와 패키지를 사용하여 iOS, Android, Windows 및 macOS의 네 가지 MAUI 대상 모두에서 실행됩니다. Windows에는 플랫폼 특정 바코드 구현이 필요하지 않으며, 애플리케이션 코드에서 #if WINDOWS 블록이 필요하지 않습니다.

어떤 입력 소스든: BarcodeReader.Read() 은 파일 경로, 바이트 배열, 스트림 및 PDF 문서를 허용합니다. 카메라 캡처, 파일 업로드, 갤러리 이미지, 서버 측 PDF 처리 등 모든 바코드 시나리오에서 동일한 결과 유형을 가진 동일한 정적 메서드를 사용합니다.

정확한 UPC-A 값:IronBarcode애플리케이션 내에 별도의 정규화 코드를 추가하지 않아도 iOS에서 정확한 12자리 UPC-A 값을 반환합니다. BarcodeScanning.Native.Maui의 동작으로 인해 이전 UPC-A 데이터에 선행 0이 추가되는 현상이 발생하지만, 이는 마이그레이션 후 읽은 값의 정확도에는 영향을 미치지 않습니다.

PDF417은 안정적으로 작동합니다. PDF417은 완벽하게 지원되며 안정적으로 읽을 수 있습니다. 배송 라벨, 운전면허증, 탑승권은 BarcodeScanning.Native.Maui의 GitHub 이슈에 기록된 "대부분의 스캔이 발생하지 않는" 제한 없이 스캔됩니다.

바코드 생성: BarcodeWriter.CreateBarcode()QRCodeWriter.CreateQrCode() 은 Code128, QR, DataMatrix 등 형식을 PNG 파일이나 바이트 배열로 생성합니다. 생성과 읽기 기능은 추가적인 종속성 없이 동일한 패키지에서 사용할 수 있습니다.

서버 측 배포: 동일한 BarcodeReader.Read() 호출이 ASP.NET, Azure Functions, Docker 컨테이너 및 AWS Lambda에서 실행됩니다. 모바일과 서버 바코드 로직은 별도의 바코드 구현을 유지 관리할 필요 없이 동일한 API, 동일한 형식 지원 및 동일한 결과 유형을 공유할 수 있습니다.

자주 묻는 질문

BarcodeScanning.MAUI에서 IronBarcode로 마이그레이션해야 하는 이유는 무엇인가요?

일반적인 이유로는 라이선스 간소화(SDK + 런타임 키 복잡성 제거), 처리량 제한 제거, 네이티브 PDF 지원 확보, Docker/CI/CD 배포 개선, 프로덕션 코드의 API 상용구 감소 등이 있습니다.

BarcodeScanning.MAUI API 호출을 IronBarcode로 대체하려면 어떻게 해야 하나요?

인스턴스 생성 및 라이선스 상용구를 IronBarCode.License.LicenseKey = "key"로 대체합니다. 판독기 호출을 BarcodeReader.Read(경로)로, 쓰기 호출을 BarcodeWriter.CreateBarcode(데이터, 인코딩)로 바꿉니다. 정적 메서드는 인스턴스 관리가 필요하지 않습니다.

BarcodeScanning.MAUI에서 IronBarcode로 마이그레이션할 때 얼마나 많은 코드가 변경되나요?

대부분의 마이그레이션은 더 적은 코드 줄로 이루어집니다. 라이선스 상용구, 인스턴스 생성자, 명시적 형식 구성이 제거됩니다. 핵심 읽기/쓰기 작업이 더 간결한 결과 객체를 가진 더 짧은 IronBarcode에 매핑됩니다.

마이그레이션하는 동안 BarcodeScanning.MAUI와 IronBarcode를 모두 설치해야 하나요?

아니요. 대부분의 마이그레이션은 병렬 작업이 아닌 직접 교체 작업입니다. 한 번에 하나의 서비스 클래스를 마이그레이션하고, NuGet 참조를 교체하고, 인스턴스화 및 API 호출 패턴을 업데이트한 후 다음 클래스로 이동합니다.

IronBarcode의 NuGet 패키지 이름은 무엇인가요?

패키지는 'IronBarCode'(대문자 B와 C 포함)입니다. '설치-패키지 IronBarCode' 또는 '닷넷 추가 패키지 IronBarCode'로 설치합니다. 코드의 사용 지시어는 'using IronBarCode;'입니다.

IronBarcode는 BarcodeScanning.MAUI와 비교하여 Docker 배포를 어떻게 간소화하나요?

IronBarcode는 외부 SDK 파일이나 마운트된 라이선스 구성이 없는 NuGet 패키지입니다. Docker에서 IRONBARCODE_LICENSE_KEY 환경 변수를 설정하면 패키지가 시작 시 라이선스 유효성 검사를 처리합니다.

BarcodeScanning.MAUI에서 마이그레이션한 후 IronBarcode가 모든 바코드 형식을 자동으로 감지하나요?

예. IronBarcode는 지원되는 모든 형식에서 기호를 자동으로 감지합니다. 명시적인 바코드 유형 열거가 필요하지 않습니다. 형식이 이미 알려져 있고 성능이 중요한 경우 BarcodeReaderOptions를 사용하여 검색 공간을 최적화하여 제한할 수 있습니다.

IronBarcode는 별도의 라이브러리 없이 PDF에서 바코드를 판독할 수 있나요?

예. BarcodeReader.Read("document.pdf")는 기본적으로 PDF 파일을 처리합니다. 결과에는 발견된 각 바코드에 대한 페이지 번호, 형식, 값 및 신뢰도가 포함됩니다. 외부 PDF 렌더링 단계가 필요하지 않습니다.

IronBarcode는 병렬 바코드 처리를 어떻게 처리하나요?

IronBarcode의 정적 메서드는 상태 저장소가 없고 스레드에 안전합니다. 스레드별 인스턴스 관리 없이 파일 목록에 직접 Parallel.ForEach를 사용할 수 있습니다. BarcodeReaderOptions.MaxParallelThreads는 내부 스레드 예산을 제어합니다.

BarcodeScanning.MAUI에서 IronBarcode로 마이그레이션하면 어떤 결과 속성이 변경되나요?

일반적인 이름 변경: 바코드 값은 값으로, 바코드 유형은 형식으로 바뀝니다. IronBarcode 결과에는 신뢰도 및 페이지 번호도 추가됩니다. 솔루션 전반의 검색 및 바꾸기 기능은 기존 결과 처리 코드에서 이름 변경을 처리합니다.

CI/CD 파이프라인에서 IronBarcode 라이선싱을 설정하려면 어떻게 해야 하나요?

IRONBARCODE_LICENSE_KEY를 파이프라인 시크릿으로 저장하고 애플리케이션 시작 코드에 IronBarCode.License.LicenseKey를 할당하세요. 하나의 비밀은 개발, 테스트, 스테이징 및 프로덕션을 포함한 모든 환경에 적용됩니다.

IronBarcode는 사용자 지정 스타일로 QR 코드 생성을 지원하나요?

예. QRCodeWriter.CreateQrCode()는 변경바코드색()을 통한 사용자 지정 색상, 추가브랜드 로고()를 통한 로고 임베딩, 구성 가능한 오류 수정 수준, PNG, JPG, PDF, 스트림을 포함한 여러 출력 형식을 지원합니다.

Curtis Chau
기술 문서 작성자

커티스 차우는 칼턴 대학교에서 컴퓨터 과학 학사 학위를 취득했으며, Node.js, TypeScript, JavaScript, React를 전문으로 하는 프론트엔드 개발자입니다. 직관적이고 미적으로 뛰어난 사용자 인터페이스를 만드는 데 열정을 가진 그는 최신 프레임워크를 활용하고, 잘 구성되고 시각적으로 매력적인 매뉴얼을 제작하는 것을 즐깁니다.

커티스는 개발 분야 외에도 사물 인터넷(IoT)에 깊은 관심을 가지고 있으며, 하드웨어와 소프트웨어를 통합하는 혁신적인 방법을 연구합니다. 여가 시간에는 게임을 즐기거나 디스코드 봇을 만들면서 기술에 대한 애정과 창의성을 결합합니다.

아이언 서포트 팀

저희는 주 5일, 24시간 온라인으로 운영합니다.
채팅
이메일
전화해