none
クリップボードへデータを転送していますが、エアロの時に、 1回だけしか転送されません。 RRS feed

  • 質問

  • VS2008でC++です。
    下記のコードのように、クリップボードへデータを転送していますが、Windows7でエアロ表示の時に、
    1回目は転送されますが、グラフィックの表示を変え再度、転送しようとしても前のままです。
    対処の仕方を教えてください。

    void CTestOpGLCtrl::ClipBoard() 
    {
    	CTestOpGLCtrl *pWnd = (CTestOpGLCtrl *)this;	// このウィンドウ
    	CRect rc;					// ウィンドウサイズを得るのに使う
    	CBitmap BitClip;				// クリップするビットマップ
    	CDC MemDC;					// メモリデバイスコンテキスト
    	CClientDC ClientDC(this);			//  クライアントデバイスコンテキスト
    
    	pWnd->GetClientRect(&rc);
    
    	int AeroV = 0;
    	BitClip.CreateCompatibleBitmap((CDC*)&ClientDC, rc.Width(), rc.Height());
    	MemDC.CreateCompatibleDC((CDC*)&ClientDC);
    	MemDC.SelectObject(&BitClip);
    	MemDC.BitBlt(0,0,
    	rc.Width(),
    	rc.Height() - AeroV,
    	(CDC*)&ClientDC,
    	0,0,
    	SRCCOPY);
    
    	// クリップボードをオープンしクリアする
    	pWnd->OpenClipboard();
    	::EmptyClipboard();
    	// クリップボードにデータを転送
    	::SetClipboardData(CF_BITMAP, BitClip.m_hObject);
    	BitClip.Detach();
    	// クリップボードをクローズ	
    	CloseClipboard();
    }
    

    2013年1月30日 2:13

すべての返信

  • ざっと見て妙な所は、

        >BitClip.Detach();

    ですかね。

     1.一般に、CloseClipboard()するまでは、設定したオブジェクトが
      有効である必要があります。CloseClipboard()を単なる符丁だと
      軽視するべきでは有りません。
     2.また、ビットマップの破棄としては、つまり構築したものを破棄するのに
      Detach()を使うべきではありません。これは単なる「取り外し」以外は
      何もしません。
      正しくは
      // BitClip.Detach();★不要
      CloseClipboard(); // クリップボードを閉じる
      MemDC.SelectObject( pOldBitmap); // 選択したときに取得しておいたもの
      BitClip.DeleteObject(); // ビットマップの破棄
      MemDC.DeleteDC(); // メモリーDCの破棄

    あたりを直してみたらどうでしょう。

    2013年1月30日 3:47
  • 以下のようにしましたが、結果は同じでした。

    void CTestOpGLCtrl::ClipBoard() 
    {
    	CTestOpGLCtrl *pWnd = (CTestOpGLCtrl *)this;	// このウィンドウ
    	CRect rc;					// ウィンドウサイズを得るのに使う
    	CBitmap BitClip;			// クリップするビットマップ
    	CDC MemDC;					// メモリデバイスコンテキスト
    	CClientDC ClientDC(this);	// クライアントデバイスコンテキスト
    
    	pWnd->GetClientRect(&rc);
    
    	BitClip.CreateCompatibleBitmap((CDC*)&ClientDC, rc.Width(), rc.Height());
    	MemDC.CreateCompatibleDC((CDC*)&ClientDC);
    	CBitmap *pOldBitmap = MemDC.SelectObject(&BitClip);
    	MemDC.BitBlt(0,0,
    				rc.Width(),
    				rc.Height(),
    				(CDC*)&ClientDC,
    				0,0,
    				SRCCOPY);
    
    	// クリップボードをオープンしクリアする
    	pWnd->OpenClipboard();
    	::EmptyClipboard();
    	// クリップボードにデータを転送
    	::SetClipboardData(CF_BITMAP, BitClip.m_hObject);
    	// クリップボードをクローズ	
    	CloseClipboard();
    	MemDC.SelectObject(pOldBitmap); // 選択したときに取得しておいたもの
    	BitClip.DeleteObject(); // ビットマップの破棄
    	MemDC.DeleteDC(); // メモリーDCの破棄
    	
    }

    2013年1月30日 5:59
  • ですか。そうなると、

     1.ペースト先で対象ビットマップを破棄している可能性。
      これを排除するにはペースト先をペイント、ワードなどの
      信頼できるものにする。
     2.固有の操作「グラフィックの表示を変え」の時に破棄されている可能性。
      これを排除するには、この操作を取りやめます。

    以上をやってみてはどうでしょう。
    その上でも、なお症状が出る場合は、

     3.元のHWNDとそれから取得したDCの特殊性を排除する。
      これを排除するには、
      3.1.CClientDC の排除
        CClientDそを取りやめ、CDC * pWndDC = pWnd->GetWindowDC()を使ってみる。
      3.2.CTestOpGLCtrlの実装を一旦おいといて、他のCWnd派生クラスに
        実装しなおしてみる。

    などをやってみます。
    その上でも、なお症状が出る場合は、「エアロ」固有の問題と考えられます。

    さて、多分杞憂だと思いますが、固有の操作「グラフィックの表示を変え」を
    行った場合で、その内容をクリップボードの内容に反映させるには、本ClipBoard() 関数を
    実行する必要があります(動的にクリップボード内のデータを更新することはできません)。
    これは、当該クリップデータを供給したアプリケーションのインスタンスが
    終了しても、同データをペーストできることから、明らかです。
    クリップボードは、

     4.SetClipboardData()で与えられたデータの完全なコピーを
      EmptyClipboard()されるまで、独自に保持する(但し、「遅延レンダリング」を使う場合を除く)。

    から、このような事が可能なのですね。
    このあたりは大丈夫ですよね(笑)。

    2013年1月30日 6:35
  • > 1.ペースト先で対象ビットマップを破棄している可能性。

    >これを排除するにはペースト先をペイント、ワードなどの
    >信頼できるものにする。

    ペースト先はペイントです。


    >2.固有の操作「グラフィックの表示を変え」の時に破棄されている可能性。
    >これを排除するには、この操作を取りやめます。

    操作を取りやめたら、グラフィックは前のままで意味がありません。
    変わったグラフィックをClipboardに転送したいのですが。

    ちなに、C++はActiveXで、C#やVB6.0で利用していますが、同じ結果です。

    2013年1月30日 7:49
  • >変わったグラフィックをClipboardに転送したいのですが。

     1.この操作の後(グラフィックを変更した後)、
     2.void CTestOpGLCtrl::ClipBoard() を実行し、
     3.ペイントにペーストしたが、操作結果か反映されない。
     4.「エアロ」でない場合は、操作結果か反映される。

    ということでしょうか。

    その場合で、かつ、前出の「3.1.」も「3.2.」もやってみたが、
    だめだったということでしょうか。

    そうである場合、次の仮説を立てます。

     A.「エアロ」の場合、当該の「グラフィックの変更」をしても
      以前の状態がキャッシュされたままとなっている可能性がある。

    この仮説が真実である場合、当該の「グラフィックを変更」は

     5.void CTestOpGLCtrl::ClipBoard()内のCClientDCに対して行う必要が
      あるかもしれない。
     6.または、「エアロ」のキャッシュを無効化する手段を探さなくてはならない。

    が、導かれます。


    2013年1月30日 9:29
  • Windows API SetClipboardDataで、クリップボードに渡すHGLOBALはOSが管理するので、これを「自分で」解放することに関しては

    SetClipboardData function

    The application may not write to or free the data once ownership has been transferred to the system

    (※日本語版ページが、「」または「」を参照してください。みたいな感じでいつも通りに消えてる部分があったので、英語版ページを見ました。)

    と書いてあるので、「may not」は「must not」ほどの強制力はないですが、やるべきではないものと思います。日本語版ページでも、「そのハンドルを解放することや、ロックし続けることを避けなければなりません。」と書かれていました。

    といっても、デバイス依存のビットマップなので、GlobalAllocな感じのHGLOBALじゃなくて、CreateCompatibleBitmapなHBITMAPになっている、ということですが、公式ドキュメントを見つけていないのでこれが絶対に正しいかは私にはホントはわかりません。ただ

    Standard Clipboard Formats

    のページを見るとCF_BITMAPはHBITMAPでいいということのようですが、CF_DIBでない以上、CF_BITMAPではデバイス依存のビットマップだから、やっぱりCreateCompatibleBitmapで作るやつでいい・・・んではないかなぁとは思いますし、いろんなサイト見ても同じように書いてありますので、たぶんこれ自体はいいんじゃないかなと思います。


    MFCの内部事情に関しては詳しく知らないのですが、おそらくこの辺の処理に関してはWindows APIを薄くラップしてある分だと思うのと、クリップボードに自分のアプリの画面を書き出すというのも、まだ実装してなくてちょっとやってみたいと思ったので、一対一のWindows API呼び出しで実験してみました。(Windows 8 VC++2012 Expressです)

    実際には自分でラップしたライブラリを使ってるので使ってる側のコードは全般にわたって違いますが、最終的に翻訳するとこんな感じで試しました。(wndが目的のHWND型、エラーチェックは本題とはずれて見辛くなるので省いています。)

    	RECT rc; 
    	GetClientRect( wnd, &rc ); 	
    	const int cx = static_cast<int>( rc.right - rc.left );
    	const int cy = static_cast<int>( rc.bottom - rc.top );
    
    	HDC const hdc = GetDC( wnd ); 
    	HBITMAP const hbmp = CreateCompatibleBitmap( hdc, cx, cy ); 
    	{
    		HDC const desti_hdc = CreateCompatibleDC( hdc ); 
    		HGDIOBJ const old = SelectObject( desti_hdc, hbmp ); 
    		
    			BitBlt( desti_hdc, 0, 0, cx, cy, hdc, 0, 0, SRCCOPY); 	
    
    		SelectObject( desti_hdc, old );
    		DeleteDC( desti_hdc ); 
    	}
    	ReleaseDC( wnd, hdc );
    
    	if(OpenClipboard(wnd)) {
    		EmptyClipboard();
    		SetClipboardData(CF_BITMAP, hbmp);
    		CloseClipboard();
    	}


    これだと、こちらの環境で、何回やっても出来ました。(ペースト対象は出来るだけ同条件に近く、Windows 8のペイントに)windows 8はエアロという概念がユーザー側からはないはずですが、内部で同じようなことやってる・・・はずなので、そう違うわけでもないとは思うのですが。

    うーむ、何が違うんだろう・・・?(このコードでも同じ結果になりますかね? そうだとしたら7のエアロ特有の問題なのだろうか)

    ※HDCはマルチスレッド考慮済みではないので、もし別のスレッドからもGetDCとかがあるとなると、排他制御の必要があるはずです。そういうのは無いのですよね?

    • 編集済み mr.setup 2013年1月30日 10:39 VS2008用にautoを使わないコードに修正
    2013年1月30日 10:04
  • CTestOpGLCtrl って、OpenGL か何か使っているのでしょうか?
    あとは、その描画先が this が示すウィンドウなのか、別の ウィンドウ なのかもきちんと確認しておいた方がいいかも。

    DWM(Aero)有効環境下では、きちんとウィンドウを切り分けて管理されていたはずなので、違うウィンドウの DC を使っていたらずっと書き換わらず、DWM(Aero)無効時のみきちんと取得できるという話もあり得るので、可能性は一つずつつぶしておいた方がいいかなと思って書きました。

    // 一気に全部解決しようとせず、一つずつ着実に。

    2013年1月30日 13:20
    モデレータ
  • 追加。
    「グラフィックの表示を変え」の最終段で、
    つまり、クリップボードにコピーする前に、
      ::GdiFlush()
    を呼びましょう。
    まぁ、自分も良く忘れちゃいますけど(vv;)。
    2013年1月31日 9:37
  • > ::GdiFlush()を呼びましょう。
    変わりませんでした。

    >  3.1.CClientDC の排除
    >    CClientDそを取りやめ、CDC * pWndDC = pWnd->GetWindowDC()を使ってみる。
    >  3.2.CTestOpGLCtrlの実装を一旦おいといて、他のCWnd派生クラスに
    >    実装しなおしてみる。
    ここらあたりよく分からず出来ていません。


    改めて

    VS2008のC++でActiveXで、OpenGLを利用してグラフックを描いています。
    そのグラフックをClipboardに転送しています。

    XPや7のWindowsクラッシックでは問題なく転送出来ていまして、ペイントに正しくペーストされます。
    7やVistaのエアロで、1回目の転送はされていますが、2回目以降は1回目の分が転送されています。
    (1回目の転送の後、Print Screenキーでできたディスクトップの画像をペイントにペーストした後、
    グラフィックを変更し転送すると、1回目のグラフィックが転送されます)

    OpenGLのグラフィック関係のところとClipboardのコードを下記に書き出します。

    // ===========================================================================
    int CabcOpGLCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
    {
    	if (COleControl::OnCreate(lpCreateStruct) == -1)
    		return -1;
    	
        PIXELFORMATDESCRIPTOR pfd =
        {
            sizeof(PIXELFORMATDESCRIPTOR), // Structure size.
            1,                             // Structure version number.
            PFD_DRAW_TO_WINDOW |           // Property flags.
                PFD_SUPPORT_OPENGL |
    			PFD_DOUBLEBUFFER,
            PFD_TYPE_RGBA,
            24,                            // 24-bit color.
            0, 0, 0, 0, 0, 0,              // Not concerned with these.
            0, 0, 0, 0, 0, 0, 0,           // No alpha or accum buffer.
            32,                            // 32-bit depth buffer.
            0, 0,                          // No stencil or aux buffer.
            PFD_MAIN_PLANE,                // Main layer type.
            0,                             // Reserved.
            0, 0, 0                        // Unsupported.
        };
    
        CClientDC clientDC(this);
    
        int pixelFormat = ChoosePixelFormat(clientDC.m_hDC, &pfd);
        BOOL success = SetPixelFormat(clientDC.m_hDC, pixelFormat, &pfd);
        DescribePixelFormat(clientDC.m_hDC, pixelFormat, sizeof(pfd), &pfd);
    
        if (pfd.dwFlags & PFD_NEED_PALETTE) {
    		avSetupLogicalPalette( &m_hPalette );	// *hPalette = CreatePalette()
    	}
    
        m_hRC = wglCreateContext(clientDC.m_hDC);
    
    	return 0;
    }
    // ===========================================================================
    void CabcOpGLCtrl::OnDraw(
    			CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
    {
    	if (m_hPalette) {
    	    SelectPalette(pdc->m_hDC, m_hPalette, FALSE);
    	    RealizePalette(pdc->m_hDC);
    	}
    	wglMakeCurrent(pdc->m_hDC, m_hRC);
    	
    	// --DrawWithOpenGL();
    	glShadeModel(GL_SMOOTH);		// GL_SMOOTH(ピクセルに対して) , GL_FLAT
        glEnable(GL_DEPTH_TEST);		// 多角形の相対位置を知らせる
    	if ( m_glBackColor == 0 ) glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    	else					  glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//カラーバッファとディプスバッファの
    
    	ViewChange(); 
    	if ( m_GL_InitFlg == 0 ) {	// 初めての時
    		avOpglDrawTextInit(pdc, m_hRC);
    		m_GL_InitFlg = 1 ;
    	}
    	
    	// ここで、OpenGLにより具体的なグラフィック描画をする
    
    	glDisable(GL_DEPTH_TEST);
    	glFlush();	// 描画操作を終了させる
    
    	// -------------DrawWithOpenGL();終わり----------
    
    	SwapBuffers(pdc->m_hDC);
    	wglMakeCurrent(pdc->m_hDC, NULL);
    }
    
    // ===========================================================================
    void CabcOpGLCtrl::ClipBoard() 
    {
    	CabcOpGLCtrl *pWnd = (CabcOpGLCtrl *)this;	// このウィンドウ
    	//CWnd *pWnd = (CWnd *)this;			// このCViewウィンドウ。こちらでも同じ
    
    	CRect rc;					// ウィンドウサイズを得るのに使う
    	CBitmap BitClip;			// クリップするビットマップ
    	CDC MemDC;					// メモリデバイスコンテキスト
    	CClientDC ClientDC(this);	// クライアントデバイスコンテキスト
    	pWnd->GetClientRect(&rc);
    
    	BitClip.CreateCompatibleBitmap((CDC*)&ClientDC, rc.Width(), rc.Height());
    	MemDC.CreateCompatibleDC((CDC*)&ClientDC);
    	CBitmap *pOldBitmap = MemDC.SelectObject(&BitClip);
    	MemDC.BitBlt(0,0,
    				rc.Width(),
    				rc.Height(),
    				(CDC*)&ClientDC,
    				0,0,
    				SRCCOPY);
    
    	// クリップボードをオープンしクリアする
    	pWnd->OpenClipboard();
    	::EmptyClipboard();
    	// クリップボードにデータを転送
    	::GdiFlush()
    	::SetClipboardData(CF_BITMAP, BitClip.m_hObject);
    	
    	// クリップボードをクローズ	
    	CloseClipboard();
    	MemDC.SelectObject(pOldBitmap); // 選択したときに取得しておいたもの
    	BitClip.DeleteObject(); // ビットマップの破棄
    	MemDC.DeleteDC();		// メモリーDCの破棄
    }
    

    2013年2月1日 4:38
  • mr.setupさんが書いているように、SetClipboardDataで渡したハンドルを削除してはいけません(APIの実装では、多分、削除を呼び出しても削除されないようになっているかも知れませんが、実装依存はまずいです)。

    それと、mr.setupさんのサンプルのとおり、hBitmapは、SelectObjejct(pOldBiitmap)の呼び出しの後に、使用可能になります。

    WindowsXPまでのNT系だと、hDCが選択したGDIオブジェクトをSelectObject(hDC, hOldObect)で元に戻さなくてもうまくいってました。Vista/7でやっと仕様どおりになったのかもしれません。


    • 編集済み snao 2013年2月1日 17:26
    2013年2月1日 6:18
  • 下に、mr.setupさんのコーディングに、
    HWND wnd;
    wnd = ::GetActiveWindow();
    だけを追加したもので、動かしてみました。
    HWND wnd取得の仕方が良く分かりませんでしたので、
    とりあえず、GetActiveWindow()を使いました。ですので、フォームも一緒に転送されます。
    プログラムはC#のフォームの上に、OpenGLで作られた今回のコントロールを乗せる形で動かしています。
    面白いことに、フォーム上で変えた数字などは正常に毎回コピーされますが、
    やはり、OpenGLで作られている部分が最初のコピーの分だけしかコピーされません。

    void CabcOpGLCtrl::ClipBoard() 
    {
    	HWND wnd;
    	wnd = ::GetActiveWindow();
    	RECT rc; 
    
    	::GetClientRect( wnd, &rc ); 	
    	const int cx = static_cast<int>( rc.right - rc.left );
    	const int cy = static_cast<int>( rc.bottom - rc.top );
    
    	HDC const hdc = ::GetDC( wnd ); 
    	HBITMAP const hbmp = ::CreateCompatibleBitmap( hdc, cx, cy ); 
    	{
    		HDC const desti_hdc = ::CreateCompatibleDC( hdc ); 
    		HGDIOBJ const old = ::SelectObject( desti_hdc, hbmp ); 
    		
    			::BitBlt( desti_hdc, 0, 0, cx, cy, hdc, 0, 0, SRCCOPY); 	
    
    		::SelectObject( desti_hdc, old );
    		::DeleteDC( desti_hdc ); 
    	}
    	::ReleaseDC( wnd, hdc );
    
    	if(::OpenClipboard(wnd)) {
    		::EmptyClipboard();
    		::SetClipboardData(CF_BITMAP, hbmp);
    		::CloseClipboard();
    	}
    }
    

    2013年2月5日 7:16
  • クサキさんご提示の

    //CWnd *pWnd = (CWnd *)this;	

    というコメントを見ると、よほどCやC++の特殊機能を使ってない限りCabcOpGLCtrlはCWndを継承してるとおもうので、MSDNの表記を見る限り、ウインドウがアタッチされてればCWndのGetSafeHwndメンバ関数で得られると思いますよ。

    CWnd::GetSafeHwnd

    あと、よく見たら、私が提示したこのコードだと、OpenClipboardに失敗したり、SetClipboardDataに失敗したりした場合にHBITMAPが解放されない形になっているので、これでもしOKとなる場合であれば、それらが失敗した場合には自分で解放する処理を書いておくべきだと思いました。(これでいい場合はDeleteDCを適宜書き足したり、その他のエラーへの対処なども加筆修正してください)


    ただ、現状それでも目的は達成できていない、ということで

    すべての処理が正常に行われているかどうか、戻り値チェックなどで片っ端から調べてみるというのも一つの手だと思います。通常どれかがエラーの時の値を返すようであれば、そこ付近に問題があるように思います。

    CreateCompatibleBitmap

    で返されるものは、少なくともこちらの環境のWindows 8 とWindows XPにおいては、DIBがメインメモリ上なのに対し、DDBとしてビデオメモリ上に確保されている、というような挙動を示したので、Windows 7でも同様なのであれば、どこかでリソースリークがあったり、すでに大量に使われている場合は、確保に失敗する、というケースも考えられます。


    ただ

    >フォーム上で変えた数字などは正常に毎回コピーされますが、
    >やはり、OpenGLで作られている部分が最初のコピーの分だけしかコピーされません。

    という部分から、別の部分にそもそも問題があるはず、と考えました。そこで、「OpenGL GDI」で検索してみると、一発目で

    OpenGLとGDIをあわせて使うAdd Star (malibu-bulldogの日記)

    こちらのページを発見。OpenGLはまだほとんどいじったことがないため私は詳しくわからないのですが、これが事実であれば,GetDCなどからの

    CreateCompatibleBitmap

    に対するBitBltでは対処法として不十分なのではないか、という推測ができます。

    もしOpenGLでクリップボードへ直接書き出す関数が用意されていないのであれば

    OpenGLの関数glReadBuffer、glReadPixelsなどを用いて

    glReadBuffer(GL_FRONT);
    // 描画内容の読込
    glReadPixels(0, 0, width , height, GL_RGBA, GL_UNSIGNED_BYTE, 読み取るバッファ);
    glFlush();
    などとしたピクセルデータ列を(この場合RGBA形式で保存される、ということのようですね)、必要があればピクセルデータの形式を適宜自分で変更しつつ、CF_BITMAP ではなくCF_DIBで、適切に数値を設定したBITMAPINFOHEADER構造体のあとに連続で置くバイナリ形式で、SetClipboardDataすることで可能、ということになるかと思います。
    • 編集済み mr.setup 2013年2月5日 12:30
    2013年2月5日 12:26
  • > 面白いことに、フォーム上で変えた数字などは正常に毎回コピーされますが、

    よくわからなかったのですが、絵は部分的には更新されているがOpenGLの描画部分が更新されないということでしょうか。

    だとすると、ひょっとして、PFD_GENERIC_FORMATでうまくいけば、アクセラレータが怪しいということになると思います。

    OpenGLで色々検索すると、ドライバー周りの不具合にヒットすることが多いです。

    2013年2月5日 21:37
  • お試しですが(たぶん別の問題が発生するので・・・)、ピクセルフォーマットを設定するさい
    dwFlagにPFD_SUPPORT_GDI|PFD_DRAW_TO_BITMAP をORでつけてみたらどうなりますか?

    2013年2月6日 3:33
  • > よくわからなかったのですが、絵は部分的には更新されているがOpenGLの描画部分が更新
    >されないということでしょうか。
    OpenGLのコントロールの他にテキストボックスも乗っていますが、その値を変えたものはチャント、転送されています。

    > PFD_GENERIC_FORMATPFD_GENERIC_FORMATでうまくいけば、
    > アクセラレータが怪しいということになると思います。
    ダメでした。

    > dwFlagにPFD_SUPPORT_GDI|PFD_DRAW_TO_BITMAP 
    をORでつけてみたらどうなりますか?
    2回目以降も正しくコピーされるようになりました。
    ただ、OpenGLでグラフを描く速度が凄く遅くなり、また5回繰り返して描画します。
    さらに、呼び出し元のフォームがビクビク動きます。

    http://www.codeguru.com/cpp/g-m/opengl/article.php/c2711/How-to-snap-an-OpenGL-client-and-send-it-to-the-clipboard.htm
    に、”How to snap an OpenGL client and send it to the clipboard”
    がありました。
    ただ、そのままでは別の場所をさしているようで、黒いものがコピーされるだけです。
    // Replace CRenderView by your own CView-derived class 
    とコメントがありまして、分からないままですが、

    CabcOpGLCtrl *pWnd = (CabcOpGLCtrl *)this;
    CClientDC ClientDC(this);
    CDC *pDC = (CDC*)&ClientDC;
    などと変えてもみましたが同じでした。正しいやり方はどうするのでしょうか?

    2013年2月6日 5:51
  • WindowsではOpenGLよりDirectXの方がゲーム向けの印象があったので、OpenGLはあまりやってなかったのですが、モデリングやレンダリングなどをするソフトも作る必要があり、OpenGLが使えるとクロスプラットフォーム的な観点で良さそうだと思ったので、この機にちょっと探ってみました。

    (ていうか、ネットの情報をちょくちょく見ると、Windows 8でOpenGLは時期尚早では・・・という気もしましたが、それなりにドライバ対応してるっぽくてよかった)


    まずは

    GLUTによる「手抜き」OpenGL入門

    でGLUTでの起動を確認。でもこれ(GLUT)だとラップされすぎてて内部でどうなってるかとか、目的のウインドウのHWNDのとりかたとか調べるのが面倒そうだったので

    その1 ライブラリのインストールとOpenGLの初期化

    にWM_LBUTTONDOWNでクリップボードへの転送コードをコピペしたりして様子を見ると、クサキさんの報告とほぼ同じような状態になりました。(なるほど)

    このサンプル、メッセージループの外側でDC操作行ったり、PeekMessageのelseな感じで描画を行っていてどうも普通と違うみょーな印象を受けたので「OpenGLってそうなの?いや、普通のDirect3Dとかそれ以外のアプリと同じような形に、たぶんできる・・・んじゃないかなぁ」と思い、調べてみると、それっぽいコードを発見

    (いろいろ調べながらで、これがアドレスメモってなかったので、どこで発見したか失念しました。すみません)

    そこで、最終的にいじってたら

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

    ↓全体像としてこんな感じで「何度でも更新内容をクリップボードにコピー」がWindows 8 64 bitで実現できました。このソース一つだけですので、他のギミックは何もないです。処理が筒抜けに見えるように、出来るだけライブラリを使わずに組んでいます。(さすがにglewはダウンロードしてください)

    これでも失敗するようだと、それはいよいよドライバの不具合なのかもしれません。

    実験にあたって、コピペでさっとやったところに、「部分的」に自分でいつも使ってるライブラリを使って試したところから、さらに、こちらに載せるにあたって出来るだけ必要物少なくなるように削ったので、一部おかしい・これだけだとコンパイル通らないなどの部分あるかもしれませんが、大概はわかる・・・と思う・・・のですが、どうにも不明な点があれば聞いてください。

    ※マウス左クリックを行うと、矩形のサイズが変わりつつ、変わる前の画面がクリップボードに転送される・・・はずです

    #pragma comment(lib, "OpenGL32.lib") #include <windows.h> #include <tchar.h> #include <stdint.h> #include "glew.h" #define null nullptr #define FBDAPI __stdcall #define MYNEW new #define MYDELETE delete typedef int32_t Si32; typedef void Void; typedef LPCTSTR const TcsLiteral; typedef unsigned char Byte; struct RectEx : RECT { RectEx(){} RectEx( Si32 x, Si32 y, Si32 x2, Si32 y2 ){ Set(x,y,x2,y2); } Void Set( Si32 x, Si32 y, Si32 x2, Si32 y2 ){ left = x; top = y; right = x2; bottom = y2; } Void GetClient( HWND hw ){ GetClientRect( hw, this ); } Si32 x() const { return left; } Si32 y() const { return top; } Si32 cx() const { return right - left; } Si32 cy() const { return bottom - top; } }; class MyWnd { static MyWnd* me; HWND wnd; RectEx client_rect; HDC hdc; HGLRC h_glrc; float x_pos__f; static LRESULT CALLBACK Proc( HWND hw, UINT mes, WPARAM wParam, LPARAM lParam ); const RectEx& GetMyClientRect() const { return client_rect; } Si32 Width() const { return client_rect.cx(); } Si32 Height() const { return client_rect.cy(); } HWND GetWnd() const { return wnd; } Void FBDAPI Invalidate( const RECT* rc = null ) const { InvalidateRect( wnd, rc, FALSE ); } BOOL FBDAPI WMPaint() const { glClearColor(0.0f, 0.4f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glRectd(-0.5,-0.5,x_pos__f,0.5); glFlush(); SwapBuffers( hdc ); ValidateRect( wnd, null ); //いらないかも(OpenGLの仕様をまだちゃんと把握してないため一応呼んでいますが) return TRUE; } Void FBDAPI WMClose(){ wglMakeCurrent( hdc, null ); wglDeleteContext( h_glrc ); ReleaseDC( wnd, hdc ); MYDELETE this; } Void FBDAPI WMLDown( LPARAM ){ const Si32 cx = Width(); const Si32 cy = Height(); const size_t NbBytes = 4 * cx * cy; BITMAPINFOHEADER header; header.biSize = sizeof( header); header.biWidth = cx; header.biHeight = cy; header.biSizeImage = (DWORD)NbBytes; header.biPlanes = 1; header.biBitCount = 4 * 8; // RGB header.biCompression = 0; header.biXPelsPerMeter = 0; header.biYPelsPerMeter = 0; header.biClrUsed = 0; header.biClrImportant = 0; HANDLE handle = (HANDLE)::GlobalAlloc (GHND,sizeof(BITMAPINFOHEADER) + NbBytes); if ( handle ){ Byte* const pData = (Byte*) ::GlobalLock((HGLOBAL)handle); // Copy header and data memcpy(pData,&header,sizeof(header)); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadBuffer(GL_FRONT); glReadPixels(0,0, cx,cy,GL_RGBA,GL_UNSIGNED_BYTE,pData+sizeof header); // Unlock ::GlobalUnlock((HGLOBAL)handle); // Push DIB in clipboard if ( OpenClipboard(wnd) ){ EmptyClipboard(); SetClipboardData(CF_DIB,handle); CloseClipboard(); } else { GlobalFree( handle ); } } x_pos__f = x_pos__f > 0 ? -0.2f : 0.2f; Invalidate(); } public: MyWnd( HINSTANCE hinst ) try : wnd(null), hdc(null), x_pos__f(0.2f) { me = this; TcsLiteral text_ = _T("OpenGLテスト____"); WNDCLASSEX wcex = { sizeof wcex, CS_OWNDC | CS_HREDRAW | CS_VREDRAW, DefWindowProc, 0, 0, hinst, null, null, (HBRUSH)(COLOR_WINDOW+1), null, text_, null }; RegisterClassEx(&wcex); RectEx rc( 0, 0, 640, 480 ); AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE ); wnd = CreateWindow( text_, text_, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, rc.right - rc.left, rc.bottom - rc.top, null, null, hinst, null ); if ( !wnd ) throw _T("Failed CreateWindow"); this->client_rect.GetClient( wnd ); // OpenGL初期化 // ピクセルフォーマット初期化 PIXELFORMATDESCRIPTOR pfd = { sizeof pfd, 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER , //Flags PFD_TYPE_RGBA, //The kind of framebuffer. RGBA or palette. 32, //Colordepth of the framebuffer. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, //Number of bits for the depthbuffer 8, //Number of bits for the stencilbuffer 0, //Number of Aux buffers in the framebuffer. PFD_MAIN_PLANE, 0, 0, 0, 0 }; hdc = GetDC( wnd ); if ( !hdc ) throw _T("Failed GetDC"); const int pixelFormat = ChoosePixelFormat( hdc, &pfd ); if ( !pixelFormat ) throw _T("Failed ChoosePixelFormat"); if ( !SetPixelFormat( hdc, pixelFormat, &pfd ) ) throw _T("Failed SetPixelFormat"); h_glrc = wglCreateContext( hdc ); wglMakeCurrent( hdc , h_glrc ); SetWindowLongPtr( wnd, GWLP_WNDPROC, (LONG_PTR)Proc ); ShowWindow( wnd, SW_SHOW ); } catch ( TcsLiteral c ){ if ( hdc ) ReleaseDC( wnd, hdc ); if ( wnd ) DestroyWindow( wnd ); me = null; throw c; } ~MyWnd(){ DestroyWindow( wnd ); me = null; } }; MyWnd* MyWnd::me( null ); // ウィンドウプロシージャ LRESULT CALLBACK MyWnd::Proc( HWND hw, UINT mes, WPARAM wp, LPARAM lp ){ switch ( mes ) { case WM_ERASEBKGND : return FALSE; case WM_PAINT : if ( !me->WMPaint() ) break; return 0; case WM_LBUTTONDOWN : me->WMLDown(lp); return 0; case WM_CLOSE : me->WMClose(); return 0; case WM_DESTROY : PostQuitMessage( 0 ); return 0; } return DefWindowProc( hw, mes, wp, lp ); } int APIENTRY _tWinMain( HINSTANCE hinst, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow ){ try { MYNEW MyWnd( hinst ); } catch ( TcsLiteral c ){ MessageBox( null, c, _T("error"), 0 ); return 0; } MSG msg; BOOL i; while( i=GetMessage( &msg, null, 0, 0 ) ){ //アプリ本体 if ( i==-1 ) break; TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }

    とりあえず、OpenGLを使うにあたって全体像は同じような構造になっていますでしょうか?

    これでOKな場合は、必要な部位の抽出やエラーチェックの修正など、適宜行ってください。

    • 編集済み mr.setup 2013年2月6日 12:38
    2013年2月6日 11:22
  • Visual C++ の空のプロジェクトにコピーしてコンパイルしましたが、
    ”error C2065: 'nullptr' : 定義されていない識別子です。”のエラーで動かせていません。
    Win32APIで作るWindowsプログラムはやったことがなく、また、最終的にはActivrXでやらなければなりません。
    また以下、新しいことも分かってきましたので、よろしくお願いします。

    ご案内のプログラムと私が紹介したプログラム
    ”How to snap an OpenGL client and send it to the clipboard”とは同じようなことだと思います。
    私が紹介したプログラムを私のActiveXのプログラムに組み込んで分かったことを報告します。

    // OpenGLのデータを読み込み、Clipboardに描くデータを置いておく場所
    int NbBytes = 3 * size.cx * size.cy;
    unsigned char *pPixelData = new unsigned char[NbBytes];

    // テストのため強制的に値を入れる
    for ( int i = 0 ; i < NbBytes ; i++ )
    *(pPixelData+i)  = i; // 60;

    //glReadPixels(0,0,size.cx,size.cy,GL_RGB,GL_UNSIGNED_BYTE,pPixelData);

    書き込んだデータがClipboardに転送されますので、Clipboardに転送するプログラムは正しく動いているようです。

    glReadPixels はOpenGLのデータを読むプログラムですが、
    OpenGLのプログラムにより、データを読まなかったり、違う場所のデータを読んでいるようです。

    問題は glReadPixelsが正しくOpenGLのデータを読み込んでくるかどうかということになったと思います。
    (元々のプログラムは直接 glReadPixels を利用していませんが、間接的には利用されていると思いますが)

    以下のようなことが起こっています。

    void CabcOpGLCtrl::OnDraw()の最後で、
    wglMakeCurrent(pdc->m_hDC, NULL); としていますが、
    // 有ると、glReadPixelsが何も実行しない。
    // 無いと、
    //     陰線処理をする  :glReadPixelsがどこかのメモリを持ってくる(Releaseモードでは黒、Debugモードでは灰色)
    //     陰線処理をしない:タイミング゙が遅れたりもするが、2回目以降もコピーできる。

    なお、glReadPixels の前で、以下のようなこともやってもいます。
    ::glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
    //glReadBuffer(GL_BACK);
    //glReadBuffer(GL_FRONT);

    以下、関係する部分のコーディングです。

    // ===========================================================================
    // クリップボードへ転送
    void CabcOpGLCtrl::ClipBoard() 
    {
    	BeginWaitCursor();
    
    	// Get client geometry 
    	CRect rect; 
    	GetClientRect(&rect); 
    	CSize size(rect.Width(),rect.Height()); 
    	// Lines have to be 32 bytes aligned, suppose 24 bits per pixel 
    	// I just cropped it 
    	size.cx -= size.cx % 4; 
    
    	// Create a bitmap and select it in the device context 
    	// Note that this will never be used ;-) but no matter 
    	CBitmap bitmap; 
    	CDC *pDC = GetDC();
    	//CClientDC ClientDC(this);		// クライアントデバイスコンテキスト  どちらでも良い。
    	//CDC *pDC = (CDC*)&ClientDC;	//
    
    	CDC MemDC; 
    	ASSERT(MemDC.CreateCompatibleDC(NULL));	// 元
    	//ASSERT(MemDC.CreateCompatibleDC(pDC));	// 同じでダメ
    	ASSERT(bitmap.CreateCompatibleBitmap(pDC,size.cx,size.cy)); 
    	MemDC.SelectObject(&bitmap);
    
    	// Alloc pixel bytes 
    	int NbBytes = 3 * size.cx * size.cy;
    	unsigned char *pPixelData = new unsigned char[NbBytes];
    
    	// ---- テスト追加 -----
    	for ( int i = 0 ; i < NbBytes ; i++ )
    		*(pPixelData+i)  = i;//60;
    	
    	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
    	//glReadBuffer(GL_BACK);
    	//glReadBuffer(GL_FRONT);
    	// Copy from OpenGL 
    	::glReadPixels(0,0,size.cx,size.cy,GL_RGB,GL_UNSIGNED_BYTE,pPixelData);	
    
    	// Fill header 
    	BITMAPINFOHEADER header; 
    	header.biWidth = size.cx; 
    	header.biHeight = size.cy; 
    	header.biSizeImage = NbBytes; 
    	header.biSize = 40; 
    	header.biPlanes = 1; 
    	header.biBitCount =  3 * 8; // RGB	
    	header.biCompression = 0; 
    	header.biXPelsPerMeter = 0; 
    	header.biYPelsPerMeter = 0; 
    	header.biClrUsed = 0; 
    	header.biClrImportant = 0;
    
    	// Generate handle 
    	HANDLE handle = (HANDLE)::GlobalAlloc (GHND,sizeof(BITMAPINFOHEADER) + NbBytes); 
    	if(handle != NULL) 
    	{ 
    		// Lock handle 
    		char *pData = (char *) ::GlobalLock((HGLOBAL)handle); 
    		// Copy header and data 
    		memcpy(pData,&header,sizeof(BITMAPINFOHEADER)); 
    		memcpy(pData+sizeof(BITMAPINFOHEADER),pPixelData,NbBytes); 
    		// Unlock 
    		::GlobalUnlock((HGLOBAL)handle);
    
    		// Push DIB in clipboard 
    		OpenClipboard(); 
    		EmptyClipboard(); 
    		SetClipboardData(CF_DIB,handle); 
    		CloseClipboard(); 
    	}
    
    	// Cleanup 
    	MemDC.DeleteDC(); 
    	bitmap.DeleteObject(); 
    	delete [] pPixelData;
    
    	EndWaitCursor(); 
    }
    
    // ===========================================================================
    void CabcOpGLCtrl::OnDraw(
    			CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
    {
    	if (m_hPalette) {
    	    SelectPalette(pdc->m_hDC, m_hPalette, FALSE);
    	    RealizePalette(pdc->m_hDC);
    	}
    	wglMakeCurrent(pdc->m_hDC, m_hRC);
    	
    	// --DrawWithOpenGL();
    	glShadeModel(GL_SMOOTH);		// GL_SMOOTH(ピクセルに対して) , GL_FLAT
        glEnable(GL_DEPTH_TEST);		// 多角形の相対位置を知らせる
    	if ( m_glBackColor == 0 ) glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    	else					  glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//カラーバッファとディプスバッファの
    
    	ViewChange(); 
    	if ( m_AvGL_InitFlg == 0 ) {	// 初めての時
    		avOpglDrawTextInit(pdc, m_hRC);
    		m_AvGL_InitFlg = 1 ;
    	}
    	
    	// ここで、OpenGLにより具体的なグラフィック描画をする
    
    	glDisable(GL_DEPTH_TEST);
    	glFlush();	// 描画操作を終了させる
    
    	// -------------DrawWithOpenGL();終わり----------
    
    	SwapBuffers(pdc->m_hDC);
    	wglMakeCurrent(pdc->m_hDC, NULL);	// ★★★これが有る無しで変わる
    	// 有ると、glReadPixelsが何も実行しない。
    	// 無いと、
    	//     陰線処理をする  :glReadPixelsがどこかのメモリを持ってくる(Releaseモードでは黒、Debugモードでは灰色)
    	//     陰線処理をしない:タイミングがずれたりもするが、2回目以降もコピーできる。
    }
    
    // ===========================================================================
    // 陰線処理しているところのコーディング
    	if ( m_backLineCut == 1 && m_surface == 0 ) {
    		glEnable(GL_POLYGON_OFFSET_FILL);
    		glPolygonOffset(1.0, 1.0);
    		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);	//GL_FILLで塗りつぶす(陰線処理)
    		if ( m_glBackColor == 0 ) {
    			materialObject[0] = 0.0f; materialObject[1] = 0.0f;
    			materialObject[2] = 0.0f; materialObject[3] = 0.0f;
    		}
    		else {
    			materialObject[0] = 1.0f; materialObject[1] = 1.0f;
    			materialObject[2] = 1.0f; materialObject[3] = 1.0f;
    		}
    		glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, materialObject);
    		DrawBirdView_P();
    		glDisable(GL_POLYGON_OFFSET_FILL);
    	}
    
    // ===========================================================================
    // PIXELFORMATDESCRIPTOR
    int CabcOpGLCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
    {
    	if (COleControl::OnCreate(lpCreateStruct) == -1)
    		return -1;
    	
        PIXELFORMATDESCRIPTOR pfd =
        {
            sizeof(PIXELFORMATDESCRIPTOR), // Structure size.
            1,                             // Structure version number.
            PFD_DRAW_TO_WINDOW |           // Property flags.
                PFD_SUPPORT_OPENGL |
    			PFD_DOUBLEBUFFER,
            PFD_TYPE_RGBA,
            24,                            // 24-bit color.
            0, 0, 0, 0, 0, 0,              // Not concerned with these.
            0, 0, 0, 0, 0, 0, 0,           // No alpha or accum buffer.
            32,                            // 32-bit depth buffer.
            0, 0,                          // No stencil or aux buffer.
            PFD_MAIN_PLANE,                // Main layer type.
            0,                             // Reserved.
            0, 0, 0                        // Unsupported.
        };
    
        CClientDC clientDC(this);
    
        int pixelFormat = ChoosePixelFormat(clientDC.m_hDC, &pfd);
        BOOL success = SetPixelFormat(clientDC.m_hDC, pixelFormat, &pfd);
        DescribePixelFormat(clientDC.m_hDC, pixelFormat, sizeof(pfd), &pfd);
    
        if (pfd.dwFlags & PFD_NEED_PALETTE) {
    		avSetupLogicalPalette( &m_hPalette );	// *hPalette = CreatePalette()
    	}
    
        m_hRC = wglCreateContext(clientDC.m_hDC);
    
    	return 0;
    }
    

    2013年2月13日 9:13
  • なるほど、2008ではnullptrでエラーでしたか。確かに。ではnullptr→NULLと変えてみてください。

    (あとReleaseDC( wnd, hdc );はWMCloseよりデストラクタに持ってきた方が綺麗といえば綺麗ですし、シングルトンなのでデストラクタ・コンストラクタはprivateにした方が綺麗ですがその辺は割愛)

    それ以外ではないですか?

    私のコードでできる、ということがわかればまだ絞れるかも、という気がします。もしそれでもできない、ということがわかればドライバ関連の問題なのかも、という可能性が高くなります。(怪しい挙動がいくらかあったので、どうなのだろうというところですが)

    仮にその情報なしで、なお現時点でわかる範囲(クサキさんの最新コード)で考えると

    GetDC
    ChoosePixelFormat
    SetPixelFormat
    wglCreateContext

    あたりの呼び出しタイミングはほぼ同じ時期になるので、あとは

    wglMakeCurrent

    の呼び出しタイミングが私のサンプルとは違う、という点でしょうか。wglMakeCurrentに関しては

    wglMakeCurrent function (Windows)

    こんな感じですけど、名前からしても解説からしても、また「引数から内部実装の概要を推測」したとても、目的となるHDCが目的となるHGLRCに合わさってないと、glReadPixelsはできないだろう、という気はしますね。

    OnDrawで

    wglMakeCurrent(pdc->m_hDC, m_hRC);

    とする意味は、関連付けの範囲を狭めることが目的に感じるので、その意思を表明するのなら同じくOnDraw内で

    wglMakeCurrent(pdc->m_hDC, NULL);

    とするべきだと思いますが、そうなればこの場合

    void CabcOpGLCtrl::ClipBoard() 

    内では関連付けがなくなっている・・・というのは必然に思います。

    そういうことなのであれば、ClipBoard関数内でもwglMakeCurrentを呼び出してはいかがでしょうか?

    おそらくは、これで陰線処理のくだり以外は解決できるのではないかと思いますが

    //陰線処理をする  :glReadPixelsがどこかのメモリを持ってくる(Releaseモードでは黒、Debugモードでは灰色)
    //陰線処理をしない:タイミングがずれたりもするが、2回目以降もコピーできる。

    これはちょっと謎です。「タイミング」って何でしょうか?
    もし別スレッドからもOnDrawが呼ばれる可能性があって、しかも排他制御とかされてない・・・となると、コード依存となるのでこれらの情報だけではとても判断できないということになります。

    ※私のサンプルと同じように、もっと広い(早いタイミングの)ところであらかじめwglMakeCurrentという手なら、対症療法としては成立している結果になるかもしれませんが、そうだとしても、マルチスレッドなら必要に応じて排他制御はするべきです。

    そうでなければ、OpenGLでの具体的な描画部分に鍵があるかもしれません。
    そうでなければ、これこそドライバ依存の不具合の可能性があります。

    -----

    How to snap an OpenGL client and send it to the clipboard

    と、クリップボード部分は確かにほぼ同じです。ちょくちょく変えたのは

    // Create a bitmap and select it in the device context
    // Note that this will never be used ;-) but no matter

    とコメントにもある通り、CompatibleBitmapの方は使ってないのに作られてるのでこのケースでは無駄となってしまう。(別途CF_BITMAPもさらに書き出すなら話は別)

    また

    unsigned char *pPixelData = new unsigned char[NbBytes];
    からそれに一旦glReadPixelsしてさらにGlobalAllocしたメモリにmemcpyして、delete[]している…というのは正直どうなのか…と思って、GlobalAllocしたメモリに直でglReadPixelsしてみました。

    (普通にできたので、これがまずいという記載がどこかにない限りこれでもいいのではないだろうかと思います。パフォーマンスはGlobalAllocやglReadPixelsの実装次第ですが、ほとんどのwindowsにて通常のnewとそう変わりがなければ、こっちの方が無駄がないというはずに思います。)

    もう一点は、何かの携帯端末でRGBが失敗する(アルファ値込の32bitで成功)、という情報をどこかで目にしたので、一応32bitで試してみた、という理由ですね。

    • 編集済み mr.setup 2013年2月13日 22:37
    2013年2月13日 21:35
  • NULLにして動かしました。(とりあえず、こちらの方だけ)

    最初、緑の中に白い正方形が描かれた形で立ち上がり、クリックすると、
    この正方形がClipBoardにコピーされ、今度は縦長の長方形が描かれます。再度、クリックすると逆になり、繰り返し正しくコピーされています。

    2013年2月14日 2:07
  • 気になっていたので、ご確認どうもどうも。その動作は私の予定通りのもので、基本的な動作については良さそうですね。(windows 7 エアロ下でも、基本的な動作はやはり可能、ということが言える状況になったと思います。)

    ということはあとは陰線処理の謎だけですね?
    やはり気になるのはマルチスレッド絡みの問題なのか、別のことがあるのか、とかですね。

    2013年2月14日 2:24
  • > 基本的な動作については良さそうですね。(windows 7 エアロ下でも、
    基本的な動作はやはり可能、ということが言える状況になったと思います。)

    そうなんでしょうか?
    OnDraw()の所で、wglMakeCurrent(pdc->m_hDC, NULL);を外したのは実験的にやっただけで、
    本来は、wglMakeCurrent(pdc->m_hDC, NULL);が有る状態で正しく動いて欲しいです。
    現在は有ると、glReadPixelsが何も実行しません。
    簡単なプログラムだけは動いているということではないのでしょうか?

    >// 無いと、
    >//     陰線処理をする  :glReadPixelsがどこかのメモリを持ってくる(Releaseモードでは黒、Debugモードでは灰色)
    >//    陰線処理をしない:タイミングがずれたりもするが、2回目以降もコピーできる。
    >これはちょっと謎です。「タイミング」って何でしょうか?


    鳥瞰図と3軸と背景を描いています。
    立ち上がっりの時に鳥瞰図は緑で描かれて、ClipBoard転送しますが、ペイントでは全体が黒です。
    赤に変えて、ClipBoard転送しましたら、ペイントでは緑です。
    さらに、黄に変えて、ClipBoard転送しましたら、シアンです。
    最初1つ前のものかなと思い、タイミングがという書き方をしました。
    実際はそうでもないようです。また、色の一対一対応もなさそうです。
    その他、3軸の色は青ですが、赤でペイントに出力されていますし、背景の色が、白と黒で軸の色がかわります。
    上のようにならない場合もあり、なかなか正確に説明することが難しいです。

    とりあえず、2回目以降もコピーできたので、
    glReadPixelsがOpenGLの状態で想定通りに動いていのではないかとの例証とのつもりでした。


    > そういうことなのであれば、ClipBoard関数内でも
    > wglMakeCurrentを呼び出してはいかがでしょうか?

    ClipBoard() の最初の辺りに
    HDC hDC = GetDC()->GetSafeHdc(); 
    wglMakeCurrent(hDC, m_hRC);
    最後に、wglMakeCurrent(hDC, NULL); といれましが、

    OnDraw()のwglMakeCurrent(pdc->m_hDC, NULL);
    をカットした時と同じような動きになりました。で、実際には使えません。

    2013年2月15日 6:03
  • よく見たら

    CClientDC clientDC(this);

    これローカル変数になってますね。

    CClientDC クラス

    >>このクラスでは、構築時に Windows 関数の GetDCを呼び出し、破棄時に ReleaseDC を呼び出します。

    ご覧のように、私のサンプルではHDC hdc;はメンバにしてあり、ウインドウの破棄の直前

    wglDeleteContext

    の後で初めてReleaseDCしています。(複数の方のサンプルに合わせただけなので普通の手順では、一例としてこんな感じなんでしょう。)

    またその場で

    HDC hDC = GetDC()->GetSafeHdc();

    とだけしても、それはwglCreateContextされてないわけですから、確かに出来ないだろうなぁと思います。

    ----

    と思ったら、なんらかの検証ミスでした。失礼しました。

    wglMakeCurrent function (Windows)

    >>The hdc parameter must refer to a drawing surface supported by OpenGL. It need not be the same hdc that was passed to wglCreateContext when hglrc was created, but it must be on the same device and have the same pixel format. GDI transformation and clipping in hdc are not supported by the rendering context. The current rendering context uses the hdc device context until the rendering context is no longer current.

    別段hdcが同じでなくてもよく、フォーマットが同じならwglCreateContext後に先にReleaseDCしてから別途後でGetDCやBeginPaintで得たHDCでwglMakeCurrentして問題ないようですね。(こちらでも動作確認済み。)

    他では少なくとも一点

    CWnd::GetDC

    >>デバイス コンテキストがウィンドウ クラスに属していない限り、描画終了後は ReleaseDC メンバ関数を呼び出してコンテキストを解放する必要があります。

    この点に引っかからないコードになっていますか?


    なっていてもダメなら、やはり疑問のひとつは画像切り替えの方法というのがシングルスレッド(同一スレッド)によるものかマルチスレッドか・・・です。


    --さらなる追記--

    <結果的にこれが本題(?)>

    ビットマップを自分で書き出したり操作してみるとわかりますが、バイトがずれれば、単にずれただけで色は大きく変わります。(・・・いや、さすがにそれは言うまでもないでしょうが)

    単に色がずれているという意味に過ぎないのであれば、ひとつの疑問なのはRGBAで転送してもおかしいのどうかです。

    あるいは、もともとのアラインメント事情がどうなってるのか(環境による違いがあるかどうか、またあったとしてその可能性の範囲について)未調査ですが

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    を呼び出したあとにglReadPixelsでどうでしょうか?

    (RGBだと3バイトずつになるが、アラインメントが3バイト境界などというものは通常ないはずなので1以外ではおかしくなる可能性あり、ということで)


    それでもやはり無理なら、もしかしたら対象のグラフィックボードの情報を調べた方がいいのかもしれません。

    --さらなる追記その2--

    あー、ビットマップの並び順は実際にはBGR(BGRA)なんですが、実はGL_RGBでこの形式ではなくて、GL_BGRでこの形式で取得、ということ・・・ぽいですね。


    私が指定してたGL_RGBAは、この用途では実際にはGL_BGRAにすべきでした。「G」の位置が同じ、Aの位置も同じために、緑と白の2択しかない画像ではその誤りに気付かなかったということのようです。

    緑・赤・黄色・青の4色にて256*256の四角形のグラデーションを作って

    header.biBitCount =  3 * 8;


    glReadPixels(0,0, cx,cy,GL_RGB,GL_UNSIGNED_BYTE,pData+sizeof header);

    な感じでやってみました。そうすると、これをそのままうつした場合


    こうなりました。(8ではまだPhotoshopを使える環境を揃えてないので・・・少々汚い画像ですが)

    R成分とB成分が逆なだけなので色の変化の仕方が完全一致はしていませんが、とりあえず

    青→赤 黄→シアン

    に関しては一致していますね。一応理論の上ではこんなコードで逆転できましたが

    			
    			Byte* const bytes__ =	pData+sizeof header;
    			const Si32 linebytes =  (((cx * 3 )+3)&~3); //1ラインのバイト数を4の倍数に
    			for ( Si32 y=0; y<cy; ++y ){
    				for ( Si32 x=0; x<cx; ++x ){
    					Byte* const b = &bytes__[y*linebytes+x*3];
    					Byte r = b[2];
    					b[2] = b[0];
    					b[0] = r;
    				}
    			}
    わざわざ逆向きに取得してから自分でこういう反転なんて2度手間なことをせずとも、glReadPixelsにGL_BGRを渡してやることで、正常に取得できました。
    • 編集済み mr.setup 2013年2月15日 14:53
    2013年2月15日 7:38
  • DELL OptiPlex 780 Windows 7 Professional 32ビット SP1(2010.11ごろ購入)
    ビデオカードは
    Intel Q43/Q45/G43/G45(Eaglelake)Grpahics Controler と
    ATI(TM) Radeon(R) HD3450 256MB  です。

    OSの再インストールをしました。
    OSをインストール直後に(まだ、ドライバーはインストールしていない)
    "How to snap an OpenGL client and send it to the clipboard"
    にあったプログラムを動かしました。正常な動作でした。
    ATI社のビデオドライバーをインストールしたら、今回の後半の現象になりました。

    昔、DELL+ATI+XP+OpenGLで問題が起こったことがありました。
    全体にはチャント動いているのですが、3軸の色をR,G,Bと変えるだけの
    単純なプログラムで色が変わりませんでした。
    DELLに問い合わせましたが、診断プログラムを動かしてくださいとのことで、
    動かしましたがエラーは出ず、問題ないということで対応頂けませんでした。

    2013年2月19日 0:23
  • Radeon HD系はDirectXの方に最適化されてるはずですが、wikipediaを見るとHD3450はOpenGL 4.1に対応している・・・という表記ではありますが

    私の提示した内容をすべて実行してダメで、なおかつという意味でしょうか?
    そういった情報がなくては後で別の方が見たときに参考にならないですし、内容に関する返答がない以上、この時点での情報だけではドライバのバグなのかどうか怪しいところと感じます。(答える義務はないですが、有意義な情報が得られないのであれば私としても時間を削って調べる意義が薄くなるので、以降返答しない可能性はありますね。)

    そもそも

    "How to snap an OpenGL client and send it to the clipboard"

    の「一番上の方にある」コードをWindows上でそのまま使ってRとBが逆向きにならないという方が普通じゃない気がします。

    過去「標準OpenGL」にGL_BGRがなかった関係上(最新ではどうか知りませんが・・・「GL_BGR」がなくて「GL_BGR_EXT」がある環境はあるらしいですし)、GL_RGBなのにGL_BGRかのように、windowsが内部でやってる・・・ATI社のビデオドライバーをインストールしたら正常になり、ちゃんとRGB順で取得される・・・とかいう可能性は0ではないように思います。

    ドライバの未対応可能性を考慮すれば、上記私の反転コードのように、ソフトで反転した方がいいのかもしれません。

    http://d.hatena.ne.jp/keim_at_Si/20050120/p1

    GeForce GTX650で

    "How to snap an OpenGL client and send it to the clipboard"

    の一番上のコードを試しましたが、やはりGL_RGBを指定していてかつ逆転ギミックがない以上、そのままではRとBは逆転します。

    少なくともこの点については、これが正常に思います。

    --追記--

    申し訳ない、ちょっときつい書き方だったかもしれません。昨日は少々呼吸が乱れていました。

    ただ、時間をかけて調べたので、試してみたのかどうかは、少なくとも個人的には事実知りたいところです。全部やっててやはりダメならば確かにドライバの不具合の可能性が高いようにも感じます。(ドライバインストール前の挙動がWindowsの機転なのかどうかもやはり気になるところではありますが)

    • 編集済み mr.setup 2013年2月19日 23:32
    2013年2月19日 1:36
  • 色の件ですが、mr.setupさんのプログラムで、

    glClearColor(0.4f, 0.0f, 0.0f, 0.0f);
    glReadPixels(0,0, cx,cy,GL_BGRA,GL_UNSIGNED_BYTE,pData+sizeof header);
    とGL_BGRAしたら、チャント赤の背景が赤でClipboardに転送出力されました。
    (グラーディエイションの説明の意図がはっきりわかりませんでしたが)

    私のプログラムですが、
    glReadPixels(0,0,size.cx,size.cy,GL_BGR,GL_UNSIGNED_BYTE,pPixelData);
    としても、おかしな現象は同じようなことでした。
    mr.setupさんの例のように、青→赤 黄→シアンのような対応がなく、
    また必ず、ある色とある色とが対応することもありません。
    タイミングと書きましたが、前のデータがきているような感じもあり、
    なんともはっきり説明できない状態です。


    > ※私のサンプルと同じように、もっと広い(早いタイミングの)ところであらかじめ
    > wglMakeCurrentという手なら、対症療法としては成立している結果になるかもしれませんが、
    > そうだとしても、マルチスレッドなら必要に応じて排他制御はするべきです。
    特に、マルチスレットのプログラムを特に組んだつもりはありませんが、
    シングルスレットということで良いでしょうか?

    以下、OnDraw()の大まかなところです。
    void CabcOpGLCtrl::OnDraw()
    {
    wglMakeCurrent(pdc->m_hDC, m_hRC);

    if ( m_GL_InitFlg == 0 ) { // 初めての時
    OpglDrawTextInit(pdc, m_hRC); // なぜかOnDrawでしかできない
    m_GL_InitFlg = 1 ;
    }
    // 描画をする

    glFlush(); // 描画操作を終了させる

    SwapBuffers(pdc->m_hDC);
    wglMakeCurrent(pdc->m_hDC, NULL);
    }

    OpglDrawTextInit(pdc, m_hRC)が、なぜかOnDrawでしかできないということで、
    wglMakeCurrent(pdc->m_hDC, m_hRC); をOnDraw()の頭で、
    対応するために、wglMakeCurrent(pdc->m_hDC, NULL);を最後に書いています。

    wglMakeCurrent(pdc->m_hDC, NULL)を抜いたテストは
    動きを探るためにカットしただけです。
    その結果2回目以降のコピーができたので報告しました。
    wglMakeCurrent(pdc->m_hDC, NULL)は必須だと考えています。
    色の説明できない現象が起きるは、必須なものをカットしているので、
    何が起こるかわからないという状態で仕方ないと考えています。


    その他の件はまだ、も少し調べさせてください。
    C++のWindowsのプログラムにも慣れていませんで、また、OpenGLも
    昔1回だけ何とか作っただけで、短時間での返答が難しい状態です。

    ビデオカードの件は重要な情報と考え、また、大変時間をとってやって頂いていますので、
    なるべく無駄な時間をとられないようにと早く報告させて頂きました。

    OSの再インストールだけは楽になりましたが、
    沢山のアプロケーションを途中幾つかトラブリながらインストールし、
    また、早く日常業務ができるよう、アプロケーションの設定を急がなければならず、
    ご不快な思いをさせてしまったようです。大変申し訳ありませんでした。
    2013年2月20日 9:44
  • おお、ご報告ありがとうございます。そうなってくると、これは謎のバグの可能性がありますね。
    それでもダメなことがあるんだと、参考になる内容に思います。

    >チャント赤の背景が赤でClipboardに転送出力されました。
    >(グラーディエイションの説明の意図がはっきりわかりませんでしたが)

    グラデーションはRとBのみ反転していたことを明確に示すためのもので、もう一つの意味は前のクサキさんの返信における

    立ち上がりの時に緑→黒
    赤→緑
    黄→シアン
    青→赤

    の、転送前の4色を適用して調べてみた、という意味合いがあります。しかしながら、私のコードでそのまま転送できて、クサキさんのソリューションにおいて

    >mr.setupさんの例のように、青→赤 黄→シアンのような対応がなく、また必ず、ある色とある色とが対応することもありません。

    となるというのは、コードの別の部分で何かがあるか、ビデオカードの測り知れない不具合か・・・またしてもちょっとわからない状況ですね。

    >特に、マルチスレットのプログラムを特に組んだつもりはありませんが、
    >シングルスレットということで良いでしょうか?

    なるほど。

    なにか想定してないところで案外スレッドが発動という可能性があるかもしれませんが、そうだとしてもそれ自体はOpenGLとか描画関係にちょっかいを出すことは考えられないと想定「したいところ」なので(デファクトスタンダード的なものほど謎な挙動は少ないと見積もって)そういうことであれば、この謎挙動を引き起こすのにはマルチスレッドである必要はないと仮定していいでしょう。(十中八九)

    wglMakeCurrent(pdc->m_hDC, NULL);
    に関しては、OnDraw()の先頭で
    wglMakeCurrent(pdc->m_hDC, m_hRC);とするからには、やはりそれに対応する形でやるべきだと思います。

    ただ、この辺でちょっと気になることもあります。

    OpglDrawTextInitとか、あるいは上の方でavOpglDrawTextInitとか書いてあるのは、これらは独自の関数でしょうか?(これだけだとそれ以外の可能性もいくつもあるのがC++なので一応)

    なぜ、OnDrawでしかできないのか、できないとはどういうことなのか。

    このあたりになんらかの鍵がある可能性もなくはないように思います。

    と、そういえばこのOnDraw関数、上の方でのOnDraw関数、いずれもCView::OnDrawとは引数が違うんですね。

    ということは、これはオーバーライドされたものではない、どこかで自分で呼び出してるコードがある・・・ということですか?(OnPaintをオーバーライドしてるとか)

    これを呼び出してる側のコードが重要かもしれません。上でも触れましたが、

    wglMakeCurrent function (Windows)

    にて、別のHDCでもいいとは書かれています。しかし、同デバイス同ピクセルフォーマットでないといけないという縛りがあります。この「同ピクセルフォーマット」を保証できてないかも、という気もするので。

    <<この辺の内容をさらなる調査後に編集で変更>>あ、しかしSetPixelFormatは一回しか許されない、と書いてありますね。

    そうなると・・・少し話が戻って、クリップボード関数でGetDCに対してReleaseDCというのは、この場合単純にこれだけだと思います。(あの辺の説明であった「ウインドウクラス」というのは、C++のクラスのことではなくWindowsのウインドウに関連付けられているやつです。)

    CDC *pdc = GetDC();

    HDC hDC = pdc->GetSafeHdc();

    wglMakeCurrent(hDC, m_hRC);

    この間でglReadPixelsとか色々

    wglMakeCurrent(hDC, NULL); ReleaseDC(pdc);

    ・・・この場合はこれだけで解決するとは思えませんが。

    ※バージョンによって(?)4バイトメモリリークするらしいので、CWnd::GetDCやCWnd::ReleaseDCは、実際にはWindows API直叩きの方がいいかもしれません。

    BUG: Memory leaks when you use the CWnd class versions of the GetDC method and the ReleaseDC method


    >その他の件はまだ~大変申し訳ありませんでした。

    そうでしたか。こちらこそすみません、少々きつい書き方だったかもしれません。
    頑張ってください。モデリングソフトの開発予定・進行の関係上、私としても解決は見たいものですが、自分の方で再現できない現象なのでどうなるか・・・

    私は8で2012 Expressを使用中で都合上MFCの調査が難しい可能性があるのですが、このフォーラムには調べられる方もいらっしゃると思いますので、差し支えなければ現象が再現するソリューションをアップロードしてはいかがでしょうか?
    (こういう場合ではどこに上げるのが適切なのかは、I'm not sureというよりI have no ideaな感じなので、わかる方よろしければ教えてください)

    • 編集済み mr.setup 2013年2月20日 14:41
    2013年2月20日 14:07
  • // OpglDrawTextInitとか、あるいは上の方でavOpglDrawTextInitとか書いてあるのは、
    // これらは独自の関数でしょうか?
    //(これだけだとそれ以外の可能性もいくつもあるのがC++なので一応)
    どちらも同じプログラムです。
    void OpglDrawTextInit(CDC* pdc, HGLRC hRC)
    {
    SelectObject (pdc->m_hDC, GetStockObject(SYSTEM_FONT));
    wglUseFontBitmaps(pdc->m_hDC, 0, 255, 1000); // BMPを使う
    glListBase (1000);
    }

    // いずれもCView::OnDrawとは引数が違うんですね。
    済みません。
    同じもので、ActiveXが作ったそのままのもので、以下のようになります。
    void CabcOpGLCtrl::OnDraw(
    CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)


    glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH ); 
    に関して、
    ネット上で、必要だとあったので、入れていましたが、
    GLUT_BGR や GLUT_BGRA の定義が無くコンパイルできません。
    glReadPixels(0,0,size.cx,size.cy,GL_BGR,GL_UNSIGNED_BYTE,pPixelData);
    で、GL_BGRとしましたので、glutInitDisplayModeは実行しないことにしました。


    > 差し支えなければ現象が再現するソリューションをアップロードしてはいかがでしょうか?

    元データを作っているところなど、他のライブラリーを使っていたりして難しいです。
    OpenGLの所だけ、アップロードします。

    // ---------------------------------------------------------------------------
    // クリップボード転送(デバイス依存のビットマップを使用)
    // 元+フォーラム(仲澤@失業者)の助言
    // OK: XP、7 + Intel Q43/Q45/G43/G45(Eaglelake)Grpahics Controler + エアロ、
    //		   7 + ATI Radeon HD3450 + Windows ベーシック 
    // NG: 7 + ATI Radeon HD3450 + エアロ、 最初の1回目だけがClipBoardに転送。
    //	   その後は、グラフィックを変えても、ペイントでみると、1回目のグラフィックのまま	  
    // ---------------------------------------------------------------------------
    void CabcOpGLCtrl::ClipBoardBMP() 
    {
    	CabcOpGLCtrl *pWnd = (CabcOpGLCtrl *)this;	// このウィンドウ
    	CRect rc;					// ウィンドウサイズを得るのに使う
    	CBitmap BitClip;			// クリップするビットマップ
    	CDC MemDC;					// メモリデバイスコンテキスト
    	CClientDC ClientDC(this);	// クライアントデバイスコンテキスト
    
    	pWnd->GetClientRect(&rc);
    	BitClip.CreateCompatibleBitmap((CDC*)&ClientDC, rc.Width(), rc.Height());
    	MemDC.CreateCompatibleDC((CDC*)&ClientDC);
    	CBitmap *pOldBitmap = MemDC.SelectObject(&BitClip);	// 2013.2 フォーラムに従い、このように
    
    	MemDC.BitBlt(0,0,
    				rc.Width(),
    				rc.Height(),
    				(CDC*)&ClientDC,
    				0,0,
    				SRCCOPY);
    	::GdiFlush();	// 2013.2 フォーラムに従い、追加  入れても変わらないが
    
    	// クリップボードをオープンしクリアする
    	pWnd->OpenClipboard();
    	::EmptyClipboard();
    	// クリップボードにデータを転送
    	::SetClipboardData(CF_BITMAP, BitClip.m_hObject);
    	//BitClip.Detach();		// 2013.2 フォーラムに従い、カット	
    	// クリップボードをクローズ	
    	CloseClipboard();	
    	MemDC.SelectObject(pOldBitmap); // 選択したときに取得しておいたもの 2013.2 フォーラムに従い、追加 
    	BitClip.DeleteObject();			// ビットマップの破棄				2013.2 フォーラムに従い、追加 
    	MemDC.DeleteDC();				// メモリーDCの破棄					2013.2 フォーラムに従い、追加 
    }
    
    // ---------------------------------------------------------------------------
    // クリップボード転送(デバイス非依存のDIBを使用)
    // 
    // ネットで見つけた "How to snap an OpenGL client and send it to the clipboard" + α
    //
    // OK: 7 + Intel Q43/Q45/G43/G45(Eaglelake)Grpahics Controler
    //		  (エアロでも、ベイシックでも、陰線処理してもしなくても)
    // NG: 7 + ATI Radeon HD3450  最初は全面黒、2回目以降は1回前のグラフィック
    //		  (エアロでも、ベイシックでも、陰線処理してもしなくても)
    //		  XP(グラフィックがずれたり壊れたり)
    // ---------------------------------------------------------------------------
    // Snap OpenGL client and send it to ClipBoard so that you can insert it 
    // in your favorite image editor, Powerpoint, etc... 
    // ---------------------------------------------------------------------------
    void CabcOpGLCtrl::ClipBoardDIB() 
    {
    	BeginWaitCursor();
    	
    	// ・Intelグラフィックカードの場合(Window7)、
    	//	  下2つ有ると、正常に動く。
    	//    下2つ無いと、glReadPixelsが値を持ってこなくなる。
    	// ・ATIグラフィックカードの場合は(Window7)、
    	//	  下2つ有ると、一つ前のものを持ってくる(陰線処理するしないは関係ない)
    	//	 下2つ無いと、glReadPixelsが値を持ってこなくなる。(陰線処理するしないは関係ない)
    	HDC hDC = GetDC()->GetSafeHdc();// 追加 hDC、m_hRCは使っていないが
    	wglMakeCurrent(hDC, m_hRC);		// 追加	hDC、m_hRCは使っていないが
    
    	// Get client geometry 
    	CRect rect; 
    	GetClientRect(&rect); 
    	CSize size(rect.Width(),rect.Height());	// client zone
    	// Lines have to be 32 bytes aligned, suppose 24 bits per pixel I just cropped it 
    	size.cx -= size.cx % 4;					// final client zone
    	// Create a bitmap and select it in the device context 
    	// Note that this will never be used ;-) but no matter 
    	CBitmap bitmap; 
    	CDC *pDC = GetDC();	
    	CDC MemDC; 
    	ASSERT(MemDC.CreateCompatibleDC(NULL));
    	ASSERT(bitmap.CreateCompatibleBitmap(pDC,size.cx,size.cy)); 
    	MemDC.SelectObject(&bitmap);
    	// Alloc pixel bytes 
    	int NbBytes = 3 * size.cx * size.cy;	// 元
    	unsigned char *pPixelData = new unsigned char[NbBytes];
    
    	for ( int i = 0 ; i < NbBytes ; i++ )	// テスト用
    		*(pPixelData+i)  = i;	//60;		// テスト用
    	
    	//glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH ); // GLUT_RGB GLUT_ALPHA GLUT_STENCILなど
    	// Copy from OpenGL 
    	//glPixelStorei(GL_PACK_ALIGNMENT, 1);	// 元々無し あってもなくても変わらない
    	// GL_RGB等は格納する側、 WindowsはBMPで☆GL_BGRでないと行けない。元はGL_RGB(RとBがひっくり返る)
    	// GL_BGRAは実行時エラー:保護されたメモリに読み取りまたは書き込み操作を行おうとしました。 
    	glReadPixels(0,0,size.cx,size.cy,GL_BGR,GL_UNSIGNED_BYTE,pPixelData);	
    
    	// BMPの File header 
    	BITMAPINFOHEADER header; 
    	header.biWidth = size.cx; 
    	header.biHeight = size.cy; 
    	header.biSizeImage = NbBytes; 
    	header.biSize = 40; 
    	header.biPlanes = 1; 
    	header.biBitCount =  3 * 8;		// RGB	
    	header.biCompression = 0; 
    	header.biXPelsPerMeter = 0; 
    	header.biYPelsPerMeter = 0; 
    	header.biClrUsed = 0; 
    	header.biClrImportant = 0;
    
    	// Generate handle 
    	HANDLE handle = (HANDLE)::GlobalAlloc (GHND,sizeof(BITMAPINFOHEADER) + NbBytes); 
    	if(handle != NULL) 
    	{ 
    		// Lock handle 
    		char *pData = (char *) ::GlobalLock((HGLOBAL)handle); 
    		// Copy header and data 
    		memcpy(pData,&header,sizeof(BITMAPINFOHEADER)); 
    		memcpy(pData+sizeof(BITMAPINFOHEADER),pPixelData,NbBytes); 
    		// Unlock 
    		::GlobalUnlock((HGLOBAL)handle);
    		// Push DIB in clipboard 
    		OpenClipboard(); 
    		EmptyClipboard(); 
    		SetClipboardData(CF_DIB,handle); 
    		CloseClipboard(); 
    	}
    	// Cleanup 
    	//ReleaseDC(pDC);	// テスト追加、関係ない
    	MemDC.DeleteDC(); 
    	bitmap.DeleteObject(); 
    	delete [] pPixelData;
    
    	wglMakeCurrent(hDC, NULL);	// 追加 wglMakeCurrent(hDC, m_hRC)との対応
    	EndWaitCursor(); 
    }

    ClipBoardDIBは、仲澤@失業者さん助言を参考にして書き換えています。
    (結果は元のものと変わりません)

    ClipBoardDIBは、
    HDC hDC = GetDC()->GetSafeHdc();
    wglMakeCurrent(hDC, m_hRC);
    wglMakeCurrent(hDC, NULL);
    を追加し、
    glReadPixels(0,0,size.cx,size.cy,GL_BGR,GL_UNSIGNED_BYTE,pPixelData);
    をGL_BGR変えたものです。
    私の Windows7 32bitで、Intelのグラフィック機能ではエアロでも正常に動いています。
    ただ、XPではダメでした。

    今回はもうギブアップです。
    ClipBoardBMP(元のプログラム)は、グラフィックボードが何であろうと、
    エアロであろうと1回は動いていますので、当面、こちらを使うことにします。
    弊社の本筋の機能でもありませんし、フォームを閉じて再度あければ
    1回目となります。パラメータも覚えていますのでとりあえず使えます。

    int CabcOpGLCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
    {
    	if (COleControl::OnCreate(lpCreateStruct) == -1)
    		return -1;
    	
        PIXELFORMATDESCRIPTOR pfd =
        {
            sizeof(PIXELFORMATDESCRIPTOR), // Structure size.
            1,                             // Structure version number.
    		
            PFD_DRAW_TO_WINDOW |           // Property flags.
                PFD_SUPPORT_OPENGL |
    			PFD_DOUBLEBUFFER,
            PFD_TYPE_RGBA,
            24,                            // 24-bit color.
            0, 0, 0, 0, 0, 0,              // Not concerned with these.
            0, 0, 0, 0, 0, 0, 0,           // No alpha or accum buffer.
            32,                            // 32-bit depth buffer.
            0, 0,                          // No stencil or aux buffer.
            PFD_MAIN_PLANE,                // Main layer type.
            0,                             // Reserved.
            0, 0, 0                        // Unsupported.
        };
    
        CClientDC clientDC(this);
        int pixelFormat = ChoosePixelFormat(clientDC.m_hDC, &pfd);
        BOOL success = SetPixelFormat(clientDC.m_hDC, pixelFormat, &pfd);
        DescribePixelFormat(clientDC.m_hDC, pixelFormat, sizeof(pfd), &pfd);
    
        m_hRC = wglCreateContext(clientDC.m_hDC);
    	return 0;
    }
    
    // ---------------------------------------------------------------------------
    void CabcOpGLCtrl::OnDestroy() 
    {
    	COleControl::OnDestroy();
    
    	if ( m_MallocFlg == 1 ) {
    		free(m_Buf);
    	}
        wglDeleteContext(m_hRC);
    	if ( m_hPalette)	 DeleteObject(m_hPalette);
    	if ( m_TimerFlg == 1 ) 	KillTimer(1);
    }
    
    // ===========================================================================
    void CabcOpGLCtrl::OnDraw(
    			CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
    {
    	if (m_hPalette) {
    	    SelectPalette(pdc->m_hDC, m_hPalette, FALSE);
    	    RealizePalette(pdc->m_hDC);
    	}
    	wglMakeCurrent(pdc->m_hDC, m_hRC);
    	
    	glShadeModel(GL_SMOOTH);		// GL_SMOOTH(ピクセルに対して) , GL_FLAT
        glEnable(GL_DEPTH_TEST);		// 多角形の相対位置を知らせる
    	if ( m_glBackColor == 0 ) glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    	else					  glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//カラーバッファとディプスバッファの
    
    	abcViewChange();			// これは wglMakeCurrent の後でなければ有効にならない
    	if ( m_GL_InitFlg == 0 ) {	// 初めての時
    		abcOpglDrawTextInit(pdc, m_hRC);	// なぜかOnDrawでしかできない
    		m_GL_InitFlg = 1 ;
    	}
    
    	DrawBirdView();
    
    	glDisable(GL_DEPTH_TEST);
    	glFlush();	// 描画操作を終了させる
    
    	SwapBuffers(pdc->m_hDC);
    	wglMakeCurrent(pdc->m_hDC, NULL);
    	// 2013.2.12 
    	// 有ると、glReadPixelsが何も実行しない。
    	// 無いと、
    	//     陰線処理をする  :glReadPixelsがどこかのメモリを持ってくる(Releaseモードでは黒、Debugモードでは灰色)
    	//     陰線処理をしない:タイミングがずれたりもするが、2回目以降もコピーできる。
    }
    
    // ---------------------------------------------------------------------------
    void abcOpglDrawTextInit(CDC* pdc, HGLRC hRC)
    {
    	SelectObject (pdc->m_hDC, GetStockObject(SYSTEM_FONT));
    	wglUseFontBitmaps(pdc->m_hDC, 0, 255, 1000); // BMPを使う
    	glListBase (1000);
    }
    
    // ===========================================================================
    void CabcOpGLCtrl::DrawBirdView() 
    {
    	LightingSelect(1) ; // 0 = Unit and so on   1 = BirdView  and so on
    	glPushMatrix();
    	glRotatef( m_xRotate, 1.0f, 0.0f, 0.0f);
    	glRotatef( m_yRotate, 0.0f, 1.0f, 0.0f);
    	glRotatef( m_zRotate, 0.0f, 0.0f, 1.0f);
    	float Aspect;
    	if ( m_RoiHsize >= m_RoiVsize ) {
    		Aspect = (float)m_RoiVsize / (float)m_RoiHsize;
    		glScalef( m_Scale, m_Scale * Aspect, m_Scale);
    	}
    	else {
    		Aspect = (float)m_RoiHsize / (float)m_RoiVsize;
    		glScalef( m_Scale * Aspect, m_Scale, m_Scale);
    	}
    	glTranslatef( -1.0f, -1.0f, 0.0f); // 原点を真中に移動
    
    	if ( m_ExeCan == C_NO ) return;		// C_NO: すべて0のデータ)
    	// ---
    	if ( m_smooth == 1 ) {  // GL_LINE_SMOOTHは働いている、 GL_POLYGON_SMOOTHは動いていない
    		glEnable(GL_LINE_SMOOTH);	//GL_LINE_SMOOTH , GL_POLYGON_SMOOTH 
    		glEnable(GL_BLEND);
    		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    		glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);	// GL_DONT_CARE  GL_NICEST=(最も正確)
    	}
    
    	glFrontFace(GL_CW);  // CW=時計回り  CCW=反時計回り
    	glPolygonMode(GL_FRONT, GL_LINE);	//GL_POINT , GL_LINE , GL_FILL
    	glPolygonMode(GL_BACK, GL_LINE);	//GL_POINT , GL_LINE , GL_FILL
    
    	float R, G, B;	// 2008.9.2
    	abcColorToOpglRGB( m_birdViewColor, &R, &G, &B );
    	GLfloat materialObject[] = {R, G, B, 1.0f}; // R, G, B, A
    														
    	glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, materialObject);
    	GLfloat materialObject2[] = {0.0f, 0.0f, 1.0f, 1.0f};
    	glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, materialObject2);//これ効かない
    	DrawBirdView_S();	// Smooth Disable をここでOK
    	if ( m_smooth == 1 ) {
    		glDisable(GL_LINE_SMOOTH);
    		glDisable(GL_BLEND);
    	}
    
    	if ( m_backLineCut == 1 && m_surface == 0 ) {
    		// 陰線処理のため。サーフェスモデルの時は、陰線処理しない
    		glEnable(GL_POLYGON_OFFSET_FILL);
    		glPolygonOffset(1.0, 1.0);
    		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);	//GL_FILLで塗りつぶす(陰線処理)
    		if ( m_glBackColor == 0 ) {
    			materialObject[0] = 0.0f; materialObject[1] = 0.0f;
    			materialObject[2] = 0.0f; materialObject[3] = 0.0f;
    		}
    		else {
    			materialObject[0] = 1.0f; materialObject[1] = 1.0f;
    			materialObject[2] = 1.0f; materialObject[3] = 1.0f;
    		}
    		glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, materialObject);
    		DrawBirdView_S();
    		glDisable(GL_POLYGON_OFFSET_FILL);
    	}
    	glPopMatrix();
    }
    
    // ---------------------------------------------------------------------------
    void CabcOpGLCtrl::DrawBirdView_S()
    {
    	if ( m_ExeCan == C_NO ) return;		// C_NO: すべて0のデータ)
    
    	float x1, y1, z1, x2, y2, z2 ;
    	float x3, y3, z3, x4, y4, z4 ;
    	int i, j, NofX, NofY, Hnotch, Vnotch ;
    
    	NofX = m_Rex-m_Rsx+1; NofY = m_Rey-m_Rsy+1;
    	Hnotch = m_Hnotch ; Vnotch = m_Vnotch ;
    
    	float NofXd2 = ((float)NofX-1.0f) / 2.0f;
    	float NofYd2 = ((float)NofY-1.0f) / 2.0f;
    	for ( i = 0 ; i < NofY - Vnotch ; i = i + Vnotch ) {
    		glBegin(GL_QUAD_STRIP);	
    		x1 = 0.0f ; y1 = (float)i / NofYd2 ; 
    		z1 = *(m_Buf+m_Hsize*(m_Rsy+i)+m_Rsx) ;
    		if ( m_HiLowCutFlg == C_YES ) {
    			if ( z1 < m_LowCut ) z1 = m_LowCut;
    			if ( z1 > m_HiCut ) z1 = m_HiCut;
    			z1 = ( z1 - m_LowCut ) * m_zWait; 
    		}
    		else z1 = z1 * m_zWait;
    
    		x2 = x1   ; y2 = (float)(i+Vnotch) / NofYd2 ; 
    		z2 = *(m_Buf+m_Hsize*(m_Rsy+i+Vnotch)+m_Rsx);
    		if ( m_HiLowCutFlg == C_YES ) {
    			if ( z2 < m_LowCut ) z2 = m_LowCut;
    			if ( z2 > m_HiCut ) z2 = m_HiCut;
    			z2 = ( z2 - m_LowCut ) * m_zWait; 
    		}
    		else z2 = z2 * m_zWait;
    
    		x3 = Hnotch / NofXd2 ; y3 = y1 ; 
    		z3 = *(m_Buf+m_Hsize*(m_Rsy+i)+m_Rsx+Hnotch);
    		if ( m_HiLowCutFlg == C_YES ) {
    			if ( z3 < m_LowCut ) z3 = m_LowCut;
    			if ( z3 > m_HiCut ) z3 = m_HiCut;
    			z3 = ( z3 - m_LowCut ) * m_zWait; 
    		}
    		else z3 = z3 * m_zWait;
    
    		x4 = x3   ; y4 = y2 ; 
    		z4 = *(m_Buf+m_Hsize*(m_Rsy+i+Vnotch)+m_Rsx+Hnotch);
    		if ( m_HiLowCutFlg == C_YES ) {
    			if ( z4 < m_LowCut ) z4 = m_LowCut;
    			if ( z4 > m_HiCut ) z4 = m_HiCut;
    			z4 = ( z4 - m_LowCut ) * m_zWait; 
    		}
    		else z4 = z4 * m_zWait;
    		glVertex3f(x1, y1, z1);
    		glVertex3f(x2, y2, z2);
    		glVertex3f(x3, y3, z3);
    		glVertex3f(x4, y4, z4);
    
    		for ( j = Hnotch * 2 ; j < NofX ; j = j + Hnotch ) {
    			x1 = (float)j / NofXd2 ; 
    			z1 = *(m_Buf+m_Hsize*(m_Rsy+i)+m_Rsx+j);
    			if ( m_HiLowCutFlg == C_YES ) {
    				if ( z1 < m_LowCut ) z1 = m_LowCut;
    				if ( z1 > m_HiCut ) z1 = m_HiCut;
    				z1 = ( z1 - m_LowCut ) * m_zWait; 
    			}
    			else z1 = z1 * m_zWait;
    
    			x2 = x1 ; 
    			z2 = *(m_Buf+m_Hsize*(m_Rsy+i+Vnotch)+m_Rsx+j);
    			if ( m_HiLowCutFlg == C_YES ) {
    				if ( z2 < m_LowCut ) z2 = m_LowCut;
    				if ( z2 > m_HiCut ) z2 = m_HiCut;
    				z2 = ( z2 - m_LowCut ) * m_zWait; 
    			}
    			else z2 = z2 * m_zWait;
    			glVertex3f(x1, y1, z1);
    			glVertex3f(x2, y2, z2);
    		}
    		glEnd();
    	}
    }
    
    // ===========================================================================
    void CabcOpGLCtrl::OnSize(UINT nType, int cx, int cy) 
    {
    	COleControl::OnSize(nType, cx, cy);	
    
    	CClientDC clientDC(this);
        wglMakeCurrent(clientDC.m_hDC, m_hRC);
        glMatrixMode(GL_PROJECTION);//GL_PROJECTION=透視投影行列 GL_MODELVIEW=
        glLoadIdentity();			//単位行列をロードする
    	double Aspect = (double)cx / (double)cy ;
    	gluPerspective( m_viewAngle, Aspect, m_NearPlane, m_FarPlane); //glFrustumと同じ:角度、アスペクト
        glViewport(0, 0, cx, cy);
    
    	glMatrixMode(GL_MODELVIEW);		//GL_MODELVIEW=モデルビュー行列
        glLoadIdentity();
    	glTranslatef(0.0f, 0.0f, - m_TransCenter);
    	glScalef( m_Scale, m_Scale, m_Scale);
    	LightingOnOff(1) ;
    
        wglMakeCurrent(NULL, NULL);		
    }
    
    // ===========================================================================
    void CabcOpGLCtrl::abcViewChange()
    {
    	glMatrixMode(GL_PROJECTION);//GL_PROJECTION=透視投影行列 GL_MODELVIEW=
    	glLoadIdentity();
    	gluPerspective( m_viewAngle, 1, m_NearPlane, m_FarPlane); //glFrustumと同じ:角度、アスペクト
    	glMatrixMode(GL_MODELVIEW);		//GL_MODELVIEW=モデルビュー行列
    	glLoadIdentity();
    	glTranslatef(0.0f, -0.0f, - m_TransCenter);
    	glScalef( m_Scale, m_Scale, m_Scale);
    }
    
    // ---------------------------------------------------------------------------
    void CabcOpGLCtrl::LightingSelect(int Flg) 
    {						// 0 = Unit and so on     1 = BirdView  and so on
    	if ( Flg == 0 ) {	// 0 = Unit and so on
    		GLfloat light0Ambient[] = {0.3f, 0.3f, 0.3f, 1.0f};
    		GLfloat light0Diffuse[] = {0.7f, 0.7f, 0.7f, 1.0f};
    		glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient);
    		glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Diffuse);
    	}
    	else {				// 1 = BirdView  and so on
    		GLfloat light0Ambient[] = {1.0f, 1.0f, 1.0f, 1.0f};
    		GLfloat light0Diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};		// 0.7
    		glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient);
    		glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Diffuse);
    	}
    }


    • 編集済み クサキ 2013年2月25日 10:02 BMPと書くところをDIBと書いたから
    2013年2月25日 9:57
  • 今回は妥協ということですか。致し方ないのかなという気もしますが、私も状況的にDirectX 9→DirectX 11(D3DXなし)への大転換をすべきとなってることに気付いたため、4日ほど前から移行を急ピッチで進めており、それ以外にもたくさんやりたいことがあるため、なかなか時間が取れなくなっています。

    ので、すみませんがひとまずパスします。(もしも、ここまでの情報のみで解決できる方がいらっしゃいましたら解説お願いいたします。)


    ただ、別の方が見たときにそのまま使うとまずいはずということで、ざっと見で気づいた数点があったのでそこだけ・・・

    MSDNを見て正しいと思うので直ってない限り何度でも言いますが、あくまでクリップボード転送成功のケースでは

    BitClip.DeleteObject();

    は「やらないべき」です。最初の方の仲澤さんのコメントは失敗時および普段での話では正しいですが、クリップボード転送成功したオブジェクトは、その後システムが管理します。なのでその場合は適用できないと考えるべきです。

    //ReleaseDC(pDC);	// テスト追加、関係ない

    と消されていますが、即見た目に変わりがなくてもマイクロソフトがやるべきと書いている以上はやるべきです。故意に反すれば将来問題が発生しても仕方ないですし、実際現状ですら「関係ない」ように見えても内部で何も問題が起きていないことを証明することは困難です。

    言語仕様またはAPIやライブラリの仕様に反する個所がプロジェクトの中に何カ所もある可能性があると、それらのコードを見ずに第3者がそれを再現することはほぼ不可能です。(今回に限って言えば、上記コードの中にもしかしたら解決法があるのかもしれませんが)

    (※ただし、OWNDCであるならReleaseDCは不要です。)

    また、これをやってもそれは

    CDC *pDC = GetDC();

    に対するものであり、それだけでは

    HDC hDC = GetDC()->GetSafeHdc();

    のGetDCに対するReleaseDCが呼ばれてません。一応この場合は、それだったら上ののまえに

    CDC *pDC = GetDC();

    をやっといてから

    HDC hDC = pDC->GetSafeHdc();

    とすれば1回で済みます。


    // GL_BGRAは実行時エラー:保護されたメモリに読み取りまたは書き込み操作を行おうとしました。 

    glReadPixelsにはバッファサイズの情報は渡されてないので「幅・高さ・ピクセルの解釈法」だけでバッファにアクセスしなければいけないことが予想できます。したがってたとえば、1ピクセル3バイト計算のサイズしか確保していないバッファを送って、その上GL_BGRAを指定したりすれば、当然バッファーオーバーランする可能性があるでしょう。

    ネイティブC/C++などのガベージコレクタのない言語は、メモリやリソースの確保法・解放手順があやふやだと、とても危険です。また、バッファーオーバーランの怖いところは、してても気づかない可能性があるということです。(そのように書いてすぐ検出されてうまいこと落ちてくれれば「わかるので助かります」が、ずっと後になって一見全く関係ないようなところに影響を及ぼして落ちる、という可能性もあります。)

    学習している時間があまり取れない場合は、出来るだけ多くの範囲をC#などで済ました方が安全かもしれません。どうしてもC++が必要で、かつライブラリを使う必要があるなら、ライブラリの仕様をしっかり把握する必要があります。(これは一般論です)

    それでは、今回はここで失礼します。

    • 編集済み mr.setup 2013年2月25日 12:38
    2013年2月25日 12:30