.NET Framework で使っていた CopyMemory が .NET Core で使えなくなった場合の対処法

計算処理や画像処理でデータのブロックコピーをする場合、WindowsAPI の CopyMemory を使っていたプログラマが直面しそうな話題です。

.NET Framework のアプリケーションでは問題なかったのに、.NET Core アプリケーションにすると CopyMemory の実行時例外が出てしまうというトラブルに遭遇することがあります。

結論から言うと、この場合の対処法は CopyMemory メソッドが格納されている kernel32.dll における該当メソッドのエントリポイントの名称を明示するか、しないか、という問題を解決すればいいです。

.NET Framework から使う場合は、エントリポイントの名前を "CopyMemory" とし
.NET Core から使う場合は "RtlCopyMemory" とすればいいです。

サンプルソース

いちおう、CopyMemory を扱うライブラリのコードを示します。冒頭の三行の条件コンパイルの定数をどれかひとつコメントインして試してみてください。

// このみっつのうち、どれかひとつをコメントインして試してみましょう.

//#define TEST_ENTRY_POINT_NOT_EXPLICIT    // .NET Framework で成功、.NET Core で失敗. 
//#define TEST_ENTRY_POINT_COPY_MEMORY     // .NET Framework で成功、.NET Core で失敗.
#define TEST_ENTRY_POINT_RTL_COPY_MEMORY // .NET Framework で失敗、.NET Core で成功.

// このみっつのうち、どれかひとつをコメントインして試してみましょう.

using System;
using System.Runtime.InteropServices;

namespace MyLibirary
{

	public class Utility
	{

		const String DLL_NAME = "kernel32.dll";

#if TEST_ENTRY_POINT_NOT_EXPLICIT

		// エントリポイントを明示しない方式, .NET Framework アプリケーション用.

		[DllImport( DLL_NAME )] // overload 0.
		public static unsafe extern void CopyMemory( void* dst, void* src, int bytes );

		[DllImport( DLL_NAME )] // overload 1.
		public static unsafe extern void CopyMemory( IntPtr dst, IntPtr src, int bytes );

		[DllImport( DLL_NAME )] // overload 2.
		public static unsafe extern void CopyMemory( byte* dst, byte* src, int bytes );

		[DllImport( DLL_NAME )] // overload 3.
		public static unsafe extern void CopyMemory( byte* dst, IntPtr src, int bytes );

		[DllImport( DLL_NAME )] // overload 4.
		public static unsafe extern void CopyMemory( void* dst, IntPtr src, int bytes );

		[DllImport( DLL_NAME )] // overload 5.
		public static unsafe extern void CopyMemory( IntPtr dst, byte* src, int bytes );

		[DllImport( DLL_NAME )] // overload 6.
		public static unsafe extern void CopyMemory( IntPtr dst, void* src, int bytes );

#elif TEST_ENTRY_POINT_COPY_MEMORY

		// エントリポイントを明示する方式, .NET Framework アプリケーション用.

		const String FUNCTION_NAME = "CopyMemory";

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 0.
		public static unsafe extern void CopyMemory( void* dst, void* src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 1.
		public static unsafe extern void CopyMemory( IntPtr dst, IntPtr src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 2.
		public static unsafe extern void CopyMemory( byte* dst, byte* src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 3.
		public static unsafe extern void CopyMemory( byte* dst, IntPtr src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 4.
		public static unsafe extern void CopyMemory( void* dst, IntPtr src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 5.
		public static unsafe extern void CopyMemory( IntPtr dst, byte* src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 6.
		public static unsafe extern void CopyMemory( IntPtr dst, void* src, int bytes );

#elif TEST_ENTRY_POINT_RTL_COPY_MEMORY

		// エントリポイントを明示する方式, .NET Core アプリケーション用.

		const String FUNCTION_NAME = "RtlCopyMemory"; // ランタイムライブラリバージョン.

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 0.
		public static unsafe extern void CopyMemory( void* dst, void* src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 1.
		public static unsafe extern void CopyMemory( IntPtr dst, IntPtr src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 2.
		public static unsafe extern void CopyMemory( byte* dst, byte* src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 3.
		public static unsafe extern void CopyMemory( byte* dst, IntPtr src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 4.
		public static unsafe extern void CopyMemory( void* dst, IntPtr src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 5.
		public static unsafe extern void CopyMemory( IntPtr dst, byte* src, int bytes );

		[DllImport( DLL_NAME, EntryPoint = FUNCTION_NAME )] // overload 6.
		public static unsafe extern void CopyMemory( IntPtr dst, void* src, int bytes );

#endif

		public Utility()
		{

		}

	}
}

CopyMemory のコール側のコードは下記になります。

.NET Framework のアプリケーションでも .NET Core のアプリケーションでも同じコードです。Form1 に button1 を配置して、ボタンクリックイベントに CopyMemory を使ったデータコピーのコードを書いてあります。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace aaa
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}

		private void Form1_Load( object sender, EventArgs e )
		{

		}

		private void button1_Click( object sender, EventArgs e )
		{

			const int NUM_ALLOC = 256;
			byte [] data_src = new byte[ NUM_ALLOC ];
			byte [] data_dst = new byte[ NUM_ALLOC ];

			for ( int k = 0; k < NUM_ALLOC; k++ )
			{
				data_src[k] = (byte)(k);
				data_dst[k] = 0x00;
			}

			unsafe
			{

				// ひとつの要素のバイト数を取得して、総データコピーのバイト数を計算する.
				int copy_bytes = NUM_ALLOC * System.Runtime.InteropServices.Marshal.SizeOf( typeof( byte ));

				fixed( byte* pt_dst = data_dst, pt_src = data_src )
				{
					// データコピーを実施する.
					MyLibirary.Utility.CopyMemory( pt_dst, pt_src, copy_bytes );
				}

			}

			StringBuilder sb = new StringBuilder();

			// とりあえず先頭の16要素だけ数値をチェックする.
			for ( int k = 0; k < 16; k++ )
			{
				String line = String.Format( "{0}\t{1}", data_src[k], data_dst[k] );
				sb.AppendLine( line );
			}

			MessageBox.Show( sb.ToString().Trim() );

		}
	}
}