Is it possible to reflect whole Windows Forms applications? - c#

EDIT: The problem was a mismatch between the two programs' Framework versions: one was using 2.0 while two was using 4.0. The below code works perfectly.
I am trying to reflect a whole Windows Form application, but invoking the Entry Point does not work: either nothing happens or a crash shows up. Is there something I can do?
public static void Reflect(object program)
{
object[] objArray;
MethodInfo entryPoint = ((Assembly)program).EntryPoint;
MethodInfo methodInfo = entryPoint;
if ((int)entryPoint.GetParameters().Length == 0)
{
objArray = null;
}
else
{
objArray = new object[] { new string[0] };
}
methodInfo.Invoke(null, objArray);
}

If both your applications are WinForms then you can't run a second application within first one because it's a single threaded environment (and each one has its own message pump).
To workaround this issue you may start your second application from another thread. This way each application will run in its own thread (but honestly I don't know if this will always work or if there are other drawbacks with Windows).
Something like this (assuming your assembly has not been loaded for Reflection only):
ThreadPool.QueueUserWorkItem(delegate (object state)
{
var entryPoint = ((Assembly)program).EntryPoint;
entryPoint.Invoke(null, new string[0]);
});
Note that application entry point (if not null) will always have same signature, no need to check. Of course I wouldn't use a thread from pool but this is just an example.

Related

Application.Current is null, but new Application() still fails?

I have an unmanaged application which uses a WPF assembly for some of its user interface. Because of this arrangement Application.Current is not created automatically. So when the first WPF window is loaded, my code does this:
if (System.Windows.Application.Current == null)
{
new System.Windows.Application();
}
This works the first time and is the approach I've seen recommended.
But if the user closes the (only) WPF window, and later loads it again, even though Current == null again an exception is thrown when the Application ctor is called.
It is clear in the documentation that you can only have one Application per AppDomain - but why then is Current null and yet I can't create it?
The exception that is thrown is of type InvalidOperationException and has the message:
Cannot create more than one System.Windows.Application instance in the
same AppDomain.
Its InnerException is null.
To work around this I have tried to:
Construct Application using ShutdownMode = ShutdownMode.OnLastWindowClose
Explicitly call Current.Shutdown() when the WPF window is closed
but neither has made any difference.
Is there some other way to manually manage the lifetime of the Current object? Or should I instead attempt to create it when the unmanaged application starts, and then rely on it always being set for the lifetime of the process?
The documentation you linked states the following in its remarks section:
Only one instance of the Application class can be created per
AppDomain, to ensure shared access to a single set of
application-scope window, property, and resource data. Consequently,
the parameterless constructor of the Application class detects whether the instance being initialized is the first instance in an
AppDomain; if it is not, an InvalidOperationException is thrown.
The part I highlighted implies that it is not checking if it is the only / single application currently running, but rather that it checks if any another Application instance has been initialized before (regardless of whether or not it has been closed yet).
Taking a look at the source code of the Application class confirms this: The Application class internally uses a static flag (_appCreatedInThisAppDomain) that is set only once when initializing the first Application instance. But apparently this flag is never reset, which prevents you from creating any more Application instances within the same AppDomain.
This is easy in WinForms, not so much in WPF.
Obviously, we don't have an Application problem, we have an AppDomain problem.
I put a reasonable amount of effort into this, but couldn't get it to behave as I wanted it to, that is to destroy the old then recreate an AppDomain on a new Thread when the spacebar is pressed, I suppose that makes sense though given the scope.
It's a work around at best, and may not even be an option in your situation.
Is there some other way to manually manage the lifetime of the Current
object?
As best I can tell, the simple answer is to just maintain a WPF message loop Thread for the life of the program (via ShutdownMode.OnExplicitShutdown), and use the Application.Current.Dispatcher to display WPF objects as needed.
Here's an example of what I mean, as implemented in a managed console application:
class Program
{
static void Main(string[] args)
{
Thread t = CreateThread();
t.Start();
bool quit = false;
while (!quit)
{
switch(Console.ReadKey().Key)
{
case ConsoleKey.Escape:
Application.Current.Dispatcher.Invoke(() => Application.Current.Shutdown());
quit = true;
break;
case ConsoleKey.W:
Application.Current.Dispatcher.Invoke(() =>
{
var w = new Window() { Width = 500, Height = 500, Title = "WPF Window" };
w.Show();
});
break;
case ConsoleKey.D:
Application.Current.Dispatcher.Invoke(() =>
{
var d = new Window() { Width = 500, Height = 500, Title = "WPF Dialog" };
d.ShowDialog();
});
break;
case ConsoleKey.Spacebar:
//// Nope!
//Application.Current.Dispatcher.Invoke(() => Application.Current.Shutdown());
//t = CreateThread();
//t.Start();
break;
}
};
}
static Thread CreateThread()
{
var t = new Thread(() =>
{
if (System.Windows.Application.Current == null)
{
new System.Windows.Application();
Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
}
Application.Current.Run();
});
t.SetApartmentState(ApartmentState.STA);
return t;
}
}
You'll need references to PresentationCore, PresentationFramework and WindowsBase to build this example.
I hope it at least inspires someone.
EDIT: FYI, this may not work anymore... It worked when I posted it, now two days later it does not. There was a cumulative update for .NET Framework (kb4538122) installed yesterday, but I'm not sure if this was the breaking change or not.
EDIT: I updated the code, it now works again.

WPF cannot close Application instance for running it a second time

I have an Console Application started as [STAThread].
That application should open a seperate Wpf UI for entering some settings.
The functions for that:
private static void openUI()
{
var application = new System.Windows.Application();
//referenced project in the same solution
var ui = new ManagerUI.MainWindow();
//blocks execution
application.Run(ui);
application.Shutdown();
}
Opening the UI for the first time works as expected.
The problem occurs when opening the UI for the second time.
I get an System.InvalidOperationException, saying that I cannot run more than one Application-Instance in the same AppDomain.
For saving ram, it must be closed between the operations.
I also tried to create the System.Windows.Application in the constructor.
But as soon as I run the application the second time, I get a very similiar exception.
The InitializeComponents() method of the UI throws an System.InvalidOperationException, saying that the Object is going to be terminated.
The StackTraces shows that the error appears when the xaml is parsed, so I conclude it cannot open it, because it is still opened by the first execution.
Neither calling ui.Close() nor calling application.Shutdown() solves the problem (Environment.Exit() closes everything, including my Console Application).
The ram profiler indicates, not everything was closed correctly because it shows an higher use after the Window was closed, than before it was opened in the firts place.
How do I properly close the Application instance, or how do I re-use it to run an Wpf Application multiple times?
Having looked at the source code for the Application class, it doesn't look like you will be able to work around this, as various static fields are initialized by the class constructor:
public Application()
{
...
lock(_globalLock)
{
if (_appCreatedInThisAppDomain == false)
{
...
_appInstance = this;
...
_appCreatedInThisAppDomain = true;
}
else
{
throw new InvalidOperationException(...);
}
}
}
...
static private object _globalLock;
static private bool _appCreatedInThisAppDomain;
static private Application _appInstance;
...
Basically the constructor sets _appCreatedInThisAppDomain to true, and as that field is private you have no way of setting it back*.
I think the only way of achieving something similar to what you want is to write a separate WPF application, then use the Process class to launch that from your console application. Alternatively, you could theoretically create a separate AppDomain to host your WPF stuff but that would be a lot more complicated.
[*] other than using Reflection, but let's not go there!
You may create a class that derives from MarshalByRefObject:
public class AppDomainWrapper : MarshalByRefObject
{
public void openUI()
{
var application = new System.Windows.Application();
var ui = new Window();
application.Run(ui);
application.Shutdown();
}
}
...and execute its openUI() method in its own application domain:
[STAThread]
static void Main(string[] args)
{
const int n = 2;
for (int i = 0; i < n; ++i)
{
AppDomain appDomain = AppDomain.CreateDomain("AppDomain");
AppDomainWrapper application = appDomain.CreateInstanceAndUnwrap(typeof(AppDomainWrapper).Assembly.FullName, typeof(AppDomainWrapper).FullName) as AppDomainWrapper;
application.openUI();
AppDomain.Unload(appDomain);
}
}
Have a look at this question:Does a WPF Application Actually Need Application.Run?.
Basically it says, that you can open windows using window.ShowDialog() method without Application instance
The think is that Application.Run does not do anything important but run Dispatcher loop. ShowDialog have its own Dispatcher. You can create Application singleton instance however, since it contains some shared resources.
Hack(run it after application.Shutdown()). I use this in tests:
var field = typeof(Application).GetField(
"_appCreatedInThisAppDomain",
BindingFlags.Static | BindingFlags.NonPublic) ??
throw new InvalidOperationException(
"Field is not found: _appCreatedInThisAppDomain.");
field.SetValue(null, false);
Steven Rands shows the problem.
I have the same problem in an external add-in. But I need an application object for xaml resources and a valid Application.Current.
In my eyes this is a bug. If you call Shutdown() this member should also be reset to false.

How to determine whether a class is instantiated within a console application or wpf?

I need to write a wrapper around a third party api that peruses message pumps and hence needs to be handled very differently depending on whether the wrapper is instantiated on a UI thread (such as within a wpf application) or not (such as a console application).
If it does not run on a UI thread then I need a dispatcher and implement my own message pump.
For that purpose I need to know whether the wrapper is instantiated within a wpf application or not. It is not enough to determine whether the instantiation happens on a UI thread or not (as even in a wpf application the thread on which the wrapper is instantiated might not be the UI thread).
Is there any way I can figure out whether I am in a wpf or windows form environment with message pump or a console application where I have to implement my own message pump?
Thanks
I think you might be best off to have three separate packages.
To make it auto-select, you would need to have references to both WPF and WinForms in your package. Assuming worstcase, your package just made me import both into my console application. Others might consider importing WinForms into an WPF application worst case and another group of people might do WinForms because they cannot access WPF (and therefor you just cut them of from using your package altogether).
If no other answer satisfies your needs - you can use reflection to check if Application.Current is null or not, without directly referencing WPF assemblies (and the same works with WinForms):
private static bool IsWpfApplication() {
var type = Type.GetType("System.Windows.Application, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
if (type == null)
return false;
var currentProp = type.GetProperty("Current", BindingFlags.Public | BindingFlags.Static);
if (currentProp == null)
return false;
return currentProp.GetValue(null, new object[0]) != null;
}
Note that this might load PresentationFramework dll into current app domain, even in console application. If that is a problem for you - you can do the same by inspecting assemblies already loaded in app domain:
private static bool IsWpfApplication2() {
var wpfAsm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(c => c.GetName().Name == "PresentationFramework");
if (wpfAsm == null)
return false;
var type = wpfAsm.GetType("System.Windows.Application");
if (type == null)
return false;
var currentProp = type.GetProperty("Current", BindingFlags.Public | BindingFlags.Static);
if (currentProp == null)
return false;
return currentProp.GetValue(null, new object[0]) != null;
}
Is there any way I can figure out whether I am in a wpf or windows form environment with message pump or a console application where I have to implement my own message pump?
You could check whether there is a top-level window available:
if (Process.GetCurrentProcess().MainWindowHandle != IntPtr.Zero)
//WPF
MainWindowHandle should return a handle provided that the WPF application has a main window.
You should also be able to use the native GetConsoleWindow function to determine whether you're in the context of a console application.
This might work:
public static void Main(string[] args)
{
var application = Application.Current;
Console.WriteLine($"Application is {(application == null ? "null": "not-null")}");
Console.ReadKey();
}
Requires references to PresentationFramework and, according to Resharper, WindowsBase and System.Xaml

Executing a c# WPF application inside an already running c# process

I´m trying to build up an automated check if several 32 bit WPF applications can be opened without problems.
I do not want to use Process.Start as i cannot be sure if each program will return a non-zero exit code in case a problem occurs (and i would have to close those WPF application with further code).
My plan instead: Loading the assemblies at runtime and triggering their start method (connecting to some exception event sinks to get infos about problems and closing the windows opened later on).
This is what i got so far:
public void Check(string executablePath)
{
try
{
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(executablePath);
}
catch (BadImageFormatException e)
{
Logger.InfoFormat("Not a 32 bit .NET application : {0}", Path.GetFileName(executablePath));
return;
}
assembly.EntryPoint.Invoke(null, new object[] { });
Logger.InfoFormat("OK : {0}", Path.GetFileName(executablePath));
}
catch (Exception e)
{
Logger.Error(e);
}
}
My problem: As soon as i invoke the EntryPoint method, an error screen from the application inside is presented telling me an IOExeption happened (it was not able to find the resource for the splash screen).
Do i have to preload those resources inside other assemblies somehow to get it working?
Update
With Dirks answer i was able to create a new application domain and delegating the call of the entry point to a MarshalByRefObject descendant created by this domain.
I was also able to change the value of Assembly.EntryAssembly thanks to this website (currently not online)
http://webcache.googleusercontent.com/search?q=cache:6POIVfrxbAcJ:dejanstojanovic.net/aspnet/2015/january/set-entry-assembly-in-unit-testing-methods/+&cd=8&hl=en&ct=clnk&gl=de
Code snippet doing the work:
private void ModifyEntryAssembly(Assembly assembly)
{
AppDomainManager manager = new AppDomainManager();
FieldInfo entryAssemblyfield = manager.GetType().GetField("m_entryAssembly", BindingFlags.Instance | BindingFlags.NonPublic);
if (entryAssemblyfield == null)
{
throw new Exception("Could not retrieve entryAssemblyField.");
}
entryAssemblyfield.SetValue(manager, assembly);
AppDomain domain = AppDomain.CurrentDomain;
FieldInfo domainManagerField = domain.GetType().GetField("_domainManager", BindingFlags.Instance | BindingFlags.NonPublic);
if (domainManagerField == null)
{
throw new Exception("Could not retrieve domainManagerField.");
}
domainManagerField.SetValue(domain, manager);
}
Now, im getting the splash screen and a login dialog from the called executable, much further now!
There is another problem with an EEntryPointException thrown, but that´s another story for another question... Thanks!
The WPF splash screen is searched in the entry assembly1, which, in your case, is your main executable and not the executable you are trying to check.
You can resolve this by starting the application under test in an own application domain, which will then get its own entry assembly:
class Test
{
public static void Main()
{
var otherDomain = AppDomain.CreateDomain("otherDomain");
otherDomain.ExecuteAssembly("MyExecutable.exe");
}
}
However, you should be aware that your approach is going to give you false positives, because the application under test runs in another environment. For example, calls Assembly.GetExecutingAssembly() will give different results in the application under test. And your approach won't be able to test 32-bit and 64-bit applications at the same time.
1
As you can see from the reference source, the below overload of the SplashScreen constructor calls Assembly.GetEntryAssembly():
public SplashScreen(string resourceName)
: this(Assembly.GetEntryAssembly(), resourceName)
{
}

How to properly set up a multithreaded Asp.Net MVC + COM server on IIS

Update 2
The queueing problem was probably solved already, as we've been able to run multiple requests concurrently and the lib nicely reported progress for each operation. Other issues we're still facing about concurrency were likely the reason for this apparent behaviour, but that's a design matter. To solve this however, it'd be helpful to have some knowledge of the inner workings of classes, modules and variables as used in VB6. A question arise: would encapsulating everything (connections, components etc.) in classes ensure that every created object does not share any data with other instances?
Update 1
We've refactored the application a bit more to cope with resource disposing, especially when dealing with OCXs. Apparently that solved the out of memory issue. What still bothers me is that I don't understand what is happening beneath the surface. In this regard, is there a way to see what objects are currently in memory and how many references they have? I know the reference counting model is different from garbage collector-based systems. Still I would suppose the RCW wrapping our com objects would keep things clean for us. In the model given, is that a safe assumption or there's something we're missing?
So, I've probably read the most variegated kind of articles and docs about the topic of COM multithreading, but I still cannot get how that's supposed to work exactly, especially when interacting with .Net technologies such as ASP.Net MVC. That could be considered a simple fancy of mine, except for the fact that we've got this quite critical project and we're experiencing severe issues in trying to tie everything up. We're getting out of memory errors (in VB6) and apparently we got wrong how objects are created and data shared between these in COM. Continue reading to know how the story goes...
How things came to be
Not much to say here. We have a legacy VB6 Desktop application made up of a number of ActiveX DLLs. These are configured to use Apartment as the threading model, and all classes are set as MultiUse. All worked well and nice until the time came when we was requested to transpose the app on the mighty web :O
The problem we faced and how we (thought we) solved it
Since we haven't got the resources to design and develop a solution from scratch, we used a third party java(script)-based framework to quickly build a web app. However, much of the real work is done by the legacy library, so we needed a way to interface these two components. The easiest way we could think of was to build a very basic (w/o auth and w/o UI) Asp.Net MVC website to use as the middle layer. This would receive requests from the web app and translate them for the COM lib to crunch data.
To this end, and since the libs were never meant to be used as a server, we tried to refactor the whole thing a bit so that most classes can now be used in a standalone manner: this included separating logic from the UI and eliminating all module and public vars where possible; unfortunately, some of the former are still present, in particular some ComponentOne OCXs to handle reports and prints. All in all, this seemed to work just fine, until we had to deal with the COM threading model :O
Making sense of nonsense
Long story short, after a lot of digging and headaches we devised the current solution, which is outlined below:
we install the legacy app as usual, so that it register its dlls in the registry;
in our MVC solution, we use System.Threading.Tasks, one per every request, to start the requested operation in an asynchronous manner. We assign the operation an id and return this id to the client. To start the task we call this method:
protected Task<TReturn> StartSTATask<TReturn>(Func<TReturn> function)
{
var task = Task.Factory.StartNew(
function,
System.Threading.CancellationToken.None,
TaskCreationOptions.None,
STATaskScheduler // property to store the scheduler instance
);
return task;
}
the task is run using the STATaskScheduler. We modified it so that it spawns a new thread if the number of threads in the pool is set to 0.
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
{
// Validate arguments
//if (numberOfThreads < 1) throw new ArgumentOutOfRangeException("concurrencyLevel");
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
if (numberOfThreads > 0)
{
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
{
var thread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
{
TryExecuteTask(t);
}
});
thread.Name = "sta_thread_" + i;
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
// Start all of the threads
_threads.ForEach(t => t.Start());
}
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
if (_threads != null)
// Push it into the blocking collection of tasks
_tasks.Add(task);
else
{
var thread = new Thread(() => TryExecuteTask(task));
thread.Name = "sta_thread_task_" + task.Id;
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
And in our base controller's OnActionExecuting method we initiliaze it so
STATaskScheduler = HttpContext.Application["STATaskScheduler"] as TaskScheduler;
if (null == STATaskScheduler)
{
STATaskScheduler = new StaTaskScheduler(0);
HttpContext.Application["STATaskScheduler"] = STATaskScheduler;
}
we use a thin wrapper to instantiate and call our COM libs through reflection:
// Libraries is a Dictionary containing the names of the registered dlls
protected object InitCom(Libraries lib)
{
return InitCom(lib, true);
}
protected virtual object InitCom(Libraries lib, bool setOperation)
{
var comObj = GetComInstance(lib);
var success = SetUpConnection(comObj);
if (!success)
throw new LeafOperationException(lib, "Errore durante la connessione: {1}".Printf(connectionString));
if(setOperation)
return InitOperation(comObj);
return comObj;
}
protected object GetComInstance(Libraries lib)
{
var comType = Type.GetTypeFromProgID(MALib[lib]);
var comObj = Activator.CreateInstance(comType);
return comObj;
}
protected virtual bool DisposeCom(object comObj)
{
var success = CloseConnection(comObj);
if(!success)
throw new LeafOperationException("Errore durante la chiusura della connessione: {1}".Printf(connectionString));
//Marshal.FinalReleaseComObject(comObj);
//comObj = null;
return success;
}
protected bool SetUpConnection(object comObj)
{
var serverName = connectionString.ServerName();
var catalogName = connectionString.CatalogName();
return Convert.ToBoolean(comObj.InvokeMethod("Set_ConnectionWeb", serverName, catalogName));
}
protected bool CloseConnection(object comObj)
{
return Convert.ToBoolean(comObj.InvokeMethod("Close_ConnectionWeb"));
}
protected object InitOperation(object comObj)
{
comObj.GetType().InvokeMember("OperationID", BindingFlags.SetProperty, null, comObj, new object[] { OperationId });
comObj.GetType().InvokeMember("OperationHash", BindingFlags.SetProperty, null, comObj, new object[] { OperationHash });
return comObj;
}
The rationale behind this is that we create a new instance of the class with each request, eventually releasing it when done. Read here to know why we commented out the ReleaseComObject part. Basically, we were trading out of memory for a lot of COM object that has been separated from its underlying RCW cannot be used exceptions.
The object is then used like this within methods of various classes:
public bool ChiusuraMese()
{
try
{
PulisciMessaggi();
var comObj = InitCom(Libraries.Chiusura);
var byRefArgs = new int[] { 2 };
var oReturn = comObj.InvokeMethodByRef("ChiusuraMese", byRefArgs, IdDitta, PeriodoGiornaliera, IdDipendenti.PadLeft(), IdGruppoInstallazione, CodGruppoGestione);
DisposeCom(comObj);
return Convert.ToInt32(oReturn) == 0;
}
catch (Exception ex)
{
using (ErrorLog Log = new ErrorLog(System.Reflection.Assembly.GetExecutingAssembly().FullName, ex)) { }
aErrorMessage = ex.Message;
return false;
}
}
where InvokeMethodByRef is an extension method defined this way:
public static object InvokeMethodByRef(this object comObj, string methodName, int[] byRefArgs, params object[] args)
{
var modifiers = new ParameterModifier(args.Length);
byRefArgs.ToList().ForEach(index => { modifiers[index] = true; });
return comObj.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, comObj, args, new ParameterModifier[] { modifiers }, null, null);
}
Left out of the apartment
For what I understood, this whole apartment stuff is really hard to get right, with its cross-thread marshalling, message loop, yadda yadda whatnot. Add to that we're using and old, unsupported technology used to develop an application that was not architected for the purpose we're forcing it into. All that said, and taken for grant that the .Net side of things is working correctly, a couple of thoughts still wander in our minds. In particular:
is this the correct way to get advantage of multithreading with COM? Sometimes, multiple requests for the same object get stuck as if queued. This makes us wonder whether COM is actually sharing some instances between threads;
are we really creating and disposing of objects with each request, or under the hood COM handles things differently? Apparently, we're getting public vars overwritten, so there's probably some resource contention and reentering somewhere we wouldn't expect;
is the setup correct? Are there alternatives which are easier to maintain and debug? Please keep in mind we don't have neither the time nor the resources to rewrite anything in great extent. We could probably try something like creating an exe ActiveX, but I wouldn't count on that.
what's the "least worse" way to use OCXs in a project of this kind (not using them is not an option at the moment)? Should we dispose of them in some particular way? We already checked we set them to nothing when finished, but maybe some other thread is still using them;
should we be aware of any particular COM limit related to our out of memory issue? We encountered the problem before when the form had more than 256 unique controls displayed. Maybe the same is happening here somehow? The error seems to be especially related to classes using UI components.
Things I've already read (and probably did not understand)
Before you point to resources online I should read, I add here some topics I've encountered, in random order:
About SingleUse/MultiUse
http://www.vb-helper.com/howto_activex_dll.html
https://msdn.microsoft.com/en-us/library/aa242108(v=vs.60).aspx
Not really much choice here, if we want to stick with ActiveX DLLs with forms.
About (apartment) threading
https://msdn.microsoft.com/en-us/library/aa716297(v=vs.60).aspx
https://msdn.microsoft.com/en-us/library/aa716228(v=vs.60).aspx. By the way, this one probably hints that calls to objects are being serialized for access by other threads.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms680112%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
About debugging
https://msdn.microsoft.com/en-us/library/aa241684(v=vs.60).aspx
https://msdn.microsoft.com/en-us/library/aa716193%28v=vs.60%29.aspx?f=255&MSPPError=-2147217396
Could a stack dump be of any help when we face the error? I don't even know how to use WinDbg, so I'd like at least to know if that would be a total waste of time :D
We're kinda stuck here, as we've got no clue as to where or what to look for, so any kind of help would be really appreciated.
Comments
So I've been pointed out I should read more about COM's threading model. I kind of expected that. Anyhow, to elaborate further, let me write some comments.
First, I don't have any control over CoInitialize or whatever, I'm just instantiating some VB6 dlls. I guess COM is doing such and such under the hood. Fact is, I could not find anywhere what that is (edit - apparently, .Net is already taking care of that for me, see the answer to this question: Do i need to call CoInitialize before interacting with COM in .NET?).
To recap:
I'm using STA threads from the client app
I'm using Activator.CreateInstance supposing it is actually creating a new object every time it is called. The call is done within a new STA thread.
Let's set aside for a moment questions about thread-safety in the actual DLLs. What I'm mainly interested in understanding here is if the described solution is a correct way (possibly not the best way, I'm aware of that) to exploit multithreading with COM libraries.
To cite some sources, to the best of my current knowledge I should be in the situation depicted in Figure 8.5 here: https://msdn.microsoft.com/en-us/library/aa716228(v=vs.60).aspx
I can't find any reason why this should not work, since as I said I'm supposing each object resides in its own apartment and has its own variables, plus a copy of global vars (see here: https://msdn.microsoft.com/en-us/library/aa261361(v=vs.60).aspx).

Categories

Resources