搜尋此網誌

2024年3月13日 星期三

C# DLL 專案與 DLL 內的檔案讀取

緣起:


    我們公司的程式有個專案,它是一個 DLL 專案,用來管理一些前後台會用到的 js、css、image 資源,只要 build 完,它就變成一個 DLL 檔,資源都包含在裡面。如果那個專案裡的檔案有被新增或修改,到時要更新的話就只要搬 DLL 檔去覆蓋就好,非常的方便。

    把東西都包進 DLL 裡,然後再寫個 class 當個介面來存取 DLL 檔的資源,感覺是個很實用的做法,所以想學起來。

    這篇想要寫個測試的小程式來達到跟我們專案一樣的功能,從 DLL 裡把檔案給取出。


新增 DLL 專案並引用:


    我是先建立一個 Console 的方案,然後再加入一個 DLL 的專案。

右鍵點方案 -> 加入 -> 新增專案

選擇 "類別庫(.NET Framework)",然後點"下一步"

為專案命名,最後按下"建立"就完成了

    哦對,你 Console 跟 DLL 專案的架構是要一樣的,我有次兩個選不一樣,它在 build 時就不給我過。

    它專案預設會有個 Class1 的 cs 檔,我們不需要,所以把它給砍了,建立自己的 cs 檔,叫 "MyManager",然後改成這樣

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyLibrary
{
    public class MyManager
    {
        public static string Hello()
        {
            return "Hello";
        }
    }
}

    class 跟它的 method 記得都要設成 public

    再來回到 Console 專案上,右鍵點專案 -> 加入 -> 參考,開啟參考管理員,然後選擇 MyLibrary,最後按確定


    在我們的 Console 專案 using MyLibrary,印出 MyManager.Hello()

using System;
using MyLibrary;

namespace ConsoleApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.Write(MyManager.Hello());
            Console.ReadKey();
        }
    }
}

    如果 build 時都沒報錯,而且可以在 console 上看到 hello,那 dll 專案就成功了。


為 DLL 專案加入資源:


    右鍵點選 DLL 專案 -> 加入 -> 新增資料夾,我把新增的資料夾命名為 Image,然後再放入這張圖



    新加入的圖片需要調整一下屬性,它才會變內嵌資源。右鍵點選圖片 -> 屬性,打開屬性的視窗,修改 "建置動作",把它從原本的 "內容" 改成 "內嵌資源"


    我有觀察了把屬性改成 "內嵌資源" 前後的差異,有選擇 "
內嵌資源",build 完之後的 dll 檔,它的大小有增加了不少

圖片建置動作是"內容",build 完的 dll 是 4KB

圖片建置動作是"內嵌資源",build 完的 dll 是 212KB


    212 - 4 = 208,確實就是我們這張 jpg 在磁碟上的大小,可見圖片真的有包在 dll 裡。


從 DLL 檔裡把圖片給取出:


    繼續處理 DLL 專案,首先需要呼叫 Assembly.GetExecutingAssembly 來取得 DLL 專案的 Assembly。我在觀察 Assembly 類別有什麼方法能呼叫時,有看到它有這方法


    嗯... 看起來是我需要的,所以就寫了程式碼來試試這個功能,我為我的 MyManager 類別新增了 GetResourceInfo 方法。要使用 Assembly 要引用 System.Reflection,要使用 List<> 跟 ToList 擴充方法,需要引用 System.Collections.Generic 跟 System.Linq。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;

namespace MyLibrary
{
    public class MyManager
    {
        public static string Hello()
        {
            return "Hello";
        }

        public static List<string> GetResourceInfo()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            return assembly.GetManifestResourceNames().ToList();
        }
    }
}

    然後 Console 專案這樣寫

using System;
using MyLibrary;

namespace ConsoleApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            foreach(string sName in MyManager.GetResourceInfo())
            {
                Console.WriteLine(sName);
            }
            Console.ReadKey();
        }
    }
}

    Console 專案成功執行後,可以看到以下的結果


    哇 ~~ 真的有抓到資源,而且它的名稱有個規律,{Dll專案名稱}.{資料夾}.{檔案名}。既然都知道名稱了,那大概就能依名稱來取得資源了,對吧 (?
    
看起來是這樣沒錯

    讚哦,如果能取得檔案的 Stream,那我們就能在程式裡把 Stream 的內容給 Copy 到 FileStream 中,把 DLL 的圖片取出,存到檔案目錄。

    所以,在 MyManager 加入 GetResourceStream 的方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;

namespace MyLibrary
{
    public class MyManager
    {
        public static string Hello()
        {
            return "Hello";
        }

        public static List<string> GetResourceInfo()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            return assembly.GetManifestResourceNames().ToList();
        }

        public static Stream GetResourceStream(string sName)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            return assembly.GetManifestResourceStream(sName);
        }
    }
}

    然後 Console 的專案這樣寫

using System;
using System.Reflection;
using MyLibrary;
using System.IO;

namespace ConsoleApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string sCurrentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            string sImagePath = Path.Combine(sCurrentPath, "saved.jpg");

            using(FileStream fileStream = new FileStream(sImagePath, FileMode.Create, FileAccess.Write))
            {
                Stream stream = MyManager.GetResourceStream("MyLibrary.Image.499106987838013856.jpg");
                stream.CopyTo(fileStream);
            }

            Console.ReadKey();
        }
    }
}

    12行 : 取得程式目前執行路徑。
    13行 : 要把圖片存成 "saved.jpg"    
    17行 : 這邊傳入的 string 名稱就是我們前一個實作看到的那個。
    18行 : 用 stream 的 CopyTo 把內容複製到 FileStream 裡。
    

    程式執行結束後,可以在 Console 專案的 bin/Debug 目錄下看到那個 saved.jpg 檔案,確實就是我們放入 DLL 的那張圖片。



沒有留言:

張貼留言