Debugging DLLImport in C# pt. 2 - c#

I am using the MySQL Embedded Library and using P/Invoke to call the necessary functions to start the server. We resolved some issues regarding it in this topic, however another issue has presented itself.
The mysql_server_init() function returns 0 if success, 1 if error. Unfortunately, in my code when it returns 1, and I use Marshal.GetLastWin32Error() the error code is 0. I am assuming that it is not picking up on the error being generated by mysql_server_init(), but I am at a loss as to how to find out where the problem is.
Here is the relevant code block...
[DllImportAttribute("libmysqld.dll", SetLastError = true)]
static extern int mysql_server_init(int argc, string[] argv, string[] groups);
static string[] server_options = new string[2];
static string[] server_groups = new string[3];
public static bool Start()
{
server_options[0] = "mysql_test"; // not used?
server_options[1] = "--defaults-file=./my.ini";
server_groups[0] = "client";
server_groups[1] = "server";
server_groups[2] = "\0";
if (mysql_server_init(2, server_options, server_groups) != 0)
{
int lastError = Marshal.GetLastWin32Error();
Console.WriteLine("MySQL Library Init Failed with error code: " + lastError);
return false;
}
Console.WriteLine("MySQL Library Started Successfully!");
return true;
}

The mysql_server_init function does not report errors via the Win32 error reporting mechanism SetLastError() and GetLastError(). So, you can"t use Marshal.GetLastWin32Error() to obtain the last error. The embedded mysql database reports errors via the functions mysql_error() and mysql_errno(). However, those functions seem to only report errors AFTER a successful call to mysql_server_init().
I think the problem of your code lies in the way you terminate your server_groups array.
You should use "null" instead of "\0" to "terminate" your array:
public static bool Start()
{
server_options[0] = "mysql_test"; // not used?
server_options[1] = "--defaults-file=./my.ini";
server_groups[0] = "client";
server_groups[1] = "server";
server_groups[2] = null;
if (mysql_server_init(2, server_options, server_groups) != 0)
{
int lastError = Marshal.GetLastWin32Error();
Console.WriteLine("MySQL Library Init Failed with error code: " + lastError);
return false;
}
}
Errors regarding your configuration should be printed to the console window by the mysql_server_init() function.
Hope, this helps.

Related

Can you check python code in csharp before pythonNet execution?

i have recently started using pythonNet for executing scripts from Csharp, on an algorithm i was doing in csharp up until now, it works pretty well:
using (Py.GIL())
{
PythonEngine.Initialize();
using (var scope = Py.CreateScope())
{
string code = File.ReadAllText(fileName);
var scriptCompiled = PythonEngine.Compile(code, "Analyze.py");
scope.Execute(scriptCompiled);
dynamic func = scope.Get("predictFromData");
PyList Pydata = new PyList(data.ToPython());
PyTuple rettp = new PyTuple(func(Pydata));
PyList pyIndexList = new PyList(rettp[0]);
foreach (PyObject intobj in pyIndexList)
{
indexList.Add(intobj.As<int>());
}
}
}
But i'd like to know if there is a way to check if the code can be executed before actually running it, since it works with compiled code, and since PythonNet does require an external python installation to see if every modules are here ect... And then switch back to my previous csharp algorithm if it is not possible in python.
For now i'm thinking about simply executing a python unit test importing modules and testing functions with dummy values and returning exceptions and units tests values to csharp code, but i'd prefer a cleaner way if anyone has an idea.
Cheers.
There are few things you can check here:
first is to see if Python code has correct syntax, it can be done with the code like this:
public static IReadOnlyList<ScriptCompilationDiagnostic> CheckErrors(ScriptEngine engine, string script, string fileName, RunFlagType mode)
{
try
{
PythonEngine.Compile(script, fileName, mode);
}
catch (PythonException e)
{
dynamic error = e.Value;
return new[]
{
new ScriptCompilationDiagnostic
{
Kind = ScriptCompilationDiagnosticKind.Error,
Line = error.lineno - 1,
Column = error.offset - 1,
Message = error.msg,
Code = error.text,
FileName = error.filename,
},
};
}
return new ScriptCompilationDiagnostic[0];
}
second is that you can check if Python is installed on a target machine, with the code like this:
var pythonHome = TryGetFullPathFromPathEnvironmentVariable("python.exe");
private static string? TryGetFullPathFromPathEnvironmentVariable(string fileName)
{
if (fileName.Length >= MAXPATH)
throw new ArgumentException($"The executable name '{fileName}' must have less than {MAXPATH} characters.", nameof(fileName));
var sb = new StringBuilder(fileName, MAXPATH);
return PathFindOnPath(sb, null) ? sb.ToString() : null;
}
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = false)]
private static extern bool PathFindOnPath([In, Out] StringBuilder pszFile, [In] string[]? ppszOtherDirs);
If your script is using third-party modules, you may check that they're installed as well:
public bool IsModuleInstalled(string module)
{
string moduleDir = Path.Combine(PythonHome, "Lib", "site-packages", module);
return Directory.Exists(moduleDir) && File.Exists(Path.Combine(moduleDir, "__init__.py"));
}
Please note that Python.NET does not officially support the latest Python version 3.9, so alternatively you can distribute and install embedded python with your application from here:
https://www.python.org/ftp/python/3.7.3/
alongside with all required third-party modules as wheels.
We use this approach in our AlterNET Studio product to check if Python is installed for our Python debugger based on Debug Adapter Protocol, and install embedded Python with wheels for our Python.NET based scripter/debugger.

How to open the "Active Directory Users and Computers" object properties dialog from c#?

Is there a way to call this dialog from c#?
I traced the apis, but non of the calls seems to call the dialog. Dsuiext.dll sounds very promissing, but there I foud just a LDAP browser.
This Microsoft sample provides the expected result. You pass an ADS path as parameter and it calls the property window.
PropSheetHost.exe "LDAP://CN=user,DC=MyDomain,DC=MyTldDomain"
It is important that it is case sensitive, so "ldap://.." doesn't work. The code is definitely not designed to get called multiple times before terminating, so it is probably the best way to use the exe without changes like that:
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = #"PropSheetHost.exe";
startInfo.Arguments = #"LDAP://CN=user,DC=MyDomain,DC=MyTldDomain";
Process.Start(startInfo);
I wrote a wrapper to call it directly from C# and corrected the error what I found. Since I haven't programmed C for nearly 30 years, I am grateful for any hint if the implementation is incorrect. All changes are explained and marked with //MW: .... This works in my code, but you can open only one windows at a time and need to close it before opening another window.
The entry point:
__declspec(dllexport) HRESULT __stdcall CallPropSheetHost(const char* ldapPath)
{
TCHAR szTemp[MAX_ADSPATH_CHARS];
LPWSTR pwszADsPath = NULL;
HRESULT hr = E_FAIL; // MW: move before "if" and preset
CoInitialize(NULL);
{
//MW: copy the parameter
_tcsncpy_s(szTemp, ARRAYSIZE(szTemp), ldapPath, MAX_ADSPATH_CHARS - 1);
}
DWORD dwChars = lstrlen(szTemp) + 1;
pwszADsPath = new WCHAR[dwChars];
if (pwszADsPath)
{
HINSTANCE hInstance = NULL;
HWND hwndConsole = GetConsoleWindow();
if (hwndConsole)
{
hInstance = (HINSTANCE)(LONG_PTR)GetWindowLongPtr(hwndConsole, GWLP_HINSTANCE);
}
CPropSheetHost* pHost = new CPropSheetHost(hInstance);
LocalToWideChar(pwszADsPath, dwChars, szTemp, dwChars);
// Hold a reference count for the CPropSheetHost object.
pHost->AddRef();
hr = pHost->SetObject(pwszADsPath);
if (FAILED(hr))
{
goto ExitMain;
}
//MW: My implmentation returns E_Fail when the registration fails
hr = pHost->Run();
if (FAILED(hr))
{
pHost->Release();
goto ExitMain;
}
//Release the CPropSheetHost object. Other components may still hold a
//reference to the object, so this cannot just be deleted here. Let
//the object delete itself when all references are released.
pHost->Release();
}
ExitMain:
if (pwszADsPath)
{
delete pwszADsPath;
return hr; //MW: return th HRESULT
}
CoUninitialize();
return hr; //MW: return th HRESULT
}
The original implementation doesn't unregister a class. Therefore it fails when it's used multiple times. These are my changes in PropSheetHost.cpp to fix that.
//MW: new method
void CPropSheetHost::_UnregisterWndClass()
{
UnregisterClass(m_szHiddenWindowClass, m_hInst);
}
//MW: added a HRESULT and calling of "_UnregisterWndClass"
HRESULT CPropSheetHost::Run()
{
if (!m_spADObject.p)
{
return E_FAIL; //MW: added a return value
}
// Create the hidden window.
m_hwndHidden = _CreateHiddenWindow();
if (!m_hwndHidden)
{
return E_FAIL; //MW: added a return value
}
/*
Display the proeprty sheet. This is a modal call and will not return
until the property sheet is dimissed.
*/
_CreatePropertySheet();
// Destroy the hidden window.
DestroyWindow(m_hwndHidden);
//WM: Unregister the class; this call was missing
_UnregisterWndClass();
return ERROR_SUCCESS; //MW: added a return value
}
... and the call from C#:
using System;
using System.Runtime.InteropServices;
using System.Windows;
const int MAX_ADSPATH_CHARS = 2047;
[DllImport("PropSheetHost.dll", EntryPoint = "CallPropSheetHost", CallingConvention = CallingConvention.Cdecl)]
private static extern int CallPropSheetHost(string ldapPath);
///CAUTION:
/// * This call is modal and won't return until the called window is closed
/// * You can open only one window at a time. Trying opening a second window before closing the the first one fails
public static int Win32PropSheetHost(string distinguishedName, string serverOrDomain = null)
{
if (string.IsNullOrEmpty(distinguishedName)) throw new ArgumentNullException("EXC262: the distinguished name must not be null nor empty");
//<----------
/// Caution: "LDAP" must be uppercase!
string ldapPath = string.IsNullOrEmpty(serverOrDomain)
? $"LDAP://{ distinguishedName }"
: $"LDAP://{ serverOrDomain }/{ distinguishedName }";
if (ldapPath.Length > MAX_ADSPATH_CHARS) throw new ArgumentException($"EXC263: the complete lds path must not be longer than { MAX_ADSPATH_CHARS } characters (current path: \"{ ldapPath }\")");
//<----------
try
{
return CallPropSheetHost(ldapPath);
}
catch (DllNotFoundException ex)
{
/// Could't find a dll, mos likely our propsheethost.dll
return ResultWin32.ERROR_DLL_NOT_FOUND;
}
}
For the translation of the Windows Error Codes I use this class.

custom implementation of iTunesMobileDevice.dll throws NullReferenceException

I had intended to implement the Manzana.dll library in order to detect iPhone connection events and interact with the device. The problem is that it only seems to work if the client machine has the iTunes dlls and resources installed, which I cannot rely on. Therefore I am trying to use a custom implementation of the Manzana source code to point it's references to the necessary iTunes files that I am including with the project.
Although everything looks ok the compiled library throws a NullReferenceException when used from my application. The application load and initializes ok, but when an iPhone is connected the connectedevent throws an exception.
The actual error is:
System.TypeInitializationException: The type initializer for 'istreamwrapper.MobileDevice' threw an exception. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at istreamwrapper.MobileDevice..cctor()
--- End of inner exception stack trace ---
at istreamwrapper.MobileDevice.AMDeviceNotificationSubscribe(DeviceNotificationCallback callback, UInt32 unused1, UInt32 unused2, UInt32 unused3, Void*& am_device_notification_ptr)
at istreamwrapper.iPhone.doConstruction()
I was able to use that to narrow down the problem to to this method from my iPhone class
private unsafe void doConstruction()
{
void* voidPtr;
this.dnc = new DeviceNotificationCallback(this.NotifyCallback);
this.drn1 = new DeviceRestoreNotificationCallback(this.DfuConnectCallback);
this.drn2 = new DeviceRestoreNotificationCallback(this.RecoveryConnectCallback);
this.drn3 = new DeviceRestoreNotificationCallback(this.DfuDisconnectCallback);
this.drn4 = new DeviceRestoreNotificationCallback(this.RecoveryDisconnectCallback);
int num = MobileDevice.AMDeviceNotificationSubscribe(this.dnc, 0, 0, 0, out voidPtr);
if (num != 0)
{
throw new Exception("AMDeviceNotificationSubscribe failed with error " + num);
}
num = MobileDevice.AMRestoreRegisterForDeviceNotifications(this.drn1, this.drn2, this.drn3, this.drn4, 0, null);
if (num != 0)
{
throw new Exception("AMRestoreRegisterForDeviceNotifications failed with error " + num);
}
this.current_directory = "/";
}
}
The issue comes from
num = MobileDevice.AMDeviceNotificationSubscribe(this.dnc, 0, 0, 0, out voidPtr);
which points to this code which is located in my MobileDevice class
[DllImport("iTunesMobileDevice.dll", CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern int AMDeviceNotificationSubscribe(DeviceNotificationCallback callback, uint unused1, uint unused2, uint unused3, out void* am_device_notification_ptr);
That in turn seems to reference this in it's own class
namespace istreamwrapper
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void DeviceNotificationCallback(ref AMDeviceNotificationCallbackInfo callback_info);
}
which then points to another class with:
namespace istreamwrapper
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct AMDeviceNotificationCallbackInfo
{
internal unsafe void* dev_ptr;
public NotificationMessage msg;
public unsafe void* dev
{
get
{
return this.dev_ptr;
}
}
}
}
The vast majority of this code was copied straight from the Manzana.dll, the only thing I changed was where the itunesmobiledevice files are located (which is now a set path, rather detected at run time)
Old code:
namespace Manzana
{
internal class MobileDevice
{
private static readonly FileInfo iTunesMobileDeviceFile = new FileInfo(Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Apple Inc.\\Apple Mobile Device Support\\Shared", "iTunesMobileDeviceDLL", (object) "iTunesMobileDevice.dll").ToString());
private static readonly DirectoryInfo ApplicationSupportDirectory = new DirectoryInfo(Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Apple Inc.\\Apple Application Support", "InstallDir", (object) Environment.CurrentDirectory).ToString());
private const string DLLName = "iTunesMobileDevice.dll";
static MobileDevice()
{
string str = MobileDevice.iTunesMobileDeviceFile.DirectoryName;
if (!MobileDevice.iTunesMobileDeviceFile.Exists)
{
str = Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles) + "\\Apple\\Mobile Device Support\\bin";
if (!File.Exists(str + "\\iTunesMobileDevice.dll"))
str = "C:\\Program Files\\Common Files\\Apple\\Mobile Device Support";
}
Environment.SetEnvironmentVariable("Path", string.Join(";", new string[3]
{
Environment.GetEnvironmentVariable("Path"),
str,
MobileDevice.ApplicationSupportDirectory.FullName
}));
}
New code:
namespace istreamwrapper
{
class MobileDevice
{
static MobileDevice()
{
string str = "[XX_MYPATHHERE_XX]\\Apple\\Mobile Device Support";
string AppSuppDirectory = #"[XX_MYPATHHERE_XX]\Apple\Apple Application Support";
Environment.SetEnvironmentVariable("Path", string.Join(";", new string[3] { Environment.GetEnvironmentVariable("Path"), str, AppSuppDirectory }));
}
Is there something I'm missing that is causing that call to return null? I'll admit I don't fully understand everything that is happening in the above code so it's entirely possible it's something simple.
Yes, I believe the answer was really that the path was incorrect and thus could not find the file. I just didn't realize this because the error it was throwing was too generic.

ExecuteInDefaultAppDomain returns 8013101B

I am trying to host CLR in my native Win32 C++ application.
CLR loading works okay, but when i try to execute a method in the assembly, then ExecuteInDefaultAppDomain returns 0x8013101B, and bails out.
Here is the code snippet:
// Managed Code
namespace ManagedLibrary
{
public class LibraryBootstrapper
{
static LibraryBootstrapper()
{
MessageBox.Show("Static LibraryBootsrapper");
}
public LibraryBootstrapper()
{
}
public static int Initialize(String str)
{
MessageBox.Show("Hi " + str + ", Library Bootsrapped");
return 0;
}
}
// Native Code
int tmain()
{
// Bind to the runtime.
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(
NULL, // Load the latest CLR version available
L"wks", // Workstation GC ("wks" or "svr" overrides)
0, // No flags needed
CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost,
(PVOID*)&pClrHost);
// Now, start the CLR.
HRESULT hrStart = pClrHost->Start();
DWORD result = 0;
// Load an assembly and execute a method in it.
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(L"C:\\KIRAN\\Workspaces\\VS 2010\\HostCLR\\ManagedLibrary\\bin\\Debug\\ManagedLibrary.dll", L"ManagedLibrary.LibraryBootstrapper", L"Initialize", L"Kiran", &result);
//HRESULT hrStop = pClrHost->Stop();
return;
}
I figured it out!
The problem was that the versions of .NET frame that was being referenced by native and managed projects were different. Syncing that up worked.
And, btw, the error code 0x8013101B, corresponds to COR_E_NEWER_RUNTIME (see corerror.h), which helped me figure out the problem.
The error codes are explained here: http://blogs.msdn.com/b/yizhang/archive/2010/12/17/interpreting-hresults-returned-from-net-clr-0x8013xxxx.aspx

p4.net cannot connect Perforce

I am using p4.net API to generate some reports from the metadata.
In one of the reports, I need to generate then number of the changes lines for each changeset report.
As a reporting tool, I am using MS SQL Reporting services 2008, and I have written a custom dll that uses p4.net API to calculate the number of changed lines. it works on the local without any problem. However, when I run the code on the server, it calculates let's say first %20 part then starts throwing Unable to connect to the Perforce Server!
Unable to connect to Perforce! exception.
I try same credentials on the local, it works.. I use commandline with same credentials on the server, it works.
Could anyone help me with that please, if encountered before?
Here is the code I use. If needed
public static class PerforceLib
{
public static P4Connection p4conn = null;
private static void CheckConn()
{
try
{
if (p4conn == null)
{
p4conn = new P4Connection();
p4conn.Port = "address";
p4conn.User = "user";
p4conn.Password = "pwd*";
p4conn.Connect();
p4conn.Login("pwd");
}
else if (p4conn != null)
{
if(!p4conn.IsValidConnection(true, false))
{
Log("Check CONN : Connection is not valid, reconnecting");
p4conn.Login("pwd*");
}
}
}
catch (Exception ex )
{
Log(ex.Message);
}
}
public static int DiffByChangeSetNumber(string ChangeSetNumber)
{
try
{
CheckConn();
P4Record set = p4conn.Run("describe", "-s",ChangeSetNumber)[0];
string[] files = set.ArrayFields["depotFile"].ToArray<string>();
string[] revs = set.ArrayFields["rev"].ToArray<string>();
string[] actions = set.ArrayFields["action"].ToArray<string>();
int totalChanges = 0;
List<P4File> lstFiles = new List<P4File>();
for (int i = 0; i < files.Count(); i++)
{
if (actions[i].ToString() == "edit")
lstFiles.Add(new P4File() { DepotFile = files[i].ToString(), Revision = revs[i].ToString(), Action = actions[i].ToString() });
}
foreach (var item in lstFiles)
{
if (item.Revision != "1")
{
string firstfile = string.Format("{0}#{1}", item.DepotFile, (int.Parse(item.Revision) - 1).ToString());
string secondfile = string.Format("{0}#{1}", item.DepotFile, item.Revision);
P4UnParsedRecordSet rec = p4conn.RunUnParsed("diff2", "-ds", firstfile, secondfile);
if (rec.Messages.Count() > 1)
{
totalChanges = PerforceUtil.GetDiffResults(rec.Messages[1].ToString(), item.DepotFile);
}
}
}
GC.SuppressFinalize(lstFiles);
Log(string.Format("{0} / {1}", ChangeSetNumber,totalChanges.ToString() + Environment.NewLine));
return totalChanges;
}
catch (Exception ex)
{
Log(ex.Message + Environment.NewLine);
return -1;
}
}
}
your help will be appreciated
Many thanks
I have solved this issue. we identified that the code is circling through the ephemeral port range in around two minutes. once it reaches the maximum ephemeral port, it was trying to use same port again. Due to each perforce command creates a new socket, available ports were running out after it processed about 1000 changesets.
I have set the ReservedPorts value of HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters default(1433,143) that gave me larger range of ephemeral port.
and also implemented singleton pattern for P4Conn which helped as I dont close the connection. I only check the validity of the connection, and login if the connection is not valid.
Please let me know if any of you guys needs any help regarding this

Categories

Resources