2012-11-08 33 views
4

我想用ATL/COM託管字節[使用ATL/COM

從C#代碼側傳遞從C#代碼到非託管C++一些圖像數據I這樣做]非託管字節數組:

void SendFrame([In, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_UI1)] ref byte[] frameData); 

但我不知道我應該如何在我的C++代碼中處理這個函數。

現在我有這樣的事情:

_ATL_FUNC_INFO OnSendFrameDef = { CC_STDCALL, VT_EMPTY, 1, { VT_SAFEARRAY | VT_UI1 } }; 

void __stdcall OnSendFrame(SAFEARRAY* ppData) 
{ 
    BYTE* pData; 
    SafeArrayAccessData(ppData, (void **) &pData); 

    // Doing some stuff with my pData 

    SafeArrayUnaccessData(ppData); 
} 

誰能給我一些建議,我怎麼能做出這種事情的工作?

謝謝。

+1

你需要刪除* ref *關鍵字,所以你不通過SAFEARRAY **,休息應該是好的。普通字節[]和傳遞數組大小的額外參數也可以起作用並節省開銷。 –

回答

3

我已經設法實現了我的目標!對於那些有興趣誰:

我的事件處理程序描述是這樣的:

_ATL_FUNC_INFO Stream::OnStreamFrameCallbackDef = { CC_STDCALL, VT_EMPTY, 1, { VT_DISPATCH } }; 

我的C++函數:

void __stdcall Stream::OnStreamFrameCallback(IDispatch* pFrame) 
{ 
    // NOTE that this "IStreamFramePtr" is COM's Ptr of my "IStreamFrame" 
    MyCOM::IStreamFramePtr pStreamFrame = pFrame; 

    // Thanks casperOne♦ for this: 
    CComSafeArray<byte> array; 
        array.Attach(pStreamFrame->GetBuffer()); 

    // Now I can do stuff that I need... 
    byte* pBuffer = &array.GetAt(0); 
} 

我 「IStreamFrame」 在我的.tlh文件看起來像這樣:

struct __declspec(uuid("1f6efffc-0ac7-3221-8175-5272a09cea82")) 
IStreamFrame : IDispatch 
{ 
    __declspec(property(get=GetWidth)) 
    long Width; 
    __declspec(property(get=GetHeight)) 
    long Height; 
    __declspec(property(get=GetBuffer)) 
    SAFEARRAY * Buffer; 

    long GetWidth (); 
    long GetHeight (); 
    SAFEARRAY * GetBuffer (); 
}; 

在我的C#代碼中,我有這樣的:

[ComVisible(true)] 
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] 
public interface IStreamFrame 
{ 
    int  Width { [return: MarshalAs(UnmanagedType.I4)]   get; } 
    int  Height { [return: MarshalAs(UnmanagedType.I4)]   get; } 
    byte[] Buffer { [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_UI1)] get; } 
}; 


[ComVisible(false)] 
public delegate void StreamFrameCallback(IStreamFrame frame); 

[ComVisible(true)] 
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] 
public interface IMyCOMStreamEvents 
{ 
    [DispId(1)] 
    void OnStreamFrameCallback([In, MarshalAs(UnmanagedType.IDispatch)] IStreamFrame frame); 
} 

事情似乎工作得很好。但如果有人有任何建議或發現我做錯了,請告訴我。

謝謝。

+0

如果您使用IDL文件來描述您的界面,那麼您可以節省很多麻煩。使用midl.exe,您可以生成C/C++接口,代理和存根,最後生成一個TLB(類型庫)文件,該文件既包含Visual C++的'#import'指令,又包含.NET的tlbimp.exe知道如何處理而無需麻煩。 TLB文件通常也由微軟以外的許多其他開發工具處理,這將使您的最終結果爲未來的互操作做好準備。最後,它會讓你花費更多時間學習MIDL。 – acelent

0

你考慮過C++/CLI嗎?在你的C++/CLI ref class你會寫一個成員函數:

void SendFrame(cli::array<System::Byte> frameData) 
{ 
    pin_ptr<System::Byte> p1 = &frameData[0]; 
    unsigned char *p2 = (unsigned char *)p1; 

    // So now p2 is a raw pointer to the pinned array contents 
} 
+0

沒有跡象表明代碼可以從COM服務器更改,它可能必須保留COM服務器。 – casperOne

+0

謝謝丹尼爾,但我不能使用CLI – Gediminas

2

由於SAFEARRAY已經封到你的非託管代碼,你使用ATL,則可以使用CComSafeArray class,因爲它處理與SAFEARRAY實例一起使用時的所有清理:

_ATL_FUNC_INFO OnSendFrameDef = { CC_STDCALL, VT_EMPTY, 1, 
    { VT_SAFEARRAY | VT_UI1 } }; 

void __stdcall OnSendFrame(SAFEARRAY* ppData) 
{ 
    // Wrap in a CComSafeArray. 
    // On the stack means all calls to cleanup 
    // will be cleaned up when the stack 
    // is exited. 
    CComSafeArray<byte> array; 
    array.Attach(ppData); 

    // Work with the elements, get the first one. 
    byte b = array.GetAt(0); 

    // And so on. The destructor for CComSafeArray 
    // will be called here and cleaned up. 
} 
+0

謝謝casperOne,但我仍然在做錯事,我在「atlcom.h」文件(第4722行)中收到帶有「Assertion failed」的消息框。看起來像「DispCallFunc」函數返回「E_INVALIDARG」 也許我的「_ATL_FUNC_INFO」是錯誤的? 我.tlh內容看起來是這樣〜: 結構__declspec(UUID( 「401d2950-7c0e-3ac7-9beb-85ea798feb2d」)) ITestStreamEvents:IDispatch接口 { HRESULT OnSendFrame(SAFEARRAY * frameData); }; – Gediminas

+0

@Gediminas你需要用這個信息更新主要問題;評論中的代碼不能很好地工作。 – casperOne

1

我強烈建議您創建一個IDL filedesign your COM interfaces

鑑於你的答案你的榜樣,一個相當最小的IDL文件可能是這樣的:

import "oaidl.idl"; 

[object, 
uuid(1f6efffc-0ac7-3221-8175-5272a09cea82), 
dual, 
oleautomation] 
interface IStreamFrame : IDispatch { 
    [propget] 
    HRESULT Width([out, retval] long *pWidth); 

    [propget] 
    HRESULT Height([out, retval] long *pHeight); 

    [propget] 
    HRESULT Buffer([out, retval] SAFEARRAY(byte) *pBuffer); 
}; 

[uuid(1f6efffc-0ac7-3221-8175-5272a09cea83)] 
library ContosoStreamFrame { 
    importlib("stdole32.tlb"); 

    interface IStreamFrame; 
}; 

您再使用midl.exe生成帶有C/C++接口.H,爲CLSID一個_i.c和用於C/C++鏈接的IID常量,用於RPC註冊的dlldata.c,用代理和存根編組的東西的_p.c以及通常稱爲.idl文件的解析表示的.tlb。這在documentation中描述得更好。

編輯:似乎有no way to avoid the C/C++ file generation

EDIT2:我剛發現一個解決方法,使用nul作爲你不想要的輸出文件。例如,下面的命令只產生file.tlb

midl.exe /header nul /iid nul /proxy nul /dlldata nul file.idl 

注:如果並不意味着你IStreamFrame接口跨進程使用,該local屬性添加到該接口。

在C/C++中,可以使用生成的特定文件或0123B的TLB文件。在.NET中,您可以在生成.NET程序集的TLB文件上運行tlbimp.exe

如果您的項目是以.NET爲中心的,那麼也可以使用tlbexp.exe。但是,這將需要您瞭解.NET COM註釋以及它們在IDL方面的含義,因此我不確定是否有任何好處以另一種語言保存一個額外的源文件,而犧牲許多裝飾界面和類定義中的噪聲。如果你想在源代碼級別完全控制類和接口,並且希望儘可能簡化.NET代碼(可讀性,可用性和速度的優化),那麼這可能是一個不錯的選擇。

最後,您可以通過創建項目來自動完成Visual Studio中的所有操作。如果使用IDL方法,請添加一個自定義構建步驟,該步驟調用midl.exetlbimp.exe,並使相關項目依賴於此項目以獲得正確的構建順序。如果您使用.NET方法,請添加一個自定義構建步驟,調用tlbexp.exe並使相關的C/C++項目依賴於此項目。

編輯:如果您不需要從midl.exe生成的C/C++文件,您可以將del命令添加到特定輸出文件的自定義生成步驟。

EDIT2:或使用上述的nul解決方法。

當類型庫已經存在時,常用的方法是使用Visual Studio將其導入到.NET中。但是,這樣,如果更新IDL文件,則必須記住重新生成TLB並再次導入它。