2009-10-30 23 views
7

[編輯]我改變了由Stephen Martin建議的源代碼(以粗體突出顯示)。並添加了C++源代碼。從C#調用非託管代碼 - 返回一個數組的結構

我想在自寫的C++ dll中調用一個非託管函數。該庫讀取機器的共享內存以獲取第三方軟件的狀態信息。由於有幾個值,我想返回結構中的值。但是,在結構中有char [](具有固定大小的char數組)。我現在嘗試接收結構從DLL調用是這樣的:

[StructLayout(LayoutKind.Sequential)] 
public struct SYSTEM_OUTPUT 
{ 
    UInt16 ReadyForConnect;   

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 
    String VersionStr; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] 
    String NameOfFile;  
    // actually more of those 
} 

public partial class Form1 : Form 
{ 
    public SYSTEM_OUTPUT output; 

    [DllImport("testeshm.dll", EntryPoint="getStatus")] 
    public extern static int getStatus(out SYSTEM_OUTPUT output); 

    public Form1() 
    { 
     InitializeComponent();   

    } 

    private void ReadSharedMem_Click(object sender, EventArgs e) 
    { 
     try 
     { 
      label1.Text = getStatus(out output).ToString(); 
     } 
     catch (AccessViolationException ave) 
     { 
      label1.Text = ave.Message; 
     } 
    } 
} 

我將發佈從C++ DLL的代碼,以及,我敢肯定有更多的追捕。原結構STATUS_DATA有一個結構SYSTEM_CHARACTERISTICS的四個實例的數組,並且在該結構中有char[] s,它們尚未被填充(導致),導致錯誤的指針。這就是爲什麼我試圖提取STATUS_DATA中第一個SYSTEM_CHARACTERISTICS項目的子集。

#include <windows.h> 
#include <stdio.h> 
#include <conio.h> 
#include <tchar.h> 
#include <iostream> 
#if defined(_MSC_VER) 
#include <windows.h> 
#define DLL extern "C" __declspec(dllexport) 
#else 
#define DLL 
#endif 

using namespace std; 

enum { SYSID_LEN = 1024, VERS_LEN = 128, SCENE_LEN = 1024 }; 
enum { MAX_ENGINES = 4 }; 

struct SYSTEM_CHARACTERISTICS 
{ 
    unsigned short ReadyForConnect; 
    char   VizVersionStr[VERS_LEN]; 
    char   NameOfFile[SCENE_LEN]; 

    char   Unimplemented[SCENE_LEN]; // not implemented yet, resulting to bad pointer, which I want to exclude (reason to have SYSTEM_OUTPUT) 
}; 

struct SYSTEM_OUTPUT 
{ 
    unsigned short ReadyForConnect;   
    char   VizVersionStr[VERS_LEN]; 
    char   NameOfFile[SCENE_LEN]; 
}; 

struct STATUS_DATA 
{ 
    SYSTEM_CHARACTERISTICS engine[MAX_ENGINES]; 
}; 


TCHAR szName[]=TEXT("E_STATUS"); 


DLL int getStatus(SYSTEM_OUTPUT* output) 
{ 
    HANDLE hMapFile; 
    STATUS_DATA* pBuf; 

    hMapFile = OpenFileMapping(
     FILE_MAP_READ,   // read access 
     FALSE,     // do not inherit the name 
     szName);    // name of mapping object 

    if (hMapFile == NULL) 
    { 
     _tprintf(TEXT("Could not open file mapping object (%d).\n"), 
      GetLastError()); 
     return -2; 

    } 

    pBuf = (STATUS_DATA*) MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);           

    if (pBuf == NULL) 
    { 
     _tprintf(TEXT("Could not map view of file (%d).\n"), 
      GetLastError()); 

     CloseHandle(hMapFile); 
     return -1; 

    } 

    output->ReadyForConnect = pBuf->engine[0].ReadyForConnect;    
    memcpy(output->VizVersionStr, pBuf->engine[0].VizVersionStr, sizeof(pBuf->engine[0].VizVersionStr)); 
    memcpy(output->NameOfFile, pBuf->engine[0].NameOfFile, sizeof(pBuf->engine[0].NameOfFile)); 

    CloseHandle(hMapFile); 
    UnmapViewOfFile(pBuf); 

    return 0; 
} 

現在我得到一個空output結構和返回值IST不爲0的預期。這是一個不斷變化的數字,有七位數字,這讓我感到困惑......我搞砸了DLL嗎?如果我讓非託管代碼可執行並調試它,我可以看到,output正在填充適當的值。

回答

3

在結構中返回信息時,標準方法是將指針傳遞給結構作爲方法的參數。該方法填充結構成員,然後返回某種類型的狀態碼(或布爾值)。所以,你可能想改變你的C++方法採取SYSTEM_OUTPUT *和成功或一些錯誤代碼返回0:

public partial class Form1 : Form 
{ 
    public SYSTEM_OUTPUT output; 

    [DllImport("testeshm.dll", EntryPoint="getStatus")] 
    public extern static int getStatus(out SYSTEM_OUTPUT output); 

    public Form1() 
    { 
     InitializeComponent();   
    } 

    private void ReadSharedMem_Click(object sender, EventArgs e) 
    { 
     try 
     { 
      if(getStatus(out output) != 0) 
      { 
       //Do something about error. 
      } 
     } 
     catch (AccessViolationException ave) 
     { 
      label1.Text = ave.Message; 
     } 
    } 
} 
+0

我編輯了帖子和來源以符合您的建議。還發布了非託管代碼的片段。 – rdoubleui 2009-11-07 12:23:24

+0

我有沒有提到我仍然不知道爲什麼它不起作用? – rdoubleui 2009-11-13 13:32:15

0

誰分配結構的內存?您無法從託管堆中刪除本機內存。一般來說,如果期望調用者釋放內存,或者返回像IMalloc這樣的回調接口以釋放返回的內存,則本地DLL應該在COM堆上進行分配。這意味着您需要將結果內存地址作爲IntPtr接收,並在釋放內存之前使用System.Runtime.InteropServices.Marshal將數據從本機複製到託管堆(可能是結構)。

編輯更新的函數簽名: 使用public static extern int getStatus(ref SYSTEM_OUTPUT output);你沒有在本地函數的COM堆上分配,所以不需要。

+1

這裏沒有內存分配問題。他返回的結構不是指向結構的指針,因此運行時使用標準C方法爲返回大於8字節的值的方法(在x86上)編組隱藏指針。 – 2009-11-06 14:50:00

+0

原始問題顯示的代碼返回的指針沒有明確的內存所有權。該操作已經修改了一個新的函數簽名的問題,並添加了本機代碼部分 – 2009-11-07 16:59:06

2

你實際上並沒有將任何數據封送到託管端。當您在受管理方聲明output時,它的默認值是null。然後,在非託管方面,您從不爲output分配任何內存。您應該分配一些非託管內存的指針,該內存傳遞到您的DLL函數,然後元帥內存到你的結構指針:

[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)] 
public struct SYSTEM_OUTPUT 
{ 
    UInt16 ReadyForConnect;   

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 
    String VersionStr; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] 
    String NameOfFile;  
    // actually more of those 
} 

public partial class Form1 : Form 
{ 
    public SYSTEM_OUTPUT output; 

    [DllImport("testeshm.dll", EntryPoint="getStatus")] 
    public extern static int getStatus(IntPtr output); 

    public Form1() 
    { 
     InitializeComponent();   

    } 

    private void ReadSharedMem_Click(object sender, EventArgs e) 
    { 
     IntPtr ptr; 
     try 
     { 
      ptr = Marshall.AllocHGlobal(Marshall.SizeOf(typeof(SYSTEM_OUTPUT))); 
      int ret = getStatus(ptr); 

      if(ret == 0) 
      { 
       output = (SYSTEM_OUTPUT)Marshal.PtrToStructure(ptr, typeof(SYSTEM_OUTPUT)); 
      } 

     //do something with output 

      label1.Text = ret; 
     } 
     catch (AccessViolationException ave) 
     { 
      label1.Text = ave.Message; 
     } 
     finally 
     { 
      Marshal.FreeHGlobal(ptr); //make sure to free the memory 
     } 
    } 
} 

編輯:

你的問題可能是有問題戰略之間的區別packing。我更新了結構定義。

+0

謝謝你的提示,但我仍然遇到了C++代碼的麻煩。當從託管端調用時,getStatus-Method返回8位數的高位(與之前一樣),而不是0或任何其他定義的返回值(我在if語句中另外定義了兩個,參見上文)。這可能會擾亂價值觀。但是,當我將C++代碼作爲可執行文件運行時,它應該返回0。在C++代碼中有什麼奇怪的東西? – rdoubleui 2009-11-20 11:01:19

+0

你可以發佈完整的C++可執行文件示例嗎? – scottm 2009-11-20 15:09:56

+0

我剛編譯並運行你的例子和我的,並在管理端收到-2(因爲我沒有映射文件)。 – scottm 2009-11-20 15:31:14

0

您是否考慮過將C++/CLI程序集添加到您的項目中?這是彌補託管和非託管代碼之間差距的一種非常簡單且強大的方法。我自己用了很多。

3
  1. 確保您的ReadyForConnect字段未填充至4個字節。在我的項目中,所有短int(2字節)字段都填充了虛擬字節,以非託管DLL中的4字節。如果是這樣的問題,您應該馬歇爾的結構是這樣的:
 
    [StructLayout(LayoutKind.Sequential)] 
    public struct SYSTEM_OUTPUT 
    {  
     [MarshalAs(UnmanagedType.I2)] 
     UInt16 ReadyForConnect; 
     [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.I1, SizeConst=2)] 
     byte[] aligment;   // 2 byte aligment up to 4 bytes margin 
     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]  
     String VersionStr;  
     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]  
     String NameOfFile;  // ... 
    } 
  • 如果字符串是ANSI空值終止字符串就可以加以註釋爲:
  •  
        [MarshalAs(UnmanagedType.LPStr)]     public String VersionStr; 
    
    +1

    使用包選項時聲明結構將爲您解決這個問題 – scottm 2009-11-24 15:33:47

    1

    編輯:我正在重寫這整個答案。

    我把你所有的C++和C#代碼都放到了一個解決方案中,然後運行它 - 而且一切都適用於我。我沒有你的具體內存映射的東西,所以我通過用一些假數據填充pBuf來模擬它,並且一切都讓它恢復正常;返回值和輸出結構都是正確的。

    你的項目設置可能有什麼不妥之處嗎?這聽起來很愚蠢,但你提到運行和調試unamnaged代碼;你正在構建一個DLL的權利?

    +0

    是的,我喜歡。我只是爲了測試目的而構建了一個應用程序。然後將其更改回dll並重新構建。 – rdoubleui 2009-11-23 20:55:42

    +0

    未管理的dll不會自動複製到bin dirs中。 Release和Debug目錄中的dll是否相同? – weismat 2009-11-24 08:38:20

    1

    你想做什麼是可能的,但我認爲你正在解決錯誤的問題。

    爲什麼不直接從C#中讀取內存映射文件? 看看Winterdom.IO.FileMap

    我已經使用它,它工作正常。

    MemoryMappedFile file = MemoryMappedFile.Open(FileMapRead, name); 
    using (Stream stream = memoryMappedFile.MapView(MapAccess.FileMapAllAccess, 0, length)) 
    { 
        // here read the information that you need 
    } 
    

    有了,你還沒有完成 - 你仍然有一個字節的緩衝區轉換成一個結構,但大家都在管理方面,它會比較容易。

    +0

    謝謝,我會肯定的嘗試一下。如果可以的話,我會提供反饋意見。我在之前的c#中搜索了內存映射,但只找到了一個.Net 4.0類。 – rdoubleui 2009-11-24 19:32:28

    相關問題