3

文字列配列を持つ C# 構造体を、c# 構造体の void * と c# 構造体文字列配列メンバーの char** を受け入れるC++ 関数に送信したいと考えています。

構造体を c++ 関数に送信できましたが、問題は、c++ 関数から c# 構造体の文字列配列データ メンバーにアクセスできないことです。文字列配列を個別に送信すると、配列要素にアクセスできました。

サンプルコードは -

C# Code:

[StructLayout(LayoutKind.Sequential)]
public struct TestInfo
{
    public int TestId;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public String[] Parameters;
}

[DllImport("TestAPI.dll", CallingConvention = CallingConvention.StdCall, EntryPoint    "TestAPI")]
private static extern void TestAPI(ref TestInfo data);

static unsafe void Main(string[] args)
{
TestInfo  testinfoObj = new TestInfo();
testinfoObj.TestId = 1;
List<string> names = new List<string>();
names.Add("first");
names.Add("second");
names.Add("third");
testinfoObj.Parameters=names.ToArray();
TestAPI(ref testinfoObj);
}



VC++ Code:

/*Structure with details for TestInfo*/
typedef struct TestInfo
{
int  TestId;
char **Parameters;
}TestInfo_t;

//c++ function
__declspec(dllexport) int TestAPI(void *data)
{
TestInfo *cmd_data_ptr= NULL;
cmd_data_ptr = (TestInfo) data;
printf("ID is %d \r\n",cmd_data_ptr->TestId);//Working fine

for(i = 0; i < 3; i++)
printf("value: %s \r\n",((char *)cmd_data_ptr->Parameters)[i]);/*Error-Additional     information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt*/
}

メモリ スタックを分析すると、((char *)cmd_data_ptr->Parameters) を出力すると、最初の配列要素 ("first") が出力されますが、((char *)cmd_data_ptr->Parameters) を使用していることがわかります。 [i]、要素にアクセスできず、上記の例外が発生します。

構造メモリ アドレスにはすべての構造要素のアドレスが含まれていますが、C++ からデータにアクセスしている間は、文字列配列の最初の要素のみにアクセスしています。

4

2 に答える 2

2

これは実際には David の回答の拡張/拡張ですが、カスタム マーシャリングをまとめる 1 つの方法を次に示します。

public struct LocalTestInfo
{
    public int TestId;
    public IEnumerable<string> Parameters;

    public static explicit operator TestInfo(LocalTestInfo info)
    {
        var marshalled = new TestInfo
            {
                TestId = info.TestId, 
            };
        var paramsArray = info.Parameters
            .Select(Marshal.StringToHGlobalAnsi)
            .ToArray();
        marshalled.pinnedHandle = GCHandle.Alloc(
            paramsArray, 
            GCHandleType.Pinned);
        marshalled.Parameters = 
            marshalled.pinnedHandle.AddrOfPinnedObject();
        return marshalled;
    }
}

[StructLayout(LayoutKind.Sequential)]
public struct TestInfo : IDisposable
{
    public int TestId;
    public IntPtr Parameters;

    [NonSerialized]
    public GCHandle pinnedHandle;

    public void Dispose()
    {
        if (pinnedHandle.IsAllocated)
        {
            Console.WriteLine("Freeing pinned handle");
            var paramsArray = (IntPtr[])this.pinnedHandle.Target;
            foreach (IntPtr ptr in paramsArray)
            {
                Console.WriteLine("Freeing @ " + ptr);
                Marshal.FreeHGlobal(ptr);
            }
            pinnedHandle.Free();
        }
    }
}

私のテストでは、CDecl にスワップしたことに注意してください。

[DllImport(@"Test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int TestAPI(ref TestInfo info);

また、C++ 側でタイプミスがあったと思います:

extern "C" 
__declspec(dllexport) int TestAPI(void *data)
{
    TestInfo *cmd_data_ptr= NULL;
    cmd_data_ptr = (TestInfo*) data;
    printf("ID is %d \r\n",cmd_data_ptr->TestId);

    // char**, not char*
    char** paramsArray = ((char **)cmd_data_ptr->Parameters);
    for(int i = 0; i < 3; i++)
    {
        printf("value: %s \r\n",paramsArray[i]);
    }
    return 0;
}

そしてテストリグ:

static void Main(string[] args)
{
    var localInfo = new LocalTestInfo()
    {
        TestId = 1,
        Parameters = new[]
        {
            "Foo", 
            "Bar",
            "Baz"
        }
    };
    TestInfo forMarshalling;
    using (forMarshalling = (TestInfo)localInfo)
    {
        TestAPI(ref forMarshalling);                
    }
}

逆マーシャリング演算子は読者への演習として残されていますが、基本的に明示的なTestInfo演算子の逆のように見えるはずです。

于 2013-07-15T16:26:08.033 に答える