興味本位でWindowsでOpenLDAPのデータでも操作してみようかな~とプログラミングを開始してみた。基のソースはMicrosoft MSDNのライブラリからパクってきたのだが。(興味がある方はリンクにアクセスしてね)
さて、どうやってローカルに取り込もうか思いあぐねた結果、構造体+std::vectorにすることに。理由は、vectorを利用することで可変データに対応するため。(無駄なメモリ使いたくないし、解放操作も面倒だしw)
構造体は下記のような入れ子。(データ構造は本文最後の方で図を掲載してあります)
struct LDAP_ITEM
{
string attr; // 属性名
vector<string> item; // 属性配下のアイテム(vectorで可変に)
};
struct LDAP_DATA
{
int entrynum; // エントリ番号
vector<LDAP_ITEM> data; // エントリデータ内容(上記入れ子構造体をvectorで可変に)
};
で、次にどうやってこの構造体にデータをぶち込んでいるかは以下のソース。Microsoftのソースコードを基にしているので何を追加・変更しているかは大体察しが付くと思いますが、簡単にコメントも入れてみました。
ソースが長いので、「ソースを表示」で伸張してくださいな。
//----------------------------------------------------------
// Get the number of entries returned.
//----------------------------------------------------------
ULONG numberOfEntries;
numberOfEntries = ldap_count_entries(pLdapConnection, pSearchResult);
if(numberOfEntries == NULL)
{
printf("ldap_count_entries failed with 0x%0lx \n",errorCode);
ldap_unbind_s(pLdapConnection);
if(pSearchResult != NULL)
ldap_msgfree(pSearchResult);
return -1;
}
else
printf("ldap_count_entries succeeded \n");
printf("The number of entries is: %d \n", numberOfEntries);
//----------------------------------------------------------
// Loop through the search entries, get, and output the
// requested list of attributes and values.
//----------------------------------------------------------
LDAPMessage* pEntry = NULL;
PCHAR pEntryDN = NULL;
ULONG iCnt = 0;
char* sMsg;
BerElement* pBer = NULL;
PCHAR pAttribute = NULL;
PCHAR* ppValue = NULL;
ULONG iValue = 0;
// コメント:入れ子構造体データの一時的な保管場所。
// これをあとで下の元データの一時的な構造体にpush_back。
LDAP_ITEM tmp_item;
// コメント:元データの一時的な保管場所。あとで実データにpash_back。
LDAP_DATA tmp_ldap;
for( iCnt=0; iCnt < numberOfEntries; iCnt++ )
{
// Get the first/next entry.
if( !iCnt )
pEntry = ldap_first_entry(pLdapConnection, pSearchResult);
else
pEntry = ldap_next_entry(pLdapConnection, pEntry);
// Output a status message.
sMsg = (!iCnt ? "ldap_first_entry" : "ldap_next_entry");
if( pEntry == NULL )
{
printf("%s failed with 0x%0lx \n", sMsg, LdapGetLastError());
ldap_unbind_s(pLdapConnection);
ldap_msgfree(pSearchResult);
return -1;
}
tmp_ldap.entrynum = iCnt; // コメント:エントリ番号を保存。
// Get the first attribute name.
pAttribute = ldap_first_attribute(pLdapConnection, pEntry, &pBer);
// Output the attribute names for the current object
// and output values.
while(pAttribute != NULL)
{
// コメント:一時的なアイテム保管場所。
// アイテムがいくつあるか分からないのでvector使用。
vector<string> tmpItem;
// コメント:属性を保存。
tmp_item.attr = pAttribute;
// Get the string values.
ppValue = ldap_get_values(pLdapConnection, pEntry, pAttribute);
// Print status if no values are returned (NULL ptr)
if(ppValue == NULL)
{
printf(": [NO ATTRIBUTE VALUE RETURNED]");
}
// Output the attribute values
else
{
iValue = ldap_count_values(ppValue);
if(!iValue)
{
printf(": [BAD VALUE LIST]");
}
else
{
tmpItem.push_back(*ppValue); // コメント:最初のアイテムを保存。
// Output more values if available
ULONG z;
for(z=1; z<iValue; z++)
{
// コメント:残りのアイテムがあれば保存。
tmpItem.push_back(ppValue[z]);
}
// コメント:push_backされてきたアイテムを入れ子構造体の
// 一時保管場所構造体データに。
tmp_item.item = tmpItem;
// コメント:一時的なアイテム保管場所のvector要素を消去。
// (解放ではないですよ。かつ<vector>.clear()でもない)
tmpItem.erase(tmpItem.begin(), tmpItem.end());
}
}
// コメント:直前で入れ子の一時データができているので、
// 一時的な本営データ保管場所にpush_back。
// なぜここで本営データに入れないかは本文で。
tmp_ldap.data.push_back(tmp_item);
// Free memory.
if(ppValue != NULL)
ldap_value_free(ppValue);
ppValue = NULL;
ldap_memfree(pAttribute);
// Get next attribute name.
pAttribute = ldap_next_attribute(pLdapConnection, pEntry, pBer);
}
// コメント:本営データにpush_back。
ldap_data->push_back(tmp_ldap);
// コメント:本営データに入れたので一時データの要素を消去し、for続きへ。
tmp_ldap.data.erase(tmp_ldap.data.begin(), tmp_ldap.data.end());
if( pBer != NULL )
ber_free(pBer,0);
pBer = NULL;
}
//----------------------------------------------------------
// Normal cleanup and exit.
//----------------------------------------------------------
ldap_unbind(pLdapConnection);
ldap_msgfree(pSearchResult);
ldap_value_free(ppValue);
return 0;
上のソースで、アイテムが全て揃ったところで、なぜ本営データにぶち込んでいないかと言えば、まだ属性データがあった場合、属性ごとに本営データができあがってしまい、意図する動作にならないためです。つまり、「1つのエントリに対し、複数の属性、1つの属性に対し複数のアイテム」といった可変データ(1xNxN)構造と微妙に異なるからなんです。
int GetLDAPData(const char* pHost, std::vector<LDAP_DATA> *ldap_data)
では、この関数の呼び元と出力事例のソースは以下の通りでやんす。
ソースが長いので、「ソースを表示」で伸張してくださいな。
// 本営データ作成(ここではメモリ確保もせずほったらかし)
vector<LDAP_DATA> ldap;
// 本営データのアドレスを引数で渡してあげます
GetLDAPData(ldap_host.c_str(), &ldap);
// データがあったら表示を開始します
if(!ldap.empty())
{
// 本営データに対するイテレータを作成しポインターもどきにします
for(vector<LDAP_DATA>::iterator iarray = ldap.begin(); iarray<ldap.end() ; iarray++)
{
cout << "ENTRY NUMBER: " << iarray->entrynum << endl;
// 入れ子構造体データに対するイテレータを作成しポインターもどきにします
for(vector<LDAP_ITEM>::iterator iattr = iarray->data.begin(); iattr<iarray->data.end() ; iattr++)
{
cout << " ATTR: " << iattr->attr << ":";
// アイテムデータに対しイテレータを作成しポインターもどきにします
for(vector<string>::iterator iitem = iattr->item.begin(); iitem<iattr->item.end() ; iitem++)
{
cout << " " << *iitem;
if(iitem<iattr->item.end()-1)
cout << ",";
}
cout << endl;
}
}
}
データ表示部分で、本営、入れ子構造体、アイテムに対しそれぞれイテレータを作成していますが、こうしておけば独立した形(表現は厳密ではないけれど)で各データにアクセス出来ます。配列カウンターで悩むことがないので便利なんです。まぁ、配列ポインタと変わりはないんですけど、プログラムを書く好みでdata[i][j][k]とか好きな方ではないので。OpenLDAPのデータ構造はこんな感じ。
足早にざっと書いては見たけれど、OpenLDAPのアクセスなんて誰もやろうなんて考えないよね。と言うことで備忘録ということで。
