初心者による VC++ プログラミング・メモ

あなたは、人目の訪問者です。


このページ以下に関する情報は、保証しません。
リンク


DCOM のセキュリティ関連



ATL でのオブジェクトのメソッドのオプショナル
メソッドの引数に
[in] VARIANT vin1,[in, optional] VARIANT vin2
とかにすると、第一引数は必須だが、第二引数はオプション(あってもなくてもよい)という事になる。

オプショナルの引数の型が VARIANT 型の場合で値の指定がなかった場合は(VT_ERROR という)エラー値 DISP_E_PARAMNOTFOUND が与えられる。


当然だけど、第二引数がオプションになったのなら、第三引数や第四引数など、それ以降もオプション属性をつけるべきです。
こんがらがっちゃうからね



引数のオプショナルのデフォルト値
上記のオプショナルでデフォルト値を用いたい場合
[in] VARIANT vin1,[in, optional, defaultvalue("defoValue")] VARIANT vin2
とする事で、指定されなかった場合のデフォルト値を設定できる。



引数の数をよりダイナミックに...
引数の最後に VARIANT の SAFEARRAY メソッドを、非オプショナルで設定する
[in] VARIANT vin1,[in] SAFEARRAY(VARIANT) vin2




DLL の作り方
「新規作成」-> Win32 Dynamic-Link Library -> 「シンボルをエクスポートする DLL」

多分、DLL のクラスができているさ。



COM(ATL 利用) の作り方
「新規作成」-> Win32 Dynamic-Link Library -> ATL COM AppWizard -> 「挿入」->「ATL オブジェクトの新規作成」->(「オブジェクト」-「シンプルオブジェクト」) -> 「C++ ショートネーム」に文字列代入 -> 一番上の インターフェイス上で、メソッドの追加。

ここでも、その COM から呼び出されるクラスができているかな。



リリースモードとデバグモード
「リビルド」時に、「デバグモード」と「リリースモード」がある。
デバグも終了して、ソフトウェアが完成したら「リリースモード」で「リビルド」すると、コードも最適化されるし、デバグ情報も実行ファイル(exe,dll)に入らないので、そうしよう。



メモリの再割り当て
#include <stdlib.h>

main(){
char *test;
char *p;

test = (char *)malloc(3);
p = test;
*p = 'a';
*(p+1) = 'b';
*(p+2) = '\0';

test = (char *)realloc(test,4);

p = test;
*(p+2) = 'c';
*(p+3) = '\0';

printf("Hello World!\n");
printf("%s\n",p);
printf("Hello World!\n");
}
realloc() しても元のデータは初期化されない。



realloc() 時の注意
char *pout = (char*)realloc(char* pin,サイズ);
pin と pout は同じ値(メモリの位置)とは限らない。
連続領域を確保できなければ、別のメモリの位置に移動して、確保するからだ。

また、確保できなかったことも考えて
char *pin = (char*)realloc(char* pin,サイズ);
としてはいけない。
再確保できなければ、NULL が返ってくる。そうなると、最初のメモリがどこかに行ってしまう(メモリリーク!?)。
必ず
char *pout;
pout = NULL;
pout = (char*)realloc(char* pin,サイズ);
if(pout != NULL){
pin = pout;
}
として、再確保が成功してから入れ替えること。
(入れ替え時に、元の pin で確保したメモリは{realloc() が成功していると}良きに計らって解放してくれているらしい)



malloc() よりも realloc()
char *p;
p = NULL;
p = (char*)realloc(p,サイズ);
if(pout != NULL){
....
}
ちゃんと NULL にしておくこと
こうすれば、ほとんど malloc() 関数



malloc() と calloc() の違い
  • malloc() は、バイト単位で指定するが、calloc() は、データ型(x バイト) * 個数という指定をする
  • malloc() は、確保したメモリを初期化しないが、calloc() はゼロクリア(初期化)する




確保したメモリサイズ
#include <malloc.h>

main(){
char *test;
unsigned int mySize;
test = (char *)realloc(test,6);

mySize = _msize(test);
printf("%u\n",mySize);
}
6 が返る。


デバグ・モードでのコンパイル/ビルドでは正常に動作するけど、リリース・モードではうまく動かない場合があるので注意。



ポインタのポインタ
win32 では、ポインタは 32bit(4byte)

p = (char **)calloc(5,sizeof(char *));

ってな感じ。

int main(int argc, char* argv[]){
char **pHako;
char **pp;
char *p;
char test[] = "c:\\winnt\\system32\\cmd.exe";

pHako = (char **)calloc(5,sizeof(char *));
pp = pHako;
p = test;
*pp = p;
pp++;

while(*p != '\0'){
if(*p == '\\'){
*p = '\0';
*pp = p+1;
pp++;
}
p++;
}
*(pp+1) = '\0';
pp = pHako;

printf("Hello World!\n");
while(*pp != '\0'){
printf("%s\n",*pp);
pp++;
}
printf("Hello World!\n");
return 0;
}




ポインタのポインタ 2




ポインタを参照渡しする
void myAlloc(char **p,int len){
*p = (char *)malloc(len);
}

main(){
char *p,*p1;
p = NULL;
myAlloc(&p,10);
p1 = p;
*p1 = 'a';
p1++;
*p1 = 'b';
p1++;
*p1 = 'c';
p1++;
*p1 = '\0';

printf("%s\n",p);
}


これ便利!!!!
ポインタ利用の真骨頂!!!って感じぃ〜(女子高生風で半疑問系!?)

関数に渡すのは、未確保の配列のポインタのアドレスだけ。
関数内部で、必要なメモリを確保して、返してくれる。
関数を呼び出す側で、メモリサイズがどうの....と苦労する必要がない。
(まるで、VB の文字列処理のような事ができる)
(当然、関数呼び出す側は free() を忘れてはいけない!!!!)

関数呼び出しも一回で済む!!!!

だけど、VC++6.0 SP6 の デバグモードでコンパイルすると、これをエラーだと判断して実行できない場合がある。
ウザすぎ〜


Win32API では、文字列のポインタを指定するだけ....サイズが足りなければ、エラーを返して、別の引数(DWORD のポインタが多い)に、必要なサイズを返す。
ってパターンが多い。
→ つまり、2回もアクセスしなきゃいけないって事だ。→ 面倒だと感じた事はないですか!!!
上記な方法で、関数を実装してくれれば、関数利用者には一回の呼び出しで済むってもんだ!!!


Win32API の場合で注意するべきは、サイズを取得するためにワザとエラーを発生させるための最初の呼び出しの時、文字列ポインタに NULL を入れてもいい場合と、駄目な場合(別のエラーとして返してきて、サイズが取得できない)がある。
つまり、文字列ポインタがヌルであれば、サイズの引数に必要なサイズを返しますよ。って場合と、

文字列ポインタがヌルだと、別のエラーを吐くので、文字列ポインタには適当なサイズで確保したメモリのポインタを当てておかないと、サイズの引数に必要なサイズを返しませんよ。って場合があるって事

前者/後者は、サイズ取得→データ取得 と二回 API を呼び出す面倒臭さがあり、 後者では加えて malloc → realloc と二回も alloc 系を呼び出すという面倒臭さがある。



\n と 0x0d 0x0a
Widnows の C 言語中の「\n」は「0x0a」ですよ。
システムが、実行時に「\n」->「0x0d 0x0a」にする。

このコードはおかしいよ
char str[] = "\n";
if((int)str[0] == 13){
}
こっちなら正解
char str[] = "\r\n";
if((int)str[0] == 13){
}
こっちなら正解
char str[] = "\n";
if((int)str[0] == 10){
}




スレッド
スレッドは、以下のような関数として作る。
スレッドとして呼ばれる関数
DWORD CALLBACK スレッド関数名(void *パラメタ);
パラメタはスレッドを作成する時に使う32bitの値

呼び出しは、
スレッドを呼び出す(作成する)関数
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThread
);
LPSECURITY_ATTRIBUTES lpThreadAttributes セキュリティ記述子
セキュリティに関する構造体のポインタ
通常は NULL
DWORD dwStackSize 初期スタックサイズ
スレッド実行環境のスタックのサイズ
通常は 0 を指定して「無指定」としている
LPTHREAD_START_ROUTINE lpStartAddress スレッドの開始アドレス
スレッドとして開始させる関数名
LPVOID lpParameter スレッドの引数
DWORD dwCreationFlags スレッド作成オプション
0 を指定してスレッドの作成後直ちに起動させるか、
「CREATE_SUSPENDED」を指定してサスペンド状態でスレッドを作成する
LPDWORD lpThread スレッドが作成された場合、この値(DWORD 型のポインタ)にスレッドのハンドルが戻る
スレッド作成が成功した場合は、作られたスレッドのハンドルが戻り値として返る。
失敗した場合は、NULL が返る。


#include <windows.h>
#include <stdio.h>
#include <conio.h>

DWORD CALLBACK thread1(void *param){
int i = 0;
while(TRUE){
Sleep(300);
printf("スレッド1: %d\n",i);
i++;
}
return 0;
}

DWORD CALLBACK thread2(void *param){
int i = 0;
while(TRUE){
Sleep(500);
printf("スレッド2: %d\n",i);
i++;
}
return 0;
}

void main(){
HANDLE myHandle1,myHandle2;
DWORD myDword1,myDword2;
myHandle1 = CreateThread(NULL,0,thread1,NULL,0,&myDword1);
myHandle2 = CreateThread(NULL,0,thread2,NULL,0,&myDword2);
getch();
CloseHandle(myHandle1);
CloseHandle(myHandle2);
}
main() が一番下でないと、コンパイル・エラーがでるかも。



スレッドの終了
スレッドは、スレッド関数内の「return」で自動的に終了する。

強制終了させる場合は、
BOOL TerminateThread(
HANDLE hThread,
DWORD dwExitCode,
);
HANDLE hThread 強制終了させたいスレッドのハンドル
DWORD dwExitCode 強制終了させたいスレッドに送るシグナル
通常は 0 を指定する
失敗すると 0 が戻る。



スレッド・クラス
参考書「極める VisualC++」(吉田弘一郎著 技術評論社)のスレッド・クラスの真似っす。
自分用で、自分が必要な機能だけをクラス化した。

CmyThread.cpp



コールバック関数と、クラス
クラスのメソッドをコールバック関数にはできない。

だが、static にしたメソッドはコールバック関数にできる。

ですが、クラスのメンバ変数にはアクセスできない。
こういう場合は、クラスのアドレス自体をコールバック関数に渡してやればよい。

コールバック関数の定義 myClass{
private:
static DWORD CALLBACK myThread(void*)
}
コールバック関数の中身 DWORD CALLBACK myClass::myThread(void* param){
myClass* pThis = (myClass*)param;
pThis->チョメ で、メンバ変数/関数にアクセス可能
return 0;
}
コールバック関数を呼び出す HANDLE myHANDLE;
DWORD myDWORD;
myHANDLE = CreateThread(NULL,0,this->myThread,this,0,&,myDWORD);



詳細は以下のリンク



winsock.h
この include 文は、ヘッダ・ファイル(*.h)で指定しないといけないようだ。
コンパイル・エラーをしている場合は、ヘッダ・ファイルに移してみよう。

これは、「windows.h より先にインクルード」と関係しているのかな?



メモリ解放
free() したからといって、NULL にはならない。
char *p;


free(p);
p = NULL;
としましょう。


応用
二重解放の防止
if(p != NULL){
free(p);
p = NULL;
}




COM ATL のアパートメントの概念
アパートメントとは、COM オブジェクトの保護単位のようなもの。(かなりいい加減です)

COM をコールする時に「マーシャリング」が必要ない領域を指す。
つまり、STA にしておければ、内部コードはスレッド対応にしなくてもよい。

STA(Single Thread Apartment)
アパートメント内には、一つのスレッドだけ
一つのプロセス中には、STA は複数
MTA(Multi Thread Apartment)
アパートメント内には、複数のスレッドだけ
一つのプロセス中には、MTA は一つ
昔は、「フリー・スレッド・アパートメント」


- プロセス アパートメント スレッド
STA プロセスA STA-A スレッドa
STA-B スレッドb
....
MTA プロセスA MTA-A スレッドa
スレッドb
....




COM ATL のイベント
かなり、うる覚え...

コネクタブル・オブジェクトという外向きなインターフェイスを使う。

VC++6.0 の ATL COM ウィザードの場合、作成時にチェックボックスをチェックするだけ。

そして、「ATL オブジェクト」を作成すれば、xxxxxEvents という外向きなインターフェイスも一緒に生成される。

その外向きなインターフェイスにメソッドを追加する(このメソッドがイベントになる)

ひとまず、ビルド

そして、通常の(内向き)なクラスから、「接続ポイントの作成」をする。

IProvideClassInfo2Impl インターフェイスを継承させる。

最後にビルド

という感じ...



ATL COM イベントとスレッド
上記のように、ATL COM イベントは、インターフェイスと強い結びつきがある。

インターフェイスは、アパートと呼ばれる制限(スレッド/プロセス)がある。

ということで、外部のスレッドから、そのインターフェイスを直接呼び出す事は COM のルールとして禁止されている。

基本的には、自分で生成した別スレッドから、イベント処理はしない方がいい。
なぜなら、それを実現するためには「手動マーシャリング」という非常に面倒な手続きを実装しなければならない。

まぁ、私のようなヘタレなプログラマが手を出してはいけない領域のようだ。
インターネット上にも、COM の書籍にもあまり詳しく書かれていないし....



ATL COM イベントとスレッド2
ウィドウハンドル
手動マーシャリングなど、非常に煩雑...というか、未だに私は理解できない。

大体、呼び出し元(VisualBASIC や VBScript)はマルチスレッド対応ではない。

という事で、逃げのひとつの考え方として、COM のメインスレッドにウィンドウ・ハンドルを持たせて、別スレッドからそのウィンドウ・ハンドル(つまり COM 内部のメインスレッド)へメッセージを送る事でイベント処理をさせる。

詳細は MS 社の KB 196026「PRB: Firing Event in Second Thread Causes IPF or GPF」
というより、そっちのページを見て。(サンプルコードも載っているし...)



時間経過


zlib
ここが参考になる。
(というか、参考サイトの方が詳細に書かれているよ)

本家はここ

参考になるサイトによると、 zlib.dll,zlib.lib,zlib.h,zconf.h が必要。

  1. zlib.h をインクルード
    #include <zlib.h>


  2. z_stream 構造体を宣言(これが zlib をいじる)
    z_stream myZ;


  3. 初期化
    myZ.zalloc = Z_NULL;
    myZ.zfree = Z_NULL;
    myZ.opaque = Z_NULL;


  4. 初期化ルーチン呼び出し
    int deflateInit(&myZ,Z_DEFAULT_COMPRESSION)
    int inflateInit(&myZ)
    戻り値は、
    Z_OK 成功
    それ以外 失敗


  5. 圧縮/解凍処理
    myZ.next_in = 入力データを示すメモリの位置(ポインタ)
    myZ.avail_in = 入力データのサイズ
    myZ.next_out = 出力データを書き出すメモリの位置(ポインタ)
    myZ.avail_out = 出力データを書き出すメモリの空き容量
    にセットして
    int deflate(&myZ,Z_NO_FLUSH)
    これは、next_in からデータを取り出して、圧縮処理をしつつ next_out に書き出す。
    next_in と next_out は、データ分だけ進行する。
    avail_in が 0 になれば次のデータをセットして、next_in,avail_in を更新
    avail_out が 0 になれば、出力データを別の所にうつすなどして、next_out,avail_out を更新
    戻り値は、
    Z_OK 成功
    それ以外 失敗


  6. 最後の入力データになったら、
    deflate(&myZ,Z_FINISH)
    戻り値は、
    Z_OK まだ残りがあるので、出力データを別の所にに移動などして、再度 deflate(&myZ,Z_FINISH)
    Z_STREAM_END 終了
    それ以外 失敗


  7. 後始末として、deflateEnd() を呼び出してメモリを解放します
    deflateEnd(&myZ)
    戻り値は、
    Z_OK 成功
    それ以外 失敗




解凍は、deflate が inflate になるだけで、基本的には変わらない。



zlib の COM ラッパー
ここ



Release バージョンでのビルド・エラーについて(ATL etc)
この方のページ を見よう。

頻繁に
「LIBCMT.lib(crt0.obj) : error LNK2001: 外部シンボル "_main" は未解決です。ReleaseMinDependency/XXXXX.dll : fatal error LNK1120: 外部参照 1 が未解決です。link.exe の実行エラー」
だったんですけど

C の標準ランタイムが Release モードでは、リンク対象から外されるのが原因らしい。

「プロジェクト」→「設定」→「C/C++」→「プリプロセッサの定義」で「_ATL_MIN_CRT」を削除すればいいらしい。



オブジェクト・コンストラクタ文字列
概要(はじめに)

  1. 機密情報を ASP などのスクリプトに書かない方がよい
  2. ということで、より安全と思われるレジストリに書くのはどうだろう
    これは、WSCript オブジェクトを CreateObject すればよい(「[2-2.] スクリプトに埋め込まれたDBパスワード」)
  3. COM(ActiveX DLL) というバイナリに埋め込む
    スクリプトに埋め込むよりは、マシだがバイナリエディタによって暴露される危険性は残る


ということで、COM+ がロードされたときに初期化される「オブジェクト・コンストラクタ文字列」に記録するのはどうだろうか。
管理者が設定するので、バイナリに機密情報がハードコーディングされない。
スクリプトにも機密情報が保存されない。
(Microsoft 「プログラマのためのセキュリティ対策テクニック」 P375)


オブジェクト・コンストラクタ文字列とは、

  • COM+ によって実装された (Windows2000 以降)
  • 必要なヘッダファイルは、comsvcs.h
    ライブラリは、comsvcs.dll
  • Comsvcs.dll のアクセス違反
  • IObjectConstruct インターフェイス (IDL) により実装されている
  • IObjectConstruct インターフェイスを実装している COM+ ライブラリがロードされた時に、Construct メソッドが呼び出される
  • Construct メソッドを COM+ ライブラリ側に実装しておく
  • Construct メソッドによって、管理者(該当の COM+ ライブラリの開発者ではなく COM+ ライブラリを使用する際のシステム管理者)によって設定された オブジェクト・コンストラクタ文字列にアクセスして COM+ モジュール内に保持しておけばよい
  • COM+ の管理をする MMC のインターフェイス
  • COM+ ライブラリのコンストラクタ文字列を管理者が設定することができる
  • MS-VC++ 6.0 SP6 の「ATL COM Wizard」では、IObjectConstruct インターフェイスを実装できない
    自前で IDL を記述して実装しなければならない
  • MS-VB6 SP6 では、簡単に実装可能である
    [HOWTO] Visual Basic コンポーネントの COM オブジェクト・コンストラクタ文字列にアクセスする方法



オブジェクト・コンストラクタ文字列とは、

  1. COM+ ライブラリを作成する
  2. コンパネ→管理ツールより、COM+ 管理用 MMC を起動する
  3. 「COM+ アプリケーション」の項目のところで、右クリックして「新規作成」→「アプリケーション」する
  4. 「空のアプリケーションを作成する」を選択する
  5. COM+ アプリケーション名を入力する
  6. 「サーバーアプリケーション」を選択する
  7. アカウントを設定する
  8. 作った COM+ アプリケーションの「コンポーネント」のところで、「新規作成」→「コンポーネント」で、「新しいコンポーネントをインストールする」COM+ ライブラリ(DLL)を登録する
  9. 該当するコンポーネントの「プロパティ」の「アクティブ化」のところで、オブジェクト・コンストラクタ文字列を設定する



VisualBASIC6.0 SP6 でのオブジェクト・コンストラクタ文字列

  1. VB6 を起動し、「ActiveX DLL」のプロジェクトを作成する
  2. VB6 のメニュー「プロジェクト」→「参照設定」にて、「COM+ Services Type Library」を選択して、参照できるようにする
  3. 参照できなかった場合は、「regsvr32.exe %WinDir%\system32\comsvcs.dll」で、comsvcs.dll という COM+ DLL をレジストリに登録する
  4. クラスファイル内に以下を記述する
    Implements IObjectConstruct
    Dim myPrivateStr As String
    Private Sub IObjectConstruct_Construct(ByVal pCtorObj As Object)
       Dim tempString As IObjectConstructString
       Set tempString = pCtorObj
       myPrivateStr = tempString.ConstructString
    End Sub
  5. 以上で、VB6 の String 型の「myPrivateStr」にオブジェクト・コンストラクト文字列が設定される


うまくいかない場合もあるよ
(うまくいかない場合は、CreateObject に渡す文字列 "プロジェクト名.クラス名" とか、全部最初から作り直すと良いでしょう)



VisualC++6.0 SP6 でのオブジェクト・コンストラクタ文字列

  1. VC++6.0 SP6 の標準では、comsvcs.h がないので、ObjectConstruct は使えないので、最新の PlatformSDK を VC++6.0SP6 と一緒にインストールする
  2. VC++6.0 SP6 を起動して、「ATL COM Wizard」で、通常通り COM を作成する
  3. COM のコードに以下を追加する
    ATL オブジェクトのヘッダファイル
    #include <comsvcs.h>
    ・・・
    class ATL_NO_VTABLE Cクラス名 :
      public IObjectConstruct, /* これを追加して、IObjectConstruct インターフェイスを継承する */
    ・・・
    BEGIN_COM_MAP(Cクラス名)
      COM_INTERFACE_ENTRY(IObjectConstruct) /* これを追加する */
    END_COM_MAP()
    ・・・
    public:
      STDMETHODIMP Construct(IDispatch *pUnk); /* public メソッドしてコレを追加する */
      BSTR m_bstrObjectConstructString;/* BSTR 型を宣言しておく */
    ----
    ATL オブジェクトの C のソースファイル
    STDMETHODIMP Cクラス名::Construct(IDispatch *pUnk){
      if( !pUnk ){
        return E_UNEXPECTED;
      }
      CComQIPtr <IObjectConstructString, &IID_IObjectConstructString> spObjectConstructString = pUnk;
      return spObjectConstructString->get_ConstructString(&m_bstrObjectConstructString);
    }
    BSTR 型の m_bstrObjectConstructString に「オブジェクト・コンストラクタ文字列」がコピーされる
    BSTR 型の m_bstrObjectConstructString を返す COM のメソッドを用意すればいいでしょう(この部分のコードは、COM 一般の話なので省略)。
  4. コンパイル(ビルト)する際には、VC++6.0 SP6 標準のインクルードファイル/ライブラリファイルのパスを優先しておくこと
  5. COM+ の管理画面で、「オブジェクト・コンストラクタ文字列」を設定する
  6. 後は、COM を実行すればよい。



オブジェクト・コンストラクタ文字列の保存先

どこでしょう?

要調査



Java の COM ラッパー
JAVA VM と COM のブリッジを作っておられる方を発見しましたので、リンク
http://sourceforge.net/projects/jcom




戻る

[PR]アナタのウラ県民性をチェック:こっそり一人で?ワイワイ皆で?診断しょ