簡體   English   中英

如何將非托管dll添加到C#項目

[英]How to add unmanaged dll to c# project

之前已經有人問過這個問題,而我對它們的審查使我受益匪淺。 但是,我仍然缺少該過程中的一些關鍵步驟。 我已經開發了一個Windows Forms應用程序,該應用程序由在使用C#編寫的gui的控制下運行的幾個Fortran可執行文件組成。 使用.NET 4.5由Visual Studio 2015使用Intel Visual Fortran和C#代碼編譯Fortran程序。 我現在正在嘗試將Fortran程序之一轉換為DLL。 這是我開發和實現DLL的第一次嘗試,並且在使一切正確的過程中遇到了麻煩。 希望這個社區中的某人能夠引導我朝正確的方向發展。

DLL中向公眾公開的唯一元素是具有以下接口的子例程:

subroutine PlanetState(path, n, date, id, mu, s, errmsg)
   character(n), intent(in)    :: path      ! the path name to an ephemeris file
   integer, intent(in)         :: n     ! the length of path
   real(8), intent(in)         :: date      ! a Julian date
   integer, intent(in)         :: id        ! a planet number
   real(8), intent(out)        :: emu       ! a parameter used in the calculations
   real(8), intent(out)        :: s(9)      ! an array of state variables
   character(256), intent(out) :: errmsg    ! 256-byte error message that may be returned
end subroutine PlanetState

此和一些支持例程已嵌入到Fortran模塊中。 開發了一個單獨的Fortran主程序來調用和測試PlanetState子例程,以確保代碼在直接調用中可以正常工作。 完成此操作后,將在子例程的聲明部分的頂部添加用於intel編譯器的編譯器指令,如下所示:

   !DEC$ ATTRIBUTES DLLEXPORT :: PlanetState
   !DEC$ ATTRIBUTES ALIAS: 'PlanetState' :: PlanetState
   !DEC$ ATTRIBUTES REFERENCE :: Path, N, Date, PlanID, Emu, S, ErrMsg

然后,該模塊被重新編譯並成功鏈接到名為DEeph.dll的dll文件。

在C#應用程序中,添加了一個類作為P / Invoke代碼的包裝,如下所示:

using System.Runtime.InteropServices;
namespace myNamespace
{
    public static class FortranDlls
    {
        [DllImport("DEeph.dll", EntryPoint = "PlanetState", CallingConvention = CallingConvention.Cdecl)]
        extern public static void PlanetState(char[] path, ref int n, ref double date, ref int planId, 
                           out double emu, out double[] s, out char[] errMsg);
   }
}

在聲明和初始化in參數並聲明out參數之后,對子例程PlanetState進行C#調用,如下所示:

   try
   {
       FortranDlls.PlanetState(dePath, ref n, ref date, ref id, out emu, out state, out errorMsg);
   }
   catch (Exception ex)
   {
       MessageBox.Show(ex.Message, "DLL Error", MessageBoxButtons.OK);
   }

最后,將DLL本身添加到項目解決方案中,並將屬性“如果更新則復制”指定為構建操作。 然后,我成功地重建了解決方案,但是在執行該程序時,在調用PlanetState時程序中斷,並顯示錯誤消息“發生FatalExecutionEngineError”。 給出了錯誤代碼0xc0000005,我相信這是AccessViolationException的代碼。 調用后的catch子句未執行。

顯然,我在正確地將dll合並到項目中的過程中搞砸了或忽略了一些步驟。 任何人都可以指出問題的原因。 感謝您提供的任何幫助。

經過幾天的進一步實驗,我解決了大多數問題。 解決該問題的早期關鍵是意識到我需要設置調試器屬性以允許本機代碼調試。 該屬性在VS項目屬性/調試窗口中設置。 有了此設置,當調試器從主程序(C#)過渡到dll時,我就可以跟蹤其執行情況。 這使我可以將錯誤跟蹤到讀取直接訪問文件的第一條記錄。 read語句顯示如下:

read (3, rec = 1) ttl, ((Ipt(I, J), I = 1, 3), J = 1, 12), ...

其中ttl是整數倍數組,而Ipt是3 x 13整數數組。 在檢查中斷發生后的值時,我發現ttl包含正確的值,而Ipt沒有。 這意味着隱含的do-loop失敗了。 為了測試這一點,我聲明了一個新數組Jpt,其大小為3 x 12,並更改了read語句,如下所示:

read (3, rec = 1) ttl, Jpt, ...

確實可以正常工作。 我認為這可能代表了英特爾運行時系統中的錯誤,因為帶有隱式do循環的代碼在直接從Fortran主程序中調用時可以工作。 消除DLL中所有隱含的do循環后,代碼可以正常工作,直到到達子例程的末尾。 在返回調用程序的步驟中,引發了AccessViolationException。 我懷疑此問題是由out字符數組ErrMsg引起的,因此為了將該問題推遲到以后,我修改了程序以將所有錯誤消息寫入磁盤上的文件,並從子例程簽名中刪除了該參數。 完成后,程序將運行到完成。 但是,然后注意到,即使在DLL子例程的末尾保留了正確的值,雙精度數組S中的值也都為零。 在進一步的實驗中,我們了解到,通過在調用程序中初始化S的值並刪除子例程簽名中的out關鍵字,可以將正確的值返回給主程序。 這似乎在Intel RT或與VS 2015的集成中也有錯誤。

因此,我現在有一個正常工作的DLL,它將錯誤消息寫入外部文件。 我希望找到一種通過調用參數返回消息的方法,但是在嘗試了幾種不同方法后,我沒有成功。 我打算向社區發布一個單獨的問題,以尋求幫助。 無論如何,我希望這種討論對遇到類似問題的其他人有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM