質問
C アプリケーションを C# に移植しています。C アプリはサードパーティの DLL から多くの関数を呼び出すため、これらの関数の P/Invoke ラッパーを C# で作成しました。これらの C 関数の一部は、C# アプリで使用する必要があるデータを割り当てるため、 を使用しIntPtr
てMarshal.PtrToStructure
ネイティブMarshal.Copy
データ (配列と構造体) をマネージド変数にコピーしました。
残念ながら、C# アプリは C バージョンよりもはるかに遅いことが判明しました。簡単なパフォーマンス分析により、前述のマーシャリング ベースのデータ コピーがボトルネックであることがわかりました。代わりにポインターを使用するように書き直して、C# コードを高速化することを検討しています。私は C# で安全でないコードとポインターを使用した経験がないため、次の質問について専門家の意見が必要です。
- and ing
unsafe
の代わりにコードとポインターを使用することの欠点は何ですか? たとえば、何らかの方法でより安全でない (しゃれが意図されている) ことはありますか? 人々はマーシャリングを好むようですが、その理由はわかりません。IntPtr
Marshal
- P/Invoking にポインターを使用すると、マーシャリングを使用するよりも本当に高速ですか? おおよそどのくらいのスピードアップが期待できますか? これに関するベンチマーク テストは見つかりませんでした。
サンプルコード
状況をより明確にするために、小さなサンプル コードをまとめました (実際のコードはもっと複雑です)。この例が、「安全でないコードとポインター」と「IntPtr と Marshal」について話しているときの意味を示していることを願っています。
C ライブラリ (DLL)
MyLib.h
#ifndef _MY_LIB_H_
#define _MY_LIB_H_
struct MyData
{
int length;
unsigned char* bytes;
};
__declspec(dllexport) void CreateMyData(struct MyData** myData, int length);
__declspec(dllexport) void DestroyMyData(struct MyData* myData);
#endif // _MY_LIB_H_
MyLib.c
#include <stdlib.h>
#include "MyLib.h"
void CreateMyData(struct MyData** myData, int length)
{
int i;
*myData = (struct MyData*)malloc(sizeof(struct MyData));
if (*myData != NULL)
{
(*myData)->length = length;
(*myData)->bytes = (unsigned char*)malloc(length * sizeof(char));
if ((*myData)->bytes != NULL)
for (i = 0; i < length; ++i)
(*myData)->bytes[i] = (unsigned char)(i % 256);
}
}
void DestroyMyData(struct MyData* myData)
{
if (myData != NULL)
{
if (myData->bytes != NULL)
free(myData->bytes);
free(myData);
}
}
C アプリケーション
Main.c
#include <stdio.h>
#include "MyLib.h"
void main()
{
struct MyData* myData = NULL;
int length = 100 * 1024 * 1024;
printf("=== C++ test ===\n");
CreateMyData(&myData, length);
if (myData != NULL)
{
printf("Length: %d\n", myData->length);
if (myData->bytes != NULL)
printf("First: %d, last: %d\n", myData->bytes[0], myData->bytes[myData->length - 1]);
else
printf("myData->bytes is NULL");
}
else
printf("myData is NULL\n");
DestroyMyData(myData);
getchar();
}
IntPtr
とを使用する C# アプリケーションMarshal
Program.cs
using System;
using System.Runtime.InteropServices;
public static class Program
{
[StructLayout(LayoutKind.Sequential)]
private struct MyData
{
public int Length;
public IntPtr Bytes;
}
[DllImport("MyLib.dll")]
private static extern void CreateMyData(out IntPtr myData, int length);
[DllImport("MyLib.dll")]
private static extern void DestroyMyData(IntPtr myData);
public static void Main()
{
Console.WriteLine("=== C# test, using IntPtr and Marshal ===");
int length = 100 * 1024 * 1024;
IntPtr myData1;
CreateMyData(out myData1, length);
if (myData1 != IntPtr.Zero)
{
MyData myData2 = (MyData)Marshal.PtrToStructure(myData1, typeof(MyData));
Console.WriteLine("Length: {0}", myData2.Length);
if (myData2.Bytes != IntPtr.Zero)
{
byte[] bytes = new byte[myData2.Length];
Marshal.Copy(myData2.Bytes, bytes, 0, myData2.Length);
Console.WriteLine("First: {0}, last: {1}", bytes[0], bytes[myData2.Length - 1]);
}
else
Console.WriteLine("myData.Bytes is IntPtr.Zero");
}
else
Console.WriteLine("myData is IntPtr.Zero");
DestroyMyData(myData1);
Console.ReadKey(true);
}
}
unsafe
コードとポインターを使用する C# アプリケーション
Program.cs
using System;
using System.Runtime.InteropServices;
public static class Program
{
[StructLayout(LayoutKind.Sequential)]
private unsafe struct MyData
{
public int Length;
public byte* Bytes;
}
[DllImport("MyLib.dll")]
private unsafe static extern void CreateMyData(out MyData* myData, int length);
[DllImport("MyLib.dll")]
private unsafe static extern void DestroyMyData(MyData* myData);
public unsafe static void Main()
{
Console.WriteLine("=== C# test, using unsafe code ===");
int length = 100 * 1024 * 1024;
MyData* myData;
CreateMyData(out myData, length);
if (myData != null)
{
Console.WriteLine("Length: {0}", myData->Length);
if (myData->Bytes != null)
Console.WriteLine("First: {0}, last: {1}", myData->Bytes[0], myData->Bytes[myData->Length - 1]);
else
Console.WriteLine("myData.Bytes is null");
}
else
Console.WriteLine("myData is null");
DestroyMyData(myData);
Console.ReadKey(true);
}
}