Quantcast
Channel: 黑暗執行緒
Viewing all 2311 articles
Browse latest View live

大型物件 Json.NET 序列化經驗一則

$
0
0

附檔管理模組裡採用 JSON 格式保存暫存物件,將附檔物件序列化暫存成檔案,稍後寫入資料庫時再還原取出資料,直覺又方便。不料因附檔物件內含檔案內容(byte[])體積龐大,在處理極端案例時踢到記憶體不足的鐵板。

批次作業程式為 32 位元模式,依經驗記憶體上限約 1.8 GB,一開始很直覺地將資料用 JsonConvert.SerialObject() 轉成 JSON 字串再用 File.WriteAllText() 寫成檔案,之後用 File.ReadAllText() 讀取 JSON 字串,再以 JsonConvert.DeserializeObject<T>() 還原回物件:

  • 寫入
    File.WriteAllText(tempFileName, JsonConvert.SerializeObject(attFile));
  • 讀取
    var attFile = JsonConvert.DeserializeObject<AttachmentFile>(File.ReadAllText(filePath, Encoding.UTF8));

這個寫法處理 50 MB 大小的檔案不成問題,但在處理一個 86MB 檔案時(轉為 JSON 約 116MB) 冒出 OutOfMemoryException 錯誤。實測單獨讀檔並 JsonConvert.DeserializeObject() 可過關,加上其他執行邏輯消耗更多記憶體就爆了。

最簡單的解法是將程式改為 64bit 模式,在我的 16GB 機器上可用記憶空間可放大二、三倍以上。但靠加大記憶體空間鋸箭,在正式環境多人使用時效能堪慮,設法減少非必要的記憶體用量才是治本之道。

依 JSON.NET 官方建議,改用 Stream 方式可節省記憶體: (參考: Performance Tips)

To minimize memory usage and the number of objects allocated, Json.NET supports serializing and deserializing directly to a stream. Reading or writing JSON a piece at a time, instead of having the entire JSON string loaded into memory, is especially important when working with JSON documents greater than 85kb in size to avoid the JSON string ending up in the large object heap.

將讀取程式修改如下,順利通過 116MB JSON 測試:

using (var streamReader = new StreamReader(filePath))
{
using (var reader = new JsonTextReader(streamReader))
    {
        var serializer = new JsonSerializer();
return serializer.Deserialize<AttachmentFile>(reader);
    }
}

好景不長,不久更上層樓,遇上 212MB 檔案,換成序列化為字串再寫檔的做法爆了,也得修改:

using (StreamWriter sw = new StreamWriter(tempFileName))
{
using (JsonTextWriter writer = new JsonTextWriter(sw))
    {
       var serializer = new Newtonsoft.Json.JsonSerializer();
       serializer.Serialize(writer, attFile);
    }
}

本次心得: 處理大型資料物件 JSON 轉換,改用 Stream 方式讀寫檔案較節省記憶體,提升效能。

【後記】雖然改走 Stream 通過 200MB 大檔的序列化反序列化考驗,究竟我還是回頭檢討了超大附檔的必要性,最後透過 PDF 解析度調整將檔案縮小到 40MB 以下,別逼系統超越顛峰,以免翻車墜崖。


使用 DebugDiag Tools 分析 ASP.NET 站台記憶體耗盡問題

$
0
0

同事報案,某測試站台不定期會發生 OutOfMemeoryException 記憶體不足錯誤,接獲通報立刻趕往事故現場,問題網站已吃掉 1.8GB 記憶體,差不多是 32 位元模式可用記憶體的上限。廢話不多說,開啟 32 位元工具管理員(C:\Windows\SysWOW64\TaskMgr.exe 參考) 擷取 Memory Dump 檔。

從工具箱搬出 CPU/Memory 茶包分析的小型戰術核武 - DebugDiag 2 Tools,之前處理的都是 CPU 滿載案例,記憶體用盡分析倒是頭一遭。選用 DotNetMemoryAnalysis - Managed Memory Analysis。(另一個選項 MemoryAnalysis – Memory analysis including LeakTrack and heap reporting 主要用於連取多個 Dump 檔抓漏,而本案例重點在找出記憶體被誰用掉)

1.8GB 檔案不小,看了一陣子進度條,取得 DebugDiag Analysis 分析報告:

身為記憶體問題生手,只能約略看出大概,問題有三:(第四點測試台開 debug 天經地義,可忽略)

  1. Dump 檔載入了 1023 個動態組件
    分析報表貼心附上 Debugger Laddy, Tess 的文章,指出有可能與 XML 元件 Memory Leak 有關。
    -NET Memory Leak- XslCompiledTransform and “leaked” dynamic assemblies – If broken it is, fix it
    -NET Memory Leak- XmlSerializing your way to a Memory Leak – If broken it is, fix it you should
  2. 有 75721 個物件等待 Finalization
  3. Process 中有超過 3 個 AppDomains

關於第三點,我在 AppDomain 統計表找到明顯異常,從 9/14 晚上 11 點起,每隔幾秒就冒出一個 Domain,載入 18 個組件,組件大小 74MB,數量高達 1022 個,數字似乎與第 1 點的 1023 個動態組件呼應。

將資料用 Excel 重排,確認 1022 個 AppDomain 的執行時間從 9/14 23:28:04 到 9/15 00:57:49。

一般 ASP.NET 程式很少會另建 AppDomain 跑程式,對照 IIS Log,很幸運是測試台又在冷門時段 Log 很乾淨,發現有個 RDLC 報表匯出 PDF 的排程從 23:28 左右開始執行,大約送出了 2500 次 Request,於 00:54:58 起出現 HTTP 500,時間相當吻合。(推測原因就是記憶體用盡)

依據文件(Expression Evaluation in Local Mode – Brian Hartman's Report Viewer Blog),RDLC 會在沙箱 AppDomain 中執行,而文末提到了 VS2010 版 ReportViewer 曾存在 AppDomain Leak Bug(但後來已修復),AppDomain 爆增極有可能是 RDLC 報表引起的。網站使用的版本 ReportViewer 2012 (ver 11),推測已無文章中所提  Bug,但我想起上回研究 RDLC 子報表效能時學到「子報表會另起 Instance 執行」,同事補上同一組程式在正式環境執行未傳出記憶體問題,進一步檢查發現問題主機的報表版本有使用子報表,而正式台已改用 List 奧步版,如此看來子報表極有可能就是嫌犯。將測試台 RDLC 也換成非子報表版本後有一段時間未再觀察記憶體爆表狀況,看來子報表是凶手的嫌疑極高,若之後有新發現,再做追蹤報導。

看完 DebugDiag Tools 在本案例強大的火力展示,讚嘆之餘,照慣例又到了呼口號時間:

DebugDiag Tools 好威呀!

【茶包射手日記】新增檔案後卻無權限修改

$
0
0

一個詭異的狀況。

為了在測試網站測網頁,我透過該主機的網路分享建立一個 HTML 檔案,測完一回,調整程式想存檔時 Notepad++ 彈出以下訊息:

這是 Notepad++ 寫入檔案存取被拒的貼心 SOP,當寫入本機檔案遇到權限不足,改用管理者身分多可克服;但本案例檔案來自網路分享資料夾,切換成本機管理者也無濟於事。但這個訊息指出一項事實 - 我沒權限覆寫剛剛才建立的檔案!新增檔案後卻不能修改是什麼奇妙的 NTFS 設定?

反覆檢查網路分享跟 NTFS 的權限設定,甚至改成 Everyone 完全控制,還是無法寫入。

更扯的事情來了,反覆測試,有幾次不知怎麼忽然可以成功寫入,但多測幾次又存取被拒,讓我懷疑問題不在 NTFS 權限設定,而是有其他因素造成,一個常見原因是被檔案被其他程式開啟鎖定造成無法更新,當訊息不明確時容易跟權限不足搞混。

終於,我找到問題來源 - 是檔案總管的預覽功能搞鬼!

原來,要編輯檔案時我習慣在檔案總管先點 Test.html 再按右鍵呼叫 Notepad++ 編輯檔案,因為預覽功能開啟,預覽視窗會讀取 Test.html 並顯示網頁內容,還一併鎖定 Test.html 不允許其他程式修改,造成我遇到的無權限寫入現象。此時只要改選其他檔案或空白處讓 Test.html 從預覽視窗消失,就能正常覆寫了。

將同樣的操作拉回本機資料夾演練,發現預覽功能並不會防礙檔案寫入,而且還會偵測到檔案被修改重新產生預覽畫面,故此一狀況似乎只發生在網路磁碟機情境。不過,未來如遇到 PDF、Excel、Word 等可預覽檔案出現無權修改的詭異狀況,我會優先檢查是不是預覽功能作祟。

【答客問】ClosedXML 日期資料解析測試

$
0
0

網友 Danny 在舊文留言提問關於 NPOI 讀取 Excel 日期,"2017/9/23"被轉成"23-九月-2017"的問題,我已棄用 NPOI 投向新歡 ClosedXML多年,沒打算再花時間研究,於是題目改成: 面對相同文件,ClosedXML 能否順利過關?

取得 Danny 提供的測試樣本,挺有趣的,共有四欄(F、G、N、AS)包含日期資料,第一列有欄名分別為A6、A7、A14與A45:

四欄儲存格格式各有千秋,A6 設成自訂 yyyy/mm/dd:

A7 設為文字,其中包含 1953.9.12、1968/1/13、1953/09/12 甚至 1953912 ,格式很混亂:

A14、A45 設為日期,取格式 2012/3/14:

測試程式如下,為因應 Excel 中五花八門的日期格式,我寫了一個 ParseDateValue() 接收 IXLCell 進行解析。若 cell.DataType == XLCellValues.DateTime 就直接回傳 cell.GetDateTime() 並將儲存格改為藍字;若否,則取得 cell.GetString() 再以 DateTime.TryParseExact() 配合預先定義的格式嘗試解析。(註: yyyyMd 格式存在爭議,例如: 2012111 可能是 1/11 也可能是 11/1,視為無效)

class Program
    {
staticvoid Main(string[] args)
        {
            var path = "D:\\日期格式.xlsx";
using (var wb = new XLWorkbook(path))
            {
                var ws = wb.Worksheets.First();
                var r = 2;
while (!ws.Row(r).Cell("F").IsEmpty())
                {
                    var row = ws.Row(r);
                    var a6 = ParseDateValue(row.Cell("F"));
                    var a7 = ParseDateValue(row.Cell("G"));
                    var a14 = ParseDateValue(row.Cell("N"));
                    var a45 = ParseDateValue(row.Cell("AS"));
                    Console.WriteLine(
$"A6:{a6:yyyy-MM-dd} A7:{a7:yyyy-MM-dd} A14:{a14:yyyy-MM-dd} A45:{a45:yyyy-MM-dd}");
                    r++;
                }
                Console.ReadLine();
using (FileStream fs = new FileStream("d:\\Output.xlsx", FileMode.Create))
                {
                    wb.SaveAs(fs);
                }
            }
        }
 
static DateTime? ParseDateValue(IXLCell cell)
        {
if (cell.DataType == XLCellValues.DateTime)
            {
                cell.Style.Font.FontColor = XLColor.Blue;
return cell.GetDateTime();
            }
            var dateString = cell.GetString();
            DateTime d;
            var dateFormats = "yyyy/M/d,yyyy/M/d,yyyy/MM/dd,yyyy.M.d".Split(',');
foreach (var fmt in dateFormats)
            {
if (DateTime.TryParseExact(dateString, fmt, null,
                    DateTimeStyles.None, out d))
                {
                    Debug.WriteLine($"Custom Format: {fmt} for {dateString}");
return d;
                }
            }
            Debug.WriteLine($"無法識別:{dateString}");
returnnull;
        }
    }

執行結果如下:

 

除 1953912 之外,其餘日期值均被正確解讀。A3 欄儲存格採自訂格式 yyyy/mm/dd、A14 及 A45 欄採日期格式,如上圖所示,ClosedXML 均視為 XLCellValues.DateTime 故變成藍字,唯一的例外是 A14 欄第一筆 1985/7/26,原因是它前方加了單引號(如下圖所示)將其宣告為字串,故要改由 DateTime.TryParseExact() 解析。

至於 A7 儲存格格式為文字,需取回字串再自行轉換。

實驗完畢,證實 ClosedXML 可正確解讀日期格式不一的 Excel 文件,我則學到透過 DataType 屬性偵測資料型別的技巧。若想進一步 DataType 與 DateFormat/NumberFormat 的應用,可參考 ClosedXML 官方範例: Data Types · ClosedXML-ClosedXML Wiki · GitHub

Coding4Fun-試聽 16 進位字串表示的 MP3 內容

$
0
0

鹹蝦專案(利用閒暇經營的 Side Project)遇到的需求,先前把 MP3 音效資料整進 SQL 資料表轉成 IMAGE 資料型別,查詢起來像這樣:

如果我想試聽這段聲音該怎麼辦? 網路上可以找到一些 T-SQL 範例,將 SQL 裡的二進位資料匯出成檔案。不過這樣子每次試聽的步驟有點麻煩: 用 SELECT 取得某一筆 IMAGE 內容 -> 以內容及檔名為參數呼叫 Stored Procedure -> 在檔案總管點選檔案試聽。

我心中理想的操作方式是: SELECT 選出數筆內容 -> 選出想試聽的內容貼到「試聽工具」輸入區 -> 試聽工具自動播放 MP3 內容。像這個樣子:

程式透過 jQuery paste 事件偵測 <textarea> 被貼上新內容,自動將 16 進位字串送到後端解析成 byte[] 存入 Cache,之後由前端 <audio>取回 MP3 內容播放,一氣喝成,用 Inline ASPX Page寫 100 行內搞定,連我自己都覺得帥氣~ 特發文紀念。:P

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Runtime.Caching" %>
<scriptrunat="server">
protectedvoid Page_Load(object sender, EventArgs e)
    {
var mode = Request["mode"];
if (mode == "parse")
        {
var hex = Request["hex"];
try
            {
if (string.IsNullOrEmpty(hex) || hex.IndexOf("0x") != 0)
                {
thrownew ApplicationException("Invalid hex data");
                }
                hex = hex.Substring(2); //remove 0x
var data = newbyte[hex.Length / 2];
for (var i = 0; i < data.Length; i++)
                {
                    data[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
                }
var token = Guid.NewGuid().ToString();
                MemoryCache.Default.Add(token, data, new CacheItemPolicy()
                {
                    AbsoluteExpiration = DateTime.Now.AddSeconds(30)
                });
                Response.Write($"OK:{token}&len={data.Length}");
            }
catch (Exception ex)
            {
                Response.Write($"Error: {ex.Message}");
            }
            Response.End();
        }
elseif (mode == "download")
        {
var data = MemoryCache.Default[Request["token"]] asbyte[];
            Response.ContentType = "audio/mpeg";
            Response.BinaryWrite(data);
            Response.End();
        }
    }
</script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Play MP3 hex data</title>
</head>
<body>
<div>
<button>Play</button>
<audio controls="true" autoplay="true"></audio>
</div>
<textarea rows="10" cols="80"></textarea>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script>
        $("button").click(function() {
            $.post("PlayMP3Hex.aspx", { mode: "parse", hex: $("textarea").val() })
                .done(function(res) {
if (res.indexOf("OK:") === 0) {
                        $("audio").attr("src",
"PlayMP3Hex.aspx?mode=download&_t=" +
                            Math.random() +
"&token=" +
                            res.substr(3));
                    } else {
                        alert(res);
                    }
                });
        });
//貼上內容後0.1秒自動解析試聽
        $("textarea").bind("paste", function() {
            setTimeout(function() {
                    $("button").click();
                },
                100);
        });
</script>
</body>
</html>

【茶包射手日記】分離式冷氣出風無冷度故障經驗

$
0
0

小木頭跑來報案,房間冷氣完全不冷。

前陣子蝦皮免運時失心瘋採購一批儀器,把居家度量衡全面電子化(左起 PH 酸鹼計,土壤濕度酸鹼日照三用錶,電子游標卡尺,紅外線測溫槍),這回輪測溫槍派上用場了。

冷氣機出風強弱、運轉模式、溫度調整等操作一切正常,唯一有問題是即使溫度調到 20 度,出風口溫度還是 30 度,跟室溫相同,毫無冷度可言。先做好基本功,洗了濾網(就像作業系統有問題要先做 Windows Update是一樣道理),無效。

翻箱倒櫃找出保證書,摸摸鼻子上網報修順便爬文,看到好多用沒幾年花五千一萬修理的鬼故事,頓時心涼了半截... 但在某篇文章看到關鍵字心生一計,找到配電箱關電重開機,它竟然就好了就好了就好了。不禁感嘆,「3R大絕」真是工作除錯居家生活不可或缺的 3C 技巧啊!

好景不常,隔日小木頭又上門報案說冷氣不涼。故技重施,再次恢復正常。但依多年茶包射手的直覺,心頭有不祥預感,果不其然,一個多小時後接獲第三度報案,自此關電重開密技完全失靈。玩票性質的故障排除人員乖乖閃開,交給專業的來。

等到上班日,原廠來電約時間前來維修。第一次來的技師先生拿著遙控器按了上下上下左右左右BA幾下(依女王轉述,猜想是某種偵錯密技讓冷氣機顯示故障代碼),觀察冷氣運轉狀態又從陽台去摸摸室外機,判定室內機沒問題,可能是冷媒洩漏或室外機機板故障,但室外機作需要裝配,需另約工班處理。隔天約好時間來了另一位技師先生,工具箱、零件、梯子裝配齊全,感覺前一天是先遣偵察兵,今天輪裝甲兵登場。做完簡單檢測,技術爬出去拆開室外機檢查,猜測是室外室感溫器故障。更換零件後,冷氣立即恢復正常,收費兩千大洋。(光是能從樓梯間氣窗爬出去這招就值了,修冷氣這行高個子跟胖子還做不來哩)

室外機的玄機要當蜘蛛人才能實地觀察,我好奇又懶得動手,爬文找到室外機內部照片(一起小強入侵造成故障的案例,另外還找到壁虎肇事燒壞兩台室外機機板的新聞),不過倒沒找到感溫器位置的照片。

室內機感溫器故障不冷比較容易理解,室溫感測錯誤就不會啟動壓縮機。但,室外機為何也需要感溫器? 又為何室外感溫器故障會導致「冷氣出風如常但沒有冷度」?

爬文找到一些線索

定頻冷氣室內機有二個感溫器 : 1.室溫感溫器  2. 化霜感溫器
定頻冷氣室外機沒有感溫器

變頻冷氣室內機有二個感溫器 : 1.室溫感溫器  2. 化霜感溫器
變頻冷氣室外機有五個感溫器 : 1. 壓縮機吐出溫感溫器 2.凝縮器感溫器 3. 室外溫感溫器 4. 高壓管(細管)感溫器 5. 低壓管(粗管)感溫器

在另一篇變頻空調機簡易檢修文則解釋了室外機感溫器的用途

  1. 室外環境溫度感測器(安裝在室外機冷凝器,由塑膠件支撐)
    A)室外溫度過低或過高時系統自動保護
    B)製冷或制熱時用於控制室外送風機風速
  2. 室外盤管溫度感測器(安裝在室外機散熱器管路)
    A)制熱時用於室外機除霜
    B)製冷或制熱時用於過熱保護或防凍結保護
  3. 室外壓縮機排氣溫度感測器(安裝在室外壓縮機排氣管)
    A)壓縮機排氣管溫度過高時系統自動進行保護
    B)在變頻空調機中用於控制電子膨脹閥開啟度以及壓縮機運轉頻率的升降。

有了這些線索,鍵盤柯南登場:兇手是「室外壓縮機排氣溫度感測器」(指)

我推測的理由是: 若感溫故障導致 1A/2A/2B/3A 屬異常主機應會收到訊號,可由遙控器或顯示面板得知狀態;而 1B 控制室外機送風不致影響冷度;至於 3B 是變頻省電關鍵,由室外機排氣溫度與設定溫度差異控制壓縮機功率高低。假設以下情境,感溫器故障但仍能傳回訊號,只是送回的是錯誤數據,例如: 10 度。程式判斷此溫度比設定的 20 度還要低,壓縮機不轉也夠冷,導致冷氣機未發現感溫故障正常出風,並產生自己正把室外10 度低溫(其實是30度)源源不絕往室內輸送的錯覺。這是我想到冷氣機未偵測到感溫器故障又毫無冷度的一個好解釋,不過一切全是猜想,就當成某個資訊領域茶包射手在空中比畫兩下,就以為自己解開了冷氣機不冷之謎的妄想吧~ 哈。(如果有專業人士路過,再請不吝開示)

總之,冷氣終於有冷氣的樣子了,讚! (實測發現,目標溫度設定 26 度時,室內機出風口內部最低可測得 14 度低溫)

【茶包射手日記】VAIO 筆電開機卡住經驗

$
0
0

我的 VAIO T13 筆電入手於 2012 年 Windows 8 上市之際,近五歲高齡但狀況不差,直到前陣子異常頻傳: 開始是桌面偶爾凍結,滑鼠游標能移動,但點選及鍵盤操作全無反應,關電重開機有效,但沒多久又再發生,常要重開數次才能完全排除,排除後可正常使用一陣子,但當機頻率愈來愈高。

終於來到這一天,某次當機重開後,電腦停在 Windows 啟動畫面轉圈圈(如下圖),等了三五分鐘還進不去,一直轉圈轉到人快被催眠,只好長按電源暴力關機重開,再開還是卡在轉圈畫面。反覆重試有時會進入 Windows 10 自動修復程序但以修復失敗收場,有兩次曾成功進入桌面,但沒多久又畫面凍結當掉,再陷入無法開機的循環。

太好了,我可以買新電腦了大事不妙,莫非我的老 V 氣數將盡?3C 設備用了四五年壞掉,最大的煩惱不是修不好,而是「修理 vs 換新」的艱難抉擇。過保固的維修費用往往逼近老機器殘值,有時再加一點都能買台新的,而修好能再用多久又是項人品考驗。To be or not to be, that it the question.

胡亂測試過程有一項重大發現 -- 拔掉電池,筆電就能順利開機且使用如常! 反覆測試數次,幾可推斷電池是導致電腦異常的源頭。在 FB 分享心得,具有相關背景的 Jesse 提到這可能是 EC(Embeded Controller,嵌入式控制器)故障造成的。

依據參考來源,EC(Embed Controller,嵌入式控制器)是一個16位單片機,它內部本身也有一定容量的Flash來存儲EC的代碼。在筆記型電腦中,無論你是在開機或者是關機狀態,EC都一直保持運行。EC其實就是一個單片機,是傳統KBC的延伸,基本上筆記型電腦上面的許多功能都是通過EC完成的,如:鍵盤、觸摸板、電池續航功能、風扇控制、溫度監控、待機或關機時期作業、智能電池電力檢測及充放電控制、特殊Hot Key… 等。

EC 與開機程序有關,與鍵盤滑鼠有關,與電池充放電控制也有關,因此電池故障導致前述種種異常是種合理解釋。只是我無法確定問題出在電池晶片還是主機EC,網拍買得到得新電池,但若問題不在電池就白花兩千大洋賭注有點大。另一個解法是找筆電維修店檢修,VAIO 在台灣市佔不高,維修選擇不多。怎麼想都是麻煩事,所以先逃避再說,拔掉電池把老 V 當成「要插電才能用的偽筆電」先擋著用了一陣子,倒也相安無事。

老天自有安排,就在上星期,比老 V 早一年服役的老 i7,原本配備兩顆 WD 黑標 1TB SATA3 硬碟,在今年五月有一顆升天了,不料幾個月後,剩下的硬碟飽受思念之苦傷心過度,幾天前在事件檢視器泣血成串,留下鮮紅一片,跟著老情人一起化成蝴蝶飛走了~ 冷氣壞了,筆電壞了,現在連電腦硬碟也壞了! (暗)

既然得去一趟光華商場補貨,就帶了老 V 碰碰運氣。VAIO 在台灣非主流廠牌,問了好幾家筆電維修店都搖搖頭說沒法修,連電池都問不到貨,最後才問到一家筆電維修店會修 VAIO。聽完我的故障狀況描述及電池禍首推論,正在拆筆電兼顧櫃枱的工程師大哥當場駁回電池是問題根源的猜想,斷定問題出在主機板,此時店內走出另一位較資深的先生,對問題是否出在電池態度較保留,提議幫我叫貨測試換新電池是否能解決問題,若非電池問題就付退貨及檢測費 800 元,感覺賭注有點大加上要留機檢修,決定先試試其他機會。最後,我在一家電池專賣店問到電池有現貨,好心的老闆還一口答應讓我先測過沒問題再買。現場換了電池重開機幾次,一切正常,判定搭配新電池可藥到病除,開開心心買單,老 V 又是一尾活龍。(不過,重出江湖的老 V 已另有安排,這裡先賣個關子)

筆電修好了但心中留下謎團,電池裡故障讓筆電開不了機的晶片,到底長什麼樣子? 網路上找不到前人分享的心得,熬不過好奇心,我做了一件危險的事 - 拆開電池一探究竟。警告: 拆卸鋰電池是危險行為,可能引發火災或爆炸(請看VCR),建議不要隨便嘗試。

小心翼翼打開電池外殼,裡面由多個 3.7V 鋰電池扁平 Cell 構成,整排四個平鋪,最左最右疊了兩層共計六個。

接頭處有塊電路板,上面有幾顆晶片,欠缺相關背景加上有一大塊被黑膠密封住,沒能力再推敲更多細節。連結頭有七根 PIN 腳加上電路板有晶片,意味電池可以對主機傳送資料,晶片故障狂發錯誤訊息讓主機端的 EC 程序陷入錯亂是有可能的。進一步確認需要更多設備、時間與精力,也超出電子麻瓜的能力範圍,就此打住。(註: 拆解研究完畢,已小心用三秒膠將電池外殼黏好送交資源回收)

JavaScript 開發者 ES6 小抄筆記

$
0
0

在網路上看到這篇 - Modern JavaScript Cheatsheet - Modern JS Cheatsheet,給既有 JavaScript 開發者看的小抄,指出因應 ES6/ES2015 新標準的注意事項。(註:如果你被 ECMAScript 6、ES6、ES2015 等術語搞到頭很昏,可以參考這篇) 而這篇則是我以一個 jQuery/TypeScript/C# 開發者角度閱讀小抄的筆記整理,主要供自己備忘(謎: 天哪,這年頭連看小抄都要做筆記了嗎?),順便分享給類似背景的同學參考。(若覺得筆記過於簡要,強烈建議閱讀原文,原文有不少文件詳解連結,包你看到懂)

var, let, const的差別與使用時機

let 宣告只在 Scope 內部有效,不會干擾外一層或全域範圍的同名變數,建議取代 var 使用;而 const 特性與 let 相似,差別在於 const 宣告後變數不能再改指向其他內容。

Arrow Function

相當於 TypeScript 或 C# LINQ 中的 (b) => b*2; 或 () => { ... } Lambda 寫法。有一點值得注意,在 Arrow Function 中 this 代表外一層執行環境的 Context,與 function() { }會另起 this 行為不同,這點與 TypeScript 依循的準則一致,小心不要踩坑。(參考: 再談TypeScript的this )

Desctucting 宣告

用 { prop1, prop2 } = someObject 快速取得 someObject 的 prop1, prop2 屬性值。[ x, y ] = [ 1, 2] 可快速取得 x=1, y=2。

Array 內建 map()/filter()/reduce()

前二者相當於 jQuery map()grep(),reduce() 則是將陣列元素彙總成單一值。(例如: 數字加總)

Spread Operator "..."

可用於函式參數

function myFunc(x,y,...params) { ... }
myFunc("a","b","c","d","e");
//結果: x="a", y="b", params=["c","d","e"]

可用於陣列
const a1=["a","b","c"];
const a2=[a1,"d","e"]; // -> [["a","b","c"],"d","e"]
const a3=[...a1,"d","e"]; // ->["a","b","c","d","e"]

也可用於物件屬性
const myObj={x:1,y:2,a:3,b:4};
const {x,y,..z}=myObj;
//結果: x=1,y=2,z={a:3,b:4}
const n={x,y,...z};
//結果: n={x:1,y:2,a:3,b:4};

物件屬性簡寫

const x=1, y=2;
const myObj = { x, y };
//myObj.x=1, myObj.x=2

常寫 C# LINQ 的同學應該不陌生,相當於超精簡的 new { Prop1, Prop2 } 匿名型別寫法

原生Promise

var p = new Promise((resolveFunc, rejectFunc) => {
//作業成功時呼叫resolveFunc(resolveArg)
//作業失敗呼叫rejectFunc(err)
});
p
//呼叫resolveFunc觸發then
.then((resolveArg) => { ... })
//呼叫rejectFunc觸發catch
.catch((err) => { ... });

參考: 小試 JavaScript Promise

Template Literal

BJ4,就是TypeScript 1.4 加入的超級好用 Template String,用過一次就回不去了。

export/import 模組引用

//** blah.js **
export const pi = 3.14;
export const authorName = "Jeffrey";
 
//** samp1.js **
import {pi,authorName} from  "./blah.js";
//pi==3.14, autherName=="Jeffrey";
 
//** samp2.js **
import * as blah from "./blah.js";
//blah.pi==3.14, blah.authorName=="Jeffrey"
 
//** samp3.js **
import { pi as PI } from "./blah.hs";
//PI==3.14

實務上還有一種常見用法: 若模組只匯出單一變數、函式、物件,可寫成 export default theVarToExport,引用端可任意引用命名 import anyName from "./blah.js" 取得其預設匯出項目。

對初學者像謎一般的this

延伸閱讀:
Javascript - 淺談this與Closure
Javascript .apply()應用實例

ES6 class 關鍵字

用來簡化原本繁瑣的 prototype 宣告。相比之下, TypeScript 的 class 更強大,可以直接享用介面、繼承等特性。

async/await

跟.NET 4.5的async/await概念相似,要搭配Promise使用,可要求等待Promise()非同步執行完傳回結果再繼續往下執行

參考: JavaScript 非同步程式革命-async、await 與 TypeScript 2.1

Truthy/Falsy

if (blah) 在什麼情況下 if 會成立?
當 blah 為以下內容時 if 將不成立,否則都視為 if 成立

  • false
  • 0
  • ""(空字串)
  • null
  • undefined
  • NaN

附註: 以上這些特性必須在支援 ES6/ES2015 規格的瀏覽器上才能運行,如果你跟我一樣必須考慮 IE 又想使用這些新方法,那麼 TypeScript 允許你用新語法寫程式並將它們轉成老瀏覽器也能執行的相容語法,更甭提強型別、物件導向這些讓程式易於維護擴充的優勢,絕對是一個好選擇。(沒聽過 TypeScript? 請參考: Hello, TypeScript!)


當心營運資訊裸奔-網站偵錯 Log 檔常犯的資安錯誤

$
0
0

「寫 Log」是很有效的線上系統偵錯手段,就像飛機黑盒子或行車記錄器,能在事故發生後提供寶貴資訊,釐清肇事原因,還能用於責任歸屬舉證。例如:

  1. 系統不定期爆炸,由 Log 歸納每次發生在某使用者進行某項操作之後
  2. 客戶否認下單,調閱 Log 舉證登入時間,來源 IP 以及操作順序,萬一客訴鬧上法院還可當作呈堂證供
  3. 資料庫發生 Deadlock,由 Log 找出事件當下兩名使用者執行的作業及輸入參數,鎖定可疑 SQL 語法進行調查

簡單來說,愈重要的網站系統愈需要 Log 機制協助維運,而 Log 該保存什麼內容,視作業性質而定,不外乎時間、IP 來源、使用者身分、操作參數… 等。而Log 儲放位置有很多選擇,本機檔案、Log 資料庫、Log 伺服器… 等。其中以本機檔案執行成本低、速度快,不易故障,可靠性最高,是最常見的 Log 選項。


Image by thom

由於 Log 會內含機敏資訊,若是處理不慎,Log 檔可能成為營運機密或個資洩漏的源頭。前陣子剛好看到營運資料透過 Log 檔在網站裸奔的案例,猜想有些開發朋友還沒意識到這類潛在風險。本文將整理我所想到處理網站 Log 常犯的錯誤及其可能衍生的資安風險:

未排除個資或機密資訊

以事後偵察的角度,保留資訊愈完整,愈能還原現場,故開發人員有時會將所有輸入參數與輸出結果都留在 Log 裡,但這裡有個常犯錯誤,機密敏感資料例如:身分證號、密碼、銀行帳號、信用卡號、地址、電話… 等也被寫入 Log,而系統管理者處理 Log 檔的資安敏感度不如原始碼、資料庫嚴謹,導致 Log 檔案外流的機率較其餘二者為高,一旦 Log 檔落入賊人之手,其中的個資會惹來無窮麻煩。

解決之道在於調整程式邏輯,排除個資或機密資料,若必須保留也請打上馬賽克,例如:身分證號寫成 A12XXXXX89,密碼只留頭尾一碼(甚至全部遮罩),電話號碼改成 0937XXX123,信用卡號改成 1234-XXXX-XXXX-5678。不保留完整內容,在某些情境會影響還原現場的完整度,但必須取捨,依實務經驗,即使參數被馬賽克,仍可從相關資訊識別請求來源,應付大部分偵錯需求綽綽有餘。衡量其風險及必需性,針對機密或個資,強烈建議排除或馬賽克處理。

將 Log 檔直接放在網站目錄下

有些開發者選擇在網站資料夾下開個 Log 之類子錄(例如:wwwroot\MyApp\Log)放記錄檔。將存取 Log 跟網站放在一起,相關資訊集中管理貌似天經地義,但要當心一個天大陷阱: 一旦 Log 路徑及檔名被惡意人士掌握,對方開個瀏覽器輸入 URL 就能光明正大將 Log 下載回去把玩,要是 Log 還包含個資、帳號密碼、交易內容... 事情就大條了。說不定接著會上演「黑先生,您最近有在我們網站買了一本單元測試的藝術 [第二版],因系統問題被設成 12 期分期每月扣款,需要您到 ATM 機器解除設定...」

因此,除了個資及敏感內容要馬賽克,也請不要將 Log 放在可直接下載的資料夾範圍。一般來說,外界很難得知 Log 路徑檔名,但無法排除以下狀況:

  1. 取得原始碼或網站檔案備份
  2. 由其他網站或測試台觀察到目錄結構
  3. 使用讀心術、通靈術瞎猜矇到

一旦路徑被掌握加上 Log 附檔名是網站允許的 MIME 型別(例如: .txt),後果會十分慘烈,不可不慎。如果一定要集中放在網站目錄,請設定排除規則禁止外部透過 GET/POST 該路徑下的內容,或是善用 ASP.NET 的 App_Data 隱身特性,但強烈建議在網站目錄之外為 Log 另開專屬目錄存放更安全。

備份與複製檔案時未排除 Log

在實務上,基於管理、偵錯需求有時我們需要備份或複製整個網站或整台主機檔案,對於程式檔案管理者的警覺性不像對待資料庫或資料檔那麼高規格,加上沒有意識到包含商業機密的 Log 檔案也在其中,備份檔可能被放在不夠安全地方且未嚴格限定存取,導致 Log 內容外流。

依我的經驗 ,備份或複製網站程式檔較常發生(換版前備份、複製到其他主機重現問題),將 Log 移出網站資料夾,除非備份整顆硬碟或刻意選取 Log 資料夾執行,Log 被意外備份或複製的機率可大幅下降。這也是我強烈建議另外為 Log 建立專屬資料夾的理由,除了避免不小心被備份或複製,管理者操作時能更明確認知到他在處理包含營運資訊的內容(前題是要有 Log 會內含機密資訊的認知),而不是當成程式有所輕忽。

非必要的偵錯詳細資訊

這個是較罕見的低級錯誤,我曾見過開發者為了偵錯時能萬無一失,居然在 Log 檔中印出資料庫連線字串備查。(我的老天鵝)
敢這麼做多半基於一個假設: Log 檔永遠被會嚴加保管,限制管理者存取。理應如此,但你知道的,實務上總會有豬隊友,有意外。面對資安議題,永遠假設機制會失靈,人為必有疏失就對了。決定要在 Log 寫入機密資料前,若評估一旦外洩會有嚴重後果,除非有絕對理由必須承擔此風險,否則別這麼做。

 

最後總結處理網站 Log 的幾點資安原則:

  1. 排除機密敏感內容,若必須保留要加馬賽克
  2. 避免允許使用者透過網站直接下載 Log 檔
  3. 為 Log 另建專屬資料夾避免因備份或複製而外流
  4. 開發者及系統管理者應建立 Log 可能內含機密資訊的認知
  5. 永遠假設 Log 可能外流,避免寫入非必要的機密資訊

TypeScript Template String 中文字元被轉為 \uxxxx 格式

$
0
0

Template String是 TypeScript 1.4 起加入的超好用功能(跟 C# Interpolated Strings 字串插值一樣,是用過就上癮的好物),今天發現一個問題 - Template String 內含的中文字元會被強制轉成 \uxxxx。(這種表示法術語叫 Unicode Escape Sequences)

例如以下範例:

var t2 = `ABC-中文-${n}`;

會變成:

var t2 = "ABC-\u4E2D\u6587-" + n;

想爬文找出避免轉換的做法,在 TypeScript Github 找到一則討論: Incorrect compilation of template strings containing Unicode characters

有不少開發者也發現了這個問題,認為 TypeScript 沒必要把所有非 ASCII 字元都換成 \uxxxx 格式。這個問題被認定是個 Bug,而開發團隊成員 DanielRosenwasser 提出解釋:

The reason I didn't preserve the original text when I implemented this was to avoid re-scanning the string when performing emit. We basically take the internal textual representation and call something that's basically an augmented JSON.stringify. This is good because it replaces newlines with \n,

However, the function takes a conservative approach and uses a unicode escape if something falls outside of ASCII. We take advantage of this if you ever use an extended unicode escapes in all strings.

For instance, the string "\u{12345} också" will get rewritten to

"\uD808\uDF45  ocks\u00E5"
in ES5 instead of

"\uD808\uDF45  också"
So what you're noticing is that this function does a teensy bit too much.

Also, given the fact that TypeScript can _finally_ assume the existence of JSON.stringify, this fix is probably a LOT easier.

DanielRosenwasser 提到不保留原始文字是為了避免注入過程需要重新掃瞄字串,程式內部借用了類似 JSON.stringify() 的函式進行轉換(好處是換行符號會換成 \n),該函式為求保險將 ASCII 字元表(0x00-0x7f)以外的字元通通換成 \uxxxx,雖然如此一舉解決罕用 Unicode 字元的轉換,但該函式的轉換範圍太過火,產生我們觀察到的後遺症。

好消息是 TypeScript 終於能假設 JSON.stringify 一定存在,這問題將比較容易修復,而壞消息是這個 Bug 被排在 Future 清單,何時會修正不得而知。

我唯一想到的 Workaround 是改寫成 `ABC-${"中文內容"}`,很醜且中英文穿插時會很噁心,算不上什麼好法子。

所幸,這在實務上不算嚴重問題。 TypeScript 預設會產生 js.map:

故實際偵錯時中斷點是停在 .ts 原始碼,可直接看到原始中文,.js 中文變成編碼的困擾不大。

只有一點要注意: 當你試圖在 js 搜尋中文關鍵字,要有可能會撲空的心理準備。

TIPS-VS2017 無法編譯新版 TypeScript 定義檔

$
0
0

以下為在 Visual Studio 2017 使用 TypeScript 定義檔可能出現的狀況。由 NuGet 或 Github 取得 TypeScript 定義檔,卻噴出大量編譯錯誤無法使用:

Visual Studio 2017 已更新至 9/19 才發行的 15.3.5 版本,TypeScript for Microsoft Visual Studio 也被一併更新至 15.3.10723.1:

前幾天剛好聽同事提起 VS2017 與 TypeScript 可各自更新(參考: Updating TypeScript in Visual Studio 2017 · Microsoft-TypeScript Wiki),猜想可能是我機器上的 TypeScript 版本太舊, Visual Studio 不認得定義檔使用的新語法(開源程式作者通常很早就開始應用新版特性)。查了一下,我專案預設使用的 TypeScript 版本是 2.1 版本,切換到 2.3 版即可正常編譯。

除非專案 TypeScript 程式有新版相容問題且不想修改升級,建議更新到最新版的 TypeScript SDK for Visual Studio並設成 Use latest available,可避免再遇類似狀況。至於主機安裝了哪些版本 TypeScript,可檢查 C:\Program Files (x86)\Microsoft SDKs\TypeScript:

擦屁股的藝術 – 聊聊前人 Bug 的緊急修補

$
0
0

身為程式開發人員,多少都有這種經驗:

線上系統出錯,原開發者已浪跡天涯,程式碼沒人熟,老闆面色猙獰問誰會修。
(遇到這種擦屁股的屎缺,同事們默契十足全都退了一步 )
老闆說「很好,想不到你剛進公司就想立此奇功!好好幹,公司不會虧待你的」...
你說「暗陰羊咧,陳近南是你?」「沒問題,這交給我!」(心中滿是狂奔的羚羊)

這類狀況跟修自己的 Bug 截然不同,有幾個特點:

  • 狀況緊急必須限時修好,砍掉重練不是選項。
  • 屬臨時救急,策略上不打算多花資源深入了解及翻修。例如: 有計劃另建新系統取代,舊系統已進入插管維生階段。
  • 處理者對系統架構、程式碼全然不熟悉,時間壓力下難以全面了解,也不敢大幅更動,需以最小幅度修改解決問題為目標,是一種「微創手術」的概念。
  • 最重要的一點,修復過程通常會五毒攻心氣血逆流,只求速速搞定:
    「喵的,又不是我搞壞的,為什麼是我」
    「暗!這是什麼鬼寫法啦?」

基於上述因素,修補者常會無視原本程式碼的明顯缺陷,陷入「讓修改幅度極小化」的迷思。最後,雖然只改一個字元就把問題修掉,但錯失了防範類似問題再次發生的機會,為下次爆炸埋入引信。

用一個範例來模擬情境。假設有個網頁程式由資料庫取得下拉選單選項,並取第二選項為預設值,前人的程式這麼寫:

    ddlSource.Items.Clear();
    ddlSource.Items.AddRange(
        GetSourceOptions()
        .Select(o => new ListItem(o.Value, o.Key)).ToArray());
    ddlSource.SelectedIndex = 1;

有一天資料庫端忽然冒出新選項,原本的第二選項變成第三個,使用者抱怨送出表單的預設選項錯誤,後續作業大亂。

你被指派修復這個問題,千辛萬苦追程式碼找到問題點,基於「微創手術」的概念,所以...

ddlSource.SelectedIndex = 2;

改完收工,跟老闆回報問題修好了。

只改了一個字元就把Bug修好了,但,這是良好的修復方式嗎?

小天使說: 如果下回資料庫再被塞入一筆資料,是不是系統又要再壞一次? 又不知是哪個倒楣鬼被踢下來辛苦追 Code,找出這段再改一次。(說不定還是你)
小惡魔說: 程式又不是我寫成這樣,我只奉命修好它,再壞掉也不是我的責任。更何況,使用者說這個選項不太會動,這次異動是個意外,以後應該不會再動。

看似兩難情境,分析利弊後不難抉擇:「如果成本不高,你應該把它改成強韌不易出錯的版本」,理由很簡單:

  • 依據墨菲定律,別人愈說不會修改,它愈可能被修改
  • 踢到石頭跌倒,把石頭搬到路邊防止別人摔跤,累積陰德抵過扶老太太過街三次
  • 「上回 XXX 改好過,這回又壞了」。就像修過的水管又漏水,就算主因是管線設計先天不良,你覺得倒楣鬼水電工會不會背負功夫差做事兩光的評價?

SelectedIndex = 2 寫法必須建立在「資料來源項目不變,順序固定」的前題上,在我眼裡脆弱得像玻璃,稍有風吹草動便碎裂一地。如果修改成本不高,改用防禦式設計可讓程式更強韌,不易因外部因素故障。這點也是有些人的程式三天兩頭故障,有些人的程式像大同電鍋一用數十年都不壞的關鍵之一。

基於預設選項順序可能不固定的考量,程式可以改成這樣:

    ddlSource.Items.Clear();
    var sources = GetSourceOptions()
        .Select(o => new ListItem(o.Value, o.Key)).ToArray();
    ddlSource.Items.AddRange(sources);
    var defaultSource = sources.SingleOrDefault(o => o.Value == "A2");
if (defaultSource == null)
thrownew ApplicationException("GetSourceOptions未包含A2項目");
    ddlSource.SelectedIndex =
        sources.ToList().IndexOf(defaultSource);

       
透過 SingleOrDefault() 用 Value 值找出預設選項的順序,既使查詢結果大風吹,它也能自動選對預設項目。要是資料庫裡的預設項目不知何故被他X的誤刪,這段程式還能明確指出問題出在"GetSourceOptions未包含A2項目",而不是噴出莫名其妙的 NullReferenceException,是不是貼心多了?

更進一步,如果某一天,預設值他X的要從 A2 改成 A3(對,那個規格書說永遠固定的A2),只能挖出程式碼重新編譯才能調整。於是我們還可以把預設值改成由 web.config appSettings 決定:

staticstring defaultSourceValue = 
    System.Configuration.ConfigurationManager.AppSettings["DefaultSource"] ?? "A2";
 
//...略...
 
    ddlSource.Items.Clear();
    var sources = GetSourceOptions()
        .Select(o => new ListItem(o.Value, o.Key)).ToArray();
    ddlSource.Items.AddRange(sources);
    var defaultSource = sources.SingleOrDefault(o => o.Value == defaultSourceValue);
if (defaultSource == null)
thrownew ApplicationException($"GetSourceOptions未包含{defaultSourceValue}項目");
    ddlSource.SelectedIndex =
        sources.ToList().IndexOf(defaultSource);

醬子,連改掉預設項目都不用重新編譯程式呢,是不是好捧捧?

原本只要 2 改成 3 就可以交差,多寫幾百個字元是比較費工,但說穿了仍在舉手之勞的範圍,多花不到10分鐘讓程式材質從玻璃升級到不鏽鋼,很划算。

擦屁股是苦差事沒人愛,你可以衛生紙抹一下交差,也可以搬出免治馬桶座洗個痛快,有人還會順便把脈開藥治好烙賽,
即使能做到什麼程度也與經驗能力相關,但最重要的是開發人員的心態。(Yo Yo,拎杯也有 Freestyle )

要成為別人眼中專業又可信賴的開發人員,先從擦得一手好屁股開始吧~

TypeScript Module 簡單練習

$
0
0

ES6 引進 Module(模組化) 概念,每個 Module 自成獨立 Scope,各 Module 可自由定義變數、型別,要開放外界存取的項目再透過 export 開放。當需要引用其他 Module 時,則必須明確使用 import 匯入才能使用。如此各 Module 可獨立開發維護而不彼此干擾,甚至能實現需要時再動態載入,大幅提升開發及應用彈性。

TypeScript 也支援 Module,我目前的專案沒用到這麼高級的技巧,原本並不打算深入了解,但發現苗頭不對。開源專案如 Angular 2、Vue 早就 Module 滿天飛,不懂 Module 就看不懂範例程式及原始碼,遇到狀況也不知從何查起,感覺自己很廢,好吧,硬著頭皮也要學會。

開始之前推薦幾篇先修知識文章:

先簡單歸納幾則重點:

  1. TypeScript 程式碼只要出現 import 或 export,就會被視為 Module,編譯結果與一般 TypeScript 大不相同。
  2. TypeScript Module 編譯產生的 js 不能單純用 <script src=".."> 載入,需依賴載入機制,載入機制分兩種:
    * 靜態載入: 編譯時將 Module js 檔打包成單一檔案,例如 Node.js 使用的 CommonJS
    * 動態載入: 網頁執行時視需要下載 Module js,例如: AMD 與 RequireJS
  3. 由於瀏覽器對 ES6 支援度不足,故需要額外的 Module 系統輔助, 目前還在百家爭鳴: AMD、CMD、closure、CommonJS、ES6。Visual Studio 的 TypeScript 編譯設定也可指定要用哪一種系統。
  4. 如果不想動用 Node.js,選擇 AMD Module 系統,網頁端使用 ReuqireJS 載入是最簡單的做法。

有了初步認識,來寫一個簡單範例當作練習。

在 ASP.NET MVC 專案 Scripts 目錄開一個 lab 資料夾,放入多個 ts 檔:

Module System 選擇 AMD:

在四個 ts ,我分別練習了不同的 export/import 寫法。首先是 common.ts,class 及 interface 前方加上 export 開放 Message 類別及 IOutput 介面:

//直接在const、function、class、interface前加上export關鍵字
export class Message {
    Time: Date = new Date();
    Text: string;
    constructor(text: string) {
this.Text = text;
    }
}
 
export interface IOutput {
    Write(msg: Message);
}

console.ts 先從 common.ts 引用 IOutput 及 Message,最後將自己定義的新類別 ConsoleOutput 跟來自 common 的 Message export 出去(註: 如果自己不用純分享,可以寫成 export { Blah } from "./blah")。

import { IOutput, Message } from "./common";
 
class ConsoleOutput implements IOutput {
    Write(msg: Message) {
        console.log(`${msg.Time.toLocaleTimeString()} ${msg.Text}`);
    }
}
 
//各模組可export相同名稱項目
export const Version = "ConsoleOutput 1.0";
 
export { ConsoleOutput, Message };

另一個 dom.ts,import 的做法不太一樣。 * as com 會將 common 所有 export 項目包入名為 com 的變數,使用時需寫成 com.IOutput、com.Message,有點像 Namespace 的觀念。dom.ts 跟 console.ts 都匯出了名為 Version 的 const,由於引用方會用 import 明確宣告,我們不用擔心名稱衝突。最後 export 時加上 default 關鍵字,引用方可以直接寫 import 名稱 from "./dom" 取得 DomOutput。

//取得模組所有匯出項目,包成變數com的成員
import * as com from "./common";
 
class DomOutput implements com.IOutput {
    Write(msg: com.Message) {
var div = $("<div></div>");
        div.text(`${msg.Time.toLocaleTimeString()} ${msg.Text }`);
        div.appendTo("body");
    }
}
 
//各模組可export相同名稱項目
export const Version = "DomOutput 1.0";
 
//export為預設項目,import時可直接引用
export default DomOutput;

測試程式 main.ts 如下,分別由 dom.ts/console.ts import 取得 DomOutput、ConsoleOutput、Message,餘下的寫法跟一般 TypeScript 無異。

import DomOutput from "./dom";
import { ConsoleOutput, Message } from "./console";
 
var c = new ConsoleOutput();
c.Write(new Message("console test"));
var d = new DomOutput();
d.Write(new Message("dom test"));

編譯出來的 main.js 如下: (注意: TypeScript Module 編譯成的 js 不能以 <script src="scripts/lab/main.js"> 直接載入,會出現 "Mismatched anonymous define() modules" 錯誤,必須改用 require.js require() 函式載入。)

define(["require", "exports", "./dom", "./console"], function (require, exports, dom_1, console_1) {
"use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
var c = new console_1.ConsoleOutput();
    c.Write(new console_1.Message("console test"));
var d = new dom_1.default();
    d.Write(new console_1.Message("dom test"));
});
//# sourceMappingURL=main.js.map

測試網頁如下: (require.js 可使用 NuGet 安裝或從官網下載,接著用 require(["scripts/lab/main"]) 就能順利載入 main.js 並執行之。)

<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8"/>
<title>TypeScript Module Test</title>
</head>
<body>
<scriptsrc="Scripts/jquery-3.2.1.min.js"></script>
<script src="Scripts/require.js"></script>
<script>
        require(["scripts/lab/main"]);
</script>
</body>
</html>

測試 OK,網頁與 Console 都成功印出訊息。而我們也能觀察到 RequireJS 先載入 main.js 再陸續載入 dom.js/console.js/common.js 的過程,代表它能解析相依性自動載入所需模組。

以上就是簡單的 TypeScript Module 演練,謝謝收看。

Vue 筆記1–也來寫 Vue.js 好了

$
0
0

觀注 Vue.js 已有很長一段時間,上個月我慣用的前端元件庫-Kendo UI 正式支援 Vue 及 React,感覺時機成熟,是可以投入心力研究的時候了。

說來尷尬,手邊已有用 knockout.js 開發的線上系統,開發專案的主力則走 Angular.js 1。短短幾年就在公司裡搞出兩套框架(我還為 KO 跟 NG 都寫了 CRUD 基礎模版及元件庫),讓同事無所適從天怒人怨(我也是萬般不願意呀,這就是他X的前端咩),貿然宣布要玩第三套新框架,有預感會被拖到牆角餵磚頭。

不過衡量情勢,維持現狀不是選項,遲早還是要做打算。Knockout.js 已熄火這點無庸置疑,Angular 版本已更新到 NG4,不升級相關資源將逐漸凋零,長遠來看還是得跟上主流。但 NG2 形式大改,NG1 專案升級形同砍掉重練,佔不到什麼便宜,不如抛棄歷史包袱,重新評估擇選擇好了。

當前檯面公認的主流前端框架不脫 React、Angular 與 Vue.js 三家。網頁開發者各有自己的設計哲學與偏好,只要系統穩定效能不差又好維護,能抓耗子就是好貓(其實還要不挑嘴不亂叫不隨地大小便不抓壞沙發,想當好貓不簡單呀),網站設計方式與開發哲學,是決定開發者與前端框架是否看對眼的關鍵。只是有情人未必終成眷屬,因父母之命(老闆指定)、媒妁之言(顧問推薦)、家族身世(前人專案選定)、政治聯姻(資源豐富可少奮鬥30年)… 貌合神離的怨偶也不在少數。

以我慣用的網站設計模式,核心邏輯主要靠 C# 後端處理,即便前端改用 TypeScript 比 JavaScript 更適合寫中型規模以上的程式,但相同功能用 C# 寫開發效率及順手度還是完勝 TypeScript,更何況部分作業(如資料庫存取、檔案讀寫、加解密運算)在性質上屬伺服器端限定。因此我的網站設計,TypeScript/JavaScript 就專注於負責即時性的網頁 UI 呈現及 AJAX 資料交換,核心邏輯主要靠 C# 實作。

在這樣的設計概念下,Angular 內含完整 MVC、UI Routing,對我來說「太多」,功能強大與擴充性的代價是複雜度高,未蒙其利前都屬無效益的學習成本。我心中理想的前端框架只需要專注打理好 UI 層 View 這段就夠了,反正 Model 跟 Controller 交給 ASP.NET MVC 做得又快又好。(雖然改用 Angular 多時,但 Knockout 只專注 View 的定位更接近我的期望,我跟 Knockout 是情深緣淺呀)

回到下一代框架的選擇,來看看 React、Angular 4 與 Vue.js。這三者能在競爭激烈的前端框架界脫穎而出,其效能與功能肯定不在話下,選擇考量聚焦在誰比較符合我的應用情境與個人偏好就好。(網路上有不少比較文章可參考: JavaScript 框架大比拼:Vue、React、AngularJS 與 Angular2 該用哪一個? - TechOrangeReact,Vue,Angular简介 - 简书)

React 可以寫行動版 App 這點很吸引人,但與傳統先做出 HTML 再加掛標籤加上 MVVM 的設計思維截然不同,學習曲線不會太平滑。另外重要一點是我的開發生涯從 ASP 義大利麵時代開始,歷經 WebForm、MVC,到最後連 CSS、JS、HTML 都獨立成檔,已經習慣個別維護不互干擾的優點,看到 React 強推 JSX 又把 HTML、CSS、JavaScript 摻在一起做瀨尿牛丸,在我眼中就像美麗的番邦公主,有種國情不同的隔閡感(個人偏好,勿戰);蘭妃 Angular 4 聲勢浩大,靠過去肯定能吃香喝辣,但有前面提到太過龐大複雜的缺點;見到 Vue.js 只專注於 View 層的理念,加上講求輕巧簡單易學,讓我想起了純元皇后(Knockout),去吧,嬛嬛,就決定是你了。(甄嬛傳混搭寶可夢來著)

現在才開始學習 Vue.js 已經慢了別人一大截,但好處是網路上的學習資源十分豐富,攀附在前輩身上吸經驗值覺得很讚。列舉我這段時間參考的一些資料:

初步心得

  • Vue.js 語法與 Angular 相似度很高,以我過去累積的 NG 經驗,在看完 Kuro 的兩小時簡介影片後就有自己可以出新手村的錯覺。(依過去學習新技術的經驗,八成是錯覺,魔鬼都在他X的細節裡呀!)
  • Kuro 介紹影片裡一些令人驚喜的語法: {{{ rawHtml }}}、.once、.sync(註: 2.3版又加回來了)、limitBy,filterBy,orderBy Filter,2.0 版都拿掉了,不知道該哭該笑,難過的是少了簡便寫法可用,慶幸的是還好不是先狂用一番等升級再連夜改掉。
  • 以 Angular 1 熟手的觀點,Vue 在語法與概念上高度相似,範圍縮小到只觀注 DOM 呈現與互動,少了 NG 裡彈性但複雜的 設計模式(例如: Dependency Injection),很容易上手。基本上花一天把官方教學(中文版)從「介紹」到「過濾器」幾十個章節讀完就差不多夠了,範圍小加上官方教學很完整(而且有中文),跟 Knockout 一樣好上手,在我心中 Vue 更適合成為 NG1 的接班人。
  • 工作上採用 KO 與 NG 專案運作正常,短期內不會積極改版翻寫,手上的 NG 共用元件庫與 CRUD 模版已很順手成熟,正在開發的專案也會繼續使用。對於 Vue 我只打算先在 Coding4Fun 閒暇專案試槍,等累積足夠經驗再評估帶到工作上。
  • 我打算跟大部分 Vue 或前端開發者走不一樣的冷僻小徑: 用 Visual Studio 2017(不是 Visual Studio Code) + ASP.NET MVC + TypeScript 寫 Vue.js,盡量避免扯到 npm、webpack 讓開發背景需求單純化,期望若有朝一日要在公司推廣,阻力可以小一點。
    初步爬文,這麼搞的人真的不多,使用 VSC、npm 寫 Vue 有很多方便的套件模版相助,教學文件充足,要用 VS2017寫又不想碰 npm,有許多環節得靠自己打通。管他的,就先試試吧! (握拳) 但也說不定深入了解後很快就投降了 XD

好一片蒼翠茂盛的森林,但我好像選了左邊這條路。 XD

之前為 KO 跟 NG 寫過三十多篇筆記,依循慣例,Vue 筆記也要來囉~

小黑 ThinkPad 懷舊暨迎新

$
0
0

我人生買的第一台筆電是小黑 ThinkPad X21,CPU 是當年的主流 Pentium III 700MHz,記憶體還插滿封頂直上"384M"呢! 加上 Dock 有的沒的,十幾年前一口氣花掉菜鳥工程師兩個月薪水。仗著經常在外遊走唬爛工作需要,高舉「投資自己」大旗,說穿了也在享受敗家樂趣 XD 當年能從電腦背包掏出一台輕薄小黑做簡報,講得好不好是一回事,光氣勢就取得先機。不過不得不承認,小黑在設計、用料方面真沒話說,踩上去也不會壞的螢幕背蓋、貼心的鍵盤頂燈、比觸控板更精準易控的小紅點、手感絕佳的鍵盤。(難忘美好的敲擊手感,我後來甚至買了 UltraNav 鍵盤接 PC 用)

從防潮箱挖出明代古物,鋰電池早已死透,但插電居然還能開機! 可惜上下鍵12年前就壞了進 BIOS 沒法調時間,再也開不進 Windows XP。

順便曬一下當年買的 Dock(套在筆電背後,讓筆電可以使用印表埠、RS232 序列埠、光碟機、3 1/2" 軟碟機... 一堆年輕人沒聽過的古董玩意兒)、WiFi PCMCIA 網卡,跟 IBM 340MB MicroDrive(CF 記憶卡大小的機械式硬碟),拍完照乖乖放回防潮箱,下回再見天日不知何年何月。

前陣子老 VAIO 先頻頻當機終至無法開機,雖然幸運查出是電池問題,換新電池後已康復。但,冥冥之中一切早有安排,VAIO 壞掉期間我預做了採購新筆電的市場調查評估,在此時家裡 PC 的硬碟恰巧也壞了,內務府順勢稟報,皇后大人老早就在抱怨她的專用 PC 又老又舊又慢,因為少用鍵盤滑鼠常被拔走移做他用,臨時要處理事情連台電腦都沒有… (以下省略三千字,奏摺太長朕沒讀完) 我於是考慮還是該買新筆電,讓修好的老 VAIO 退居第二線回鄉服務以保耳根清靜。好死不死前陣子評估筆電去過 Lenovo 網站,之後爬文滑臉書甚至看自己的部落格,到處都是 ThinkPad 25 週年紀念限時優惠的 AdSense 廣告,鼓吹自訂規格升級記憶體、SSD、三年保固有半價優惠,臨門一腳則是連我要加買放公司的第二顆整流器也一起包了,根本是為我量身打造的促銷,加上之前使用小黑的美好回憶湧上心頭,腦波一弱就…

薑! 薑! 薑! 薑~

我的全新行動配備: ThinkPad T470p i5 7440HQ + 16G RAM + 256G SSD + 2560x1440 IPS。

Mobile01 關於 T470 的精美詳實開箱文很多,在此不班門弄斧了,以上照片就當開箱交差。

試玩一天的感想:

  1. 外殼質感不錯,接縫精密度與上蓋密合度挺好,螢幕的金屬鉸鏈十分穩固但轉動很順(這點大勝老 VAIO T13)
  2. 原本在 i5/i7 間猶豫,但 T470p 若選 i7 一定得配獨顯,對我是發熱耗電卻沒大用的雞肋,評估 i5 7440HQ 與 i7 7800HQ 都是四 Core 頻率相同,只差在 Thread 數是 4 vs 8,非強調多核運算的應用效能 7440 不輸 7800,省錢又不綁獨顯,i5 勝出。試了一些日常使用,感覺效能的確不輸家裡的 i7 2600。
  3. 指紋登入好好用! (顯示為從沒用過的土包子)
  4. 比較懷念 RGB 三原色 IBM 字樣 Thinkpad Logo(見本文第一張照片),每回開機看到 Lenovo 紅底白字的大 Logo,我有面前正攤著一條 LEVIS 牛仔褲的錯覺。
  5. 聽說近代筆電拿掉硬碟動作燈是趨勢,但不知道磁碟現在有沒有在忙,心理超不踏實。 (有在工作列顯示 HD 活動的軟體替代方案,但沒法在當機或開關機關鍵期提供資訊,功能不大)
  6. 要不要從 1920x1280 FullHD 加價升級成 2560x1440 WQHD 當初也很掙扎。有一種說法是 WQHD 塞進 14" 螢幕,字型不調大很傷眼,調大則又失去高解析度的意義。原本想去光華看看實機,問過一輪才知客製規格沒有展示機可以摸,只能賭一把。
    初步試用的感想: 就算字調大可視內容沒有變多,字體細緻度大增倒很賞心悅目,至於實際使用會不會處處是坑留待時間驗證。

    2560 寬度有個優點,左右開弓兩個視窗並列時各自的顯示空間都還堪用,方便對照做事。
  7. 有些舊程式沒考慮到 Windows 字型縮放,版面會跑掉。不過這問題無關WQHD,只要調了 Windows 字型大小就會爆炸,故不列入優劣評比。
  8. 跟 iPad/iPhone/Mac 相比,Windows 字型總不如 Apple 家族好看。
    將瀏覽器預設字型改成思源體後體驗明顯提升,但在某些網站會被保守的 CSS 打回原形,呵!
  9. 字型調大能克服大部分解析度過高的問題,但不是全部。像螢幕擷圖寬度動輒一千多 Pixel,有時必須縮到 25% 以符合常用圖檔尺寸,縮小後的原圖很吃眼力,需要 Zoom In 才好操作,這點有待適應。(下圖為 WQHD 桌面檢視 571x443 圖檔的比例示意)

    (另外有想到兩個 Workaround: 拉到 1280 或 1024 外接螢幕操作,或暫時切換成 1280x720)
  10. 在我心中小黑傳統七列鍵盤的手感已成傳奇,如今改用孤島式鍵盤自然不可能追上,但回饋感比預期的好很多,有點小驚喜。
    忽然想到一事,從櫃子挖出大掃除時本要丟掉的小紅點備品,居然有再派上用場的一天,呵~

Vue筆記2-在 ASP.NET 專案使用 Vue.js

$
0
0

相信大家看完官方教學已經躍躍欲試,就讓我們動手在 VS2017 ASP.NET 專案開個網頁試試 Vue.js。

好消息是 NuGet 上使用 vue 關鍵字就能找到 Vue.js 作者(Evan You, 尤雨溪)自己維護的 Vue 套件,Developer 版本包含較完整詳細的錯誤訊息,如果你沒有自虐傾向,建議裝 Vue.js.Developers.Version。(註: 這兩天剛好發佈了 Vue 2.5 版,NuGet Package 版本近期應該會更新)

安裝 Package 後 /Scripts/vue.js 已就定位,新增一個 Lab2\First.html 加入幾行程式:

<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8"/>
<title>Vue Lab 2</title>
</head>
<body>
<divid="main">
<inputtype="text"v-model="firstName"/>
<inputtype="text"v-model="lastName"/>
<br/>
<span>{{ fullName }}</span>
</div>
<scriptsrc="../Scripts/vue.js"></script>
<script>
new Vue({
            el: "#main",
            data: {
                firstName: "Jeffrey",
                lastName: "Lee"
            },
            computed: {
                fullName: function () {
returnthis.firstName + " " + this.lastName;
                } 
            }
        });
</script>
</body>
</html>

搞定收工,就這麼簡單!

不過如果要玩真的,我還是會選擇用 TypeScript 寫 Vue,初步試了發現若不依循 npm、webpack、VSC 這類主流前端開發模式,單純用 VS2017 + TypeScript + Vue 這條路不如想像中好走... 但我們先不要破壞寫出 Hello World 的喜悅氣氛,下回再談。

Vue筆記3-Vue TypeScript 定義檔簡便做法

$
0
0

要用 TypeScript 寫 Vue 程式,首先要取得 Vue.js TypeScript 定義檔才能享受強型別的好處。

Vue 2.0 釋出於 2016/9/30(最新版為 2.5 版),NuGet 上的 vue.TypeScript.DefinitelyTyped 更新時間為 2016/9/26,版本只到 1.0,己不適用最新版 Vue.js。(前端開發者已多改從 npm 體系取得定義檔,猜想 NuGet 定義檔因此不再更新)

因此,我們可改由 Vue Github 取得最新定義檔: https://github.com/vuejs/vue/tree/dev/types

Github 上的 Vue TypeScript 定義檔除了 vue.d.ts 之外還有好幾個檔案,有個 index.d.ts,同時每個 d.ts 出現的大量 export/import,代表它們 TypeScript Module 系統,你的 TypeScript 檔案必須先 import { Vue } from "vue" 才認得 new Vue()。過去用 TypeScript 寫 Knockout、Angular 1,NuGet 裝好 TypeSciprt 定義檔開始幹活的做法已不適用 Vue 2.0 的 TypeScript 開發。然而,一旦使用 import 會讓我們的 .ts 變成 Module 的副作用,得引進 AMD/RequireJS 或使用 Browserify或 Webpack 等工具打包才能用在網頁上,十分麻煩。(延伸閱讀: TypeScript Module 簡單練習 )

我希望 TypeScript 寫 Vue 可以比照 jQuery/Angular 1 下載 d.ts 就開心寫 Code,不要改變過去單純的開發模式,為達成此理想,得花點功夫處理定義檔。

將前述的一堆 d.ts 檔放入 Scripts/typings/vue 目錄,但由於它採用 Module 系統,不像 jQuery.d.ts 只要放在 typings 目錄就直接生效,我們需要加點工。

如果你跟我一樣不想為了喝牛奶養牛,為了加定義檔被迫使用 TypeScript Module 系統(當然,使用 Module 有很多額外好處,但需付出複雜化的代價,在簡單應用情境有揮牛刀之嫌),請在 Scripts/typings/vue 新增一個 vue-global.d.ts,加入以下內容 :

//https://stackoverflow.com/a/43257916/288936
import * as _vue from "./vue";
 
declare global {
const Vue: typeof _vue.Vue;
}

vue-global.d.ts 將引用 vue Module 並將 Vue 定義開放為全域範圍。

接著如下圖所示,一般 .ts 不必使用 import 也能大方享用 Vue 強型別囉~

JavaScript 中文排序問題

$
0
0

今天才發現 JavaScript 中文字串排序有個大問題! 下圖是 KendoGrid 在 Chrome 使用 JavaScript 排序的結果,如圖所示,一到七由小到大排序結果為一、七、三、二、五、六、四,既不是依筆劃,也不是依注音: (SQL 的中文定序就區分筆劃跟注音,例如: Chinese_Taiwan_Stroke_CI_AS vs Chinese_Taiwan_Bopomofo_CI_AS 參考)

爬文後得知這是 JavaScript 中文字串排序的己知問題(我 Lag 真大),字串型別有個 localeCompare()可傳入語系參數進行比較,貌似能解決問題但實際不行。

我寫了測試程式如下。測試字元陣列擷取自 BIG5 字碼表,前面的 BIG5 內碼較小,筆劃較少,並插花加入黑、暗兩個超過五劃的字。分別測試三種排序方法,分別是 直接使用 sort()、sort() 搭配 localeCompare() 不指定語系、sort() 搭配 localeCompare() 指定語系,排序結果以 document.write() 印出。

<html>
<bodystyle="font-size: 9pt">
<script>
var raw = "一乙丁七乃九二人八力十三丈久元六公四黑暗";
var ary = [];
for (var i = 0; i < raw.length; i++)
  ary.push(raw.substr(i, 1));
document.write("原始順序(BIG5/筆劃)<br />")
document.write(JSON.stringify(ary, null ," "));
document.write("<hr />")
ary.sort();
document.write("內建 sort()<br />")
document.write(JSON.stringify(ary, null ," "));
document.write("<hr />")
ary.sort(function(a,b) { return a.localeCompare(b); });
document.write("localeCompare 排序<br />")
document.write(JSON.stringify(ary, null ," "));
document.write("<hr />")
ary.sort(function(a,b) { return a.localeCompare(b, "zh-TW"); });
document.write("localeCompare zh-TW 排序<br />")
document.write(JSON.stringify(ary, null ," "));
</script>
</body>
</html>

以下擷圖分別是 Edge、IE、Firefox 的執行結果,如圖所示,內建 sort() 順序即一開始 KendoGrid 案例的排序結果,而 localeCompare 排序與 localeCompare zh-TW 排序結果相同,雖然與 BIG5 順序有一些出入,至少有遵循筆劃由少到多的順序。

內建 sort() 應是依據 UTF-8 Byte 排序,數字部分依序為一、七、三、二、六、四,使用 Encoding.UTF8.GetBytes() 轉成位元組可證實推測:

同樣的測試在 Chrome 及 Safari 就精采了,sort() 與 localeCompare() 不指定語系的排序結果相同,即上述 UTF8 轉 Byte 後的順序;而 localeCompare() 指定 zh-TW 語系的排序結果讓人莫名其妙,暗字衝到第一個,我說不出是依什麼規則。

另外我還試了用 C# 排序,其結果與 Edge、IE、Firefox 的 localeCompare() 排序順序一致。

由以上測試,我的結論是 localeCompare() 無法跨瀏覽器實現符合預期且一致的中文字串排序,可能因瀏覽器實作不同出現非預期結果,若求保險還是在 C# 或資料庫端處理排序為上。

IE showModalDialog + IFrame 內嵌網頁無法複製貼上

$
0
0

今天遇到的奇妙 IE 問題。同事報案,有個網頁單獨開啟操作正常,使用 ModalDialog 顯示時無法複製貼上。( Ctrl-C/Ctrl-V 快速鍵與右鍵選單同步失效)

深入研究後發現這現象在特殊條件下才會發生: 網頁 A 先以 showModalDialog 顯示網頁 B,網頁 B 以 <iframe> 內嵌來自另一站台的網頁 C,此時在網頁 C 上將無法執行複製貼上作業。

使用以下程式重現問題。

Parent.html

以<iframe>內嵌跨站台(localhost vs 127.0.0.1,視為不同站台) Child.html,另有按鈕以 showModalDialog() 彈出 Dialog.html。

<!DOCTYPEhtml>
<html>
<head>
<script>
function test(url) {
            showModalDialog(url, "",
"dialogTop:10px;dialogLeft:10px;dialogHeight:400px");
        }
</script>
</head>
<body>
<h4>Parent</h4>
<buttononclick="test('dialog.html')">Modal Dialog</button>
<br/>
<iframesrc="http://127.0.0.1/aspnet/child.html"></iframe>
</body>
</html>

Dialog.html

有兩個 <iframe>,一個內嵌同站台的 Child.html,一個內嵌跨站台的 httq://127.0.0.1/aspnet/child.html,以方便對照比較。

<!DOCTYPEhtml>
<html>
<body>
<h4>
<script>document.write(location.href)</script>
</h4>
<iframesrc="child.html"></iframe><br/>
<iframesrc="http://127.0.0.1/aspnet/child.html"></iframe>
</body>
</html>

Child.html

內含 <textarea> 方便測試複製貼上功能

<!DOCTYPEhtml>
<html>
<body>
<h4>
<script>document.write(location.href)</script>
</h4>
<textarea>ABC</textarea>
</body>
</html>

實測結果如下:

測試1 Parent 內嵌跨站台 Child 可複製貼上
測試2 Dialog 內嵌同站台 Child 可複製貼上
測試3 Dialog 內嵌跨站台 Child 無法複製貼上

經查該問題是測試台配置特殊造成,並可藉由網頁移入同站台避免,而 IE + ModalDialog 設計方式將逐步淘汰,故不花精神深入研究,僅記錄此一特性備查,結案。

參考: IE8-IE9 Copy-Cut-Paste doesn't work on cross-domain IFRAME in Showmodaldialog window

【茶包射手日記】只能跑 32 位元的 AnyCPU .NET 程式

$
0
0

測試某個 COM+ 元件應用專案,開發者所附的範例專案測試成功,我自己新增 Console Application 或 Windows Form 專案則卡在找不到 Registry 無法執行。強烈懷疑與 x86/x64 有關,由於只有註冊 64 位元 COM+,專案跑 x86 找不到 Registry 是意料中事,但詭異之處在於我已確認過範例專案跟我新增的專案都是設 Any CPU 無誤,甚至放在同一個 Solution 測試,卻一個成功一個失敗。

實測將新增 WinForm 或 Console 專案平台目標(Platform Target)改為 x64 可排除問題,但無法解答為什麼範例專案設 Any CPU 可以,我卻得寫死 x64 才行的疑惑。

挖出 CorFlags 工具一探究竟,發現一個奇妙差異,有個沒學過的新旗標: 32BITPREF,我新增的專案被設為 1,範例專案則為 0。

有了這項新發現,重新檢查 Visual Studio 的專案設定,這才看出玄機,兩個專案都設定 Any CPU, 但範例專案沒勾選 Prefer 32-bit,我新增的專案有。

用關鍵字查詢,我學到了新知識。參考: Make sure "Prefer 32-bit" option is turned off for .NET 4.5 executables

原來這是 Visual Studio 2012 起針對 .NET 4.5 專案的新增選項,在新增 WinForm、WPF、Console 專案時,預設為 Any CPU + Prefer 32-bit。

AnyCPU + Prefer 32-bit 在 Windows 平台永遠跑 32 位元模式,跟平台目標設成 x86 的行為相同,唯一差別在於 AnyCPU + Perfer 32-bit 可以在 ARM 機器執行。參考: What AnyCPU Really Means As Of .NET 4.5 and Visual Studio 11

換句話說,AnyCPU + Prefer 32-bit 骨子裡根本是 x86,取消 Prefer 32-bit 後才是我原本想像的那個 32/64 都能跑的 AnyCPU。 下回看到 .NET 4.5+ 設定 AnyCPU 時,記得要確認沒有勾選 Prefer 32-bit,才代表 .NET 程式能 32/64 通吃。

又上了一課~

Viewing all 2311 articles
Browse latest View live