将 2D 结构数组从 C# 编组到本机 C++

发布于 2024-12-11 15:19:20 字数 1254 浏览 0 评论 0原文

我需要从 C# 切换到 C++ 并带回结构体的二维数组。我已设置好所有内容,如果我附加调试器,一切似乎都会正常进行,除了我的 2D 数组未正确封送。如果我在调用本机方法之前加载值,然后从本机端查看数组,我会在 VS 的监视窗口中看到很多“无效”指针。然后,C++ 代码继续并用值加载数组,但在封送回 C# 期间,我遇到了内存访问冲突。

我不想将其作为一维数组来执行。

这是我的 C++ 结构和方法定义:

struct DoubleStringStruct
{
    BSTR Value;
    BSTR NumberFormat;
};

HRESULT WINAPI NativeArrayHandler(LONG rMax, LONG cMax, DoubleStringStruct** values)
{
    for(LONG rn=1; rn <= rMax; rn++)
    {
         for (LONG cn = 1; cn <= cMax; cn++)
         {
               DoubleStringStruct s;
               s.Value = _wcsdup(L"Test");
               s.NumberFormat = _wcsdup(L"Test");
               values[rn][cn] = s;
         }
     }

     return S_OK;
}

以及我的 C# 代码:

[StructLayout(LayoutKind.Sequential)]
public struct DoubleStringStruct
{
    [MarshalAs(UnmanagedType.BStr)]
    public string value;
    [MarshalAs(UnmanagedType.BStr)]
    public string numberFormat;
}


[System.Runtime.InteropServices.DllImport(c_dllName)]
public static extern void NativeArrayHandler(int hMax, int cMax, DoubleStringStruct[,] args);

public void sometMethod()
{
     DoubleStringStruct[,] someDSS= new DoubleStringStruct[4,3];

     NativeArrayHandler(4, 3, someDSS);
}

I need to drop into C++ from C# and bring back a 2D array of a struct. I have everything set up, and if I attach a debugger everything appears to be going right, except my 2D array isn't marshaling appropriately. If I load it with values before calling the native method, and then view the array from the native side I get lots of "invalid" pointers in my watch window in VS. Then the C++ code goes ahead and loads up the array with values just fine, but during marshaling back to C# I get a memory access violation.

I'd rather not do this as a 1d array.

Here's my C++ struct and method definition:

struct DoubleStringStruct
{
    BSTR Value;
    BSTR NumberFormat;
};

HRESULT WINAPI NativeArrayHandler(LONG rMax, LONG cMax, DoubleStringStruct** values)
{
    for(LONG rn=1; rn <= rMax; rn++)
    {
         for (LONG cn = 1; cn <= cMax; cn++)
         {
               DoubleStringStruct s;
               s.Value = _wcsdup(L"Test");
               s.NumberFormat = _wcsdup(L"Test");
               values[rn][cn] = s;
         }
     }

     return S_OK;
}

and my C# code:

[StructLayout(LayoutKind.Sequential)]
public struct DoubleStringStruct
{
    [MarshalAs(UnmanagedType.BStr)]
    public string value;
    [MarshalAs(UnmanagedType.BStr)]
    public string numberFormat;
}


[System.Runtime.InteropServices.DllImport(c_dllName)]
public static extern void NativeArrayHandler(int hMax, int cMax, DoubleStringStruct[,] args);

public void sometMethod()
{
     DoubleStringStruct[,] someDSS= new DoubleStringStruct[4,3];

     NativeArrayHandler(4, 3, someDSS);
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

儭儭莪哋寶赑 2024-12-18 15:19:20

嗯,汉斯·帕桑特帮助我找到了这个答案,所以支持他。

我的代码存在三个问题

1) _wcsdup 返回一个 WCHAR_T * 但我的结构包含 BSTR,这实际上是一个 WHCAR *

2) 编组器不会为我们创建一个 2d 数组,而是一个必须在其中索引的 1d 数组一种有趣的方式。请注意以下。

3)我需要确保我在本机代码中创建的任何内存都被我自己或编组器清理干净。例如,我在问题中使用的几乎所有本机内存都从未被释放,导致巨大的内存泄漏。目前,当本机代码返回到托管代码时,我会丢失所有指向需要释放的内存的本机指针。我在调用本机代码时通过回调解决了这个问题。本机代码完成其工作,对托管进行功能回调,托管返回并允许本机代码执行内务处理。执行此操作的一个简单方法是使用 CComSafeArray 和 CComBSTR 的功能,它们将进行自我管理。 (我知道我应该能够简单地将 CComSafeArray 传递给编组器,它们将在 .net 代码中得到清理,但我还不知道如何做到这一点)。

不幸的是,2D 数组的封送需要自定义封送,这会导致太多的 COM 调用(不符合我的口味)。因此,我整理了一个一维数组,并根据汉斯·帕桑特的建议对其进行了相应的索引。此外,由于时间限制,我为 DoubleStringStruct 中的每个字符串创建了一个数组,尽管我可以将 DoubleStringStruct COMVisible 创建,然后将其编组到单个数组中。

这是我最终得到的代码。

extern "C" __declspec(dllexport)
HRESULT WINAPI NativeArrayHandler(LONG rMax, LONG cMax, void (WINAPI*callback)(SAFEARRAY*, SAFEARRAY*))
{
    CComSafeArray<BSTR> valuesArr = CComSafeArray<BSTR>(rMax*cMax);
    CComSafeArray<BSTR> formatsArr = CComSafeArray<BSTR>(rMax*cMax);


    for(LONG rn=0; rn < rMax; rn++)
    {
         for (LONG cn = 0; cn < cMax; cn++)
         {
               int index = cMax * rn + cn;
               valuesArr[index] = CComBSTR(L"Test");
               formatsArr[index] = CComBSTR(L"Test");
         }
     }

    callback(valuesArr, formatsArr);

    valuesArr.Destroy();
    formatsArr.Destroy();

    return S_OK;
}

还有 C#

static void Main(string[] args)
{
        NativeArrayHandler(4, 3, (v, f) => { printArrays(4, 3, v, f); });
}


public static void printArrays(int rmax, int cmax, string[] valuesArr, string[] formatsArr)
{
     // can print the arrays in managed code here
}


    [System.Runtime.InteropServices.DllImport("dll location")]
    public static extern void NativeArrayHandler(int hMax, int cMax, NativeArrayHandlerCallback cb);


    public delegate void NativeArrayHandlerCallback(
        [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] string[] arr1,
        [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] string[] arr2);

Hrm, well Hans Passant helped me get to this answer, so props to him.

There were three problems with my code

1) _wcsdup returns a WCHAR_T * but my struct contains BSTR, which is really a WHCAR *

2) The marshaller doesn't create a 2d array for us, rather a 1d array which has to be indexed in a funny way. Note below.

3) I need to make sure that any memory I create in the native code gets cleaned up, either by myself or by the Marshaller. For example, just about all the native memory I used in the question never gets freed, resulting in a huge memory leak. Currently when the native code returns to managed code I lose all my native pointers to memory that needs to be freed. I solved this passing a callback when making the call into native code. The native code does its work, makes a functional call back to managed, which returns, and allows the native code to do housekeeping. An easy way to do this housekeeping was to use the power of CComSafeArray's and CComBSTR's, which will manage themselves. (I know I should be able to simply pass the CComSafeArray's to the marshaller and they'll get cleaned up in the .net code, but I haven't been able to figure out how to do that).

Unfortunately, the marshaling of a 2D array requires custom marshaling, which results in too many COM calls for my tastes. Consequently, I marshalled a 1D array and indexed it accordingly per Hans Passant's suggestion. Futhermore, due to time constraints I created an array for each string in DoubleStringStruct, though I could have made DoubleStringStruct COMVisible and then I could have marshalled it in a single array.

Here's the final code I ended up with.

extern "C" __declspec(dllexport)
HRESULT WINAPI NativeArrayHandler(LONG rMax, LONG cMax, void (WINAPI*callback)(SAFEARRAY*, SAFEARRAY*))
{
    CComSafeArray<BSTR> valuesArr = CComSafeArray<BSTR>(rMax*cMax);
    CComSafeArray<BSTR> formatsArr = CComSafeArray<BSTR>(rMax*cMax);


    for(LONG rn=0; rn < rMax; rn++)
    {
         for (LONG cn = 0; cn < cMax; cn++)
         {
               int index = cMax * rn + cn;
               valuesArr[index] = CComBSTR(L"Test");
               formatsArr[index] = CComBSTR(L"Test");
         }
     }

    callback(valuesArr, formatsArr);

    valuesArr.Destroy();
    formatsArr.Destroy();

    return S_OK;
}

And the C#

static void Main(string[] args)
{
        NativeArrayHandler(4, 3, (v, f) => { printArrays(4, 3, v, f); });
}


public static void printArrays(int rmax, int cmax, string[] valuesArr, string[] formatsArr)
{
     // can print the arrays in managed code here
}


    [System.Runtime.InteropServices.DllImport("dll location")]
    public static extern void NativeArrayHandler(int hMax, int cMax, NativeArrayHandlerCallback cb);


    public delegate void NativeArrayHandlerCallback(
        [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] string[] arr1,
        [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] string[] arr2);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文