青島のしま〜Blue Islands〜


C++ Tips

DirectX Tipsはこちら(作成中..)

コンストラクタのオーバーロード

C++では,あるコンストラクタから,違う引数のコンストラクタを呼び出すことはできない.
そこで,他の関数をつくりそれを呼び出すようにすればよい.
class Foo {
public:
  Foo(int a, int b) { set(a, b); }
  Foo(int a) { set(a, 0); }
protected:
  Init(int a, int b);
}

std::stringでsprintfやCString::Formatのようなことをしたい

以下のようにすればできます
#include <windows.h>
#include <string>
#include <iostream>

using namespace std;

int Format(std::string& dest, const char *format, va_list params)
{
    int iResult;
    char *buff;
    #if defined(_WIN32) | defined(_WIN32_WCE)
        int length = _vscprintf(format, params);
        buff = new char [length + 1];
        iResult = vsprintf(buff, format, params);
        buff[length] = '\0';
    #else
        iResult = vasprintf(&buff, format, params);
    #endif
    if (buff != NULL) {
        dest = buff;
        delete[] buff;
    }
    return iResult;
}

int Format(std::string& dest, const char *format, ...)
{
    va_list params;

    va_start(params, format);
    int iResult = Format(dest, format, params);
    va_end(params);

    return iResult;
}

int main()
{
	std::string hoge;
	Format(hoge, "%s: %s: %d", "aaa", "bbb", 256);
	cout << hoge << endl;

	return 0;
}

文字列を全て小文字に変換する

CString(MFC/ATL)のMakeLowerを使います
CString hoge = "ABCabc";
hoge.MakeLower();
printf("%s", hoge); // "abcabc"

ファイルを1行ずつ読み込む

ifstreamとgetlineを使うバージョンです.
この場合,fgetsと違い,バッファの文字数制限がないのがメリットです.
また,ReadFileだと,1行毎読むことができないので,ネイティブC++の場合は,この方法でやるのが簡単です.

#include <fstream>
#include <string>


HRESULT ReadFile()
{
	// ファイルを読み込み用に開く
	std::ifstream fileInput( "hoge.txt" );
	if (!fileInput.is_open()) {
		// ファイルが読み込み用に開けない
		return E_FAIL;
	}
	
	// 1行ずつ読み込む
	std::string line;
	// 1行ずつ読み込み
	while (std::getline(fileInput, line)) {
		cout << line.c_str() << endl;
	}

	// ファイルを閉じる
	fileInput.close();
}

C++標準ライブラリ(Standard Library)のリファレンス

XMLスキーマでの検証を行う(xerces-c)

XMLスキーマでの検証を行うには,SAX2XMLReader::setFeature()を使えばできます.

http://www-06.ibm.com/jp/developerworks/xml/050915/j_x-xsdxerc.shtml より抜粋

// Necessary includes. We refer to these as "common includes" 
// in the following examples.
#include <xercesc/sax2/XMLReaderFactory.hpp>
#include <xercesc/sax2/SAX2XMLReader.hpp>
#include <xercesc/sax2/DefaultHandler.hpp>

// Handy definitions of constants.
#include <xercesc/util/XMLUni.hpp>

// Create a SAX2 parser object.
SAX2XMLReader* parser = XMLReaderFactory::createXMLReader();

// Set the appropriate features on the parser.
// Enable namespaces, schema validation, and the checking 
// of all Schema constraints.
// We refer to these as "common features" in following examples.
// NameSpaceを有効にする
parser->setFeature(XMLUni::fgSAX2CoreNameSpaces, true);
// Validationを有効にする
parser->setFeature(XMLUni::fgSAX2CoreValidation, true);
// スキーマが読み込めなかった場合は,エラーを出力しない
parser->setFeature(XMLUni::fgXercesDynamic, true);
// DTDではなく,XMLスキーマでの検証をを有効にする
parser->setFeature(XMLUni::fgXercesSchema, true);
// スキーマフルチェックを有効にする
parser->setFeature(XMLUni::fgXercesSchemaFullChecking, true);

// Set appropriate ContentHandler, ErrorHandler, and EntityResolver.
// These will be referred to as "common handlers" in subsequent examples.

// You will use a default handler provided by Xerces-C++ (no op action).
// Users should write their own handlers and install them.
DefaultHandler handler;
parser->setContentHandler(&handler);

// The object parser calls when it detects violations of the schema.
parser->setErrorHandler(&handler);

// The object parser calls to find the schema and 
// resolve schema imports/includes.
parser->setEntityResolver(&handler);

// Parse the XML document.
// Document content sent to registered ContentHandler instance.
parser->parse(xmlFile);

// Delete the parser instance.
delete parser;

ここでは,例外のtry/catchでハンドリングしてますが,
DefaultHandlerを継承したクラスに,fatalError()/error()/warning()をオーバーライドする方法でもスキーマエラーがハンドリングできます.
スキーマレベルのエラーは,error()が呼ばれます.

また,上記の方法は,XMLファイルに,XMLスキーマの指定が書かれる必要がありますが,
強制的にXSDファイルを指定して読み込むには,loadGrammar()を使います.

バイナリデータのリソースを読み込む

まず,リソースの追加手順は次のように行います.
・プロジェクトのフォルダに,リソースファイルをコピーする
・ソリューションエクスプローラに,追加したいリソースファイルをドラッグする
・リソースファイル(.rc)をメモ帳等で開き,次のように追加する
/////////////////////////////////////////////////////////////////////////////
//
// Data
//

IDR_RCDT_XML_XML1            RCDATA             "xml1.xml"

・resource.hにも,上記と同じ定数を定義して,ユニークな値を割り振っておく
#define IDS_RCDT_XML_XML1    101

次に,追加したりソースを読み込むコードです.

	int resourceId = IDS_RCDT_XML_XML1;
	// リソースの検索
	HRSRC hRes = FindResource( hInst, MAKEINTRESOURCE(resourceId), RT_RCDATA );
	if( hRes == NULL ) {
		return S_FALSE;
	}
	// リソースの読み込み
	HGLOBAL hGlobal = LoadResource( hInst, hRes );
	if( hGlobal == NULL ) {
		return S_FALSE;
	}
	// リソースのポインタの取得
	BYTE* pt = (BYTE*)LockResource( hGlobal );
	if ( pt == NULL) {
		return S_FALSE;
	}

	// データのコピー
	BYTE buff[2048];
	int dataLen = SizeofResource( hInst, hRes);
	if (dataLen > 2048) {
		dataLen = 2048;
	}
	memcpy(buff, pt, dataLen);

	// リソースの開放などは特に必要ない

注意:ファイルの種類がxsdファイルの場合,プロジェクトに追加すると,
VisualStudioが勝手にコンパイルしようとしてエラーをはくので,
ファイルを右クリック→プロパティ→ビルドから除外を「はい」にしておく必要がある.

リリースビルドにしたら動かなくなった

デバッグビルドでは動いてたのに,リリースビルドにしたら動かなくなるってことがたまにあります..
VisualStudio2003の段階のことですが,
resource.hとリソースファイル(.rc)の定義が大文字小文字が違ったときに,
debugビルドではうまく動作するのですが,
releaseビルドにすると読み込みに失敗するようになります.

こういう仕様は,releaseビルドでは,デバッグしづらいので困り者ですね..

バージョン情報をリソースから取得する

以下のようにするとexeのバージョン情報を取得できます.
ただし,version.libをリンクに追加する必要があります.
void GetFileVersion(char* szVersion, int strLength)
{
	DWORD dwLen;
	DWORD dwHandle;

	// バージョン情報リソースのサイズを取得
	dwLen = ::GetFileVersionInfoSize(__argv[0], &dwHandle);
	if(dwLen)
	{
		// バッファの確保
		char *lpData = new char[dwLen + 1];
		if(lpData)
		{
			// バージョン情報リソースをバッファに読み込む
			if(::GetFileVersionInfo(__argv[0], 0, dwLen, lpData))
			{
				UINT nLen;
				DWORD *dwLang;
				char lpSubBlock[64] = _T("\\VarFileInfo\\Translation");

				// lang-codepageを取得
				if(::VerQueryValue(lpData, lpSubBlock, (void **)&dwLang, &nLen))
				{
					// lang-codepageをフォーマット
					char lpLang[16];
					sprintf(lpLang, "%04x%04x", LOWORD(*dwLang), HIWORD(*dwLang));

					// 情報を取得する
					LPTSTR lpBuffer;
					LPTSTR lpString = _T("FileVersion");  // ファイルバージョン名
					// 同様に,lpString = _T("ProductVersion")とするとProductVersionが取得できる
					sprintf(lpSubBlock, "\\StringFileInfo\\%s\\%s", lpLang, lpString);
					::VerQueryValue(lpData, lpSubBlock, (void **)&lpBuffer, &nLen);

					// コピー
					_snprintf(version, strLength, lpBuffer);
				}
			}
			delete [] lpData;
		}
	}
}

W2Tマクロの危険性

W2Tマクロは,forループ等で使用すると,ループを抜けるまでは,メモリを開放しません.
そのためか,物理メモリが残っているのにもかかわらず,
他の部分でmallocやnew等でメモリを取得しようとしたときに失敗します.
この問題の解決策としては,
W2Tマクロを使っている部分を,CW2Tまたは,COLE2Tマクロに置き換えます.
そうすることで安全に使用することができます.

regsvr32で,LoadLibrary()に失敗するときは

パスはあっているはずなのに,
LoadLibrary("hogehoge.ax")に失敗しました.指定されたモジュールが見つかりません.
というエラーが出るときは,以下を試してみてください.

VisualStudioツールから Dependency Walker を起動し,(VisualStudio6に付属しています.NETにはないかも..)
「?」のついているdll(またはax,ocx)があるかどうかチェックし,
そのライブラリを入手しておいてください.
それがLoadLibraryになかったためdllの登録ができなかったということです.

可変引数のマクロを使う

以下のようにすると使えます
#define DEBUG(msg, ...) printf(msg, __VA_ARGS__)

int main() {
    DEBUG("hoge %s %d", "hoge", 3);
}

const関数の使い方

const変数は,変更が禁止されているという意味ですが,
メンバ関数にも,const属性を指定できます.
これをやると,const指定されたオブジェクトから呼び出しができようになります.
逆に,const指定されたオブジェクトからは,const以外の関数は呼び出せません.

class A {
	int	i;
public:
	int& m1() {return i;}
	const int& m2() {return i;}
	int m3() const {return i;}
};

int main() {
	int	i;
	const A	a1;
	A	a2;

	i = a1.m2();	// ×constオブジェクトからはconstメンバ関数のみ使用可
	i = a1.m3();	// ○

	i = a2.m2();	// ○
	i = a2.m3();	// ○

	a2.m1() = i;	// ○
	a2.m2() = i;	// ×戻り値がconstなので,左辺値になれない

	return 0;
}

関数ポインタ

以下のようにすると,変数として,関数のポインタが使えます.
#include<stdio.h>

void Test1() {
	printf("test1\n");
}

void Test2(char *str) {
	printf("test2:%s\n", str);
}

int main() {
	void (*pTest1)() , (*pTest2)(char *);
	pTest1 = Test1;
	pTest2 = Test2;

	(*pTest1)();
	(*pTest2)("hogehoge");

	return 0;
}

テキストからグラフィックを作成(ビットマップに描画する)

/// テキストをBitmapに描画,保存する
void CSaveBitmapSampleDlg::SaveBitmap()
{
	HWND hwnd = NULL; // 画面全体のハンドルを指定

	// サイズ
	RECT rec;
	rec.right = 1280;
	rec.bottom = 1024;
	rec.top = 0;
	rec.left = 0;
	DWORD dwWidth = rec.right;
	DWORD dwHeight = rec.bottom;
	DWORD dwLength = dwWidth * 3;

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

	// バッファ確保とポインタ設定
	LPBYTE lpBuf = new BYTE [dwFileSize];
	LPBITMAPFILEHEADER lpHead = (LPBITMAPFILEHEADER) lpBuf;
	LPBITMAPINFOHEADER lpInfo = (LPBITMAPINFOHEADER) (lpBuf + sizeof(BITMAPFILEHEADER));
	LPBYTE lpPixel = lpBuf + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	ZeroMemory(lpHead, sizeof(BITMAPFILEHEADER));
	ZeroMemory(lpInfo, sizeof(BITMAPINFOHEADER));
	ZeroMemory(lpPixel, dwLength * dwHeight);

	// 24bitBMPファイルのヘッダ作成
	lpHead->bfType = ('M' << 8) | 'B';
	lpHead->bfSize = dwFileSize;
	lpHead->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	lpInfo->biSize = sizeof(BITMAPINFOHEADER);
	lpInfo->biWidth = dwWidth;
	lpInfo->biHeight = dwHeight;
	lpInfo->biPlanes = 1;
	lpInfo->biBitCount = 24;
	lpInfo->biCompression = 0;

	// ==== デスクトップのデバイスコンテキストをコピーして,バックバッファを作成 ====
	// ウインドウのデバイスコンテキスト取得
	HDC hdc = ::GetDC(hwnd);
	// BITMAPにウインドウのクライアント領域をコピー
	HDC hdcMem = ::CreateCompatibleDC(hdc);


	// デバイスコンテキスト互換のBITMAP作成
	HBITMAP hBMP = ::CreateCompatibleBitmap(hdc, dwWidth, dwHeight);

	// 出力用BITMAPを選択
	HBITMAP hOld = (HBITMAP)::SelectObject(hdcMem, hBMP);


	// ==== 描画処理 ============================
	//::BitBlt(hdcMem, 0, 0, dwWidth, dwHeight, hdc, 0, 0, SRCCOPY);
	ZeroMemory(lpPixel, dwLength * dwHeight);
	// テキストを描画
	::DrawText(hdcMem, "hogehogehoge", 12, &rec, DT_CENTER);

	// ==== 描画処理 ここまで =====================


	// BITMAPを元に戻す
	::SelectObject(hdcMem, hOld);

	// 出力用BITMAPをDIBに変換
	::GetDIBits(hdc, hBMP, 0, dwHeight, lpPixel, (LPBITMAPINFO)lpInfo, DIB_RGB_COLORS);

	// 開放
	::ReleaseDC(hwnd, hdc);
	::DeleteObject(hBMP);
	::DeleteObject(hdcMem);

	// バッファをファイルに書き出す
	char lpszFn[] = "hoge.bmp";
	HANDLE hFile = CreateFile(lpszFn, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	DWORD dwSize;
	WriteFile(hFile, lpBuf, dwFileSize, &dwSize, NULL);
	CloseHandle(hFile);

	delete [] lpBuf;
}

スピンボタン(アップダウンコントロール)を使う

・スピンボタンのメッセージは,WM_COMMANDではなく,WM_VSCROLLで飛んできます.
・初期化時に,最大値をセットしておかないとイベントが発生しないようです.
・GUIのリソース作成は,VC++のリソースエディタを使えば簡単に作れると思いますので省略します.
// ==== ヘッダ(.h)ファイル  ==================================
// クラス用ヘッダ
class CTestDialog : public CDialog
{
	/// スピンボタンのハンドル
	HWND	m_hSpinButton;

	// その他...
};


// ==== cppファイル  ==================================
// 初期化
BOOL CTestDialog::OnInitDialog()
{
	// ==== スピンボタンの初期化 ====
	char text[] = "0";
	LONG param = 0;
	// テキストボックスに初期値をセット
	::SetDlgItemText(m_hwnd, IDC_EDIT_HOGEHOGE, text);
	m_hSpinButton = GetDlgItem(m_hwnd, IDC_SPIN_HOGEHOGE);
	// とりうる値の範囲を指定
	SendMessage(m_hSpinButton, UDM_SETRANGE, TRUE, (LPARAM)MAKELONG(99,0));
	// デフォルト値
	SendMessage(m_hSpinButton, UDM_SETPOS, TRUE, (LPARAM)param);
}

// Windowプロシージャ
LRESULT CTestDialog::WindowProc( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	HRESULT hr = S_OK;
	LONG param;
	switch( uMsg )
	{
		// ==== スピンボタンが押されたとき ====
		case WM_VSCROLL: // スピンボタンはIDではなくWindowハンドルが送られてくる
		{
			char text[256];
			int currentPos;

			if ((HWND)lParam == m_hSpinButton) {
				// POS(スピンボタン)の値を得る(戻り値がPOS)(※なぜか下から9bit目が1になってるのでshortにいれて切り捨てる)
				short pos = (short) SendMessage(m_hSpinButton, UDM_GETPOS, 0, 0);
				// テキストボックスから取得
				::GetDlgItemText(m_hwnd, IDC_EDIT_HOGEHOGE, text, 3);
				sscanf(text, "%d", ?tPos);
				// 同じだったら無視(最大値を超えているときなどは変更されないため)
				if (currentPos != pos) {
					// テキストボックスの内容を変更
					wsprintf(text, "%d", pos);
					::SetDlgItemText(m_hwnd, IDC_EDIT_HOGEHOGE, text);
				}
			}
		}
	}
	return CDialog::WindowProc(message, wParam, lParam);
}

// ===========================================================

コンストラクタで戻り値(返り値)を返す

コンストラクタには戻り値がなく,エラー処理に困る場合があります.
こんなときは,引数にポインタを渡せばできます.
CHoge::CHoge(HRESULT *hr) {
    *hr = S_OK;
}

描画のちらつきを防止する

一時的に裏画面に描画するダブルバッファリングの方法です.
int CHogeView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    CClientDC dc(this);
    m_backDc.CreateCompatibleDC( &dc );
    m_backBmp.CreateCompatibleBitmap( &dc, 1024,768 );
    m_backDc.SelectObject( &m_backBmp );

    SetTimer(1,100,NULL);// 描画用タイマー
    return 0;
}

void CHogeView::OnTimer(UINT nIDEvent)
{
    Invalidate(FALSE); // 再描画するよう通知

    CView::OnTimer(nIDEvent);
}

void CHogeView::OnDraw(CDC* pDC)
{

    // 裏画面への描画
    //m_backDc...

    // 裏画面をメイン画面に転送
    pDC->BitBlt( 0, 0, 1024, 768, &m_backDc, 0, 0, SRCCOPY );
}

ウィンドウのメッセージを調べる.ウィンドウのクラスを調べる

VisualStudio付属ツールのSpy++を使うと,
ウィンドウメッセージやウィンドウのクラス(どのコントロールを使っているか)など
いろいろ見ることができます.

スレッドの同期(クリティカルセクション)

class Hoge
{
private:
    // クリティカルセクションオブジェクト
    CRITICAL_SECTION m_cs;
    // スレッドハンドル
    HANDLE m_hThread;
    // スレッドID
    DWORD m_dwThreadId;

public:
    // コンストラクタ
    Hoge() {
        // クリティカルセクション初期化
        InitializeCriticalSection(&m_cs);
    }

    // デストラクタ
    ~Hoge() {
        // クリティカルセクション削除
        DeleteCriticalSection(&m_cs);
        // スレッド終了を待つ
        WaitForSingleObject(m_hThread, INFINITE);
    }

    // スレッド開始メソッド
    void Start()
    {
        m_hThread = CreateThread(NULL, 0, ThreadProc, this, 0, &m_dwThreadId);
    }

    // スレッド関数
    static DWORD WINAPI ThreadProc(LPVOID lpParam) {
        // クリティカルセクション開始
        EnterCriticalSection(&m_cs);
        
        // 同期中に行う処理
        
        // クリティカルセクション終了
        LeaveCriticalSection(&m_cs);
    }
};

HRESULT型の定数を自分で追加する方法

次のように,キャストするように書くとできます.
ただし,FAILED()などでは判断してもらえないのでそれなりのエラー検知処理が必要
#define E_MY_ERROR ((HRESULT)0x00040501L)

ビットフィールドについて

構造体などを作るときに,
intやcharよりもさらに小さい単位でメンバを指定できます.
以下の例では,1Byteの中に,4bit,3bit,1bitのメンバを指定しています.
#pragma pack(1) // 構造体のバイト境界を1バイトごとに設定

struct Player {
    UINT hitPoint : 4; // 最大255
    UINT level : 3; // 最大127
    UINT flag : 1; // On or Off
};

#pragma pack()

メモリリークの検出方法

次のようにすると,プログラム終了時に開放していないヒープ領域があると教えてくれます.
#include <windows.h>
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>

int main()
{
    // プログラムの一番最初で呼び出しておく
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);

    // わざとメモリ確保
    int *hoge = new int;

    return 0 ;
}

演算子のオーバーロード

class CHoge
{
public:
	int m_i;
	int m_j;

	void SetData(int newi, newj)
	{
		this->m_i = newi;
		this->m_j = newj;
	}

	CHoge operator+(CHoge& right)
	{
		CHoge newHoge;
		newHoge.m_i = this->m_i + right.m_i;
		newHoge.m_j = this->m_j + right.m_j;
		return newHoge;
	}
};

int main()
{
        CHoge hoge1, hoge2, hoge3;
        hoge1.SetData(1, 2);
        hoge2.SetData(3, 4);
        hoge3 = hoge1 + hoge2;
}

unionとは(メモ)

unionって何?って思うときがあるかもしれないのでメモ
unionの中で定義された複数のメンバ変数は,同じメモリ上に1つだけ存在します.
つまり,あるメンバ変数に値が代入されると,他のメンバ変数も値が変更されることになります.
詳しくは以下のサンプルを見てください.
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>

typedef union {
  int data;
  char name[8];
} Hoge;

int main(int argc,char *argv[]) {
  
  Hoge data,data2;
  data.data = 3;
  cout << data.data << endl;

  sprintf(data.name,"hogehoge");
  cout << data.name << endl; // 上で,nameに代入したので変な値になる
  cout << data.data << endl; // "hogehgoe"が入っている
  cout << "sizeof(Hoge) = " << sizeof(Hoge) << endl; // 8 (メンバ変数の中での最大値)
  return 0;
}

C++の構造体はtypedef不要?

従来,構造体をtypedefで宣言して便利にしていましたが,
C++では,変数宣言時にstructやunionなどをつけなくてもよいので
typedefする意味がなくなっている気がします
//従来の方法
typedef struct _Hoge1{
    int iHoge;
    char chHoge;
} Hoge1;

Hoge1 a;

// C++の方法
struct Hoge2{
    int iHoge;
    char chHoge;
};

Hoge2 b;

構造体のサイズに注意

構造体のサイズは,メンバ変数が必要とするバイト数の合計にならないときもあります.
多くのコンパイラでは,最適化のために,4バイト毎のサイズになります.
typedef struct tagHoge{
    char chHoge; // 1Byte
    int iHoge; // 4Byte
} Hoge;

sizeof(Hoge) → 8 (5にならない)
この問題を解決するためには,
pragma pack()が使えます.
#pragma pack(1) // 1Byte境界にする
struct tagHoge{
 //変数
} Hoge;
#pragma pack() // 戻す

構造体にコンストラクタを持たせる

C++の構造体には,コンストラクタを持たせることができるようです.
これで,初期値をセットしておくことができます.
struct hoge{
    hoge() {
        x = 0;
        y = 0;
    }
    int x;
    int y;
};

また,コンストラクタ以外にも,構造体にメンバ関数を持たせることもできるようです.

Win32API関数のエラーの詳細を表示する

MSDNなどで,「GetLastError()を参照してください」と書いてあるときの
詳細の見方は以下のようにすればテキストで表示できます.
LPVOID *lpErr
//エラー取得
FormatMessage(
    FORMAT_MESSAGE_ALLOCATE_BUFFER |
    FORMAT_MESSAGE_FROM_SYSTEM |
    FORMAT_MESSAGE_IGNORE_INSERTS,
    NULL,
    GetLastError(),
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 既定の言語
    (LPTSTR) lpErr,
    0,
    NULL);
printf("Err:%s\n",(LPCTSTR)lpErr);

シリアル通信(RS-232C)で送信・受信を行うプログラム

よく使うのでソースコードを載せておきます.
詳しくはコチラ↓
シリアル通信(RS-232C)で送信・受信を行うプログラム

VC++でActiveXコンポーネント(COMも含む)を,簡単に使う方法

ActiveXコントロール以外の見てくれを持たないCOMなどは,
「#Importディレクティブ」を使うことで利用できる.
以下では,スマートポインタを使った方法でCOMを使用している.
#include <atlbase.h> // CComPtrなどを利用するため

#import "MyLib.tlb" no_namespace, named_guids // namespaceを使う場合は適宜書き換えてください

void CTestClass::Test() 
{
	// COMのクラスのインスタンス変数
	CComPtr<ICounter> pMyClass; // スマートポインタ
	// COM インスタンスの作成
	HRESULT hr = pMyClass.CoCreateInstance(CLSID_myClass));
	if(FAILED(hr)) {
		// エラー処理
		AfxMessageBox("失敗しました");
		return;
	}

	// メソッドなどを使う
	pMyClass->MyMethod();

	// スマートポインタは,定義したところのスコープを外れると,自動的に開放される
}

VC++でActiveXコンポーネント(COMも含む)のイベントを扱う

VCでCOMのイベントを受信するには,イベントシンクを実装する必要がある.

長いので別ページにしてあります.↓
VCでActiveXコンポーネント(COMも含む)のイベントを扱う

パラレルポート(プリンタケーブル)で通信を行う

プリンタ操作用APIを使う方法と,CreateFileでポートを開く方法があります.
  • プリンタ操作用APIを使う方法
    • OpenPrinter()
    • DOC_INFO_1構造体
    • StartDocPrinter
    • StartPagePrinter
    • WritePrinter()
    • EndPagePrinter(hPrinter);
    • EndDocPrinter(hPrinter);
    • ClosePrinter(hPrinter);

BSTRからchar文字列に変換する

	// BSTR bstrからchar temp[]に変換
	BSTR bstr;
	char temp[256];
	::ZeroMemory(temp, 256);
	int n = WideCharToMultiByte(CP_ACP, 0, bstr, SysStringLen(bstr), temp, sizeof(temp) - 1, 0, 0);
	::MessageBox(NULL, temp, temp, MB_OK);

BSTRの文字列長を得る

// wcslenを使った方法(NULLを渡すとエラーになるらしい)
int len = wcslen((bstr) ? bstr : L"");
// APIを使った方法
int len2 = SysStringLen(bstr);

BSTRの比較

// 文字列の比較(こちらもlen > 0であることをチェックしてから使うこと)
wcscmp(bstr, L"比較対象");

Unicode対応について

VS.NETの場合は以下のプロジェクト設定を変更

【プロジェクト】→【プロパティ】→【構成プロパティ】→【文字セット】
「マルチバイト文字セットを使用する」→「Unicode文字セットを使用する」

変数の方や関数の対応

  • CHAR → WCHAR
  • LPCSTR → LPCWSTR

sprintf → wsprintf

WCHAR wstr[50];
wsprintf(wstr, L"テスト");
// ↑Lをつけることで,WCHARの文字列として認識される

CHAR → WCHAR 変換

CHAR str[50];
sprintf(str, "テスト");
WCHAR wstr[50];
MultiByteToWideChar(CP_UTF8, 0, str, strlen(str)+1, wstr, (int)sizeof(wstr));

逆変換(WCHAR → CHAR)は,WideCharToMultibyte()を使う

MultiByteToWideChar(CP_ACP, 0, wstr, -1, str, strlen(str)); 
// ↑第4引数(length)に-1を指定すると,NULL文字まで数えてくれます

UDP通信を行う

TCPソケットでの通信と似ていますが,
・ソケット作成のときにSOCK_DGRAMを渡す.
 socket(AF_INET, SOCK_DGRAM, 0);
・受信にrecvfrom()を,送信にsendto()を使う(recv()やsend()でも可)
・listen()とaccept()は不要
あぁ,ws2_32.lib をリンカに渡すのを忘れずに..(VSウィザード使えば不要)

VCのデバッグウィンドウに文字を出力する(MFCを使わないで)

OutputDebugString()を使えばできますが,
以下のやつをヘッダファイルにしてプロジェクトに追加すれば,
printf()みたい%sとか%dとかが使えます.

#pragma once

#include <windows.h>
#include <tchar.h>
#include <stdarg.h>

// Visual Studio のDebugWindowの横幅文字数(バイト数ではない)
#define HPRINTF_MES_LENGTH 256

// TRACEのようにデバッグビルドのときだけ,メッセージを出す.
#ifdef _DEBUG
inline void hprintf( const _TCHAR * format, ... )
{
    _TCHAR buff[HPRINTF_MES_LENGTH];
    va_list vl;
    va_start(vl, format);
    _vsntprintf(buff, HPRINTF_MES_LENGTH, format, vl);
    va_end(vl);
    OutputDebugString(buff);
}
#else
#define hprintf __noop
#endif

相互にヘッダファイルをincludeするとエラーになる.これを回避する方法.

相互に参照したいためにメンバ変数に持ちいたいときがありますが,
コンパイルエラーになってしまいます.
#paragma once や,#ifndef などを使用してもなんともなりません..
例:以下の2クラスのような構成はコンパイルエラーになります...
//<hogeMaster.h>
#include "hogeSlave.h"

class CHogeMaster
{
private:
    CHogeSlave *m_hogeSlave;
public:
    // コンストラクタ
    CHogeMaster();
}
//<hogeMaster.cpp>
#include "hogeMaster.h"

CHogeMaster()
{
    m_hogeSlave = new CHogeSlave(this);
}
//<hogeSlave.h>
#include "hogeMaster.h"

class CHogeSlave
{
private:
    CHogeMaster *hogeSlave;
public:
    CHogeSlave(CHogeMaster *master);
}

解決法:ここで,hogeSlave.hを次のように変更するとコンパイルできます.
・includeではなく,class宣言 を使います.

//<hogeSlave.h>
//#include "hogeMaster.h"
class CHogeMaster;

class CHogeSlave
{
private:
    CHogeMaster *hogeSlave;
public:
    CHogeSlave(CHogeMaster *master);
}
※邪道的方法:void * で変数を宣言しておいて,キャストして使うこともできるが,可読性が著しく低くなる恐れあり..

現在の時間(時刻)を得る(MFC版)

CTimeクラスを使います.
	CString strTime; // 表示用文字列
	CTime time; // 時間を扱うクラス
	time = CTime::GetCurrentTime(); // 現在の時間オブジェクト(CTime)を得る
	strTime = time.Format("%H:%M:%S"); // 時間を指定したフォーマットの文字列として取得
	TRACE("現在の時間:%s\n", strTime); // 表示

	// 補足:time_tを使いたいときは,GetTime()を使う
	time_t timeT = time.GetTime();
	TRACE("time_t:%ld 秒(1970年からの経過秒数)\n", timeT);
	TRACE("time_t:%ld 年\n", timeT/60/60/24/365);

Win32APIでファイルの書き込み(バイナリ形式,テキストはそのままOK)

・CreateFile()
・WriteFile()
・CloseHandle()
を使う
	string pFileName; // ファイル名
	HANDLE hFile; // ファイルハンドル
	DWORD dwWriteSize; // ファイルに書き込んだサイズ
	DWORD dwDataSize; // データサイズ
	string pData; // 書き込むデータ

	pFileName = "hoge.dat";
	pData = "キタ━━━━ヽミ´ー`  彡ノ━━━━!!!!(σ´Д`)σゲッツ!!";
	dwDataSize = pData.length();

	// ファイルを開く
	hFile = CreateFile(pFileName.c_str(), GENERIC_WRITE, 0, NULL, 
					CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		AfxMessageBox("ファイルが開けません");
		return;
	}
	
	// ファイルに書き込む
	WriteFile(hFile , TEXT("data") , 4 , &dwWriteSize , NULL); // 文字列書き込み
	WriteFile(hFile , &dwDataSize , 4 , &dwWriteSize , NULL); // DWORD型のバイナリ書き込み
	WriteFile(hFile , pData.c_str() , dwDataSize , &dwWriteSize , NULL); // 文字列書き込み

	// ファイルを閉じる
	CloseHandle(hFile);
#ちなみに,SetFilePointer()を使えば,必要な位置にファイルポインタをシークしてから,その位置にデータを書き込むことができます.
 つまり,必要な部分だけデータを書き換えることができます.
 参考: http://black.sakura.ne.jp/~third/system/winapi/win113.html

※CStringをWriteFile()に渡すと,おかしな動作になるので注意..
 std::stringならOK

GDI+について

GDI+では,アプリで描画用するためのクラスが用意されています.
ペンやブラシを使ったり,
グラデーションやアルファブレンディングなどの便利な機能が使えます.
  • Win32 PlatformSDK のC++用クラスとして提供される
  • .NET Framework SDKにも同様のクラスが提供されている
  • "gdiplus.h"などをincludeして使う
  • "gdiplus.lib"をリンクする
  • using namespace Gdiplus;
    
    int APIENTRY WinMain(...)
    {
    	ULONG_PTR gdiplusToken;
    	GdiplusStartupInput gdiplusStartupInput;
    	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, 0); // GDIの初期化
    	// 処理
    	GdiplusShutdown(); // GDIの終了処理
    }
    
    void OnPaing(HWND hwnd, HDC hdc)
    {
    	Graphics graphics(hdc);
    	LinearGradientBrush brsh(Point(0,0), Point(200,200),
    				Color(255,0,0), Color(255,255,255));
    	graphics.FillRectangle(&brsh, 0, 0, 200, 100);
    }
    
    参考:日経ソフトウェア(2004-06)

VCでmakefileを使いたい

コマンドプロンプトで,nmakeコマンドを使うと,できます.

なぜかC++としてコンパイルできない

「.cpp」ファイルなのに_cplusplusのプリプロセッサの部分でエラーが出る.
未解決.
これってTipsじゃないじゃん...

sscanfは危険?(文字列→数値の変換)

なぜか意味不明にエラーが発生する.
しかも,sscanfをつかったちょっと後で.
私の場合は,DirectXを使っているときになぜかエラーが出て悩まされました.
文字列→数値変換には,atoi()を使いましょう.

画面の解像度を得る

RECT rect;
GetWindowRect(hwnd, &rect); // 画面の大きさを取得
TRACE("横%d, 縦%d\n", rect.right, rect.bottom);

MFCでファイルの読み込み

	CString line;	// ファイルから1行読み込んだ文字列をいれる
	CStdioFile file;	// ファイルオブジェクト
	CString filename = "c:/hoge"; // ファイル名

	// ファイル存在チェック
	if(!file.Open(filename, CFile::modeRead) ) {
		return false;	// ファイルが開けなかったらエラー
	}

	// ** ファイルを読み込んで処理 **
	while (file.ReadString(line)) {
		// 改行を取り除く
		line.TrimRight();
		// 空白行だったら読み飛ばす
		if (line.GetLength() == 0) {
			continue;
		}
		// デバッグ表示
		TRACE("%s\n", line);
	}
ちなみに,CStdioFile::WriteString()を使えば,書き込みができます.
ファイルをOpenするときに,CFile::modeWriteオプションで開く必要があります.

「ファイルを開く」ダイアログでファイル名を得る

MFCの場合,CFileDialogを使う
    // ファイルダイアログの作成
    CFileDialog dlg(TRUE, _T("*.*"), _T("default.txt"), 
        OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
        _T("テキスト (*.txt)|*.txt|All Files (*.*)|*.*||"), this);
    // ファイルを開くダイアログを開く
    if( dlg.DoModal() == IDOK) {
        // 選択されたファイルパス名を表示
        AfxMessageBox(dlg.GetPathName());
    }
非MFCの場合
BOOL CSDBViewerDlg::GetOpenFileName(LPTSTR szName)
{
    static OPENFILENAME ofn={0};
    static BOOL bSetInitialDir = FALSE;

    // Reset filename
    *szName = 0;

    // Fill in standard structure fields
    ofn.lStructSize       = sizeof(OPENFILENAME);
    ofn.hwndOwner         = m_hWnd;
    ofn.lpstrFilter       = "All Files (*.*)\0*.*;\0\0";
    ofn.lpstrCustomFilter = NULL;
    ofn.nFilterIndex      = 1;
    ofn.lpstrFile         = szName;
    ofn.nMaxFile          = MAX_PATH;
    ofn.lpstrTitle        = TEXT("Open Media File...\0");
    ofn.lpstrFileTitle    = NULL;
    ofn.lpstrDefExt       = TEXT("*\0");
    ofn.Flags             = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST;

    // Remember the path of the first selected file
    if (bSetInitialDir == FALSE)
    {
		ofn.lpstrInitialDir = ".";
        bSetInitialDir = TRUE;
    }
    else
        ofn.lpstrInitialDir = NULL;

    // Create the standard file open dialog and return its result
	return ::GetOpenFileName((LPOPENFILENAME)&ofn);
}

フォルダ内のファイルリスト(一覧)(フォルダも含めて)を取得する

MFCの場合,CFileFindを使えばよい
	BOOL bResult;
	CFileFind fileFind;

	if( fileFind.FindFile() ) {
		do {
			bResult = fileFind.FindNextFile();
			TRACE("%s\n", fileFind.GetFilePath());
		} while( bResult );
		fileFind.Close();
	}

自作のActiveXコントロールを追加するには

regsvr32.exeを使う
スタートメニュー→プログラム名を指定して実行→
regsvr32.exe c:\windows\system32\hogehoge.ocx
ファイル名は適宜あわせてください

stringの扱い方について

・stringって,最後に'\0'がはいってなくても大丈夫なのか?
 →そもそも,コピーするときに,assign()やappend()などのメソッドを使えば,
  そういうことは考えなくても大丈夫.
string str = "";
str.assign("hogehoge", 3); // 先頭の3文字だけコピーする
str.append("ppppp", 3); // 先頭の3文字だけ追加する
printf("%s\n", hoge.c_str()); // 「hogppp」と表示される
・stringに,配列のようにアクセスして,charが取り出せるか?
 →OK.とりだせます.
string str = "hoge";
printf("%c", str[0]); // 「h」が表示される
printf("%c", str[3]); // 「e」が表示される

プロセス間通信

SendMessageでWM_COPYDATAを送る方法

 送り先のhWndが分かっている場合,これで,文字列などのデータを送ることができる.

TCPソケットを用いる方法(ソケットは他にも種類があるようです)

 socketを用いて,listenしてconnectしてsendする方式.
 Windowsの場合,CSocketやWinSockなどが利用できる.
 Unixでも,よく使われているので調べればたくさん見つかるでしょう...

共有メモリを用いる方法

 Win32APIでは,CreateFileMapping.MFCでは,CMemFileが使えます.
 CreateFile→CreateFileMapping→MapViewOfFile→UnmapViewOfFile→CloseHandle
 ※双方のプロセスでCreateFileMapping()を行う必要があるそうです.
 hFileはCreateFileで生成されたファイルハンドルだが、0xffffffffを指定した場合はページングファイル上のメモリ領域が使用される。即ち共有メモリである。作成するファイルマッピングのサイズは64bit指定なので、上位32bitをMaxSizeHighで、下位32bitをMaxSizeLowで指定する。ただし、両方を0指定すると、hFileで指定したファイルのサイズとなる。従って、サイズが0のファイルへマッピングしようとするとエラーとなる。また、共有メモリとして0xffffffffを指定した場合は0とは指定できない。
参考:http://www.02.246.ne.jp/~torutk/cxx/sharedmemory/win32_shmem.html

π(パイ)を使う

・math.hをインクルード
・M_PIという定数が用意されている
※VC++ではmath.hをincludeしてもM_PIが使えないらしい
 そこで,以下の定義を自分で追加するべし
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

複素数を使う

複素数は,実部と虚部に分かれている.(つまりx+yiという形)
C++ではSTLに複素数用のライブラリが用意されている.
※C言語でも,complex.hを使えばできるらしいが,使えない環境も多い気がする..

以下に使用例を示す

#include <math.h>
#include <stdio.h>
#include <complex>
using namespace std;

int main()
{
    complex<double> y; // 解
    complex<double> I = complex<double>(0.0, 1.0); // 複素数の虚数単位i
    int i; // ループ変数

    y = 10.0 * I * 5.5 + 30; // 適当な計算(^_^;)
    printf("絶対値:%le\n", abs(y)); // 複素数の絶対値
    printf("対数(log):%le\n", log10(y)); // 10を底にlogをとる(eを底にするならlog()を使う)
    return 0;
}

演算子のオーバーライドの方法

普通のメソッドのオーバーライドみたいにできます.
サンプルとして,メンバとしてvectorを持つクラスに,
配列のようにアクセスできる仕組みを設けます.
// ヘッダファイル(VectorWrapper.h)
#include <vector>
#include <string>

class CDataBufferList
{
private:
	vector<string> m_vector;

public:
	/**
	 * 演算子 [] (配列の要素でのアクセス)をオーバーロード.
	 * 指定したインデックスの要素を返す.
	 * 引数:int index ほしい要素のインデックス(順番)
	 * 戻り値:string 指定したインデックスの文字列.ないインデックスを指定したらNULLを返す.
	 */
	string operator[](int index);
}
// C++ファイル(VectorWrapper.cpp)
// 演算子 [] のオーバーロード
string CVectorWrapper::operator[](int index)
{
	// 指定されたインデックスがサイズをオーバーしていないかチェック
	if (index >= m_vector.size()) {
		// サイズをオーバーしていたらNULLを返す
		return NULL;
	}
	// 指定されたインデックスにあるstringを返す
	return m_dataBufferList[index];
}

スレッドの使い方(MFC版)

AfxBeginThread()を使います.
サンプルは以下の通り
class CHoge
{
private:
	CWinThread *m_thread;	// スレッドオブジェクト
public:
	bool m_isAlive;	// スレッドの存続を示すフラグ

public:
	// デストラクタ
	~CHoge();
	// スレッドを開始する
	void RunThread();
	// スレッド関数
	// (※staticにすること)
	static UINT MyThreadFunc(LPVOID pParam);

}

// デストラクタ
CHoge::~CHoge()
{
	// スレッドの終了処理
	if (m_isAlive) {
		m_isAlive = false;
		// スレッドの終了を待つ
		if (m_thread != NULL) {
			WaitForSingleObject(m_thread->m_hThread, INFINITE);
			m_thread = NULL;	// 参照をはずす(deleteはMFCが勝手にやってくれるはず.(たぶん))
		}
	}
}

// スレッドを開始する
void CHoge::RunThread()
{
	m_isAlive = true;
	// スレッド呼び出し
	m_thread = AfxBeginThread(MyThreadFunc, this);
	// ↑自分の参照を渡す(スレッド関数はstaticなため,直接メンバ変数にアクセスできない.
	//   このインスタンスを通してメンバ変数にアクセスできるようにする)

	Sleep(10000);	// 10秒待つ
	m_isAlive = false;	// スレッドを終了させる
}

// スレッド関数
UINT CHoge::MyThreadFunc(LPVOID pParam)
{
	CHoge *cHoge = (CHoge *) pParam;	// クラスのインスタンスを得る
	while (cHoge->m_isAlive) { // m_isAliveがtrueの間続ける
		TRACE("hoge\n");	// デバッグ表示
		Sleep(1000);	// 1秒待つ
	}

	return 0;
}

// あとはmainからRunThread()を呼び出してください

STLでreverse_iteratorを使う

挿入した順番と逆順で要素を取得したいときはreverse_iteratorを使います.
#include <vector>

typedef std::vector<int> INT_LIST;

int main(){
	INT_LIST list;
	INT_LIST::reverse_iterator ite;
	
	list.push_back(1);
	list.push_back(2);
	list.push_back(3);
	
	for (ite = list.rbegin(); ite != list.rend(); ++ite)
	{
		printf((*ite));
	}
	
	return 0;
}

STLでmapを使うときにiterator(イテレータ)を使って要素に対して順次アクセス(総なめ)する

つまり,perlとかでいうforeachでkeyとvalueを操作する方法
// ** ライブラリのインクルード **
#include <map>
#include <string>

using namespace std;

// ** 定義 **
typedef map<string, string*> MY_MAP;
typedef pair<string, string*> MY_PAIR;
MY_MAP table;

// ** データを格納する **
string strKey;    // キーにいれるための変数
string *strValue; // 値に入れるための変数(のポインタ)
// データその1
strKey = "key1";
strValue = new string("value1");
table.insert(MY_PAIR(strKey, strValue));
// データその2
strKey = "key2";
strValue = new string("value2");
table.insert(MY_PAIR(strKey, strValue));
// データその3
strKey = "key3";
strValue = new string("value3");
table.insert(MY_PAIR(strKey, strValue));

// ** MAPが保持するポインタリストの指す先のメモリを全て解放する **
// 最初の要素から順次アクセスして,
for(MY_MAP::iterator ite = table.begin(); ite != table.end(); ++ite ) {
    string * str = ite->second; // valueを取り出す
    // ちなみに,keyを取り出したかったらite->first
    delete [] str;
}
// mapが保持する内容は自動的に解放されるため気にしなくていい

はまったこと(std::stringの使用について)

デバッグ表示しようとするとなぜかエラーになる.
実は,coutはstring(unsigned short)を文字列として解釈してくれますが,
TRACEでは正しく解釈してくれません.
string.c_str()で(char *)の文字列として渡してやる必要があります.
サンプル:以下のコードです.
#include "stdafx.h" // MFC使用してます
#include <string>
#include <iostream>
using namespace std;

void hoge() {
    string hoge;
    hoge = "hogehoge";
    cout << "hoge=" << hoge << endl; // こちらは問題なし
    //TRACE("hoge=%s\n", hoge); // こちらはエラーになる
    TRACE("hoge=%s\n", hoge.c_str()); // こうしないとうまくいかない
}

VC++(Visual Studio)で,アウトプットウィンドウに出力を表示する方法

MFCアプリケーションの場合

TRACE("hoge=%d,hogeStr=%s", hogeInt, hogeStr);
ただし,256文字以上の文字を表示しようとするとエラーになるので注意.

MFCでないアプリケーションで,アウトプットウィンドウに出力する

#include <crtdbg.h>

_RPT0(_CRT_WARN, "hoge\n");
_RPT1(_CRT_WARN, "hoge=%s", hogeStr);

STLを使ったときの警告(warning)を表示しないようにする

問題:
STLを使うと,
c:\program files\microsoft visual studio\vc98\include\vector(48) : warning C4786:...
というエラーがでることがある.
以下のようにすれば警告が出なくなる.
#pragma warning (disable:4786)
しかし,MFCを使っているときに,これでも警告が消えないときがある.
そんなときは,stdafx.hに上記のコードを追加すれば表示されなくなる.

MSXMLの使い方

<準備>
・MSXMLをダウンロードする(古いやつでよければそのまま使える)
・msxml.dllまたはmsxml4.dllをimportする
#import "msxml4.dll" rename_namespace("msxml")
※COMなので,CoInitializeをあらかじめmain関数などで実行しておく必要がある.

<XMLファイルの読み出し>

// ** Documentノードの作成 **
IXMLDOMDocumentPtr doc("MSXML.DOMDocument");
doc->validateOnParse = VARIANT_FALSE;

// ** ファイルを読み込む **
string file = "hoge.xml";
HRESULT result = doc->load(file.c_str());
if (!result) {
	cout << "読み込みエラー" << endl;
	return;
}

// ** ノードを見ていく **
// rootノードを取得
IXMLDOMNodePtr root = doc->documentElement;
// 汎用ノード(たどっていくときに使う)
IXMLDOMNodePtr node = root;

// ノードのチェック
if (node == NULL) {
	cout << "ありません" << endl;
	return;
}
// ** ノードタイプのチェック **
if (node->nodeType) {
cout << "nodeName:" << (LPCSTR)(_bstr_t(node->nodeName)) << endl;
//cout << "nodeValue:" << (LPCSTR)(_bstr_t(node->nodeValue)) << endl;

// ** 続く...

・メンバ変数
nodeType:ノードタイプ
nodeName:ノード名
nodeValue:ノードの値(属性値やテキスト).ELEMENT_NODEではNULL
firstChild:最初の子ノード
lastChild:最後の子ノード
parentNode:親ノード
nextSibling:次のノード(自ノードと並列に位置するノードで).なかったらNULL
previousSibling:前のノード(自ノードと並列に位置するノードで).なかったらNULL
ownerDocument:ルートドキュメント

attributes:属性
 nodeType==NODE_ELEMENTのときに有効
 IXMLDOMNamedNodeMap型のインスタンス
childNodes:子ノードリスト
 IXMLDOMNodeList型のインスタンス

・メソッド
hasChildNodes:子ノードがあるか調べる


<編集,書き込み>
・メンバ変数

・メソッド
appendChild:ノードをつける(属性やテキストもこれでつけれらる)
insertBefore:ノードを挿入

C++でのメモリ領域の確保の方法

C++では,newとdeleteが使えるようになった.
そのため,mallocとfreeを使わなくてもよい.
サンプル:2次元配列を作る方法
char **hoge; // 2次元配列用のポインタ
int rowNum = 5; // 行数(扱う文字列の数となる)
int colNum = 20; // 列数(こっちが文字列として使うほう)
hoge = new char *[rowNum];
for (int i = 0; i < rowNum; i++) {
	hoge[i] = new char[colNum];
}

for (i = 0; i < rowNum; i++) {
	delete(hoge[i]);
}
delete(hoge);

DLLを動的に読み込む(ロードする)

<使用する関数>
・LoadLibrary():DLLを読み込むための関数
・FreeLibrary():読み込んだDLLを解放する関数
・GetProcAddress():読み込んだDLLから関数などを取得する関数

<関数の仕様>
HINSTANCE LoadLibrary(
LPCTSTR lpLibFileName // DLLのファイル名
);

BOOL FreeLibrary(
HMODULE hLibModule // モジュールのハンドル(LoadLibraryで返されたHANDLE)
);

FARPROC GetProcAddress(
HMODULE hModule, // モジュールのハンドル(LoadLibraryで返されたHANDLE)
LPCSTR lpProcName // 関数名
);

<サンプル>

    HINSTANCE h;

    if((h = LoadLibrary( "mydll.dll" )) != NULL){
        int (FAR WINAPI *MyFunc)(int,int);
        MyFunc = (int (FAR WINAPI *)(int,int))GetProcAddress(h,"MyFunc");
        int res = (*MyFunc)(0,0);
        FreeLibrary(h);
    }else{
        MessageBox(NULL,"DLLが見つかりません。","Load Error",MB_OK);
    }
参考サイト:http://www.bc.wakwak.com/~wmasayoshi/prog/builder/tips/tps0003.htm

DLLで動的にバッファを確保して,戻り値(DLLの外側)で使用する

/* hello_what2.c */
#include <stdlib.h>

LPSTR pszBuffer; /* ヒープ領域に確保するメモリへのポインタ */
int nBufSize;    /* 確保したメモリのサイズ */

#define BUF_SIZE 16 /* バッファの初期サイズ */

/* DLLエントリポイント関数 */
BOOL APIENTRY
DllMain(HINSTANCE hInstance, DWORD ul_reason_for_call, LPVOID pParam)
{
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        /* loaddll された時に実行される */
        nBufSize = BUF_SIZE;
        pszBuffer = malloc(BUF_SIZE); /* 初期サイズでバッファを確保 */
        break;
    case DLL_PROCESS_DETACH:
        /* freedll された時に実行される */
        free(pszBuffer); /* ヒープ領域に確保したメモリを開放 */
        break;
    }
    return TRUE;
}

#define HEADER_SIZE 6  /* "hello " の文字数 */

/* "hello " + what を返す(エラーの時は "" を返す) */
HIDEMARU_MACRO_DLL
LPCSTR hello_what(LPCSTR what)
{
    int len;

    if (IsBadStringPtr(what,1)) return ""; /* 引数が無効 */

    len = lstrlen(what) + HEADER_SIZE + 1; /* 戻り値に必要なメモリサイズ */
    if (len > nBufSize) {
        /* 現在のバッファではサイズが足らないのでメモリを(再)確保 */

        free(pszBuffer); /* 古いメモリを開放 */
        pszBuffer = malloc(len); /* 新たにヒープ領域にメモリを確保 */
        if (pszBuffer == NULL) return ""; /* no memory */
        nBufSize = len; /* 確保したバッファのサイズを記憶 */
        lstrcpy(pszBuffer, "hello "); /* ヘッダをコピー */
    }

    /* "hello " の後ろに what の内容をコピー */
    lstrcpy(pszBuffer + HEADER_SIZE, what);

    return pszBuffer;
}
参考:http://homepage2.nifty.com/jr-kun/hidemaru_qa/make_dll.html

DLLの関数名一覧を取得する方法(正しく動作するかは不明)

Cのサンプル2:
user32.dllが(非遅延)インポートしている関数のリストを表示する。
void DispImportFuncs(void)
{
	DWORD BaseAddress;
	ULONG Size;
	PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor;
	PIMAGE_THUNK_DATA pThunkData;
	PIMAGE_IMPORT_BY_NAME pImportByName;

	/* user32.dllを読み込み */
	BaseAddress = (DWORD)LoadLibrary("user32.dll");

	/* インポートディレクトリを取得 */
	pImportDescriptor = ImageDirectoryEntryToData(
		(LPVOID)BaseAddress, 
		TRUE, 
		IMAGE_DIRECTORY_ENTRY_IMPORT, 
		&Size);

	/* 各インポート関数名を表示 */
	while(pImportDescriptor->OriginalFirstThunk != 0) {
		printf("DLL名: %s\n", BaseAddress + pImportDescriptor->Name);

		pThunkData = (PIMAGE_THUNK_DATA)(BaseAddress + pImportDescriptor->OriginalFirstThunk);

		while(pThunkData->u1.Ordinal != 0) {
			if(IMAGE_SNAP_BY_ORDINAL(pThunkData->u1.Ordinal)) {
				printf("\t序数: %d\n", IMAGE_ORDINAL(pThunkData->u1.Ordinal));
			} else {
				pImportByName = (PIMAGE_IMPORT_BY_NAME)(BaseAddress
								 + (DWORD)pThunkData->u1.AddressOfData);
				printf("\tヒント: %d, 関数名: %s\n", 
						pImportByName->Hint, pImportByName->Name);
			}

			pThunkData++;
		}

		printf("\n");
		pImportDescriptor++;
	}

	/* 後処理 */
	FreeLibrary((HMODULE)BaseAddress);
}

VBのサンプル:
kernel32.dllがエクスポートしている関数の一覧をテキストボックスに表示する。

'宣言
Declare Function MoveMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
    ByRef Dest As Any, _
    ByRef Src As Any, _
    ByVal Size As Long) As Long

Declare Function PtrToStr Lib "kernel32" Alias "lstrcpyA" ( _
    ByVal DestStr As String, _
    ByVal pSrc As Any) As Long

'コード
Private Sub Command1_Click()
    Dim Size As Long
    Dim BaseAddress As Long
    Dim pExportDirectory As Long
    Dim ExportDirectory As IMAGE_EXPORT_DIRECTORY
    Dim ExportNamePointerTable() As Long
    Dim S As String
    Dim FuncName As String * 1000
    Dim I As Long
    
    'kernel32.dllのベースアドレスを取得
    BaseAddress = GetModuleHandle("kernel32.dll")
    
    'エクスポートディレクトリを取得
    pExportDirectory = ImageDirectoryEntryToData(BaseAddress, 1, IMAGE_DIRECTORY_ENTRY_EXPORT, Size)
    MoveMemory ExportDirectory, ByVal pExportDirectory, Len(ExportDirectory)
    
    'エクスポートされている関数の名前のリストを取得
    ReDim ExportNamePointerTable(ExportDirectory.NumberOfNames - 1)

    MoveMemory _
        ExportNamePointerTable(0), _
        ByVal (BaseAddress + ExportDirectory.AddressOfNames), _
        ExportDirectory.NumberOfNames * 4
    
    '各関数名を表示
    For I = 0 To ExportDirectory.NumberOfNames - 1
        PtrToStr FuncName, BaseAddress + ExportNamePointerTable(I)
        S = S & Left(FuncName, InStr(FuncName, vbNullChar) - 1) & vbCrLf
    Next
    
    Text1.Text = S
End Sub
参考:http://nienie.com/~masapico/api_ImageDirectoryEntryToData.html

コントロール(GUIコンポーネント)を動かす

MoveWindow()
みやすいところに

タスクトレイのアプリケーションを作る

http://hakoten.com/com/
・タスクバーに表示されないようにする
 →ウィンドウのプロパティで,WS_EX_APPWINDOWからWS_EX_TOOLWINDOWに変更する
 #これだけだと,ウィンドウには最大化,最小化ボタンがついていないものになる.
  →子ウィンドウを作成してそれを表示するようにすればOK
   「ResourceView」→「ダイアログ」→右クリック→「ダイアログの挿入」
   ClassWizard→「クラスの追加」→「クラスの新規作成」
   OnInitDialog(OnInitInstance)で,作成したDlgクラスをDoModal()する
   子ウィンドウが閉じられたときに終了するために,DoModalの後に,EndDialog(iID)する.

・タスクトレイにアイコンを表示する
==アイコンを追加するコード====================

		m_stNotifyIcon.cbSize = sizeof(NOTIFYICONDATA);
		m_stNotifyIcon.uID = 0;
		m_stNotifyIcon.hWnd = m_hWnd;
		m_stNotifyIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
		m_stNotifyIcon.hIcon = AfxGetApp()->LoadIcon( IDR_MAINFRAME );
		m_stNotifyIcon.uCallbackMessage = 0;
		lstrcpy( m_stNotifyIcon.szTip, "タスクトレイアプリのテスト" );
		::Shell_NotifyIcon( NIM_ADD, &m_stNotifyIcon );

		// この際なのでダイアログを常に最前面に表示するようにします
		// (この処理はタスクトレイとは何の関係もありません)
		SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
==アイコンを削除するコード====================
		::Shell_NotifyIcon( NIM_DELETE, &m_stNotifyIcon );

・タスクトレイのクリックを得る
上記の"m_stNotifyIcon.uCallbackMessage = 0;"を,
   "m_stNotifyIcon.uCallbackMessage = WM_USER_POPUP;"に変更
そうすることで,WindowProcにWM_USER_POPUPメッセージが来るようになる.
右クリックや左クリックの判断は,lParamでできる.

COMを使う

・VCの場合
#import ディレクティブを使う
CoInitialize(0)
CoCreateInstance()
CoUnInitialize();
・VBの場合
CreateObjectを使う
F2でオブジェクトブラウザを開く→右クリック→参照設定
→tlbのCOMコンポーネントを取り込むことができる

MFCアプリケーションの戻り値を自分で指定する

InitInstanceでFALSEを返すようになっている場合,
ExitInstanceの戻り値が,アプリケーションの終了コードとなる.
ExitInstanceをオーバーライドして,返したい値を返すようにすればよい.

ウィンドウハンドルからウィンドウオブジェクトを得る

CWnd::FromHandle?メソッドを使うとウィンドウハンドルからCWndオブジェクトを得ることができます。このメソッドはstaticです。つまり、CWndのインスタンスがなくても使用できます。
(例)
  CWnd* win1 = CWnd::FromHandle(handle1);  // handle1はHWND型

ウィンドウオブジェクトのウィンドウハンドルを得る

MFCの場合,ウィンドウは基本的に,CWndクラスを継承しているのでそれのメンバが使えます.
CWnd::m_hWndにウィンドウハンドルが保持されています。CWnd::GetSafeHwnd?メソッドを使うこともできます。

中間ファイルは別フォルダに出力する

プロジェクトの設定で、中間ファイルを出力するフォルダを指定できる。
デフォルトでは、"Debug"になっているため、実行ファイルとごっちゃになってしま
う。

VC++.NETになってから、ClassWizardがなくなった。

 →メソッドのオーバーライドなどは、右下のプロパティウィンドウからやることが
できる
  イベントやメソッドの追加は、クラスビューの右クリックメニューから追加する
ことができる

デフォルトのダイアログ処理を止めるには?

(ダイアログをEnterやEscで閉じないようにするには?)
void CMachineCtrlTestAppDlg::OnCancel()
{
	// TODO : ここに特定なコードを追加するか、もしくは基本クラスを呼び出してく
ださい。
	// デフォルトのダイアログの終了処理を止める(ESCで終了しないように)
	//CDialog::OnCancel();
}

void CMachineCtrlTestAppDlg::OnOK()
{
	// TODO : ここに特定なコードを追加するか、もしくは基本クラスを呼び出してく
ださい。
	// デフォルトのダイアログの終了処理を止める(Enterで終了しないように)
	//CDialog::OnOK();
}

void CMachineCtrlTestAppDlg::OnClose()
{
	// TODO : ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出
します。
	// 閉じるボタンで終了するために追加
	EndDialog(0);
	CDialog::OnClose();
}

MFCでコマンドライン引数を使う

"__argc","__argv"という隠し変数があるため,これを使えばよい.

外部プロセス(アプリケーション)呼び出し

	// ==== プロセス呼び出し ==== 
	// セキュリティ情報(これがないとstdout等がファイルハンドルに出力されない)
	SECURITY_ATTRIBUTES secAttr;
	ZeroMemory(&secAttr, sizeof(SECURITY_ATTRIBUTES));
	secAttr.bInheritHandle = TRUE;

	// 子プロセス用startup情報
	STARTUPINFO startupInfo;
	ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
	startupInfo.cb = sizeof(STARTUPINFO);
	startupInfo.dwFlags = STARTF_USESTDHANDLES; // 標準出力するのに必要
	// 標準出力(stdout)に、出力先ファイルのファイルハンドルを関連付ける
	HANDLE hOut = CreateFile( "output.txt", GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, &secAttr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hOut == INVALID_HANDLE_VALUE) {
		// ファイルが書き込み用に開けない
		return E_FAIL;
	}
	startupInfo.hStdOutput = hOut;
	//startupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); 自分のプロセスのstrerrを子プロセスに直結するときはこのように
	//startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);

	// 子プロセスのprocess情報
	PROCESS_INFORMATION processInfo;
	ZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION));

	// プロセス呼び出し
	BOOL bResult = CreateProcess(NULL, "HelloWorld.exe konnitiwa", &secAttr, &secAttr, TRUE, 
					NORMAL_PRIORITY_CLASS, NULL, NULL, &startupInfo, &processInfo);

	// プロセスの終了を待つ
	WaitForSingleObject(processInfo.hProcess, INFINITE);
	// プロセスの終了
	CloseHandle(processInfo.hThread);
	CloseHandle(processInfo.hProcess);

	// 出力ファイルのハンドルを閉じる
	CloseHandle(hOut);

イベント待ちを行う

使用関数
CreateEvent イベント作成
SetEvent イベントをセット
WaitForSingleObject イベント待ち
PulseEvent イベント停止

・CRITICAL_SECTION 構造体
スレッドの同期に使います.
http://black.sakura.ne.jp/~third/system/winapi/win145.html

・ミューテックス
プロセスの同期に使います.
http://black.sakura.ne.jp/~third/system/winapi/win146.html

・スレッドの終了を待つ
WaitForSingleObject(hThread,INFINITE); /* スレッド終了まで待つ */

コールバック関数を使う

・EnumWindows+EnumWindowsProc
・RegisterClass

CSV読み込み

CString::Tokenize()を使う
CString str( "hoge,hoge2,hoge3" );
CString resToken;
int curPos = 0;

resToken= str.Tokenize(",", curPos);
while (resToken != "")
{
   TRACE("Resulting token: %s\n", resToken);
   resToken= str.Tokenize(",", curPos);
};

iniファイル読み込み

・GetPrivateProfileInt
・GetPrivateProfileString
・WritePrivateProfileString
※注:ファイルパスには,"./ファイル名"
 というようにしないとうまくいかないかも


作成日: 2006年09月20日16時29日10秒
更新履歴
2008年07月22日 2008年02月08日 2008年02月06日 2008年02月05日 2007年10月25日 2006年12月20日 2006年12月19日 2006年12月05日 2006年09月20日 2006年06月18日 2006年06月12日 2006年04月22日 2006年03月30日 2006年02月28日 2006年02月23日 2006年02月18日 2006年01月13日 2005年12月21日