【问题标题】:Passing C# structure with string array to c++ function which accepts void * for c# structure and char** for c# string array将带有字符串数组的 C# 结构传递给 c++ 函数,该函数接受 c# 结构的 void * 和 c# 字符串数组的 char**
【发布时间】:2013-07-13 08:32:37
【问题描述】:

我想将带有字符串数组的 C# 结构发送到 C++ 函数,该函数接受 c# 结构的 void * 和 c# 结构字符串数组成员的 char**。

我能够将结构发送到 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++中访问数据时,它只访问字符串数组的第一个元素。

【问题讨论】:

    标签: c# pinvoke


    【解决方案1】:
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public String[] Parameters;
    

    是一个内联数组。匹配的 C++ 声明是:

    char* Parameters[2];
    

    但您正在尝试将其匹配到:

    char** Parameters;
    

    这完全不同。

    您需要手动编组。在 C# 结构中,将 Parameters 声明为 IntPtr。然后用Marshal.AllocHGlobal 分配本机内存来保存一个指针数组。然后用指向你的字符串的指针填充这些指针。

    [StructLayout(LayoutKind.Sequential)]
    public struct TestInfo
    {
        public int TestId;
        public IntPtr Parameters;
    }
    
    static void Main(string[] args) // no need for unsafe
    {
        TestInfo testInfo;
        testInfo.TestId = 1;
        testInfo.Parameters = Marshal.AllocHGlobal(2*Marshal.SizeOf(typeof(IntPtr)));
        IntPtr ptr = testInfo.Parameters;
        Marshal.WriteIntPtr(ptr, Marshal.StringToHGlobalAnsi("foo"));
        ptr += Marshal.SizeOf(typeof(IntPtr));
        Marshal.WriteIntPtr(ptr, Marshal.StringToHGlobalAnsi("bar"));
        TestAPI(ref testinfoObj);
        // now you want to call FreeHGlobal, I'll leave that code to you
    }
    

    另一种方法是使用固定的 IntPtr[] 并将其放入 testInfo.Parameters。

    【讨论】:

      【解决方案2】:

      这实际上更像是对 David 的回答的扩展/扩展,但这是结束自定义编组的一种方法:

      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 运算符的逆操作。

      【讨论】:

      • @DavidHeffernan Meh,这是一团糟,但还是谢谢你——我已经被咬了很多次,因为我在使用 Pinvokes 之后没有清理,所以我有很多首选模式用于自动清理。
      猜你喜欢
      • 1970-01-01
      • 2015-09-09
      • 2010-10-15
      • 1970-01-01
      • 2011-04-06
      • 2013-06-27
      • 2019-09-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多