14

GetModuleFileName()バッファとバッファのサイズを入力として受け取ります。ただし、その戻り値は、コピーされた文字数と、サイズが十分でない場合 ( ERROR_INSUFFICIENT_BUFFER) しかわかりません。

のファイル名全体を保持するために実際に必要なバッファー サイズを決定するにはどうすればよいですGetModuleFileName()か?

ほとんどの人が使用MAX_PATHしていますが、パスがそれを超える可能性があることを覚えています(デフォルトの定義では260)...

(バッファのサイズとしてゼロを使用するトリックは、この API では機能しません - 以前に試しました)

4

8 に答える 8

10

MAX_PATH から開始し、連続する各サイズを前のサイズよりも 1.5​​ 倍 (または反復回数が少ない場合は 2 倍) 大きくするなど、バッファを拡張するための合理的な戦略を実装します。関数が成功するまで繰り返します。

于 2009-04-30T09:48:55.587 に答える
2

API は悪い設計の証拠ですが、解決策は実際には非常に単純です。シンプルですが、悲しいことに、このようにしなければなりません。複数のメモリ割り当てが必要になる可能性があるため、パフォーマンスがやや低下するからです。ソリューションのキーポイントを次に示します。

  • 異なる Windows バージョン (XP など) では異なるセマンティクスを持つ可能性があるため、異なる Windows バージョン間の戻り値に実際に依存することはできません。

  • 指定されたバッファが小さすぎて文字列を保持できない場合、戻り値は 0 ターミネータを含む文字数になります。

  • 提供されたバッファが文字列を保持するのに十分な大きさである場合、戻り値は 0 ターミネータを除いた文字数です。

これは、返された値がバッファ サイズと正確に等しい場合でも、成功したかどうかはまだわからないことを意味します。もっとデータがあるかもしれません。か否か。最終的には、バッファー長が実際に必要な長さよりも大きい場合にのみ、成功を確信できます。悲しいことに...

したがって、解決策は小さなバッファから始めることです。次に、正確なバッファー長 (TCHAR 単位) を渡して GetModuleFileName を呼び出し、返された結果をそれと比較します。返された結果がバッファ長より小さければ成功です。返された結果がバッファ長以上の場合は、より大きなバッファで再試行する必要があります。すすぎ、完了するまで繰り返します。完了したら、バッファの文字列コピー (strdup/wcsdup/tcsdup) を作成し、クリーンアップして、文字列コピーを返します。この文字列は、一時バッファからのオーバーヘッドではなく、適切な割り当てサイズになります。呼び出し元は、返された文字列を解放する責任があることに注意してください (strdup/wcsdup/tcsdup mallocs メモリ)。

実装と使用コードの例については、以下を参照してください。私はこのコードを 10 年以上使用しており、非常に長いパスが多数存在する可能性があるエンタープライズ ドキュメント管理ソフトウェアも含まれています。もちろん、コードはさまざまな方法で最適化できます。たとえば、最初に返された文字列をローカル バッファ (TCHAR buf[256]) にロードします。そのバッファーが小さすぎる場合は、動的割り当てループを開始できます。他の最適化も可能ですが、ここでは説明しません。

実装と使用例:

/* Ensure Win32 API Unicode setting is in sync with CRT Unicode setting */
#if defined(_UNICODE) && !defined(UNICODE)
#   define UNICODE
#elif defined(UNICODE) && !defined(_UNICODE)
#   define _UNICODE
#endif

#include <stdio.h> /* not needed for our function, just for printf */
#include <tchar.h>
#include <windows.h>

LPCTSTR GetMainModulePath(void)
{
    TCHAR* buf    = NULL;
    DWORD  bufLen = 256;
    DWORD  retLen;

    while (32768 >= bufLen)
    {
        if (!(buf = (TCHAR*)malloc(sizeof(TCHAR) * (size_t)bufLen))
        {
            /* Insufficient memory */
            return NULL;
        }

        if (!(retLen = GetModuleFileName(NULL, buf, bufLen)))
        {
            /* GetModuleFileName failed */
            free(buf);
            return NULL;
        }
        else if (bufLen > retLen)
        {
            /* Success */
            LPCTSTR result = _tcsdup(buf); /* Caller should free returned pointer */
            free(buf);
            return result;
        }

        free(buf);
        bufLen <<= 1;
    }

    /* Path too long */
    return NULL;
}

int main(int argc, char* argv[])
{
    LPCTSTR path;

    if (!(path = GetMainModulePath()))
    {
        /* Insufficient memory or path too long */
        return 0;
    }

    _tprintf("%s\n", path);

    free(path); /* GetMainModulePath malloced memory using _tcsdup */ 

    return 0;
}

とはいえ、GetModuleFileName(Ex) に関するその他のさまざまな注意事項を十分に認識しておく必要があることを指摘したいと思います。32/64 ビット/WOW64 の間でさまざまな問題があります。また、出力は必ずしも完全な長いパスではありませんが、短いファイル名であるか、パスのエイリアシングの影響を受ける可能性があります。そのような関数を使用する場合、呼び出し元に使用可能で信頼性の高い完全な長いパスを提供することが目標であると期待しています。したがって、使用可能で信頼性の高い完全な長い絶対パスを確実に返すことをお勧めしますさまざまな Windows バージョンとアーキテクチャ間で移植可能です (これも 32/64 ビット/WOW64)。それを効率的に行う方法は、ここでは範囲を超えています。

これは現存する中で最悪の Win32 API の 1 つですが、コーディングを楽しんでいただけることを願っています。

于 2015-01-20T22:05:21.460 に答える
1

使用する

extern char* _pgmptr

動作する可能性があります。

GetModuleFileName のドキュメントから:

グローバル変数 _pgmptr は、実行可能ファイルのフル パスに自動的に初期化され、実行可能ファイルのフル パス名を取得するために使用できます。

しかし、_pgmptr について読んだ場合:

プログラムがコマンド ラインから実行されていない場合、_pgmptr は、プログラム名 (ファイル名拡張子を除いたファイルのベース名) またはファイル名、相対パス、または完全パスに初期化される場合があります。

_pgmptr の初期化方法を知っている人はいますか? SO がフォローアップの質問をサポートしていた場合、この質問をフォローアップとして投稿します。

于 2013-07-27T14:59:17.870 に答える