[初心者] メモリーアクセス違反で困っています


初心太郎  2010-10-15 18:48:40  No: 71961

お世話になります。

環境:WinXP SP2, VS2005 Express

C++入門したばかりの初心者です。
いろいろサンプルに触れながら基礎講座で勉強しています。

やりたいこと:
ウィンドウのスクリーンショットをJPEG保存したいです。

ググってみたら以下のようなサンプルがありました。(長くて申し訳ありません。)

----------------------------------------

/*
   BITMAP-DIB変換とウインドウのBMP保存

   2000/ 4/22 - 2001/ 4/10  宍戸  輝光
*/

#include <windows.h>
#include <stdlib.h>
#include <time.h>

HWND hwMain;
HINSTANCE hInst;

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
void saveBMP(HWND,LPCTSTR);

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
                    PSTR szCmdLine,int iCmdShow){

  MSG  msg;
  WNDCLASSEX wndclass;

  hInst=hInstance; /* プロセスのハンドルを保存 */

  srand((unsigned)time(NULL));

  wndclass.cbSize        = sizeof(wndclass);
  wndclass.style         = CS_HREDRAW | CS_VREDRAW;
  wndclass.lpfnWndProc   = WndProc;
  wndclass.cbClsExtra    = 0;
  wndclass.cbWndExtra    = 0;
  wndclass.hInstance     = hInstance;
  wndclass.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
  wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
  wndclass.lpszMenuName  = NULL;
  wndclass.lpszClassName = "CWindow";
  wndclass.hIconSm       = LoadIcon(NULL,IDI_APPLICATION);

  RegisterClassEx(&wndclass); /* ウインドウクラス登録 */

  hwMain = CreateWindow("CWindow","BITMAP-DIB変換",
    WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,256,96,
      NULL,NULL,hInstance,NULL);

  ShowWindow(hwMain,iCmdShow); /* ウインドウを表示 */
  UpdateWindow(hwMain);

  while (GetMessage(&msg,NULL,0,0)) { /* メッセージループ */

    TranslateMessage(&msg);
    DispatchMessage(&msg);

  }

  return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {

  int i,x,y,r,g,b;
  RECT rec;
  HDC hdc;
  PAINTSTRUCT ps;
  HWND btSave;

  switch (iMsg) {

    case WM_CREATE: /* ウインドウ作成時の処理 */

      /* ボタン作成 */
      btSave=CreateWindow("Button","保存",WS_CHILD|WS_VISIBLE,
        8,8,80,32,hwnd,(HMENU)0,hInst,NULL);

      return 0;

    case WM_COMMAND: /* コマンドメッセージ */

      switch (LOWORD(wParam)) {

        case 0: /* 保存ボタン */

          saveBMP(hwnd,"test.bmp");

          break;

      }

      return 0;

    case WM_PAINT:

      hdc=BeginPaint(hwnd,&ps);

      GetClientRect(hwnd,&rec); /* クライアント領域取得 */

      for (i=0;i<(rec.right*rec.bottom)/48;i++) {

        x=(int)(((double)rand()/(double)RAND_MAX)*rec.right);
        y=(int)(((double)rand()/(double)RAND_MAX)*rec.bottom);
        r=(int)(((double)rand()/(double)RAND_MAX)*160)+96;
        g=(int)(((double)rand()/(double)RAND_MAX)*128)+128;
        b=(int)(((double)rand()/(double)RAND_MAX)*192)+64;

        SetPixel(hdc,x,y,RGB(r,g,b));

      }

      EndPaint(hwnd,&ps);

      return 0;

    case WM_DESTROY : /* 終了処理 */

      PostQuitMessage(0);

      return 0;

  }

  return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

void saveBMP(HWND hwnd,LPCTSTR lpszFn) {

  DWORD dwSize,dwFSize,dwWidth,dwHeight,dwLength;
  HANDLE fh;
  LPBITMAPFILEHEADER lpHead;
  LPBITMAPINFOHEADER lpInfo;
  LPBYTE lpBuf,lpPixel;
  RECT rec;
  HDC hdc,hdcMem;
  HBITMAP hBMP,hOld;

  GetClientRect(hwnd,&rec); /* クライアント領域取得 */

  dwWidth=rec.right;
  dwHeight=rec.bottom;

  if ((dwWidth*3) % 4==0) /* バッファの1ラインの長さを計算 */
    dwLength=dwWidth*3;
  else
    dwLength=dwWidth*3+(4-(dwWidth*3) % 4);

  /* 書き込み用バッファのサイズ計算 */
  dwFSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+dwLength*dwHeight;

  /* バッファ確保とポインタ設定 */
  lpBuf=(LPBYTE)GlobalAlloc(GPTR,dwFSize);
  lpHead=(LPBITMAPFILEHEADER)lpBuf;
  lpInfo=(LPBITMAPINFOHEADER)(lpBuf+sizeof(BITMAPFILEHEADER));
  lpPixel=lpBuf+sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);

  /* 24ビットBMPファイルのヘッダ作成 */
  lpHead->bfType='M'*256+'B';
  lpHead->bfSize=dwFSize;
  lpHead->bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
  lpInfo->biSize=sizeof(BITMAPINFOHEADER);
  lpInfo->biWidth=dwWidth;
  lpInfo->biHeight=dwHeight;
  lpInfo->biPlanes=1;
  lpInfo->biBitCount=24;

  /* ウインドウのデバイスコンテキスト取得 */
  hdc=GetDC(hwnd);
  /* ウインドウのデバイスコンテキスト互換のBITMAP作成 */
  hBMP=CreateCompatibleBitmap(hdc,dwWidth,dwHeight);

  /* BITMAPにウインドウのクライアント領域をコピー */
  hdcMem=CreateCompatibleDC(hdc);
  hOld=SelectObject(hdcMem,hBMP);
  BitBlt(hdcMem,0,0,dwWidth,dwHeight,hdc,0,0,SRCCOPY);
  SelectObject(hdcMem,hOld);
  GetDIBits(hdc,hBMP,0,dwHeight,lpPixel,(LPBITMAPINFO)lpInfo,DIB_RGB_COLORS);

  ReleaseDC(hwnd,hdc);
  DeleteObject(hBMP);
  DeleteObject(hdcMem);

  /* バッファをファイルに書き出す */
  fh=CreateFile(lpszFn,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
  WriteFile(fh,lpBuf,dwFSize,&dwSize,NULL);
  CloseHandle(fh);

  GlobalFree(lpBuf);

}

----------------------------------------

これはBMP保存だったのでJPEG保存する方法をまたググって
以下のサンプルが見つかりました。
ATLが使えないのでGDI+で…

----------------------------------------

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
  UINT  num = 0;          // number of image encoders
  UINT  size = 0;         // size of the image encoder array in bytes

  ImageCodecInfo* pImageCodecInfo = NULL;

  GetImageEncodersSize(&num, &size);
  if(size == 0)
    return -1;  // Failure

  pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
  if(pImageCodecInfo == NULL)
    return -1;  // Failure

  GetImageEncoders(num, size, pImageCodecInfo);

  for(UINT j = 0; j < num; ++j)
  {
    if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
    {
      *pClsid = pImageCodecInfo[j].Clsid;
      free(pImageCodecInfo);
      return j;  // Success
    }
  }

  free(pImageCodecInfo);
  return -1;  // Failure
}

...

  ULONG dwToken;
  Gdiplus::GdiplusStartupInput input;
  Gdiplus::GdiplusStartupOutput output;
  Gdiplus::Status status = Gdiplus::GdiplusStartup(&dwToken, &input, 
&output);
  if(status == Gdiplus::Ok)
  {
    CLSID jpgClsid;
    CLSID bmpClsid;
    GetEncoderClsid(L"image/jpeg", &jpgClsid);

    Gdiplus::Bitmap SrcBitmap(L"c:\\test.bmp", FALSE);
    SrcBitmap.Save(L"c:\\test.jpg", &bmpClsid);

    Gdiplus::GdiplusShutdown(dwToken);
  }
...

----------------------------------------

そのままJPEGに保存するように修正するのはまだ無理だと思って
上記のBMPでキャプチャするコードに挿入してみました。

----------------------------------------

/*
   BITMAP-DIB変換とウインドウのBMP保存

   2000/ 4/22 - 2001/ 4/10  宍戸  輝光
*/

#include "stdafx.h"
#include <time.h>

using namespace Gdiplus;

HWND hwMain;
HINSTANCE hInst;

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
void saveBMP(HWND,LPCTSTR);

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
  UINT  num = 0;          // number of image encoders
  UINT  size = 0;         // size of the image encoder array in bytes

  ImageCodecInfo* pImageCodecInfo = NULL;

  GetImageEncodersSize(&num, &size);
  if(size == 0)
    return -1;  // Failure

  pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
  if(pImageCodecInfo == NULL)
    return -1;  // Failure

  GetImageEncoders(num, size, pImageCodecInfo);

  for(UINT j = 0; j < num; ++j)
  {
    if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
    {
      *pClsid = pImageCodecInfo[j].Clsid;
      free(pImageCodecInfo);
      return j;  // Success
    }
  }

  free(pImageCodecInfo);
  return -1;  // Failure
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
                    PSTR szCmdLine,int iCmdShow){

……

  ReleaseDC(hwnd,hdc);
  DeleteObject(hBMP);
  DeleteObject(hdcMem);

  /* バッファをファイルに書き出す */
  fh=CreateFile(lpszFn,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
  WriteFile(fh,lpBuf,dwFSize,&dwSize,NULL);
  CloseHandle(fh);

  GlobalFree(lpBuf);

  //**************test
  
  ULONG dwToken;
  GdiplusStartupInput input;
  GdiplusStartupOutput output;
  Status status = GdiplusStartup(&dwToken, &input, &output);
  if(status == Ok)
  {
    CLSID jpgClsid;
    CLSID bmpClsid;
    GetEncoderClsid(L"image/jpeg", &jpgClsid);

    Bitmap SrcBitmap(L"test.bmp", FALSE);
    SrcBitmap.Save(L"test.jpg", &bmpClsid);

    GdiplusShutdown(dwToken);
  }
  
  //**************test

}

----------------------------------------

……の所は一緒です。

これをデバッグしたら、保存ボタンを押したとき、以下のエラーが発生します。

----------------------------------------

test4.exe の 0x4af171cf で初回の例外が発生しました: 0xC0000005: 場所 0x00d940a4 を読み込み中にアクセス違反が発生しました。
test4.exe の 0x4af171cf でハンドルされていない例外が発生しました: 0xC0000005: 場所 0x00d940a4 を読み込み中にアクセス違反が発生しました。

----------------------------------------

場所はここです。

===========(GdiplusBitmap.h)============
inline 
Image::~Image()
{
    DllExports::GdipDisposeImage(nativeImage);
} //<-ここです。
----------------------------------------

ポインター等まだちゃんと把握できていないまま
質問させていただき申し訳ありませんが
ご指導よろしくお願いします。


maru  2010-10-15 23:08:02  No: 71962

> ポインター等まだちゃんと把握できていないまま
のレベルで
> ウィンドウのスクリーンショットをJPEG保存したいです。
はちょっと無茶ではないか?とも思いますが...

どこに問題があるを直接指摘することはできませんが、以下のようなことが
考えられます。
エラーが発生しているのはImageのデストラクタだが、コード中にImage
クラスのオブジェクトはない。考えるにImageクラスはコード中に出てくる
オブジェクトの派生元クラスであると思われ、そのオブジェクトの操作に
なにか正しくないところがあり、そのためにエラーが発生している。
アクセス違反が発生したところでスタックトレースを見て呼び出し元オブ
ジェクトを特定、そのオブジェクトの操作におかしいところが無いかをチ
ェックすることで、エラーの原因を絞る事ができるかも知れない。
ほかの原因としてはそのオブジェクトの前後(に宣言している)の変数に
対しておかしな操作を行ってメモリに不正値を書き込んでしまっていること
が考えられる。

サンプルを探して実行してみることも重要ですが、ただ単に動かすだけで
なく、そのコードの一行一行が何をやっているかを理解しないと応用でき
ません。
まぁ、先は長いと思いますががんばってみてください。


gak  2010-10-16 02:55:47  No: 71963

> CLSID jpgClsid;
> CLSID bmpClsid;
> GetEncoderClsid(L"image/jpeg", &jpgClsid);
>
> Bitmap SrcBitmap(L"test.bmp", FALSE);
> SrcBitmap.Save(L"test.jpg", &bmpClsid);
bmpClsid って宣言してるだけで中身未設定じゃね?
それを SrcBitmap.Save() に渡してるのはマズくね?


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加