首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 呼叫 Haskell 遭遇「嘗試讀取或寫入受保護的記憶體」:竟是 Windows Server 2008 的坑

     編輯:關於C#

問題現象

C# 透過 P/Invoke 呼叫 Haskell 編譯的非託管 DLL,只要 Haskell 端操作字串(如 Pandoc Markdown 轉 HTML),C# 端即拋出:


System.AccessViolationException: 
Attempted to read or write protected memory. 
This is often an indication that other memory is corrupt.

但 Haskell 直接回傳字串(不處理)則正常。GHCi 互動模式執行也正常。

環境與架構

  • Haskell:GHC 7.6.3, Haskell Platform 2013.2.0.0
  • C#:.NET Framework 4.x
  • 互通方式:Haskell → C (FFI) → 編譯為非託管 DLL → C# DllImport 呼叫
  • 關鍵庫:Pandoc (Haskell 寫的最強 Markdown 引擎)

排查歷程

  1. 懷疑 P/Invoke 簽名錯誤(CallingConvention, CharSet, 封送處理)
  2. 嘗試 Win32 API 讓 ToolStrip 獲得焦點模擬 Button 訊息(無關但同樣失敗)
  3. 檢查 Haskell 記憶體管理(newString、CString、malloc/free)
  4. 對比 Marshal.PtrToStringAnsi vs PtrToStringUni vs PtrToStringAuto
  5. 在 Windows Server 2008 R2 編譯、測試皆失敗
  6. 靈光一現:將編譯好的 DLL 複製到 Windows Server 2012 執行 → 奇蹟般正常!

根本原因

Windows Server 2008 R2 的執行環境(載入器/記憶體管理/安全性機制)與 Haskell/GHC 產生的非託管程式碼不相容,導致記憶體存取違規。同樣的 DLL 在 Windows Server 2012 完美運作。

相關歷史教訓

  • 阿里雲「黑色 10 秒」問題:WS2008 WAS 使用自旋鎖,虛擬化不支援導致效能崩潰,升級 WS2012 解決
  • WS2008 於 2020 年 1 月停止主流支援,2023 年 1 月停止延伸支援
  • GHC 7.6.3 較舊,新版 GHC (9.x) 對 Windows 相容性大幅改善

解決方案

  1. 立即方案:部署至 Windows Server 2012 R2 / 2016 / 2019 / 2022
  2. 根治方案:升級 Haskell 工具鏈(GHC 9.x + Cabal 3.x),重新編譯 DLL
  3. 替代方案:改用 pandoc CLI 進程呼叫(Process.Start),避免 P/Invoke 複雜度
  4. 容器化:Docker Linux 容器運行 Haskell 服務,C# 透過 HTTP/gRPC 呼叫

程式碼備忘(P/Invoke 關鍵點)


// C# 端
class Native 
{
    [DllImport("libpandoc", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public static extern IntPtr markdownToHtml(byte[] markdown);
}

public class Processor 
{
    public string Process(string text) 
    {
        var ptr = Native.markdownToHtml(Encoding.UTF8.GetBytes(text));
        // 注意:Haskell 端若用 CString (以 null 結尾),這裡用 PtrToStringAnsi
        // 若 Haskell 端回傳 UTF-8,需用 PtrToStringUTF8 (.NET Core 3.1+)
        var html = Marshal.PtrToStringAnsi(ptr); 
        // 記得釋放 Haskell 端配置的記憶體(需額外匯出 free 函式)
        return html;
    }
}

教訓總結

  • 跨語言互通(Managed ↔ Unmanaged)極易受 OS 版本、執行環境、編譯器版本影響
  • 排查不果時,嘗試更換 OS 環境,有時問題不在程式碼而在平台
  • 避免在已停止支援的 OS (WS2008) 上開發/部署新專案
  • 優先考慮進程隔離(CLI/HTTP/gRPC)而非進程內 P/Invoke,穩健度高得多

這個坑花了數天才踩出來,記錄下來希望能幫後人省下時間。技術債不僅在程式碼,也在基礎設施選型上。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved