リード開発メモ

大阪のソフトウェア会社です。 技術的な事柄についてのメモとしてブログを始めます。

VC++ メモリリークの検出(その2)

VC++ でメモリリークを検出する方法として、これまで _CrtDumpMemoryLeaks 関数を使っていた。 VC++ メモリリークの検出

ただ、これだと OpenCV や Qt といったライブラリ側でのどうしようもないメモリリークまで拾ってしまい、非常に困っていた。

そこで別の方法としてVisual Leak Detectorを試してみる。

以下よりダウンロードして、インストールする。
Visual Leak Detector for Visual C++ 2008-2015

VC++ プロジェクトのプロパティから以下を設定する。
[C/C++] -[全般] - [追加のインクルードディレクトリ]に以下を追加。
  C:\Program Files (x86)\Visual Leak Detector\include
[リンカー] - [全般] - [追加のライブラリディレクトリ]に以下を追加。
  ビルド構成が Win32 の場合
    C:\Program Files (x86)\Visual Leak Detector\lib\Win32
  ビルド構成が x64 の場合
    C:\Program Files (x86)\Visual Leak Detector\lib\Win64
[リンカー] - [入力] - [追加の依存ファイル]に以下を追加。
  vld.lib

リンクは、#pragma comment(lib, xxxx) の方法ではビルドができなかった。

最後に、プログラムのいずれかの cpp ファイル、たとえば main.cpp などに以下を記載する。
#include <vld.h>

あとはビルドして実行するだけ。
終了時、出力ウィンドウにメモリリークの状況が出力される。
OpenCV や Qt といったライブラリ側でのメモリリークは検知しなかった。モノによってはするかもしれないが。

メモリリークがないときはこのように出力される。
No memory leaks detected.
Visual Leak Detector is now exiting.

メモリリークがあるときはこう。
WARNING: Visual Leak Detector detected memory leaks!
---------- Block 281 at 0x088800B0: 320 bytes ----------
  Leak Hash: 0x46FC2A4A, Count: 1, Total 320 bytes
  Call Stack (TID 6024):
    MSVCR120D.dll!operator new()
    c:\work\src\movieeditor\movieeditor\movieeditor\editwindow.cpp (42): MovieEditor.exe!EditWindow::EditWindow() + 0xA bytes
    c:\work\src\movieeditor\movieeditor\movieeditor\mainwindow.cpp (1032): MovieEditor.exe!MainWindow::editButton_clicked() + 0x27 bytes
    c:\work\src\movieeditor\movieeditor\movieeditor\generatedfiles\debug\moc_mainwindow.cpp (185): MovieEditor.exe!MainWindow::qt_static_metacall() + 0x8 bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x30C5F4 bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x30C03C bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x4E73FB bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x184F67 bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x1844EB bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x183912 bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x7E0FD bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x183471 bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x244A1B bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x33AAE bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x3053B bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x2CA892 bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x3E9A43 bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x35813 bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0xB2743 bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0xB17F8 bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x33AAE bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x2FECE bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x2CA892 bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x3E9A43 bytes
    Qt5Guid.dll!QPainter::drawImage() + 0x61D85 bytes
    Qt5Guid.dll!QPainter::drawImage() + 0x63DB9 bytes
    Qt5Guid.dll!QPainter::drawImage() + 0x38901 bytes
    qwindowsd.dll!qt_plugin_query_metadata() + 0x40295 bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x35AFFB bytes
    USER32.dll!SetManipulationInputTarget() + 0x53 bytes
    USER32.dll!CallWindowProcW() + 0x300 bytes
    USER32.dll!DispatchMessageW() + 0x251 bytes
    USER32.dll!DispatchMessageW() + 0x10 bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x35BB49 bytes
    qwindowsd.dll!qt_plugin_query_metadata() + 0x401B0 bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x2C65F7 bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x2C67FD bytes
    Qt5Cored.dll!QSortFilterProxyModel::mapToSource() + 0x2C8EBD bytes
    Qt5Guid.dll!QPainter::drawImage() + 0x606C8 bytes
    Qt5Widgetsd.dll!QAbstractScrollArea::setHorizontalScrollBar() + 0x2FB29 bytes
    c:\work\src\movieeditor\movieeditor\movieeditor\main.cpp (56): MovieEditor.exe!main() + 0x6 bytes
    c:\work\build\qt5_workdir\w\s\qtbase\src\winmain\qtmain_win.cpp (113): MovieEditor.exe!WinMain() + 0xD bytes
    f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c (466): MovieEditor.exe!WinMainCRTStartup()
    KERNEL32.DLL!BaseThreadInitThunk() + 0x24 bytes
    ntdll.dll!RtlUnicodeStringToInteger() + 0x253 bytes
    ntdll.dll!RtlUnicodeStringToInteger() + 0x21E bytes
  Data:
    00 00 00 00    00 00 00 00    00 00 00 00    80 E1 F9 57     ........ .......W
    00 00 CD CD    00 00 00 00    20 28 88 08    80 E1 F9 57     ........ .(.....W
    00 00 CD CD    00 00 00 00    D0 04 97 08    80 E1 F9 57     ........ .......W
    00 00 CD CD    00 00 00 00    60 7F 8F 05    80 E1 F9 57     ........ `......W
    00 00 CD CD    00 00 00 00    50 30 A7 08    80 E1 F9 57     ........ P0.....W
    00 00 CD CD    00 00 00 00    00 00 00 00    00 00 00 00     ........ ........
    00 00 00 00    CD CC CC 3E    01 00 00 00    FF FF FF FF     .......> ........
    00 00 FF FF    00 00 CD CD    70 00 9E 05    80 02 00 00     ........ p.......
    68 01 00 00    90 C0 93 05    7F 03 00 00    88 06 90 05     h....... ........
    80 52 79 05    C0 B9 79 05    30 42 8B 05    B0 7F A2 08     .Ry...y. 0B......
    B0 8D 9F 08    48 94 57 57    48 94 57 57    FF FF FF FF     ....H.WW H.WW....
    FF FF FF FF    00 CD CD CD    48 94 57 57    E0 FE 9D 05     ........ H.WW....
    01 CD CD CD    68 94 57 57    90 ED 90 05    88 0A 9E 05     ....h.WW ........
    20 0C 9E 05    E0 6F 8B 08    88 84 92 05    00 00 CD CD     .....o.. ........
    B8 FB 9D 05    68 7F 70 00    00 00 00 00    00 00 00 00     ....h.p. ........
    FF FF 00 00    00 00 00 00    00 00 CD CD    68 94 57 57     ........ ....h.WW


Visual Leak Detector detected 1 memory leak (356 bytes).
Largest number used: 4858632 bytes.
Total allocations: 18686028 bytes.
Visual Leak Detector is now exiting.

上のほうに、「editwindow.cpp (42)」とあるが、これがばっちり今回メモリリークの原因となったnewの位置を示していた。_CrtDumpMemoryLeaks 関数のように問題の場所を特定するための手間が不要なのがいい。

ただし、処理速度は恐ろしく遅い。そのため、常時使うというよりはメモリリークを調べるときだけ、プロジェクトの設定を変えることになりそう。

以上。

独自 MediaTypeFormatter を追加する

ASP.NET Web API のテスト用HTML において、enctype="multipart/form-data" なフォームからpostしたところ、「メディアの型 'multipart/form-data' のコンテンツから型 'RequestData' のオブジェクトを読み取るために使用可能な MediaTypeFormatter がありません。」というエラーが出た。

RequestDataは Controller の API の引数として定義しているクラス。

調べてみると、enctype="multipart/form-data" のときは、自分で MediaTypeFormatter を定義し、バインドできるようにする必要があった。

まず、RequestData の定義。
public class RequestDataFile
{
	public byte[] Buffer { get; set; }
	public string FileName { get; set; }
	public string MediaType { get; set; }
}

public class RequestData
{
	public string textdata { get; set; }
	public RequestDataFile filedata { get; set; }
}

そして、以下のような MediaTypeFormatter を継承したクラスを作る。
public class CustomMediaFormatter : MediaTypeFormatter
{
	public CustomMediaFormatter()
	{
		SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/octet-stream"));
		SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
	}

	public override bool CanReadType(Type type)
	{
		return type == typeof(RequestData);
	}

	public override bool CanWriteType(Type type)
	{
		return false;
	}

	public async override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
	{
		if (!content.IsMimeMultipartContent())
		{
			throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
		}

		var ret = new RequestData();

		var Parts = await content.ReadAsMultipartAsync();

		// textdata という名前のパートを探す
		foreach (var Part in Parts.Contents.Where(x => x.Headers.ContentDisposition.DispositionType == "form-data"
			&& x.Headers.ContentDisposition.Name.ToLower() == "\"textdata\""))
		{
			var Data = await Part.ReadAsStringAsync();
			ret.textdata = Data;
			break;
		}

		// filedataという名前のパートを探す
		foreach (var Part in Parts.Contents.Where(x => x.Headers.ContentDisposition.DispositionType == "form-data"
			&& x.Headers.ContentDisposition.Name.ToLower() == "\"filedata\""))
		{
			HttpContent FileContent = Part;
			if (FileContent.Headers.ContentLength > 0)
			{
				var fileInfo = new RequestDataFile();
				fileInfo.FileName = FileContent.Headers.ContentDisposition.FileName;
				fileInfo.MediaType = FileContent.Headers.ContentType.MediaType;
				using (var Imgstream = await FileContent.ReadAsStreamAsync())
				{
					fileInfo.Buffer = new byte[16 * 1024];
					using (var Ms = new MemoryStream())
					{
						int Read;
						while ((Read = Ms.Read(fileInfo.Buffer, 0, fileInfo.Buffer.Length)) > 0)
						{
							Ms.Write(fileInfo.Buffer, 0, Read);
						}
					}
				}
				ret.filedata= fileInfo;
			}
			break;
		}
		
		return ret;
	}
}

最後に App_Start の下にある WebApiConfig クラスの Register メソッドで、上のクラスを登録する。
public static class WebApiConfig
{
	public static void Register(HttpConfiguration config)
	{
		// multipart/form-data のリクエストをバインディングする
		config.Formatters.Add(new CustomMediaFormatter());

		config.Routes.MapHttpRoute(
			name: "DefaultApi",
			routeTemplate: "api/{controller}/{id}",
			defaults: new { id = RouteParameter.Optional }
		);
	}
}

以上です。

シンプルな ASP.NET Web API のプロジェクトを作成する。

ASP.NET Web API プロジェクトを作成すると、多くの不要なコンポーネントを参照することになるようだ。 packages フォルダを見てみると、Entity Framework や jQuery などがあり、単に Web API だけのプロジェクトには不要なものがたくさんある。

そこでシンプルな ASP.NET Web API プロジェクトを作成する。Visual Studio Professional 2013 を使う。

まず、
  ファイル - 新規作成 - プロジェクト
より、ダイアログを起動し、
  Visual C# - Web - Visual Studio 2012 - ASP.NET MVC 4 Web アプリケーション
を選択する。
aspnet_api1

次に、「Web API」ではなく、「空」を選ぶだけ。
aspnet_api2

できたプロジェクトはこんな感じ。
サンプルとして入っているコントローラーがないが、どうせ削除するので要らない。
aspnet_api3

ちなみに「Web API」を選んだ場合はこんな感じ。
aspnet_api4

以上です。

アクセスカウンター
  • 今日:
  • 昨日:
  • 累計: