ODP.NET的版本問題一直像鬼魅般苦苦糾纏,最近又碰上了... orz

前一篇文章證實,在參考ODP.NET 10.2的ASP.NET網站,若再引用參考了ODP.NET 11.2的其他元件,將產生has a higher version than referenced assembly編譯錯誤,且無法以bindingRedirect解決。有顆共用元件要提供給多個專案使用,有些專案仍須維持ODP.NET 10.2,有些專案則已經更新到ODP.NET 11.2,且實務上無法要求大家統一。因此,要解決此一問題,我想到幾種做法:

方法一是共用元件改回參照ODP.NET 10.2,在11.2版本環境中則用bindingRedirect設定或是發行者原則檔進行繫結導向

方法二則是共用元件分別編譯成10.2與11.2兩個版本,供不同專案使用。

兩種做法都要需要準備10.2舊版ODP.NET的編譯環境,不想回頭裝舊版的任性鬼想到另一個點子--ODP.NET版本相容問題的暴力解法,決定把這個程序寫成自動工具,將來不管什麼共用元件DLL,只要有ODP.NET 11.2改10.2的需求,就拿來過一下香爐,問題馬上解決。

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Win32;
 
namespace ChgOdpRefVersion
{
    static class Program
    {
        static void Main(string[] args)
        {
            if (args.Length < 2 || !args[0].Contains(".dll") ||
                !Regex.IsMatch(args[1], @"\d+\.\d+\.\d+\.\d+"))
            {
                Console.WriteLine("Syntax Example: ChgOdpRefVersion Boo.dll 2.112.3.0");
                return;
            }
            string dllFile = args[0].Trim('\"'), version = args[1];
            string outDllFile =
                Path.Combine(Path.GetDirectoryName(dllFile),
                    Path.GetFileNameWithoutExtension(dllFile) + ".odp-" + version + ".dll");
            string ilFile = Path.GetTempFileName() + ".il";
            ildasm(dllFile, ilFile);
 
            StringBuilder sb = new StringBuilder();
            bool found = false;
            using (StreamReader sr = new StreamReader(ilFile))
            {
                string line = null;
                while ((line = sr.ReadLine()) != null)
                {
                    if (line.StartsWith(".assembly extern Oracle.DataAccess"))
                    {
                        sb.AppendFormat(@"
.assembly extern Oracle.DataAccess
{{
  .publickeytoken = (89 B4 83 F4 29 C4 73 42 )
  .ver {0}
}}
", version.Replace(".", ":"));
                        do
                        {
                            line = sr.ReadLine();
                        } while (!line.StartsWith("}"));
                        sb.Append(sr.ReadToEnd());
                        found = true;
                        break;
                    }
                    else
                    {
                        sb.AppendLine(line);
                    }
                }
            }
            if (found)
            {
                File.WriteAllText(ilFile, sb.ToString());
                ilasm(ilFile, outDllFile);
                Console.WriteLine("Done!");
            }
            else
                Console.WriteLine("Oracle.DataAccess reference not found!");
        }
        public static void ildasm(string dllPath, string ilPath)
        {
            exec(Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
                    @"Microsoft SDKs\Windows\v6.0A\bin\ildasm.exe"),
                string.Format("\"{0}\" /output:\"{1}\"", dllPath, ilPath));
        }
        public static void ilasm(string ilPath, string dllPath)
        {
            exec(Path.Combine(GetFrameworkDirectory(), "ilasm.exe"),
                string.Format("\"{0}\" /dll /output:\"{1}\"", ilPath, dllPath));
        }
        public static void exec(string program, string arguments)
        {
            ProcessStartInfo psi = new ProcessStartInfo(program, arguments);
            Process p = Process.Start(psi);
            p.WaitForExit();
        }
        //REF: http://bit.ly/PLA9Zw
        public static string GetFrameworkDirectory()
        {
            string framworkRegPath = @"Software\Microsoft\.NetFramework";
            RegistryKey netFramework =
                Registry.LocalMachine.OpenSubKey(framworkRegPath, false);
            string installRoot =
                netFramework.GetValue("InstallRoot").ToString();
            string version = string.Format(@"v{0}.{1}.{2}\",
              Environment.Version.Major, Environment.Version.Minor, Environment.Version.Build);
            return System.IO.Path.Combine(installRoot, version);
        }
    }
}

程式是以Console Application形式編譯,主要著眼於未來可加掛在Visual Studio AfterBuild事件或MSBuild程序中執行,而程式必須在有安裝.NET Framework SDK的機器上執行(才有ildasm及ilasm),透過Command視窗下達指令:

ChgOdpRefVersion myAssembly.dll 2.102.2.20

程式會呼叫ildasm將myAssembly.dll反組譯成.il檔,尋找其中的Oracle.DataAccess參考版號註記,修改成指定版號,再使用ilasm重新編譯成myAssembly.odp-2.102.2.20.dll儲存在myAssembly.dll的相同目錄下。就醬,未來就不用再為了ODP.NET版本切換傷腦筋囉~

【提醒】 純改變版號宣告的表面功夫當然不像重新編譯能解決新舊版本間的API差異,採用前請自行評估風險,但依我自己的實務使用經驗,用在ODP.NET 10.2與11.2間的切換,運作得蠻順利的,僅供大家參考。


Comments

Be the first to post a comment

Post a comment