2019-07-22 16:49

C# COM 元件使用 MTA

先前有用到一個通訊用的 COM 元件,因為連線不穩的時候會影響到 Main Thread 造成這個 WinForm UI 卡住,連帶所有 Main Thread 下的其他 Thread 都卡住,最先找到的方法是在 Program Main 上改用 MTAThreadAttribute,的確是可以解決卡住的問題。

但 WPF 就不可以用 MTAThreadAttribute,因為 WPF 必須執行在 STAThread 的環境上,又開始苦惱這個問題了,問題應該還是有解套的辦法的只是知識不足,最後在 WIKI 中看到重要的知識。

WIKI 元件物件模型
一個COM物件只能存在於一個套間。COM物件一經建立就確定所屬套間,並且直到銷毀它一直存在於這個套間。

所以只要用其他 Thread 去建立 COM 元件就不會影響到 Main Thread 了,簡單的解決問題,果然是知識不足。

  1. ActEasyIF actConnection; 
  2.  
  3. var waiter = new AutoResetEvent(false); 
  4.  
  5. new Thread(() => 
  6. { 
  7.    /* 建構 COM 元件 */ 
  8.    _actConnection = new ActEasyIF(); 
  9.  
  10.    waiter.Set(); 
  11. }).Start(); 
  12.  
  13. waiter.WaitOne(500); /* 等待建立結束 */ 
2019-07-22 15:36

C# struct 轉換到 byte array

StructLayout: https://docs.microsoft.com/zh-tw/dotnet/api/system.runtime.interopservices.layoutkind?view=netframework-4.8
Pack: 資料欄位的對齊,這會影響最短欄位的 byte 長度

  1. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 
  2. public struct PollResponse 
  3. { 
  4.    public int AppId; 
  5.    public byte Serial; 
  6.    public short Station; 
  7. } 
  8.  
  9.  
  10. void Main() 
  11. { 
  12.    var data = new PollResponse 
  13.    { 
  14.        AppId = 1, 
  15.        Serial = 2, 
  16.        Station = 3, 
  17.    }; 
  18.  
  19.    Type type = typeof(PollResponse); 
  20.    int size = Marshal.SizeOf(type); 
  21.    var bytes = new byte[size]; 
  22.  
  23.    /* struct to byte array */ 
  24.    IntPtr ptrIn = Marshal.AllocHGlobal(size); 
  25.    Marshal.StructureToPtr(data, ptrIn, true); 
  26.    Marshal.Copy(ptrIn, bytes, 0, size); 
  27.    Marshal.FreeHGlobal(ptrIn); 
  28.  
  29.    BitConverter.ToString(bytes).Dump(); 
  30.    /* 01-00-00-00 - 02 - 03-00 */ 
  31.  
  32.  
  33.    /* byte array to struct */ 
  34.    IntPtr ptrOut = Marshal.AllocHGlobal(size); 
  35.    Marshal.Copy(bytes, 0, ptrOut, size); 
  36.    var result = (PollResponse)Marshal.PtrToStructure(ptrOut, type); 
  37.    Marshal.FreeHGlobal(ptrOut); 
  38.  
  39.    result.Dump(); 
  40.    /* { AppId = 1, Serial = 2, Station = 3 } */ 
  41. } 
2019-07-22 13:52

C# 用 Lambda 設定 MVC Route Constraint

MVC 的 Route Constraint 支援 Regular Pattern (string) 以及 IRouteConstraint,簡單的限制還可以用 Regular 處理,複雜的就需要實作 IRouteConstraint 了,既然都是一次工,索性就想要搭配 Lambda 讓設定可以更彈性的點。


  1. routes.MapRoute( 
  2.    name: "Default", 
  3.    url: "{controller}/{action}/{id}", 
  4.    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, 
  5.    constraints: new { controller = new ValueConstraint(x => x.ToUpper() != "WCF") } 
  6. ); 

  1. public class ValueConstraint : IRouteConstraint 
  2. { 
  3.    private Func<string, bool> _match; 
  4.  
  5.    public ValueConstraint(Func<string, bool> match) 
  6.    { 
  7.        _match = match; 
  8.    } 
  9.  
  10.    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) 
  11.    { 
  12.        return _match("" + values[parameterName]); 
  13.    } 
  14. } 


順便增加了 HttpContextBase 的處理
  1. routes.MapRoute( 
  2.    name: "Default", 
  3.    url: "{controller}/{action}/{id}", 
  4.    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, 
  5.    constraints: new { authenticated = new HttpConstraint(x => x.Request.IsAuthenticated) } 
  6. ); 

  1. public class HttpConstraint : IRouteConstraint 
  2. { 
  3.    private Func<HttpContextBase, bool> _match; 
  4.  
  5.    public HttpConstraint(Func<HttpContextBase, bool> match) 
  6.    { 
  7.        _match = match; 
  8.    } 
  9.  
  10.    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) 
  11.    { 
  12.        return _match(httpContext); 
  13.    } 
  14. } 
2019-07-21 17:21

C# 讓 Dequeue 更方便的擴充方法

  1. void Main() 
  2. { 
  3.    var queue = new Queue<int>(); 
  4.  
  5.    for (int i = 0; i < 10; i++) { queue.Enqueue(i); } 
  6.    string.Join(",", queue).Dump(); /* 0,1,2,3,4,5,6,7,8,9 */ 
  7.  
  8.  
  9.    var take = queue.EnumerateDequeue().Take(4).ToList(); 
  10.    string.Join(",", take).Dump(); /* 0,1,2,3 */ 
  11.    string.Join(",", queue).Dump(); /* 4,5,6,7,8,9 */ 
  12.  
  13.  
  14.    var take2 = queue.EnumerateDequeue().Take(40).ToList(); 
  15.    string.Join(",", take2).Dump(); /* 4,5,6,7,8,9 */ 
  16.    string.Join(",", queue).Dump(); /* */ 
  17. } 
  18.  
  19.  
  20.  
  21. public static class QueueExtensions 
  22. { 
  23.  
  24.   public static IEnumerable<T> EnumerateDequeue<T>(this Queue<T> source) 
  25.   { 
  26.       while (source.Count > 0) { yield return source.Dequeue(); } 
  27.   } 
  28.  
  29.   public static IEnumerable<T> EnumerateDequeue<T>(this ConcurrentQueue<T> source) 
  30.   { 
  31.       T outValue; 
  32.       while (source.TryDequeue(out outValue)) { yield return outValue; } 
  33.   } 
  34.  
  35. } 
2019-07-21 17:06

C# 在 Enum 上增加附加資訊

C# 的 Enum 是個很方便的類型,如果可以再增加額外的資訊就更方便了,這裡利用 Attribute 去定義 Enum 額外的資訊,再用擴充方法取得 Enum 所屬的資訊。

用 Attribute 來定義有個好處,未來在增減 Enum 時可以一起進行修改,不用擔心會有遺漏而沒修改的問題。

  1. void Main() 
  2. { 
  3.    PortAreaCode.F1Front.GetFloor().Dump(); /* F1 */ 
  4. } 
  5.  
  6.  
  7. public enum PortAreaCode 
  8. { 
  9.    [AreaMeta("None", 0)] 
  10.    None, 
  11.  
  12.    [AreaMeta("F1", 1)] 
  13.    F1Front, 
  14.  
  15.    [AreaMeta("F2", 1)] 
  16.    F2Front, 
  17. } 
  18.  
  19.  
  20.  
  21. /// <summary>PortAreaCode 額外附屬資訊定義的 Attribute</summary> 
  22. [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] 
  23. class AreaMetaAttribute : Attribute 
  24. { 
  25.    public string Floor { get; private set; } 
  26.    public int WarehouseId { get; private set; } 
  27.  
  28.    public AreaMetaAttribute() : this("None", 0) { } 
  29.  
  30.    public AreaMetaAttribute(string floor, int warehouseId) 
  31.    { 
  32.        Floor = floor; 
  33.        WarehouseId = warehouseId; 
  34.    } 
  35. } 
  36.  
  37.  
  38.  
  39. /// <summary>PortAreaCode 的擴充方法</summary> 
  40. public static class PortAreaCodeExtensions 
  41. { 
  42.    private static AreaMetaAttribute _defaultMeta = new AreaMetaAttribute(); 
  43.  
  44.    private static AreaMetaAttribute getMeta(PortAreaCode value) 
  45.    { 
  46.        FieldInfo field = typeof(PortAreaCode).GetField(value.ToString()); 
  47.        if(field == null) { return _defaultMeta; } 
  48.  
  49.        var meta = field.GetCustomAttribute<AreaMetaAttribute>(); 
  50.        return meta ?? _defaultMeta; 
  51.    } 
  52.  
  53.    public static string GetFloor(this PortAreaCode value) 
  54.    { 
  55.        return getMeta(value).Floor; 
  56.    } 
  57.  
  58.    public static int GetWarehouseId(this PortAreaCode value) 
  59.    { 
  60.        return getMeta(value).WarehouseId; 
  61.    } 
  62. } 
2019-07-21 16:29

C# 用 gzip 壓縮字串並取得 base64 字串

這個使用方式的效果是有但書的,當 Source 的重複率不高壓縮的效果就不會好,再加上 base64 就是用可見文字去表示 byte 值,這會讓 base64 後的結果比 byte array 還要長,所以壓縮率沒有到達一定的程度下,輸出反而會比 Source 的字串還要長。

  1. //using System.IO.Compression; 
  2.  
  3.  
  4. void Main() 
  5. { 
  6.    string text = "OptionPostal,OptionClassType,OptionTalentItem"; 
  7.    text.Length.Dump(); /* 45 */ 
  8.  
  9.    string compressBase64 = compress(text); 
  10.    compressBase64.Length.Dump(); /* 76 */ 
  11.    compressBase64.Dump();  
  12.    /* H4sIAAAAAAAEAPMvKMnMzwvILy5JzNHxB3OccxKLi0MqC1Kh/JDEnNS8Es+S1FwAaY6qVC0AAAA= */ 
  13.  
  14.    string decompressText = decompress(compressBase64); 
  15.    decompressText.Dump();  
  16.    /* OptionPostal,OptionClassType,OptionTalentItem */ 
  17.  
  18.    text = "OptionPostal,OptionClassType,OptionTalentItem,OptionPostal,OptionClassType,OptionTalentItem,OptionPostal,OptionClassType,OptionTalentItem,OptionPostal,OptionClassType,OptionTalentItem"; 
  19.    text.Length.Dump(); /* 183 */ 
  20.  
  21.    compressBase64 = compress(text); 
  22.    compressBase64.Length.Dump(); /* 84 */ 
  23.    compressBase64.Dump();  
  24.    /* H4sIAAAAAAAEAPMvKMnMzwvILy5JzNHxB3OccxKLi0MqC1Kh/JDEnNS8Es+S1FyowCBQDQBPmlWktwAAAA== */ 
  25.  
  26. } 
  27.  
  28.  
  29. /*壓縮*/ 
  30. private static string compress(string text) 
  31. { 
  32.    if (string.IsNullOrEmpty(text)) { return text; } 
  33.  
  34.    byte[] buffer = Encoding.UTF8.GetBytes(text); 
  35.  
  36.    using (var outStream = new MemoryStream()) 
  37.    using (var zip = new GZipStream(outStream, CompressionMode.Compress)) 
  38.    { 
  39.        zip.Write(buffer, 0, buffer.Length); 
  40.        zip.Close(); 
  41.  
  42.        string compressedBase64 = Convert.ToBase64String(outStream.ToArray()); 
  43.        return compressedBase64; 
  44.    } 
  45. } 
  46.  
  47.  
  48. /*解壓縮*/ 
  49. private static string decompress(string compressed) 
  50. { 
  51.    if (string.IsNullOrEmpty(compressed)) { return compressed; } 
  52.  
  53.    byte[] buffer = Convert.FromBase64String(compressed); 
  54.  
  55.    using (var inStream = new MemoryStream(buffer)) 
  56.    using (var outStream = new MemoryStream()) 
  57.    using (var zip = new GZipStream(inStream, CompressionMode.Decompress)) 
  58.    { 
  59.        zip.CopyTo(outStream); 
  60.        zip.Close(); 
  61.  
  62.        string text = Encoding.UTF8.GetString(outStream.ToArray()); 
  63.        return text; 
  64.    } 
  65. } 
2019-07-21 15:53

產生 IP v6 的 mask byte array

  1. int length = 121; /* total 128 */ 
  2.  
  3. var mask = new byte[16]; 
  4.  
  5. for (int i = 0; i < 16; i++) 
  6. { 
  7.    mask[i] = 0xff; 
  8.    if (length > -8) { length -= 8; } 
  9.    if (length < 0) { mask[i] = (byte)(mask[i] << -length); } 
  10.    /* 當 length 出現負值時代表需要進行位移 */ 
  11. } 
  12.  
  13. BitConverter.ToString(mask).Dump(); 
  14. /* FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-80 */ 
2019-07-21 15:43

C# DateTimeOffset Parse Patch

DateTimeOffset 在 Parse 時會使用 Local TimeZone,這會與期望的 TimeZone 產生偏差,需要進行差值修補。

  1. //TimeZoneInfo.GetSystemTimeZones().Dump(); 
  2.  
  3. /* (UTC+02:00) 開羅 */ 
  4. var zone = TimeZoneInfo.FindSystemTimeZoneById("Egypt Standard Time"); 
  5. zone.Dump(); 
  6.  
  7. var date = DateTimeOffset.Parse("2019-07-01 15:00:00"); 
  8. date.Dump(); /* 2019/7/1 下午 03:00:00 +08:00 */ 
  9.  
  10. var diff = date.Offset - zone.BaseUtcOffset; 
  11. diff.Dump(); /* 06:00:00 */ 
  12.  
  13. date = date.Add(diff); 
  14. date.Dump(); /* 2019/7/1 下午 09:00:00 +08:00 */ 
  15.  
  16. date = TimeZoneInfo.ConvertTime(date, zone); 
  17. date.Dump(); /* 2019/7/1 下午 03:00:00 +02:00 */ 
2019-07-19 16:46

WCF IP Filter

  1. <!-- Web.config --> 
  2. <system.serviceModel> 
  3.  <extensions> 
  4.    <behaviorExtensions> 
  5.      <add name="ipFilter" type="XXX.XXX.IpFilterElement, XXX.XXX" /> 
  6.    </behaviorExtensions> 
  7.  </extensions> 
  8.  
  9.  <!-- .... --> 
  10.  
  11.  <behaviors> 
  12.    <serviceBehaviors> 
  13.      <behavior> 
  14.        <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" /> 
  15.        <serviceDebug includeExceptionDetailInFaults="true" /> 
  16.        <ipFilter allow="192.168.1.0/24, 127.0.0.1" /> 
  17.      </behavior> 
  18.    </serviceBehaviors> 
  19.  </behaviors> 
  20. </system.serviceModel> 

  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.Collections.ObjectModel; 
  4. using System.Configuration; 
  5. using System.Linq; 
  6. using System.Net; 
  7. using System.Net.Sockets; 
  8. using System.Runtime.Serialization; 
  9. using System.Security.Authentication; 
  10. using System.ServiceModel; 
  11. using System.ServiceModel.Channels; 
  12. using System.ServiceModel.Configuration; 
  13. using System.ServiceModel.Description; 
  14. using System.ServiceModel.Dispatcher; 
  15. using JustWin.API.Extensions; 
  16.  
  17.  
  18. public class IpFilterElement : BehaviorExtensionElement 
  19. { 
  20.    [ConfigurationProperty("allow", IsRequired = true)] 
  21.    public virtual string Allow 
  22.    { 
  23.        get { return this["allow"] as string; } 
  24.        set { this["allow"] = value; } 
  25.    } 
  26.  
  27.    public override Type BehaviorType 
  28.    { 
  29.        get { return typeof(IpFilterBehaviour); } 
  30.    } 
  31.  
  32.    protected override object CreateBehavior() 
  33.    { 
  34.        return new IpFilterBehaviour(Allow); 
  35.    } 
  36. } 
  37.  
  38.  
  39.  
  40.  
  41. public class IpFilterBehaviour : IDispatchMessageInspector, IServiceBehavior 
  42. { 
  43.    private readonly List<IPAddressRange> _allowList; 
  44.  
  45.    public IpFilterBehaviour(string allow) 
  46.    { 
  47.        _allowList = allow.Split(',').Select(x => new IPAddressRange(x)).ToList(); 
  48.    } 
  49.  
  50.  
  51.    void IServiceBehavior.Validate(ServiceDescription service, ServiceHostBase host) 
  52.    { 
  53.    } 
  54.  
  55.    void IServiceBehavior.AddBindingParameters(ServiceDescription service, ServiceHostBase host, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters) 
  56.    { 
  57.    } 
  58.  
  59.    void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription service, ServiceHostBase host) 
  60.    { 
  61.        foreach (ChannelDispatcher dispatcher in host.ChannelDispatchers) 
  62.        foreach (EndpointDispatcher endpoint in dispatcher.Endpoints) 
  63.        { 
  64.            endpoint.DispatchRuntime.MessageInspectors.Add(this); 
  65.        } 
  66.    } 
  67.  
  68.  
  69.  
  70.    object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) 
  71.    { 
  72.        var remoteEndpoint = request.Properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty; 
  73.  
  74.        var address = IPAddress.Parse(remoteEndpoint.Address); 
  75.        if(_allowList.Any(x => x.IsMatch(address))) { return null; } 
  76.  
  77.        request = null; 
  78.        return new AuthenticationException($"IP address ({remoteEndpoint.Address}) is not allowed."); 
  79.    } 
  80.  
  81.  
  82.    void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState) 
  83.    { 
  84.        var ex = correlationState as Exception; 
  85.        if (ex == null) { return; } 
  86.  
  87.        MessageFault messageFault = MessageFault.CreateFault( 
  88.            new FaultCode("Sender"), 
  89.            new FaultReason(ex.Message), 
  90.            ex, 
  91.            new NetDataContractSerializer() 
  92.        ); 
  93.  
  94.        reply = Message.CreateMessage(reply.Version, messageFault, null); 
  95.    } 
  96.  
  97. } 
  98.  
  99.  
  100.  
  101.  
  102.  
  103.  
  104.  
  105. public class IPAddressRange 
  106. { 
  107.    private readonly byte[] _rangeAddress; 
  108.    private readonly byte[] _rangeMask; 
  109.  
  110.    public IPAddressRange(string ipAndMask) 
  111.    { 
  112.        string[] split = (ipAndMask + "/128").Split('/'); 
  113.  
  114.        var ip = IPAddress.Parse(split[0].Trim()); 
  115.  
  116.        int maskLength = int.Parse(split[1].Trim()); 
  117.        if (ip.AddressFamily == AddressFamily.InterNetwork) { maskLength += 96; } 
  118.  
  119.        _rangeMask = createMask(maskLength); 
  120.  
  121.        _rangeAddress = ip.MapToIPv6().GetAddressBytes() 
  122.            .Select((x, i) => x & _rangeMask[i]) 
  123.            .Select(x => (byte)x) 
  124.            .ToArray(); 
  125.    } 
  126.  
  127.  
  128.    public bool IsMatch(IPAddress ip) 
  129.    { 
  130.        byte[] address = ip.MapToIPv6().GetAddressBytes(); 
  131.  
  132.        for (int i = 0; i < 16; i++) 
  133.        { 
  134.            if ((address[i] & _rangeMask[i]) != _rangeAddress[i]) { return false; } 
  135.        } 
  136.  
  137.        return true; 
  138.    } 
  139.  
  140.  
  141.  
  142.    private byte[] createMask(int length) 
  143.    { 
  144.        var mask = new byte[16]; 
  145.  
  146.        for (int i = 0; i < 16; i++) 
  147.        { 
  148.            mask[i] = 0xff; 
  149.            if (length > -8) { length -= 8; } 
  150.            if (length < 0) { mask[i] = (byte)(mask[i] << -length); } 
  151.        } 
  152.        return mask; 
  153.    } 
  154. }