5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[C#] CopyMemory関数が使用する.NETのバージョンやプラットフォームの違いで使えない理由 (Win32 API)

Last updated at Posted at 2025-05-21

この問題に遭遇している人を見かけることがあるためメモ。

Win32 APIのCopyMemory 関数が使用する.NETのバージョンやプラットフォームの違いで使えない理由は.NET Frameworkでは存在したCopyMemory 関数をP/Invokeした際のフォールバック処理が.NET Coreには存在しないため。

CopyMemory関数は実際にはkernel32.dllには存在せず、アンマネージド (C/C++) の環境ではこの関数は実際にはRtlCopyMemoryという関数へのマクロであり、RtlCopyMemory関数は更にCランタイムライブラリのmemcpy関数へのマクロになっている。

CopyMemory関数のMicrosoft Learnの公式ドキュメントにも以下の記載がある。

This function is defined as the RtlCopyMemory function. Its implementation is provided inline. For more information, see WinBase.h and WinNT.h.

以下の画像は64ビットのkernel32.dllのエクスポート (CopyMemory関数ではなくRtlCopyMemory関数がエクスポートされている)
k32_x64_exports_copymemory.png

以下の画像は32ビットのkernel32.dllのエクスポート (CopyMemory関数やRtlCopyMemory関数はエクスポートされていない)
k32_x86_exports_copymemory.png

プラットフォームが64ビットの場合はRtlCopyMemory関数のみkernel32.dllからエクスポートされているが、実際には64ビットのntdll.dll(他のRtlXxx関数やOSのネイティブAPI (NtXxx関数/システムコール) が実装されているDLL) からエクスポートされている同名の関数へのスタブ (RtlCopyMemoryStub) である。

以下の画像は64ビットのntdll.dllRtlCopyMemory関数のエクスポート
ntdll_exports_copymemory.png

.NET FrameworkではCopyMemory 関数をP/Invokeした場合はRtlCopyMemory関数かmemmove関数にフォールバックする仕様となっているが、.NET Coreにはこの特別処理は無い。

以下がこの関数に対応するWindows SDKに含まれるヘッダーファイル内の該当部分。

minwinbase.h
#define CopyMemory RtlCopyMemory
winnt.h / wdm.h (WDK)
#define RtlCopyMemory(Destination,Source,Length) memcpy((Destination),(Source),(Length))

ちなみにだが、MoveMemory FillMemory ZeroMemory関数も同様にマクロである。

minwinbase.h
#define MoveMemory RtlMoveMemory
#define FillMemory RtlFillMemory
#define ZeroMemory RtlZeroMemory
winnt.h / wdm.h (WDK)
#define RtlEqualMemory(Destination,Source,Length) (!memcmp((Destination),(Source),(Length)))
#define RtlMoveMemory(Destination,Source,Length) memmove((Destination),(Source),(Length))
#define RtlFillMemory(Destination,Length,Fill) memset((Destination),(Fill),(Length))
#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))

CopyMemory関数を使用しなくても.NET Framework 4.6 (Coreは1.0) 以降はSystem.BufferクラスのMemoryCopy メソッドが用意されており。
.NET 7以降はSystem.Runtime.InteropServices.NativeMemoryクラスにも同様のメソッドが用意されている。
またSystem.Runtime.CompilerServices.UnsafeクラスのCopyBlock メソッドも同様にコピーに使用できる。

どうしてもCopyMemory関数を使用する必要がある場合は、代わりにCランタイムライブラリ (msvcrt.dll / ucrtbase.dll) のmemcpy 関数の方をP/Invokeした方が良いと思われる。

C#でのmemcpy関数P/Invoke例
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, SetLastError = false)]
// void*
public static extern IntPtr memcpy(
    IntPtr _Dst, // void* (or byte* or byte[])
    IntPtr _Src, // void* (or byte* or byte[])
    UIntPtr _Size // size_t (UIntPtr = UInt32 (on 32-Bit), UInt64 (on 64-Bit))
    );

併せてだが、size_tと定義されているバッファーサイズを受け取るパラメーターをintuint型として定義している例が見受けられるが、これは間違い (プラットフォームを32ビットに固定する場合を除く)。

理由はsize_t型はコンパイルするプラットフォームでサイズが異なり、32ビットでは32ビット符号なし整数であるunsigned int(マネージド型はuint)型、64ビットでは64ビット符号なし整数であるunsigned __int64(マネージド型はulong)型となるので、マネージド型はUIntPtrnuint(C# 9+)を使用するのが正しい。

vcruntime.h (size_t)
// Definitions of common types
#ifdef _WIN64
    typedef unsigned __int64 size_t;
#else
    typedef unsigned int     size_t;
#endif
basetsd.h (SIZE_T)
#if defined(_WIN64)
    typedef __int64 LONG_PTR, *PLONG_PTR;
    typedef unsigned __int64 ULONG_PTR, *PULONG_PTR;
#else
    typedef _W64 long LONG_PTR, *PLONG_PTR;
    typedef _W64 unsigned long ULONG_PTR, *PULONG_PTR;
#endif

//
// SIZE_T used for counts or ranges which need to span the range of
// of a pointer.  SSIZE_T is the signed variation.
//

typedef ULONG_PTR SIZE_T, *PSIZE_T;
typedef LONG_PTR SSIZE_T, *PSSIZE_T;
intsafe.h / ntintsafe.h (WDK) (SIZE_T)
#ifdef _WIN64
typedef __int64             LONG_PTR;
typedef unsigned __int64    ULONG_PTR;
#else
typedef _W64 long           LONG_PTR;
typedef _W64 unsigned long  ULONG_PTR;

#ifdef _WIN64
typedef unsigned __int64    size_t;
#else
typedef _W64 unsigned int   size_t;
#endif

typedef LONG_PTR    SSIZE_T;
typedef ULONG_PTR   SIZE_T;
minwinbase.h (CopyMemory)
void CopyMemory(
  _In_       PVOID  Destination,
  _In_ const VOID   *Source,
  _In_       SIZE_T Length // SIZE_Tはsize_tと同じ
);
vcruntime_string.h (memcpy)
void* __cdecl memcpy(
    _Out_writes_bytes_all_(_Size) void* _Dst,
    _In_reads_bytes_(_Size)       void const* _Src,
    _In_                          size_t      _Size
    );
5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?