10

ここで、誰かが答えを知っているのではないかと頭を悩ませています。

セットアップは基本的に次のとおりです。

//in Visual Studio plug-in application
SpinUpProgramWithDebuggerAttached();

//in spun up program
void Start()
{
    StaticClass.StaticVariable = "I want to use this.";
    XmlSerializer.Deserialize(typeof(MyThingie), "xml");
}

class MyThingie : IXmlSerializable
{
     ReadXml()
     {
         //why the heck is this null?!?
         var thingIWantToUse = StaticClass.StaticVariable;
     }
}

私の髪を引っ張っている問題は、変数が設定された直後に呼び出されたにもかかわらず、StaticClass.StaticVariable が IXmlSerializable.ReadXml() メソッドで null であることです。

注目すべきは、ブレークポイントがヒットせず、問題が発生した正確な場所で Debugger.Launch() が無視されることです。

不思議なことに、例外を発生させることで、AppDomain.CurrentDomain.FriendlyName プロパティは、静的変数が設定されている場所と null の場所で同じであることがわかりました。

なぜ静的変数が範囲外なのですか?!? どうしたの?!?変数を共有するにはどうすればよいですか?

編集:

応答の提案に従って、静的コンストラクターを追加し、Debug.WriteLine を実行させました。すべてのコードが同じ AppDomain で実行されているように見えますが、2 回呼び出されていることに気付きました。出力ウィンドウに表示される内容は次のとおりです。これが有用な手がかりになることを願っています。

呼び出された静的コンストラクター: 2015-01-26T13:18:03.2852782-07:00

...ロードされた 'C:...\GAC_MSIL\System.Numerics\v4.0_4.0.0.0__b77a5c561934e089\System.Numerics.dll'...

...ロードされた 'Microsoft.GeneratedCode'...

...ロードされた 'C:...\GAC_MSIL\System.Xml.Linq\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.Linq.dll'....

...「C:\USERS...\APPDATA\LOCAL\MICROSOFT\VISUALSTUDIO\12.0EXP\EXTENSIONS...SharePointAdapter.dll」をロードしました。シンボルが読み込まれました。

...「Microsoft.GeneratedCode」をロードしました。

呼び出された静的コンストラクター: 2015-01-26T13:18:03.5196524-07:00

追加の詳細:

いくつかのコメント投稿者が役立つと考えたので、実際のコードは次のとおりです。

//this starts a process called "Emulator.exe"
var testDebugInfo = new VsDebugTargetInfo4
{
    fSendToOutputWindow = 1,
    dlo = (uint)DEBUG_LAUNCH_OPERATION.DLO_CreateProcess,
    bstrArg = "\"" + paramPath + "\"",
    bstrExe = EmulatorPath, 
    LaunchFlags = grfLaunch | (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_StopDebuggingOnEnd | (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_WaitForAttachComplete,
    dwDebugEngineCount = 0,
    guidLaunchDebugEngine = VSConstants.CLSID_ComPlusOnlyDebugEngine,
};

var debugger = Project.GetService(typeof(SVsShellDebugger)) as IVsDebugger4;
var targets = new[] { testDebugInfo };
var processInfos = new[] { new VsDebugTargetProcessInfo() };

debugger.LaunchDebugTargets4(1, targets, processInfos);

//this is in the emulator program that spins up
public partial class App : Application
{
    //***NOTE***: static constructors added to static classes.
    //Problem still occurs and output is as follows (with some load messages in between):
    //
    //MefInitializer static constructor called at: 2015-01-26T15:34:19.8696427-07:00
    //ContainerSingleton static constructor called at: 2015-01-26T15:34:21.0609845-07:00. Type: SystemTypes.ContainerSingleton, SystemTypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=...
    //ContainerSingleton static constructor called at: 2015-01-26T15:34:21.3399330-07:00. Type: SystemTypes.ContainerSingleton, SystemTypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=...

    protected override void OnStartup(StartupEventArgs e)
    {
         //...

         //initializes a MEF container singleton (stored as static variable)
         MefInitilizer.Run(); 

         //here's where it blows up. the important details are that 
         //FullSelection implements IXmlSerializable, and its implemention
         //ends up referencing the MEF container singleton, which ends up
         //null, even though it was initialized in the previous line.
         //NOTE: the approach works perfectly under a different context
         //so the problem is not the code itself, per se, but a problem
         //with the code in the environment it's running in.
         var systems = XmlSerialization.FromXml<List<FullSelection>>(systemsXml);
    }
 }

public static class MefInitilizer
{
    static MefInitilizer() { Debug.WriteLine("MefInitializer static constructor called at: " + DateTime.Now.ToString("o")); }

    public static void Run()
    {
        var catalog = new AggregateCatalog();
        //this directory should have all the defaults
        var dirCatalog = new DirectoryCatalog(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
        //add system type plug-ins, too
        catalog.Catalogs.Add(dirCatalog);

        var container = new CompositionContainer(catalog);
        ContainerSingleton.Initialize(container);
    }
}

public class ContainerSingleton
{
    static ContainerSingleton() 
    {
        Debug.WriteLine("ContainerSingleton static constructor called at: " + DateTime.Now.ToString("o") + ". Type: " + typeof(ContainerSingleton).AssemblyQualifiedName);
    }
    private static CompositionContainer compositionContainer;

    public static CompositionContainer ContainerInstance
    {
        get 
        {
            if (compositionContainer == null)
            {
                var appDomainName = AppDomain.CurrentDomain.FriendlyName;
                throw new Exception("Composition container is null and must be initialized through the ContainerSingleton.Initialize()" + appDomainName);
            }
            return compositionContainer; 
        }
    }

    public static void Initialize(CompositionContainer container)
    {
        compositionContainer = container;
    }
}
4

4 に答える 4

2

私が理解したように、あなたのコードは Visual Studio のプラグインであり、アプリケーションの主な問題は、クラスが 2 回インスタンス化されているAppDomainことです。 .

まず第一に、ここに Visual Studio の可能性sandboxingが見えます。Visual Studio は、コードが Visual Studio の他の部分やエンド ユーザーの作業に害を及ぼさないことを確認するために、さまざまな権限セットでコードをテストしたいと考えています。この場合、コードはいくつかの権利なしで別の AppDomain に読み込まれる可能性があるため ( MSDN で適切な記事を見つけることができます)、コードがアプリケーションごとに 2 回呼び出される理由を理解できます。

constructor次に、 staticと staticの考え方を誤解していることを指摘したいと思いmethodます。

public static void Initialize(CompositionContainer container)
{
    compositionContainer = container;
}

と同じではありません

public static ContainerSingleton()
{
    compositionContainer = container;
}

したがって、すべての初期化ロジックを次のような静的コンテナーに移動することをお勧めします。

public class ContainerSingleton
{
    private static CompositionContainer compositionContainer;

    public static CompositionContainer ContainerInstance
    {
        get 
        {
            if (compositionContainer == null)
            {
                var appDomainName = AppDomain.CurrentDomain.FriendlyName;
                throw new Exception("Composition container is null and must be initialized through the ContainerSingleton.Initialize()" + appDomainName);
            }
            return compositionContainer; 
        }
    }

    public static ContainerSingleton()
    {
        var catalog = new AggregateCatalog();
        //this directory should have all the defaults
        var dirCatalog = new DirectoryCatalog(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
        //add system type plug-ins, too
        catalog.Catalogs.Add(dirCatalog);

        compositionContainer = new CompositionContainer(catalog);
    }
}

2 番目のアプローチ: シングルトンを取得するために使用するパターンが古くなっていることを指摘したいと思います。次のLazy<T>ようなクラスを使用してみてください。

public class ContainerSingleton
{
    private static Lazy<CompositionContainer> compositionContainer;

    public static CompositionContainer ContainerInstance
    {
        get 
        {
            return compositionContainer.Value;
        }
    }

    public static ContainerSingleton()
    {
        compositionContainer = new Lazy<CompositionContainer>(() => Initialize());
    }
    public static void Initialize()
    {
         // Full initialization logic here
    }
}

また、空の静的コンストラクターを追加するだけでは不十分であることを覚えておく必要があります。すべての割り当てをそれに移動する必要があるため、そのようなコードを置き換える必要があります。

public class AnotherClass
{
    static AnotherClass()
    {

    }

    public static String Test = ContainerSingleton.ContainerInstance;
}

これで:

public class AnotherClass
{
    static AnotherClass()
    {
        Test = ContainerSingleton.ContainerInstance;
    }

    public static String Test;
}

アップデート:

@Colin [ LazyTasktype][ https://msdn.microsoft.com/en-us/magazine/dn683795.aspx]を使用することもできます-コンストラクターに a を渡すだけFuncで、スレッドセーフなアプローチになります。詳細を参照してください記事で。同じIdことAppDomainは何も意味しません-サンドボックスはメソッドを介してコードを実行しAppDomain.ExecuteAssembly(4.5では廃止されましたが、まだ可能なバリアントである可能性があります)、さまざまな権限セットでどのように動作するかを確認できます.

.NET 4.5 には別の手法があるかもしれませんが、現在関連する記事は見つかりません。

更新 2:

コードからわかるように、ディスクからいくつかの情報を読み取っています。コード アクセス セキュリティルールを追加して、コードが次のように制限されたアクセス許可で実行されているかどうかを確認してください。

FileIOPermission f2 = new FileIOPermission(FileIOPermissionAccess.Read, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
//f2.AddPathList(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, "C:\\example\\out.txt");
try
{
    f2.Demand();
}
catch (SecurityException s)
{
    Console.WriteLine(s.Message);
}

クラスの詳細についてFileIOPermissionは、MSDN を参照してください。

于 2015-01-26T22:51:31.693 に答える
1

に静的コンストラクターを追加してみてくださいContainerSingleton。これは、BeforeFieldInitが再び醜い頭を上げていると思います。

于 2015-01-26T19:26:29.033 に答える
0

提案を提供してくれたすべての人に感謝します!何が起こっているのかを正確に把握したことはありませんでしたが(もしそうなら、調査/更新を続けます)、最終的に回避策を思いつきました.

静的クラスの初期化を内部化することを提案する人もいましたが、実際の問題の性質上、初期化ロジックを外部化する必要がありました (基本的に、構成が環境によって異なる DI/サービス ロケーション コンテナーをロードしていました)。

また、静的コンストラクターが 2 回呼び出されたことを確認できたので、役に立たなかったと思います (したがって、そこにあった初期化ロジックが 2 回呼び出されるだけで、問題に直接対処することはできませんでした)。

しかし、その提案は私を正しい軌道に乗せました。

私の場合、ロードしたサービスはステートフルである必要はありませんでした。そのため、パフォーマンス ヒットを除けば、初期化が 2 回発生しても問題はありませんでした。

したがって、MEF コンテナーがロードされているかどうかを静的クラスでチェックするだけで済みました。ロードされていない場合は、初期化を処理するクラスを指定した構成ファイルを読み取ります。

そうすることで、MEF コンテナーの構成を環境ごとに変えることができました。これは、理想的なソリューションではありませんが、現在はかなりうまく機能しています。

助けてくれたすべての人に報奨金を分けてあげたいのですが、それは不可能のようですので、OakNinja には報酬を与えるつもりです。なぜなら、彼は私の情報から現実的に期待できる限り多くの良いアイデアを吐き出した英雄だったからです。提供された。再度、感謝します!

于 2015-02-02T23:02:55.033 に答える