Windows7でユーザー名の取得

[戻る]
dsk 2010/02/03(水) 11:21:55
Windows7以前はXPを使用していて、GetUserName()でユーザー名が取得できていました。
今回はWindows7で同APIを使用したところユーザー名が取得できず、エラーとなります。
GetLastError()は1114を返します。
> 1114:ダイナミック リンク ライブラリ (DLL) 初期化ルーチンの実行に失敗しまし
た。 

GetUserName()に代わりGetUserNameEx()を使用してみましたが、これまた同様の
エラーが返ってきてしまいます。

Windows7(vista以降?)は何か制約、もしくは回避策が必要なのでしょうか?

よろしくお願いいたします。

環境:Win7 32bit,VC2005,WIN32SDK

aetos 2010/02/03(水) 11:34:28
もうすこし状況を詳しく書いてみてはいかがでしょう。
たとえば、こんなケースと同じだったりはしませんか?

http://social.msdn.microsoft.com/Forums/ja-JP/vcgeneralja/thread/68161dc5-4219-
484b-9005-56a8022c9629

dsk 2010/02/03(水) 12:11:36
お返事ありがとうございます。

> http://social.msdn.microsoft.com/Forums/ja-JP/vcgeneralja/thread/68161dc5-
4219-484b-9005-56a8022c9629

これは色々調べてる間に見つけていたのですが、ヒントになりそうな気もしつつ
リンク中1〜3の条件には合致していないので違うかなと判断しています。

というのも、当方プログラムはsetup.exe等の名前ではなく、DLLでもなくEXEであるた
め。
あと、決定的に違うのはアクセス違反が起きるわけではなく、あくまでもGetUserName()
が失敗するのみなのです。

なお、UACレベルがどこにあろうと現象に違いは出ないようです。

他に必要な情報がありましたらご指摘ください。

よろしくお願いいたします。

PATIO 2010/02/03(水) 13:29:16
Web上で検索すると以下のようなケースが結構引っかかるんですが、
心当たりは有りませんか?

http://pasokoma.jp/bbs6/lg112888

dsk 2010/02/03(水) 16:25:50
ウィルススキャンを行ってみましたが、特に異常は見つかりませんでした。

あれから試行錯誤した結果、少し分かってきました。


現状まではGetUserName()よりGetUserNameEx()を使うように変更していた
(ユーザー名の変更に対応できるようなので)のですが、GetUserNameEx()コール部
を一切削除し、GetUserName()のみのコールにすると問題なくユーザー名が取得
できるようになりました。

GetUserNameEx()をコールするようにしただけでGetUserName()までエラーになる
のが意味不明ですが・・・


元々GetUserName()のみでもエラーになっていたと最初に書いていましたが、これは
aetosさんのご指摘通りの件に引っかかってたようです。

テストのため、GetUserName()のみを行う単体アプリを作った際、setup等の語句を
含まないファイル名にしつつ、単体アプリではGetUserNameEx()を使っていたため
結局エラーとなってしまい、リンク先の件とは関係ないと判断してしまいました。

どうも申し訳ありませんでした。


そういうわけで、setup等を含むアプリでは先のリンクや、以下のリンクなどを
見直してmanifestを作成・リンクすることでGetUserName()の実行は成功するように
なりました。

http://blogs.wankuma.com/yo/archive/2007/10/04/99879.aspx
http://blogs.wankuma.com/jitta/archive/2006/09/26/39667.aspx


さて、とりあえずは問題解決と行きたいところなのですが、なぜGetUserNameEx()は
相変わらずエラーなのでしょうか。

GetUserNameEx()を使うため、sspi.h、secext.hのインクルード、SECURITY_WIN32の
宣言、secur32.libのリンクを別途行っていますので、これらの宣言が無くても動作
するGetUserName()とは名前以上に何かが異なっているのは確かなのでしょうが・・・


引き続き情報がありましたら、よろしくお願いいたします。

aetos 2010/02/03(水) 17:27:54
Windows Server 2008 R2 x64 と、ちょっと違う環境ではありますが、再現できませんで
した。
問題を再現できる最小限のコードを載せることはできるでしょうか?

dsk 2010/02/04(木) 22:15:42
遅くなってすみません。

テスト用単体アプリを何度か作り直して傾向が見えてきました。

まず、単純にWinMain()にてGetUserName(),GetUseNameEx()のみを実行して即終了する
場合は問題が出ませんでした。
しかし、これに一件無関係なDLLを追加すると、ユーザー名が取得できない不具合が
再現できました。

---exe:GetUserName.c---
#define SECURITY_WIN32
#include <windows.h>
#include <sspi.h>
#include <secext.h>
#include <stdio.h>

#pragma comment(lib, "secur32.lib")

__declspec(dllexport) int dummy(void);

int PASCAL
WinMain(
    HINSTANCE    hInstance,
    HINSTANCE    hPrevInstance,
    LPSTR        lpszCmdOption,
    int            iCmdShow
)
{
    char    szUserName[256];
    DWORD    dwUserNameSize;
    int        iRet;
    DWORD    dwErr;
    char    szMsg[256];

    szUserName[0] = '\0';
    dwUserNameSize = sizeof(szUserName);
    iRet = GetUserName(szUserName, &dwUserNameSize);
    dwErr = GetLastError();
    sprintf_s(szMsg, sizeof(szMsg), "name:[%s] ret:%d ec:%ld", szUserName, 
iRet, dwErr);
    MessageBox(NULL, szMsg, "GetUserName()", MB_OK);

    szUserName[0] = '\0';
    dwUserNameSize = sizeof(szUserName);
    iRet = GetUserNameEx(NameSamCompatible, szUserName, &dwUserNameSize);
    dwErr = GetLastError();
    sprintf_s(szMsg, sizeof(szMsg), "name:[%s] ret:%d ec:%ld", szUserName, 
iRet, dwErr);
    MessageBox(NULL, szMsg, "GetUserNameEx()", MB_OK);

    dummy();
    return 0;
}

---dll:CreateWindow.c---
#include <windows.h>

int        iProcCount;
HWND    hDllWnd;

static LRESULT CALLBACK
WndProc(
    HWND    hWnd,
    UINT    uMsg,
    WPARAM    wParam,
    LPARAM    lParam
)
{
    LRESULT    lRet = 0;

    switch (uMsg) {
        default:
            lRet = DefWindowProc(hWnd, uMsg, wParam, lParam);
            break;
    }
    return lRet;
}

static void
ProcessAttachProc(
    HINSTANCE    hInstance
)
{
    if (hDllWnd == NULL) {
        WNDCLASS    wndclass;

        wndclass.style            = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc    = (WNDPROC)WndProc;
        wndclass.cbClsExtra        = 0;
        wndclass.cbWndExtra        = 0;
        wndclass.hInstance        = hInstance;
        wndclass.hIcon            = LoadIcon(NULL, IDI_APPLICATION);
        wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground    = GetStockObject(WHITE_BRUSH);
        wndclass.lpszMenuName    = NULL;
        wndclass.lpszClassName    = "dll window";
        RegisterClass(&wndclass);
#if 1
        hDllWnd = CreateWindow("dll window", "dll window", 0, 0, 0, 0, 0, 
NULL, NULL, hInstance, NULL);
#endif
    }
}

static void
ProcessDetachProc(void)
{
    DestroyWindow(hDllWnd);
}

// libを作成するためのダミー
__declspec(dllexport) int
dummy(void)
{
    return 1;
}

BOOL APIENTRY
DllMain(
    HINSTANCE    hDllInst,
    DWORD    fdwReason,
    LPVOID    lpvReserved
)
{
    BOOL    bRet = TRUE;

    UNREFERENCED_PARAMETER(lpvReserved);

    switch (fdwReason) {
        case DLL_PROCESS_ATTACH:
            if (iProcCount++ == 0) {
                ProcessAttachProc(hDllInst);
            }
            break;

        case DLL_PROCESS_DETACH:
            if (--iProcCount == 0) {
                ProcessDetachProc();
            }
            break;
    }
    return bRet;
}

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

DLLのProcessAttachProc()にてCreateWindow()を行っていますが、これをコメント
アウトするだけで正常にユーザー名が取得できます。


補足というか余談というか・・・CreateWindow()を行っている理由ですが、元々は
このタイミングでLoadLibrary()をしたかったのですが、これはやってはいけない
とのこと(実際に正常に動作しなかった)なので、代替案としてCreateWindow()を
行ってからユーザーメッセージをポストするようにしたのものです。


そもそもDLLのアタッチプロセスでウィンドウを作成していることが間違ってると
言われればそれまでですが・・・


引き続き、よろしくお願いいたします。

ITO 2010/02/04(木) 22:43:38
しばらくROMしてたのですが、一言。
 開発環境がVC2005のWIN32SDKで、OSWindows7上で動くアプリを作るのに無理がある
のではないでしょうか。

 Windows7だから分かりませんが、FrameWorkを使ううまくいくのではないでしょうか。

PATIO 2010/02/05(金) 13:21:48
その後、DllMainと中に記述できる処理に関して色々調べていたのですが、
MSDNにもDllMainに記述しても良い処理としてはいけない処理に関する記述があります。

これを見る限りでは、DllMain無いで記述して良いのは
ごく簡単な初期化程度のコードまでのようです。

以下に一部抜粋します。

> 以下の操作は、DllMain 関数内部で実行しても安全であると明確に認識されています。
> 
>     * 静的変数とグローバル変数の初期化。
>     * Kernel32.dll 内の関数の呼び出し。 
>       DllMain を呼び出すときには、Kernel32.dll が読み込まれている必要がある
>    ので、これは常に安全です。
>     * クリティカル セクションやミューテックスなどの同期オブジェクトの作成。
>       詳細については、 Synchronization Objects (英語) を参照してください。
>     * スレッド ローカル ストレージ (TLS) へのアクセス。
> 
> 以下の操作は、大部分の状況で、DllMain 関数内部で実行すると安全ではないと
> 明確に認識されています。
> 
>     * 直接的または間接的な、LoadLibrary 関数、LoadLibraryEx 関数、または 
>    FreeLibrary 関数の呼び出し。
>     * レジストリ関数の呼び出し。
>     * Kernel32.dll 内に存在しないインポートされた関数の呼び出し。
>     * 他のスレッドまたはプロセスとの通信。
> 
> DllMain 関数の規則は、まだ、システムによっては設定されません。代わりに、
> OS がデッドロックや依存関係のループの検出を試み、規則に違反している場合や
> 何らかの問題が生じそうな場合にそのプロセスを終了します。
> 通常、OS はデッドロックを検出しないので、プロセスはハングアップするでしょう。

今回の場合、CreateWindowはKernel32.dll 内に存在しない関数なので
DllMain中で実行してはいけない対象になると思います。
むしろ、今まで動いていたのが運が良かったのではないでしょうか。
Microsoftがはっきりと安全では無いと書いていますので、
例え動いていたとしても保証された動作では無いと思います。

dsk 2010/02/13(土) 06:05:30
[[解決]]
長らくお待たせしました。

PATIOさんのご指摘通り、DllMainからCreateWindow()を呼び出さない仕組みに
書き換え、既存プロジェクトでGetUserNameEx()が動作することが確認できました。

MSDNのDllMainにある「警告」の項目にて、LoadLibrary()を使用してはいけないなどの
情報を得ていたつもりでしたが、LoadLibrary()を回避するためにCreateWindow()を
実行するなんて問題回避になっていないことをしてしまっていたとは反省です。


しばらく既存プロジェクトの見直しに時間を費やしてしまいましたが、問題が解決
できてほっとしております。

どうもありがとうございました。

古いスレッドには追加発言できません。