Calling .NET Assembly from PowerBuilder (Exposing .NET Framework Components to COM) - c#

Here is the programming environment.
Framework: ASP.NET Framework 4
Language: Visual C# 2010, PowerBuilder 12.5.2 Build 5609 Built on Jan 2 2014 at 01:29:51
Here is the scenario.
I'm creating a PowerBuilder application that fires up a simple window with a multi-line editing textbox where you can type something in there and click a button to load a C# COM class that checks for spelling errors and returns the value back to the PowerBuilder application's textbox.
C# ClassLibrary.cs
using System;
using System.Runtime.InteropServices;
namespace InteropServices
{
[Guid("Let's just assume that I have a correct Guid string here.")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISpellChecker
{
[(DispId(1)]
string CheckText(string inputMsg);
[(DispId(2)]
void Dispose();
}
[ClassInterface(ClassInterfaceType.None)]
[Guid("Let's just assume that I have a correct Guid string here.")]
[ProgId("InteropServices.SpellChecker")]
public class SpellChecker: ISpellChecker
{
private string newInputMsg
public string inputMsg
{
get
{
return newInputMsg;
}
set
{
newInputMsg = value;
}
}
private App spellCheckerApp;
private MainWindow spellCheckerMainWindow;
public SpellChecker() { }
public string CheckText(string inputMsgBase)
{
inputMsg = inputMsgBase;
spellCheckerApp = new App();
spellCheckerMainWindow = new MainWindow(inputMsg);
spellCheckerApp.Run(spellCheckerMainWindow);
txtCorrected = MainWindow.TextReturned;
return txtCorrected;
}
public string txtCorrected { get; set; }
// This function was my futile attempt to resolve this issue, but it seemingly has no effect whatsoever.
public void Dispose()
{
spellCheckerMainWindow.Close();
spellCheckerApp.Shutdown();
spellCheckerMainWindow = null;
spellCheckerApp = null;
}
}
}
C# MainWindow.xaml
<Window x:Class="InteropServices.MainWindow"
xmlns="http://schemas.microsft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsft.com/winfx/2006/xaml"
Title="Spell Checker .NET" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<TextBox Name="TextBoxSpellCheck" SpellCheck.IsEnabled="True" AcceptsReturn="True" TextWrapping="Wrap" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True" Margin="50" />
<Button Name="ButtonAccept" Margin="229,267,146,12" Width="128" Height="32" Content="Accept" IsDefault="True" Click="ButtonAccept_Click" />
<Button Name="ButtonCancel" Margin="364,267,12,12" Width="128" Height="32" Content="Cancel" IsDefault="True" Click="ButtonAccept_Click" />
</Grid>
</Window>
C# MainWindow.xaml.cs
using System;
// Omitting the rest of the default Usings
namespace InteropServices
{
public partial class MainWindow : Window
{
private string txtChecked;
private int caretIdx;
private SpellingError spellingErr;
private string p;
public MainWindow(string p)
{
this.p = p;
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
txtChecked = p;
TextBoxSpellCheck.Text = txtChecked;
caretIdx = TextBoxSpellCheck.CaretIndex;
spellingErr = TextBoxSpellCheck.GetSpellingError(caretIdx);
if (spellingErr == null)
{
MessageBox.Show("No spelling error was found. Click OK to continue.", "Congratulations!");
txtReturned = p;
Application.Current.Shutdown();
}
}
private void ButtonAccept_Click(object sender, RoutedEventArg e)
{
txtReturned = TextBoxSpellCheck.Text;
Application.Current.Shutdown();
}
private void ButtonCancel_Click(object sender, RoutedEventArg e)
{
txtReturned = p;
Application.Current.Shutdown();
}
public static string txtReturned
}
}
PowerBuilder event clicked for type cb_1 from commandbutton within main
mle_1.Text = "Teh quik brownn fox junps ober teh lazy dgo!" // Too lazy to copy and paste this into the app's textbox, so here it is...
txtChecked = mle_1.Text
myOLEObject = CREATE OLEObject
result = myOLEObject.ConnectToNewObject("InteropServices.SpellChecker")
IF result < 0 THEN
DESTROY myOLEObject
MessageBox("Connecting to COM Object Failed", "Error: " + String(result))
RETURN
ELSE
txtCorrected = myOLEObject.CheckText(txtChecked) // This is the line that causes an error.
mle_1.Text = txtCorrected
END IF
myOLEObject.Dispose() // This function was my futile attempt to resolve this issue, but it seemingly has no effect whatsoever.
myOLEObject.DisconnectObject()
DESTROY myOLEObject
PowerBuilder Instance Variables for main
String txtChecked
String txtCorrected
Int result
OLEObject myOLEObject
The solution that I came up with so far has been acting very weird. Clicking the button from the PowerBuilder application works only once after launching PowerBuilder or the deployed executable, and it would give the following message on further attempts at clicking the button again:
PowerBuilder application execution error (R0035)
Application terminated.
Error: Error calling external object function checktext at line 11 in
clicked event of object cb_1 of main.
What should I change from these codes to make it work every time?

The diagnostic you get from PowerBuilder is entirely too inadequate to ever have a shot at debugging the problem. Best thing to do here is to use the Visual Studio debugger:
Project + Properties, Debug tab. Select the "Start external program" radio button and select your PowerBuilder test program.
Debug + Exceptions, tick the Thrown checkbox for CLR Exceptions. This makes the debugger stop when your program throws an exception.
Set breakpoints at the start of CheckText() and on the statement after the Run() call. One in the Loaded event handler ought to be useful.
Press F5 to start debugging.
When you click the PowerBuilder button, your first breakpoint should hit. Check if you like the inputMsgBase value, press F5 to continue. Press the button again, good odds that the debugger now stops and tells you what is wrong. I have some guesses, nothing I'd risk right now without knowing what you see.

Related

WPF App exits when window is newed up as a field

I start with a default WPF project, then I change the entry point in the App.xaml to be Startup instead of StartupUri, as follows:
<Application ...
Startup="Application_Startup">
<Application.Resources>
</Application.Resources>
</Application>
Then I add a button to the MainWindow.xaml...
<Window ...>
<Grid>
<Button Click="Button_Click">Test</Button>
</Grid>
</Window>
With the following code:
void Button_Click(object sender, RoutedEventArgs e)
{
new Window
{
Height = 300,
Width = 300,
}.ShowDialog();
}
TheProblem
Now in the App.xaml.cs code I have the following:
using System.Windows;
namespace wpfCrash3
{
public partial class App : Application
{
readonly MainWindow _mainWindowBad = new MainWindow();
MainWindow _mainWindowGood;
private void Application_Startup(object sender, StartupEventArgs e)
{
// Scenario 1: This fails
_mainWindowBad.Show();
// Scenario 2: This succeeds
//_mainWindowGood = new MainWindow();
//_mainWindowGood.Show();
// Scenario 3: This also succeeds
//new MainWindow().Show();
}
}
}
If you run Scenario 1 in Visual Studio with the debugger attached - it works fine.
Bug Repro Steps:
CTRL+F5 / Run in VS without debugger
Click the button
Close the pop window
TEST: Main window should stay open
Scenario 1 fails this test!
If you comment out Scenario 1, then uncomment out either Scenarios 2 or 3 - they each work fine.
Scenario 1 succeeds only when the debugger is attached.
I'm using VS2022 and I'm seeing the same behaviour for .NET6 and .NET Framework 4.6.1. Confirmed with a friend he sees the same behaviour in VS2019.
Can anyone else confirm this behaviour?
Question
Is this a bug in WPF? I spent 2 days trying to find this bug. What is going on!?
Scenario 1 - App exits immediately after closing sub window:
Scenario 2 - App stays open after closing sub window:
This seems to be a bug, you're right.
If you try it like this, it works:
public partial class App : Application
{
private readonly MainWindow _mainWindowBad;
public App()
{
_mainWindowBad = new MainWindow();
}
private void Application_Startup(object sender, StartupEventArgs e)
{
// Scenario 1: This works now...
_mainWindowBad.Show();
// Scenario 2: This succeeds
//_mainWindowGood = new MainWindow();
//_mainWindowGood.Show();
// Scenario 3: This also succeeds
//new MainWindow().Show();
}
//this says that application exited with success even when it's closed automatically
protected override void OnExit(ExitEventArgs e)
{
string[] lines =
{
e.ApplicationExitCode.ToString()
};
File.WriteAllLines("WriteLines.txt", lines);
base.OnExit(e);
}
}
Hilarious behavior, not sure what is causing this, but I guess that it's runtime binding error when using field initializer.

Passing Command Line Arguments from VB to C# WPF Application

I have a C# WPF application which I want to be able to open from another existing application, which was written in VB.net. As far as the c# application goes, I think I know how to get command line parameters that are passed to it two different ways, which I got while researching google and using others' answers.
App.xaml Header
<Application x:Class="ChallengeHandler.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ChallengeHandler"
Startup="Application_Startup">
App.xaml.cs Method 1
private void Application_Startup(object sender, StartupEventArgs e)
{
string[] args = Environment.GetCommandLineArgs();
if (args.Length < 1)
{
MessageBox.Show("No parameter provided. Failed to run.");
Shutdown();
}
else
{
MainWindow wnd = new MainWindow(args[0]);
wnd.Show();
}
}
The above method will result in the application opening but none of the data, which relies on the parameter, is populated. So the comboboxes and stuff in the views are just empty. That fails.
App.xaml.cs Method 2
private void Application_Startup(object sender, StartupEventArgs e)
{
if (e.Args.Length < 1)
{
MessageBox.Show("No parameter provided. Failed to run.");
Shutdown();
}
else
{
MainWindow wnd = new MainWindow(e.Args[0]);
wnd.Show();
}
}
This method just shows the error messagebox each time, as the args is empty.
I have a feeling the issue is when I'm trying to open the application from the VB.NET application and pass the string parameter to the c# app from there. But I am out of ideas on how to pass a string like a command line parameter from the VB.net code.
Calling from a VB.net Application
Dim sPath As String = Environment.GetFolderPath(Environment.SpecialFolder.Programs) + "\Microsoft\ChallengeHandler.appref-ms"
Dim pHelp As New ProcessStartInfo
If System.IO.File.Exists(sPath) Then
pHelp.FileName = sPath
pHelp.Arguments = "097"
pHelp.UseShellExecute = True
pHelp.WindowStyle = ProcessWindowStyle.Normal
Dim proc As Process = Process.Start(pHelp)
End If
I have tried the VB code without the
pHelp.UseShellExecute = True
pHelp.WindowStyle = ProcessWindowStyle.Normal
with no avail; I added them in the hope the shell execute would force the parameters as command line parameters. I have also tried this in VB:
2nd VB Method
Dim sPath As String = Environment.GetFolderPath(Environment.SpecialFolder.Programs) + "\Microsoft\ChallengeHandler.appref-ms"
If System.IO.File.Exists(sPath) Then
System.Diagnostics.Process.Start(sPath, "097")
End If
Any insight would be GREATLY appreciated! Thanks.
I see you're using "\Microsoft\ChallengeHandler.appref-ms". This is a ClickOnce application. Getting the parameters for a ClickOnce application is completely different from a normal application. You need to use ApplicationDeployment.CurrentDeployment.ActivationUri.Query and HttpUtility.ParseQueryString for retrieving them.
I believe to send them across you'll have to add them to the launch url by using "?param=value". I've only tried launching it from a web page, so I'm unsure if this is how it works.
The method you're currently using is valid for normal application. If you can locate the exe and launch that directly, you should be fine.
I created 2 projects: Line command C# invoker and a WPF Test application;
The invoker code on Program.cs:
namespace WPFInvoker
{
class Program
{
static void Main(string[] args)
{
Process.Start(#"C:\Users\...\bin\Debug\WPF_Test.exe", "example_parameter");
}
}
}
Then on the WPF App.xaml I have the startup event app_Startup and the main form MainWindow.xaml:
<Application x:Class="WPF_Test.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF_Test"
Startup="app_Startup"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
And the App.xaml.cs code is:
namespace WPF_Test
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
void app_Startup(object sender, StartupEventArgs e)
{
if (e.Args != null && e.Args.Length > 0)
{
MessageBox.Show(e.Args[0]);
}
else
{
MessageBox.Show("e.Args is null");
}
}
}
}
When I open the WPFTest.exe with a double click shows a MessageBox with the message "e.Args is null":
Application without parameters
But if I open the WPFTest application through WPFInvoker:
Application with parameter
Finally I close the MessageBox and the MainWindow.xaml its shown.

Strange WPF error

I have some code that works intermittently and I can't understand why (worked perfectly until today morning when windows automatically installed some updates, but none related to .NET 4 - version used in my project).
My password box ...
<PasswordBox x:Name="TboxPassword" Grid.Row="1" Grid.Column="0"
controls:TextboxHelper.Watermark="Password ..."
controls:TextboxHelper.ClearTextButton="True"
Margin="10, 10, 0, 0">
<i:Interaction.Behaviors>
<misc:PasswordBoxBehavior Password="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
</i:Interaction.Behaviors>
</PasswordBox>
My behavior:
public class PasswordBoxBehavior : Behavior<PasswordBox>
{
#region Fields
private readonly object _tryToExecuteActionSyncObject = new object();
private bool _isUpdating;
#endregion
#region Properties
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(string), typeof(PasswordBoxBehavior),
new PropertyMetadata(string.Empty, OnPasswordPropertyChanged));
#endregion
#region Methods
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PasswordChanged += OnAssociatedObjectPasswordChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PasswordChanged -= OnAssociatedObjectPasswordChanged;
}
private void OnAssociatedObjectPasswordChanged(object sender, RoutedEventArgs e)
{
TryToExecuteAction(() => Password = AssociatedObject == null
? string.Empty
: AssociatedObject.Password);
}
private static void OnPasswordPropertyChanged
(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
PasswordBoxBehavior passwordBoxBehavior;
if (sender == null
|| (passwordBoxBehavior = sender as PasswordBoxBehavior) == null
|| passwordBoxBehavior.AssociatedObject == null)
{
return;
}
passwordBoxBehavior.TryToExecuteAction
(() => passwordBoxBehavior.AssociatedObject.Password =
(e.NewValue == null
? string.Empty
: (string) e.NewValue));
}
private void TryToExecuteAction(Action actionToExecute)
{
bool continueExecution;
lock (_tryToExecuteActionSyncObject)
{
continueExecution = _isUpdating == false;
_isUpdating = true;
}
if (continueExecution == false)
{
return;
}
try
{
if (actionToExecute != null)
{
actionToExecute();
}
}
finally
{
lock (_tryToExecuteActionSyncObject)
{
_isUpdating = false;
}
}
}
#endregion
}
I get 0 (zero) compilation errors. When running the application, 90% of the time I'm getting a runtime exception stating that:
{"Cannot add instance of type 'PasswordBoxBehavior' to a collection of type 'BehaviorCollection'. Only items of type 'T' are allowed."}
Debugger stops at the tag Interaction.Behaviors
Please keep in mind that I never received this error until today. Now I receive it even after I revert everything I done today.
Please advise .. :D
PS: I just commented out all the code from inside the behavior. Also removed the Password binding. Still doesn't work :(
PPS: If I close Visual Studio (2012), delete my bin folder, open VS, open project, rebuild all, the application WORKS until the first change to the code.
I know this question is very old. But I will post the answer in case someone face it again.
I just came across the same issue and I found a solution, I noticed that you are using behaviors in your code. Just make sure that your are referencing the right version of Blend SDK Windows.Interactivity
In my case the problem was that I installed the NugetPackage for Blend SDK for WPF 4.5 only on the main WPF project, and I forgot to install it on the other WPF projects in the solution that uses behaviors.
I solved the problem by installing the same NugetPackage on the project containing the code causing the problem.
Hope this helps you!
Blend SDK Windows.Interactivity has been abandoned, and the file System.Windows.Interactivity.dll has been removed from GAC. Use Microsoft.Xaml.Behaviors.Wpf instead, you can install it from Nuget.org.
If you switch the "interactive library" from Blend SDK Windows.Interactivity(System.Windows.Interactivity) to Microsoft.Xaml.Behaviors.Wpf(Microsoft.Xaml.Behaviors)
You can:
Remove System.Windows.Interactivity reference from project.
Close/Unload this solution/project, and open the solution/project directory.
Delete belows folders :.vs, bin, obj.
Reopen/Reload the solution/project.
Replace all namespace of System.Windows.Interactivity to Microsoft.Xaml.Behaviors
Build the solution/project.
Then it will be fine.

WPF Application exit code

I am trying to set and get the application exit code .
I am trying to do something following :
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
if ( e.Args.Length != 0)
{
}
else
{
new MainWindow().ShowDialog();
}
Environment.ExitCode = 110;
this.Shutdown();
}
And then I am trying in cmd to get it by echo %ERRORLEVEL%
But I get always result 0 , any idea what is the issue ?
For WPF, try
Application.Current.Shutdown(110);
Note that the application needs to be running as a console app. This answer is the easiest way I know of; the accepted answer looks more difficult.
An easy test to tell if you're running in console mode: call your app from the command line (make sure your code doesn't shut down right away). The main window should be showing. If you can type another command in the console, your app is not running in its context. The command prompt should be locked, waiting for you to close the window.
You can do it in Main method. Just change its return-value-type to int instead of void and return your exit-code
static int Main(string[] args) {
// something to do
Console.ReadKey();
return 110;
}
UPDATE:
To create a custom Main in WPF application, you should follow these steps:
First: unload the project by right-click on it in Solution Explorer
and click on Unload Project
Modify the .csproj file by change the <ApplicationDefinition Include="App.xaml"> to this one: <Page Include="App.xaml">
Now you can create your own Main method in your project:
Sample Main method and App class:
public partial class App : Application {
[STAThread]
public static int Main() {
App app = new App();
app.InitializeComponent();
var i = app.Run();
return i;
}
public App() : base() { }
protected override void OnExit(ExitEventArgs e) {
e.ApplicationExitCode = 110;
base.OnExit(e);
}
}
override the OnExit method, and in the ExitEventArgs you can set that value.
protected override void OnExit(ExitEventArgs e)
{
e.ApplicationExitCode = your_value;
}
It works for me with either method (Environment.ExitCode=110 or Environment.Exit(110)). I hope you are calling the program from the console and not from Visual Studio to then check the ExitCode...
Do it like this:
Environment.Exit(110);
This will terminate the current application with exit code 110.
You can do this in the following ways...
Application.Current.Shutdown(110);
Environment.Exit(10);
this.Close();
Shoutdown() returns a code. and Exit() also returns an exit code, but Close() only closes the application.

c# - WPF command-line argument for opening file gives infinite loop

This is a weird one! I am working on an application that reads vCard files, which contain contact etc. information for a person. Each file may contain separate 'sections' that each contain the details for one person, which are separated by BEGIN:VCARD [data here] END:VCARD.
To enable my users to view all of the different details, I've allowed my program to populate the textboxes in my app with the details and then open a new Window and do this with that one, but for each of the different sections in the file.
The problem comes about when my program opens when a vCard file has been double clicked in Explorer. It keeps looping through the vCard. I don't know what to do, but below is my problematic code:
public void readVcard(string fname)//Reads vCard and then loops through sections
{
try
{
using (StreamReader r = new StreamReader(fname))
{
string input = File.ReadAllText(fname);//read through file
String[] vArray = input.Split(new string[] { "BEGIN:VCARD" }, StringSplitOptions.None);
int i;
for (i = 1; i < vArray.Length; i++)
{
MainWindow a = new MainWindow();
a.parser(vArray[i]); //Parser is the function that populates the app
a.Show();
}
return;
}
}...
This function is called from here:
void MainWindow_Loaded(object sender, RoutedEventArgs e)//Processes a file when opened externally
{
if (Application.Current.Properties["ArbitraryArgName"] != null)
{
string fname = Application.Current.Properties["ArbitraryArgName"].ToString();
readVcard(fname);
}
}
If anyone could help, it would be greatly appreciated.
I think that Artyom is on the right track.
Every time you create another MainWindow and load it you will be getting the current applications argurment and jumping back in to readVcard, which will process the same vCard that you are already processing and open yet another MainWindow which will continue the process.
Consider moving all of the code you have inside of MainWindow_Loaded() to the Startup event for your application. That way it will only get called once when your program first loads, instead of every time you create a new window.
To do this you need to register for the event in your App.xaml file like so:
<Application x:Class="MyProgram.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
</Application>
And then in the code behind App.xaml you put your code for reading the vCard. Like this:
namespace MyProgram
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
if (Application.Current.Properties["ArbitraryArgName"] != null)
{
string fname = Application.Current.Properties["ArbitraryArgName"].ToString();
readVcard(fname);
}
}
}
}
When you create and show new MainWindow (a.Show()), the MainWindow_Loaded event fires again and it again calls a readVcard method. So there is an infinite loop.
Or may be not really infinite, because, I belive, some time later a StackOverflowException may happen.
You just need to review startup logic, so readVcard will launch not in the MainWindow_Loaded event, but, for example, in the Main method (in program.cs file). Or you may add some flag, which will be set when readVcard method first called.
I get it! I've now got the following code in App.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
if (e.Args != null && e.Args.Count() > 0)
{
this.Properties["ArbitraryArgName"] = e.Args[0];
}
base.OnStartup(e);
if (Application.Current.Properties["ArbitraryArgName"] != null)
{
string fname = Application.Current.Properties["ArbitraryArgName"].ToString();
MainWindow mw = new MainWindow();
mw.readVcard(fname);
}
}
}
It works fine! Thanks everyone. BTW the following blog contains the command-line info I originally used if anyone needs it: http://blogs.msdn.com/b/avip/archive/2008/10/27/wpf-supporting-command-line-arguments-and-file-extensions.aspx.

Categories

Resources