2012-10-29 63 views
2

我想馬歇爾是我的家鄉的dll通過CoTaskMemAlloc分配到我的C#應用​​程序,並想知道一些數據,如果我這樣做的方法就是明顯錯誤或我錯過了方法C#方的一些sublte裝飾。馬歇爾正確的方式UCHAR []從本機DLL在C#中的byte []

目前,我有C++的一面。

extern "C" __declspec(dllexport) bool __stdcall CompressData( unsigned char* pInputData, unsigned int inSize, unsigned char*& pOutputBuffer, unsigned int& uOutputSize) 
{ ... 
    pOutputBuffer = static_cast<unsigned char*>(CoTaskMemAlloc(60000)); 
    uOutputSize = 60000; 

而在C#端。

private const string dllName = "TestDll.dll"; 

    [System.Security.SuppressUnmanagedCodeSecurity] 
    [DllImport(dllName)] 
    public static extern bool CompressData(byte[] inputData, uint inputSize, out byte[] outputData, out uint outputSize); 
    ... 
    byte[] outputData; 
    uint outputSize; 
    bool ret = CompressData(packEntry.uncompressedData, (uint)packEntry.uncompressedData.Length, out outputData, out outputSize); 

這裏outputSize爲60000不如預期,但outputData的大小爲1,當我memset的緩衝C++方面,它似乎只跨1個字節複製,所以這是絕對錯誤的,我需要馬歇爾使用IntPtr + outputSize的調用之外的數據,還是有一些sublte我錯過了我已經工作的東西?

謝謝。

+0

當前的行爲是有道理的:因爲有任何跡象表明'outputSize'以任何方式在所有'outputData'相關,所以'outputData'被處理作爲指向單個'unsigned char'的指針。不幸的是,我無法用正確的方式來寫這個,只是對當前行爲的解釋。 – hvd

+0

是的,這確實是有道理的,outputSize是爲了我的測試好處,顯然如果byte []知道它應該是過時的。 :) 不管怎麼說,還是要謝謝你。 – Niksan

回答

3

有兩件事情。

首先,P /調用層不處理在C++參考參數,它只能使用指針工作。最後兩個參數(pOutputBufferuOutputSize),特別是不能保證正確元帥。

我建議你改變你的C++方法聲明(或創建形式的包裝):

extern "C" __declspec(dllexport) bool __stdcall CompressData( 
    unsigned char* pInputData, unsigned int inSize, 
    unsigned char** pOutputBuffer, unsigned int* uOutputSize) 

也就是說,第二個問題來自於一個事實,即P /調用層也不會知道如何(在COM它知道它的大小,而不是說,一個SAFEARRAY),它們在非託管代碼分配元帥回「生」陣列。

這意味着,在.NET方面,你要當元帥的指針所創建的背部,然後手動元帥的數組中的元素(以及對其進行處理,如果這是你的責任,它看起來像是)。

你的.NET的聲明是這樣的:

[System.Security.SuppressUnmanagedCodeSecurity] 
[DllImport(dllName)] 
public static extern bool CompressData(byte[] inputData, uint inputSize, 
    ref IntPtr outputData, ref uint outputSize); 

一旦你的outputData作爲IntPtr(這將指向非託管內存),你可以通過調用Copy method轉換爲字節數組在Marshal class像這樣:

var bytes = new byte[(int) outputSize]; 

// Copy. 
Marshal.Copy(outputData, bytes, 0, (int) outputSize); 

注意,如果是你的責任,以釋放內存,你可以調用FreeCoTaskMem method,像這樣:

Marshal.FreeCoTaskMem(outputData); 

當然,你可以用這個成更好的東西,像這樣:

static byte[] CompressData(byte[] input, int size) 
{ 
    // The output buffer. 
    IntPtr output = IntPtr.Zero; 

    // Wrap in a try/finally, to make sure unmanaged array 
    // is cleaned up. 
    try 
    { 
     // Length. 
     uint length = 0; 

     // Make the call. 
     CompressData(input, size, ref output, ref length); 

     // Allocate the bytes. 
     var bytes = new byte[(int) length)]; 

     // Copy. 
     Marshal.Copy(output, bytes, 0, bytes.Length); 

     // Return the byte array. 
     return bytes; 
    } 
    finally 
    { 
     // If the pointer is not zero, free. 
     if (output != IntPtr.Zero) Marshal.FreeCoTaskMem(output); 
    } 
} 
+0

謝謝你,我沒有意識到SAFEARRAY,如果這會讓調用者清理內存的負擔(而不是調用者/ GC處理它,我認爲它會這樣做)使用SAFEARRAY?)如果沒有,我會去IntPtr/Copy路由,我只是想方設法避免它。 Upvote正在等待。 :) – Niksan

+0

@Niksan'SAFEARRAY'將在C++端創建問題,如果你像最後一樣包裝它(上面的代碼只需要編寫一次代碼),上面的代碼就沒有那麼糟糕了。最後,對你來說更容易。 – casperOne

+0

那麼,對我來說最簡單的就是一個副本,但是對於SAFEARRAY的瞭解不會在我的武庫中出現問題,在SAFEARRAY方面會在C++方面造成什麼問題?我目前正在處理我的dll中的new []/delete []組合,並且最終將CoTaskMemAlloc作爲最終推送到.NET的潛在reallocs,我對SAFEARRAY實際上也會這樣做,但如果它更麻煩不值得我跳過。再次感謝。 – Niksan

1

的PInvoke的編組無法猜測多大返回的byte []可能。在C++中指向內存的原始指針沒有指向內存塊的可發現大小。這就是爲什麼你添加了uOutputSize參數。良好的客戶端程序,但不夠好的pinvoke編組。您必須幫助並將[MarshalAs]屬性應用於pOutputBuffer,並指定SizeParamIndex屬性。

請注意數組已被編組人員複製。這不是很理想,您可以通過允許客戶端代碼傳遞數組來避免它。編組人員將釘住它並將指針傳遞給託管陣列。唯一的麻煩是客戶端代碼將沒有體面的方式來猜測數組的大小。典型的解決方案是允許客戶端調用它兩次,首先使用uOutputSize = 0,函數返回所需的數組大小。這將使C++函數是這樣的:

extern "C" __declspec(dllexport) 
int __stdcall CompressData(
    const unsigned char* pInputData, unsigned int inSize, 
    [Out]unsigned char* pOutputBuffer, unsigned int uOutputSize)