How to Build .NET 6 Barcode and QR Code SDK for Windows, Linux & macOS
Tags: dotnet, dotnet6, barcode, qrcode
Published: 2022-03-31 03:18:48

Microsoft .NET 6 SDK empowers C# developers to build cross-platform DotNet applications for Windows, Linux, and macOS from one codebase. This article describes the steps to build a .NET 6 barcode and QR code decoding library based on [Dynamsoft C/C++ Barcode SDK](https://www.dynamsoft.com/barcode-reader/sdk-desktop-server/), as well as how to pack the library into a NuGet package.

## .NET 6 SDK Installation
* [Windows](https://dotnet.microsoft.com/en-us/download#windowscmd)
* [Linux](https://dotnet.microsoft.com/en-us/download#linuxubuntu)
* [Mac](https://dotnet.microsoft.com/en-us/download#macos)

## Dynamsoft Barcode Reader
1. Download C/C++ SDK [v9.0](https://www.dynamsoft.com/barcode-reader/downloads//#desktop).
2. Get a valid [license key](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr) for activating the SDK.

## Steps to Develop and Build .NET 6 Barcode and QR Code SDK 
1. Create a new library project:
    
    ```bash
    dotnet new classlib -o BarcodeQRCodeSDK
    ```
2. Copy shared library files from the C/C++ SDK package to the project root directory. For different platforms, the minimum required shared library files are:
    
    - Windows: `DynamsoftBarcodeReader.dll`, `vcomp110.dll`
    - Linux: `libDynamsoftBarcodeReader.so`
    - macOS: `libDynamsoftBarcodeReader.dylib`    

3. Rename `Class1.cs` to `BarcodeQRCodeReader.cs`.
4. [P/Invoke](https://docs.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke) is the technology used for bridging C/C++ and .NET. In the `BarcodeQRCodeReader.cs` file, we use `DllImport` to load the unmanaged shared library (e.g. `*.dll`, `*.so`, `*.dylib`) and define some managed methods to communicate with the native component.

    ```cs
    [DllImport("DynamsoftBarcodeReader")]
    static extern IntPtr DBR_CreateInstance();

    [DllImport("DynamsoftBarcodeReader")]
    static extern void DBR_DestroyInstance(IntPtr hBarcode);

    [DllImport("DynamsoftBarcodeReader")]
    static extern int DBR_InitLicense(string license, [Out] byte[] errorMsg, int errorMsgSize);

    [DllImport("DynamsoftBarcodeReader")]
    static extern int DBR_DecodeFile(IntPtr hBarcode, string filename, string template);

    [DllImport("DynamsoftBarcodeReader")]
    static extern int DBR_FreeTextResults(ref IntPtr pTextResultArray);

    [DllImport("DynamsoftBarcodeReader")]
    static extern void DBR_GetAllTextResults(IntPtr hBarcode, ref IntPtr pTextResultArray);

    [DllImport("DynamsoftBarcodeReader")]
    static extern int DBR_DecodeBuffer(IntPtr hBarcode, IntPtr pBufferBytes, int width, int height, int stride, ImagePixelFormat format, string template);

    [DllImport("DynamsoftBarcodeReader")]
    static extern int DBR_DecodeBase64String(IntPtr hBarcode, string base64string, string template);
    ```
5. In addition, we need to define some native structs in C#:
    
    ```csharp
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    internal struct PTextResult
    {
        BarcodeFormat emBarcodeFormat;
        public string barcodeFormatString;
        BarcodeFormat_2 barcodeFormat_2;
        string barcodeFormatString_2;
        public string barcodeText;
        IntPtr barcodeBytes;
        int barcodeBytesLength;
        IntPtr localizationResult;
        IntPtr detailedResult;
        int resultsCount;
        IntPtr results;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 56)]
        char[] reserved;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    internal struct TextResultArray
    {
        public int resultsCount;
        public IntPtr results;
    }
    ``` 
5. The memory operation between the managed structure and the unmanaged pointer is a little bit tricky. We need to use [Marshal](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal?view=net-6.0) to convert data:

    ```cs
    IntPtr pTextResultArray = IntPtr.Zero;

    DBR_GetAllTextResults(hBarcode, ref pTextResultArray);

    if (pTextResultArray != IntPtr.Zero)
    {
        string[]? resultArray = null;
        TextResultArray? results = (TextResultArray?)Marshal.PtrToStructure(pTextResultArray, typeof(TextResultArray));
        if (results != null)
        {
            int count = results.Value.resultsCount;
            if (count > 0)
            {
                IntPtr[] barcodes = new IntPtr[count];
                Marshal.Copy(results.Value.results, barcodes, 0, count);
                resultArray = new string[count];

                for (int i = 0; i < count; i++)
                {
                    PTextResult? result = (PTextResult?)Marshal.PtrToStructure(barcodes[i], typeof(PTextResult));
                    if (result != null)
                    {
                        resultArray[i] = result.Value.barcodeText;
                    }
                }
            }
        }

        DBR_FreeTextResults(ref pTextResultArray);

        return resultArray;
    }
    ```
6. Once the communication problem between managed and unmanaged code is solved, we can define some high-level C# methods:
    
    ```csharp
    public class BarcodeQRCodeReader
    {
        private IntPtr hBarcode;
        private static string? licenseKey;
    
        public static void InitLicense(string license) {
            byte[] errorMsg = new byte[512];
            licenseKey = license;
            DBR_InitLicense(license, errorMsg, 512);
            Console.WriteLine(Encoding.ASCII.GetString(errorMsg) + "\n");
        }

        private BarcodeQRCodeReader()
        {
            hBarcode = DBR_CreateInstance();
        }
    
        public static BarcodeQRCodeReader Create()
        {
            if (licenseKey == null)
            {
                throw new Exception("Please call InitLicense first.");
            }
            return new BarcodeQRCodeReader();
        }
    
        ~BarcodeQRCodeReader()
        {
            if (hBarcode != IntPtr.Zero)
            {
                DBR_DestroyInstance(hBarcode);
                hBarcode = IntPtr.Zero;
            }
        }
    
        public void Destroy()
        {
            if (hBarcode != IntPtr.Zero)
            {
                DBR_DestroyInstance(hBarcode);
                hBarcode = IntPtr.Zero;
            }
        }
    
        public string[]? DecodeFile(string filename)
        {
            if (hBarcode == IntPtr.Zero) return null;
    
            int ret = DBR_DecodeFile(hBarcode, filename, "");
            return OutputResults();
        }
    }
    ```
7. Build the source code to generate the `*.dll` file.
    
    ```bash
    dotnet build --configuration Release
    ```

## How to Generate and Publish NuGet Package
To generate the `*.nupkg` file, the easiest way is to add `<GeneratePackageOnBuild>true</GeneratePackageOnBuild>` to the `*.csproj` file:

```xml
<PropertyGroup>
  ...
  <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
  ...
</PropertyGroup>
```

Then the `*.nupkg` file will be generated automatically when you build the project.

Our package contains some native library files. To pack them into the `*.nupkg` file correctly, we set the corresponding `PackagePath` in `*.csproj` file according to the [Runtime Identifier](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog):

```xml
<ItemGroup>
  <None CopyToOutputDirectory="Always" Include="DynamsoftBarcodeReader.dll" Pack="true" PackagePath="runtimes/win-x64/native/DynamsoftBarcodeReader.dll" />
  <None CopyToOutputDirectory="Always" Include="vcomp110.dll" Pack="true" PackagePath="runtimes/win-x64/native/vcomp110.dll" />
  <None CopyToOutputDirectory="Always" Include="libDynamsoftBarcodeReader.dylib" Pack="true" PackagePath="runtimes/osx-x64/native/libDynamsoftBarcodeReader.dylib" />
  <None CopyToOutputDirectory="Always" Include="libDynamsoftBarcodeReader.so" Pack="true" PackagePath="runtimes/linux-x64/native/libDynamsoftBarcodeReader.so" />
</ItemGroup>
```

As the `*.nupkg` file is ready, we can publish it to the NuGet Gallery either via the [dotnet command](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-push):

```bash
dotnet nuget push *.nupkg -k <api-key> -s https://api.nuget.org/v3/index.json
```

or the [NuGet online page](https://www.nuget.org/packages/manage/upload).

![nuget package upload](https://www.dynamsoft.com/codepool/img/2022/03/nuget-upload.png)

Here is the final page of BarcodeQRCodeSDK: 
[https://www.nuget.org/packages/BarcodeQRCodeSDK/](https://www.nuget.org/packages/BarcodeQRCodeSDK/)   


## How to Add a .NET Library Project as a Reference Locally

For source code, add `<ProjectReference>` in `*.csproj` file:

```xml
<ItemGroup>
    <ProjectReference Include="..\..\BarcodeQRCodeSDK.csproj" />
</ItemGroup>
```

For generated `*.nupkg` file, add the package directory to NuGet source list and then install the package via `dotnet add package`:

```bash
dotnet nuget add source <package directory>
dotnet add package <package name>
```

## A Simple .NET 6 Command-line Example
1. Create a new .NET console app:
    
    ```bash
    dotnet new console -o Test
    ```
2. Install the .NET Barcode and QR Code SDK:
    
    ```bash
    dotnet add package BarcodeQRCodeSDK
    ```
3. Use the following code to decode barcode and QR code from an image file:
    
    ```csharp
    using System;
    using System.Runtime.InteropServices;
    using Dynamsoft;

    namespace Test
    {
        class Program
        {
            static void Main(string[] args)
            {
                BarcodeQRCodeReader.InitLicense("LICENSE-KEY");
                BarcodeQRCodeReader? reader = null;
                try {
                    reader = BarcodeQRCodeReader.Create();
                    Console.WriteLine("Please enter an image file: ");
                    string? filename = Console.ReadLine();
                    if (filename != null) {
                        string[]? results = reader.DecodeFile(filename);
                        if (results != null) {
                            foreach (string result in results) {
                                Console.WriteLine(result);
                            }
                        }
                        else {
                            Console.WriteLine("No barcode found.");
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
                finally
                {
                    if (reader != null)
                    {
                        reader.Destroy();
                    }
                }
            }
        }
    }
    ```
4. Run the application in Windows, Linux or macOS:
    
    ```bash
    dotnet run
    ```

    ![command-line .NET barcode and QR code reader](https://www.dynamsoft.com/codepool/img/2022/03/command-line-dotnet-barcode-qr-code-reader.png)


## Source Code
[https://github.com/yushulx/dotnet-barcode-qr-code-sdk](https://github.com/yushulx/dotnet-barcode-qr-code-sdk)