5

私は、いくつかのC ++コードをフェッチし、ウィザードを実行した後にコンパイルすることになっている小さなC#GUIツールを開発しています。有名なを実行した後にコマンドプロンプトから実行すると、これはすべてうまく機能しますvcvarsall.bat。ここで、ユーザーが最初にコマンドプロンプトに移動するのではなく、プログラム呼び出しvcvarsに続いてnmake必要なその他のツールを使用するようにします。そのためには、によって設定された環境変数vcvarsを明らかに保持する必要があります。

どうやってやるの?

私がまだ見つけた最善の解決策は、他のツールを呼び出す一時的なcmd/batスクリプトを作成することでしたが、もっと良い方法があるのではないかと思います。


更新:その間、バッチファイルとcmdを試しました。バッチファイルを使用する場合、vcvarsは完全なバッチ実行を終了するため、2番目のコマンド(つまりnmake)は実行されません。私の現在の回避策は次のようなものです(短縮):

string command = "nmake";
string args = "";
string vcvars = "...vcvarsall.bat";
ProcessStartInfo info = new ProcessStartInfo();
info.WorkingDirectory = workingdir;
info.FileName = "cmd";
info.Arguments = "/c \"" + vcvars + " x86 && " + command + " " + args + "\"";
info.CreateNoWindow = true;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
Process p = Process.Start(info);

これは機能しますが、cmd呼び出しからの出力はキャプチャされません。まだもっと良いものを探しています

4

3 に答える 3

3

私はいくつかの異なる提案があります

  1. NMakeの代わりにMSBuildを使用して調査することをお勧めします

    より複雑ですが、.Netから直接制御でき、VS 2010以降のすべてのプロジェクトおよびC#/ VB/etcのVSプロジェクトファイルの形式です。それより前のプロジェクト

  2. 小さなヘルパープログラムを使用して環境をキャプチャし、それをプロセスに注入することができます

    これはおそらく少しやり過ぎですが、うまくいくでしょう。vsvarsall.batは、いくつかの環境変数を設定する以外に魔法のようなことは何もしません。したがって、実行した結果を記録し、それを作成したプロセスの環境で再生するだけです。

ヘルパープログラム(envcapture.exe)は簡単です。環境内のすべての変数を一覧表示し、それらを標準出力に出力するだけです。これはプログラムコード全体です。貼り付けますMain()

XElement documentElement = new XElement("Environment");
foreach (DictionaryEntry envVariable in Environment.GetEnvironmentVariables())
{
    documentElement.Add(new XElement(
        "Variable",
        new XAttribute("Name", envVariable.Key),
        envVariable.Value
        ));
}

Console.WriteLine(documentElement);

このプログラムの代わりに呼び出してその出力を解析するだけで解決できるかもしれませんがset、環境変数に改行が含まれていると、それが機能しなくなる可能性があります。

メインプログラムの場合:

まず、vcvarsall.batによって初期化された環境をキャプチャする必要があります。これを行うには、のようなコマンドラインを使用しますcmd.exe /s /c " "...\vcvarsall.bat" x86 && "...\envcapture.exe" "。vcvarsall.batは環境を変更し、envcapture.exeがそれを出力します。次に、メインプログラムはその出力をキャプチャし、辞書に解析します。(注:vsVersionこれは90または100または110のようなものになります)

private static Dictionary<string, string> CaptureBuildEnvironment(
    int vsVersion, 
    string architectureName
    )
{
    // assume the helper is in the same directory as this exe
    string myExeDir = Path.GetDirectoryName(
        Assembly.GetExecutingAssembly().Location
        );
    string envCaptureExe = Path.Combine(myExeDir, "envcapture.exe");
    string vsToolsVariableName = String.Format("VS{0}COMNTOOLS", vsVersion);
    string envSetupScript = Path.Combine(
        Environment.GetEnvironmentVariable(vsToolsVariableName),
        @"..\..\VC\vcvarsall.bat"
        );

    using (Process envCaptureProcess = new Process())
    {
        envCaptureProcess.StartInfo.FileName = "cmd.exe";
        // the /s and the extra quotes make sure that paths with
        // spaces in the names are handled properly
        envCaptureProcess.StartInfo.Arguments = String.Format(
            "/s /c \" \"{0}\" {1} && \"{2}\" \"",
            envSetupScript,
            architectureName,
            envCaptureExe
            );
        envCaptureProcess.StartInfo.RedirectStandardOutput = true;
        envCaptureProcess.StartInfo.RedirectStandardError = true;
        envCaptureProcess.StartInfo.UseShellExecute = false;
        envCaptureProcess.StartInfo.CreateNoWindow = true;

        envCaptureProcess.Start();

        // read and discard standard error, or else we won't get output from
        // envcapture.exe at all
        envCaptureProcess.ErrorDataReceived += (sender, e) => { };
        envCaptureProcess.BeginErrorReadLine();

        string outputString = envCaptureProcess.StandardOutput.ReadToEnd();

        // vsVersion < 110 prints out a line in vcvars*.bat. Ignore 
        // everything before the first '<'.
        int xmlStartIndex = outputString.IndexOf('<');
        if (xmlStartIndex == -1)
        {
            throw new Exception("No environment block was captured");
        }
        XElement documentElement = XElement.Parse(
            outputString.Substring(xmlStartIndex)
            );

        Dictionary<string, string> capturedVars 
            = new Dictionary<string, string>();

        foreach (XElement variable in documentElement.Elements("Variable"))
        {
            capturedVars.Add(
                (string)variable.Attribute("Name"),
                (string)variable
                );
        }
        return capturedVars;
    }
}

後で、ビルド環境でコマンドを実行する場合は、新しいプロセスの環境変数を以前にキャプチャした環境変数に置き換えるだけです。CaptureBuildEnvironmentプログラムを実行するたびに、引数の組み合わせごとに1回だけ呼び出す必要があります。ただし、実行の合間に保存しようとしないでください。保存すると古くなります。

static void Main()
{
    string command = "nmake";
    string args = "";

    Dictionary<string, string> buildEnvironment = 
        CaptureBuildEnvironment(100, "x86");

    ProcessStartInfo info = new ProcessStartInfo();
    // the search path from the adjusted environment doesn't seem
    // to get used in Process.Start, but cmd will use it.
    info.FileName = "cmd.exe";
    info.Arguments = String.Format(
        "/s /c \" \"{0}\" {1} \"",
        command,
        args
        );
    info.CreateNoWindow = true;
    info.UseShellExecute = false;
    info.RedirectStandardOutput = true;
    info.RedirectStandardError = true;
    foreach (var i in buildEnvironment)
    {
        info.EnvironmentVariables[(string)i.Key] = (string)i.Value;
    }

    using (Process p = Process.Start(info))
    {
        // do something with your process. If you're capturing standard output,
        // you'll also need to capture standard error. Be careful to avoid the
        // deadlock bug mentioned in the docs for
        // ProcessStartInfo.RedirectStandardOutput. 
    }
}

これを使用する場合は、vcvarsall.batが見つからないか失敗すると恐らく死ぬ可能性があり、en-US以外のロケールのシステムで問題が発生する可能性があることに注意してください。

于 2012-12-31T09:03:29.000 に答える
1

必要なすべてのデータを収集し、batファイルを生成して、Processクラスを使用して実行するよりも良い方法はおそらくありません。あなたが書いたように、あなたは出力をリダイレクトしています。つまり、設定する必要がUseShellExecute = false;あるので、batファイルからSETを呼び出す以外に変数を設定する方法はないと思います。

于 2012-12-22T22:28:25.390 に答える
0

編集:nmake呼び出しの特定のユースケースを追加

私は過去にさまざまな「ビルドパスのもの」を入手する必要がありました。これは私が使用したものです。ここかそこかで調整する必要があるかもしれませんが、基本的に、vcvarsが行うのはセットアップだけです。パスの束; これらのヘルパーメソッドはこれらのパス名をフェッチします。開始情報にそれらを渡す必要があります。

public static string GetFrameworkPath()
{
    var frameworkVersion = string.Format("v{0}.{1}.{2}", Environment.Version.Major, Environment.Version.Minor, Environment.Version.Build);
    var is64BitProcess = Environment.Is64BitProcess;
    var windowsPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
    return Path.Combine(windowsPath, "Microsoft.NET", is64BitProcess ? "Framework64" : "Framework", frameworkVersion);  
}

public static string GetPathToVisualStudio(string version)
{   
    var is64BitProcess = Environment.Is64BitProcess;
    var registryKeyName = string.Format(@"Software\{0}Microsoft\VisualStudio\SxS\VC7", is64BitProcess ? @"Wow6432Node\" : string.Empty);
    var vsKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(registryKeyName);
    var versionExists = vsKey.GetValueNames().Any(valueName => valueName.Equals(version));
    if(versionExists)
    {
        return vsKey.GetValue(version).ToString();
    }
    else
    {
        return null;
    }
}

そして、あなたは次のようなものを介してこのようなものを利用するでしょう:

var paths = new[]
    { 
        GetFrameworkPath(), 
        GetPathToVisualStudio("10.0"),
        Path.Combine(GetPathToVisualStudio("10.0"), "bin"),
    };  

var previousPaths = Environment.GetEnvironmentVariable("PATH").ToString();
var newPaths = string.Join(";", previousPaths.Split(';').Concat(paths));
Environment.SetEnvironmentVariable("PATH", newPaths);

var startInfo = new ProcessStartInfo()
{
    FileName = "nmake",
    Arguments = "whatever you'd pass in here",
};
var process = Process.Start(startInfo);
于 2012-12-31T20:22:21.697 に答える