FC2カウンター FPGAの部屋 Visual Studio 2008でのコンソールCアプリケーション
FC2ブログ

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

Visual Studio 2008でのコンソールCアプリケーション

久しぶりにVC++でコードを書いたら、いつの間にか、mainが_tmainになって、char* argv[]が_TCHAR* argv[]になっていた。
argv[1]をsscanf(argv[1], "%d", &a)でint aに値を取り込もうと思ってもエラーになってしまう。(プロパティで変えられると思うけど。。。)
この最、勉強して、Unicodeで書いてみた。コードはBMPのRGBコードの指定された何ビットかを落とすソフトウェアだ。

sscanf(argv[1], "%d",&bit_and);



_stscanf_s(argv[1], _T("%d"), &bit_and, sizeof(int));


に書き換えた。
下にすべてのソースコードを示す。

// Data2BitLimit.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//
// 使い方:Data2BitLimit <落とすビット数> <元のBMPファイル名> <ビット数を落としたBMPファイル名>
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <cstring>

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
    _TCHAR org_file[100];
    _TCHAR new_file[100];
    BITMAPFILEHEADER bmpfh; // BMPファイルのファイルヘッダ
    BITMAPINFOHEADER bmpih; // BMPファイルのINFOヘッダ
    FILE *orgfp, *newfp;
    BMP24FORMAT **bmp_data; // 24ビットのBMPファイルのデータ 640*480
    int i, j;
    int bit_and;
    unsigned char bit_and_pt;

    // 引数の処理
    if (argc==1){ // 引数なしはエラー
        fprintf(stderr, "使い方:Data2BitLimit <落とすビット数> <元のBMPファイル名(cam_bmp_file.bmp)> <ビット数を落としたBMPファイル名(new_bmp_file.bmp)>\n");
        exit(1);
        //bit_and = 2;
        //_stprintf_s(org_file, 100, _T("cam_bmp_file.bmp"), 138);
        //_stprintf_s(new_file, 100, _T("new_bmp_file.bmp"), 138);
    } else if (argc==2) { // 引数1つ、落とすビット数
        _stscanf_s(argv[1], _T("%d"), &bit_and, sizeof(int));
        _stprintf_s(org_file, 100, _T("cam_bmp_file.bmp"), 138);
        _stprintf_s(new_file, 100, _T("new_bmp_file.bmp"), 138);
    } else if (argc==3) {
        _stscanf_s(argv[1], _T("%d"), &bit_and, sizeof(int));
        _stprintf_s(org_file, 100, argv[2]);
        _stprintf_s(new_file, 100, _T("new_bmp_file.bmp"), 138);
    } else { // それ以外
        _stscanf_s(argv[1], _T("%d"), &bit_and, sizeof(int));
        _stprintf_s(org_file, 100, argv[2]);
        _stprintf_s(new_file, 100, argv[3]);
    }

    
    if (_tfopen_s(&orgfp, org_file, _T("rb")) != 0) { // org_fileをバイナリリードモードでオープン
        fprintf(stderr, "Can't Open %s\n", org_file);
        exit(1);
    }
    if (_tfopen_s(&newfp, new_file, _T("wb")) != 0) { // new_fileをバイナリライトモードでオープン
        fprintf(stderr, "Can't Open %s\n", new_file);
        exit(1);
    }
    
    // BMPファイルヘッダの読み出し
    fread(&bmpfh.bfType, sizeof(short), 1, orgfp);
    fread(&bmpfh.bfSize, sizeof(long), 1, orgfp);
    fread(&bmpfh.bfReserved1, sizeof(short), 1, orgfp);
    fread(&bmpfh.bfReserved2, sizeof(short), 1, orgfp);
    fread(&bmpfh.bfOffBits, sizeof(long), 1, orgfp);

    fread(&bmpih.biSize, sizeof(long), 1, orgfp);
    fread(&bmpih.biWidth, sizeof(long), 1, orgfp);
    fread(&bmpih.biHeight, sizeof(long), 1, orgfp);
    fread(&bmpih.biPlanes, sizeof(unsigned short), 1, orgfp);
    fread(&bmpih.biBitCount, sizeof(unsigned short), 1, orgfp);
    fread(&bmpih.biCompression, sizeof(unsigned long), 1, orgfp);
    fread(&bmpih.biSizeImage, sizeof(unsigned long), 1, orgfp);
    fread(&bmpih.biXPixPerMeter, sizeof(long), 1, orgfp);
    fread(&bmpih.biYPixPerMeter, sizeof(long), 1, orgfp);
    fread(&bmpih.biClrUsed, sizeof(unsigned long), 1, orgfp);
    fread(&bmpih.biClrImporant, sizeof(unsigned long), 1, orgfp);

    // BMPファイルヘッダの書き込み
    fwrite(&bmpfh.bfType, sizeof(short), 1, newfp);
    fwrite(&bmpfh.bfSize, sizeof(long), 1, newfp);
    fwrite(&bmpfh.bfReserved1, sizeof(short), 1, newfp);
    fwrite(&bmpfh.bfReserved2, sizeof(short), 1, newfp);
    fwrite(&bmpfh.bfOffBits, sizeof(long), 1, newfp);

    fwrite(&bmpih.biSize, sizeof(long), 1, newfp);
    fwrite(&bmpih.biWidth, sizeof(long), 1, newfp);
    fwrite(&bmpih.biHeight, sizeof(long), 1, newfp);
    fwrite(&bmpih.biPlanes, sizeof(unsigned short), 1, newfp);
    fwrite(&bmpih.biBitCount, sizeof(unsigned short), 1, newfp);
    fwrite(&bmpih.biCompression, sizeof(unsigned long), 1, newfp);
    fwrite(&bmpih.biSizeImage, sizeof(unsigned long), 1, newfp);
    fwrite(&bmpih.biXPixPerMeter, sizeof(long), 1, newfp);
    fwrite(&bmpih.biYPixPerMeter, sizeof(long), 1, newfp);
    fwrite(&bmpih.biClrUsed, sizeof(unsigned long), 1, newfp);
    fwrite(&bmpih.biClrImporant, sizeof(unsigned long), 1, newfp);

    // メモリをアロケートする
    if ((bmp_data=(BMP24FORMAT **)malloc(sizeof(BMP24FORMAT *)*bmpih.biHeight)) == NULL){
        fprintf(stderr, "bmp_dataの1次元目の480ののメモリを確保できません\n");
        exit(1);
    }
    for (i=0; i<bmpih.biHeight; i++){
        if ((bmp_data[i]=(BMP24FORMAT *)malloc(sizeof(BMP24FORMAT) * bmpih.biWidth)) == NULL){
            fprintf(stderr, "bmp_dataの2次元目の%d番目のメモリが確保できません\n");
            exit(1);
        }
    }

    // ビットを落として書き込む
    for (i=0, bit_and_pt=0; i<bit_and; i++)
        bit_and_pt = bit_and_pt | (1<<i);
    bit_and_pt = 0xff ^ bit_and_pt;

    for (i=0; i<bmpih.biHeight; i+=1){
        for (j=0; j<bmpih.biWidth; j+=1){
            bmp_data[i][j].blue = fgetc(orgfp);
            bmp_data[i][j].green = fgetc(orgfp);
            bmp_data[i][j].red = fgetc(orgfp);
            
            fputc((int)(bmp_data[i][j].blue & bit_and_pt), newfp);
            fputc((int)(bmp_data[i][j].green & bit_and_pt), newfp);
            fputc((int)(bmp_data[i][j].red & bit_and_pt), newfp);
        }
    }

    fclose(orgfp);
    fclose(newfp);
    
    for (i=0; i<bmpih.biHeight; i++)
        free(bmp_data[i]);
    free(bmp_data);
    return 0;
}


ヘッダは、ここにある
最初に、BMPヘッダの読み出しで、下のコードで良いと思った。

fread(&bmpfh, sizeof(BITMAPFILEHEADER), 1, orgfp);
fread(&bmpih, sizeof(BITMAPINFOHEADER), 1, orgfp);


上のコードを使用したところ、構造体tagBITMAPFILEHEADERは、最初にshort bfTypeがあって、次がlong bfSizeだったが、bfSizeの値が違っていた。おかしいと思ったら、どうやら、short bfSize、2バイトの後に、2バイトのダミー・パッディングが入っていて、その後にlong bfSizeだと、状況があう。どうやら、4バイト境界に構造体のメンバを揃えているようだ。(少なくともintやlongは)というわけで、構造体を一気にファイルから読むという目論見は甘いという教訓を得た。
改めて考えてみると、4バイト境界に揃っていたほうが1サイクルで読めるし、効率が良いと思う。

#fprintfがそのまま使ってあるから、後で直すことにする。
fprintfを_ftprintf_sに変更して、文字列は、_T( )でくくりました。

参考にしたWebサイト”Visual C++のUNICODE対応”、”VC++2005よりセキュリティが強化された関数の一覧"
  1. 2010年10月13日 21:16 |
  2. VC++
  3. | トラックバック:0
  4. | コメント:4

コメント

おはようございます。

こういうときは構造体のアラインメントを1にして使うのが
常套手段良いですね。やり方は処理系依存になってしまうの
ですが、VC++ だと http://tinyurl.com/2ax3jzl の様な方法が
あるようです。

一番分かりやすそうなのは、

#pragma pack(1)
struct foo {
 ...
}
#pragma pack()

でしょうか。

http://tinyurl.com/23y8uch によると、
【プロジェクトの設定】>【C/C++】>【コード生成】ページの【構造体メンバのアライメント】
でも変えられるみたいですが、他の処理系に持って行ったときに
バグの元になりかねないので、ちゃんとコード中に記述する形が
良いのだと思います。
  1. 2010/10/14(木) 08:57:29 |
  2. URL |
  3. 武内 #-
  4. [ 編集 ]

武内さん、情報ありがとうございます。
この辺は、やはりコンパイラがやりたいようにアライメントを決めてもらったほうが、実行が早くなると思うので、メンバごとにやるようにしました。いろいろなコンパイラの実装が考えられる気がします。それと、今回はBMPなのでダメですが、データ構造の最適化が考えられます。
  1. 2010/10/14(木) 10:17:26 |
  2. URL |
  3. marsee #f1oWVgn2
  4. [ 編集 ]

> 今回はBMPなのでダメですが、データ構造の最適化が考えられます。

pragma を使う書き方は処理系依存となるので、それを嫌って使いたがらないプログラマーもいるかもしれません。しかし、こういった入出力部分で処理速度を理由として packed な構造体の使用を避ける必要はないと思います。

Windows SDK のヘッダファイルにある Bitmap 関連構造体にはアラインメントの指定が付いていますので、作ったデータを画面に表示する場合などは必ず packed 形式の構造体を使わなければなりません。そのため、通常の Windows アプリケーションでは packed 形式のデータをそのまま fread/fwrite している場合がほとんどだと思います。このあたりは 16bit 時代からの遺産を引きずっているという部分もありますが、互換性からすると仕方がないところで、今後 64bit 時代になったからと言って、すべてを 64bit 境界にするようなことにはならないと思います。

実行速度についても、このプログラムであればパッキングを指定して一気に fread/fwrite するのが最速だと思います。1つには API 呼び出し回数が大きく異なるためですが、アラインメントについても、せっかく構造体側で合わせても、システム内のデータバッファでずれていれば、書き込む度にオーバーヘッドが生じてしまうためです。

アラインメントのずれたメンバをループ中で多数回書き換えるようなプログラムであれば、ユーザー側でアラインメントが合っていることが重要になりますが、そういった問題がなければ、個人的にはコードの読みやすさの点でも一気に読み書きした方が分かりやすいと思います。

ま、一旦動いたプログラムをわざわざ書き直すほどのことでもないわけですが(汗
  1. 2010/10/14(木) 13:16:59 |
  2. URL |
  3. 武内 #-
  4. [ 編集 ]

了解しました。次に作るときには、ヘッダ部分はpack(1)してつくろうと思います。ありがとうございました。
どうも、体調が悪くて、リスポンスが遅れてしまいます。
  1. 2010/10/15(金) 04:47:50 |
  2. URL |
  3. marsee #f1oWVgn2
  4. [ 編集 ]

コメントの投稿


管理者にだけ表示を許可する

トラックバック URL
http://marsee101.blog.fc2.com/tb.php/1617-2d688108
この記事にトラックバックする(FC2ブログユーザー)