custom implementation of iTunesMobileDevice.dll throws NullReferenceException - c#

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.

Related

Cause of "500 No individual errors" in Google Play Store edit commit

I have a simple tool in C# which uses the Google.Apis.AndroidPublisher.v3 nuget package to deploy an app to the Internal Testing track as the last step in my automated build. It worked without problems from mid-July when I wrote the tool until mid-September (last successful execution: 2018-09-17). Then I didn't touch the app for a couple of weeks; as of last Friday (2018-09-28), the tool fails with a Google.GoogleApiException with no inner exception, message
Google.Apis.Requests.RequestError
[500]
No individual errors
and stack trace
at Google.Apis.Requests.ClientServiceRequest`1.<ParseResponse>d__34.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Google.Apis.Requests.ClientServiceRequest`1.Execute()
at MyProject.Tools.PlayStoreUploader.Program.Deploy(AndroidPublisherService service, String packageName, String releaseName, FileInfo apkFile, Int32 mainObbVersionCode, FileInfo patchObbFile, String releaseNotes) in C:\dev\MyProject\Tools\MyProject.Tools.PlayStoreUploader\Program.cs:line 211
at MyProject.Tools.PlayStoreUploader.Program.Main(String[] args) in C:\dev\MyProject\Tools\MyProject.Tools.PlayStoreUploader\Program.cs:line 126
The Deploy method, which essentially does all the work, is
private static void Deploy(
AndroidPublisherService service,
string packageName,
string releaseName,
FileInfo apkFile,
int mainObbVersionCode,
FileInfo patchObbFile,
string releaseNotes)
{
var edits = service.Edits;
// Create a new edit
string editId = edits.Insert(null /* no body */, packageName).Execute().Id;
// Upload new apk
int apkVersion;
using (Stream strm = apkFile.OpenRead())
{
var uploadRequest = edits.Apks.Upload(packageName, editId, strm, MimeTypeApk);
uploadRequest.Upload();
apkVersion = uploadRequest.ResponseBody.VersionCode.Value;
}
// Attach an existing main obb
edits.Expansionfiles.Update(
new ExpansionFile { ReferencesVersion = mainObbVersionCode },
packageName,
editId,
apkVersion,
UpdateRequest.ExpansionFileTypeEnum.Main).
Execute();
// Attach a new patch file
if (patchObbFile != null)
{
using (Stream strm = patchObbFile.OpenRead())
{
edits.Expansionfiles.Upload(
packageName,
editId,
apkVersion,
// This Google API is clearly auto-generated by a badly written tool, because it duplicates the enums.
UploadMediaUpload.ExpansionFileTypeEnum.Patch,
strm,
MimeTypeObb).
Upload();
}
}
// Assign apk to "Internal test" track.
var release = new TrackRelease
{
Name = releaseName,
VersionCodes = new long?[] { apkVersion },
ReleaseNotes = new List<LocalizedText> { new LocalizedText { Language = "en", Text = releaseNotes } },
Status = TrackReleaseStatus.Completed
};
edits.Tracks.Update(
new Track { Releases = new List<TrackRelease> { release } },
packageName,
editId,
TrackIdentifier.Internal).
Execute();
// Publish
edits.Commit(packageName, editId).Execute();
}
Relevant constants are
const string MimeTypeApk = "application/vnd.android.package-archive";
const string MimeTypeObb = "application/octet-stream";
// As documented at https://developers.google.com/android-publisher/tracks
static class TrackIdentifier
{
public const string Alpha = "alpha";
public const string Beta = "beta";
public const string Internal = "internal";
public const string Production = "production";
}
// As (poorly) documented at https://developers.google.com/android-publisher/api-ref/edits/tracks#resource
// See also https://android-developers.googleblog.com/2018/06/automating-your-app-releases-with.html
static class TrackReleaseStatus
{
/// <summary>Not yet rolled out to anyone</summary>
public const string Draft = "draft";
/// <summary> For staged rollouts to a small percentage of users</summary>
public const string InProgress = "inProgress";
/// <summary> Suspends a staged rollout</summary>
public const string Halted = "halted";
/// <summary> Full rollout</summary>
public const string Completed = "completed";
}
The exception is thrown in the penultimate line of Deploy, edits.Commit(packageName, editId).Execute();. That rules out authentication failure as a cause, since the earlier calls succeeded. The possible failure causes listed by the documentation for edits/commit are
You open another edit for the same app after you open this edit
Any other user commits an edit for the app while your edit is open
You or any other user makes a change to the app through the Developer Console while your edit is open
but I'm certain that none of those apply.
What else would explain why I can set up the edit, including uploading APK and OBB, but not commit it?
This sounds like a bug in the Play console, as demonstrated by the 500 error code which means "Internal Error".
When situations like this occur I would recommend contacting Play Console support to let them know of the problem. You can do this via the help menu in the Play Console, which is behind the "?" (question mark) icon.

Cannot kill process: "No process is associated with this object" error

In my program I try to start a new process (open video file in a default player). This part works OK. Later when I try to close the process (player) I get an error:
System.InvalidOperationException: No process is associated with this object.
My code:
string filename = "747225775.mp4";
var myProc = new Process()
{
StartInfo = new ProcessStartInfo(filename)
};
myProc.Start();
Thread.Sleep(5000);
try
{
myProc.Kill(); //Error is here
}
catch (Exception ex)
{
Debug.WriteLine(ex);
Debugger.Break();
}
What is wrong?
Process.Start associates the Process object with native process handle only when it spawns new process directly. When filename is used as an argument instead of executable name, Process searches registry for association settings via shell32.dll functions and the exact behavior depends on them.
When association is configured in traditional way, to call command line and transfer file name as 1st argument (such as for Notepad), Process.Start spawns new process directly and correctly associates object with native handle. However, when association is configured to execute COM-object (such as for Windows Media Player), Process.Start only creates some RPC query to execute COM object method and returns without associating object with process handle. (The actual process spawn occurs in svchost.exe context, according to my tests)
This issue can be solved by following modified process start method:
using System;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
namespace ProcessTest
{
public partial class Form1 : Form
{
[DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string pszExtra, [Out] StringBuilder pszOut, ref uint pcchOut);
/*Modified Process.Start*/
public static Process TrueProcessStart(string filename)
{
ProcessStartInfo psi;
string ext = System.IO.Path.GetExtension(filename);//get extension
var sb = new StringBuilder(500);//buffer for exe file path
uint size = 500;//buffer size
/*Get associated app*/
uint res = AssocQueryString(AssocF.None, AssocStr.Executable, ext,null, sb, ref size);
if (res != 0)
{
Debug.WriteLine("AssocQueryString returned error: " + res.ToString("X"));
psi = new ProcessStartInfo(filename);//can't get app, use standard method
}
else
{
psi = new ProcessStartInfo(sb.ToString(), filename);
}
return Process.Start(psi);//actually start process
}
public Form1()
{
InitializeComponent();
}
private void button2_Click(object sender, EventArgs e)
{
string filename = "c:\\images\\clip.wmv";
var myProc = TrueProcessStart(filename);
if (myProc == null)
{
MessageBox.Show("Process can't be killed");
return;
}
Thread.Sleep(5000);
try
{
myProc.Kill();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
[Flags]
enum AssocF : uint
{
None = 0,
Init_NoRemapCLSID = 0x1,
Init_ByExeName = 0x2,
Open_ByExeName = 0x2,
Init_DefaultToStar = 0x4,
Init_DefaultToFolder = 0x8,
NoUserSettings = 0x10,
NoTruncate = 0x20,
Verify = 0x40,
RemapRunDll = 0x80,
NoFixUps = 0x100,
IgnoreBaseClass = 0x200,
Init_IgnoreUnknown = 0x400,
Init_FixedProgId = 0x800,
IsProtocol = 0x1000,
InitForFile = 0x2000,
}
enum AssocStr
{
Command = 1,
Executable,
FriendlyDocName,
FriendlyAppName,
NoOpen,
ShellNewValue,
DDECommand,
DDEIfExec,
DDEApplication,
DDETopic,
InfoTip,
QuickTip,
TileInfo,
ContentType,
DefaultIcon,
ShellExtension,
DropTarget,
DelegateExecute,
SupportedUriProtocols,
Max,
}
}
Here we get the file type's associated application via AssocQueryString. The returned value is then passed to ProcessStartInfo. However it does not always work, so we sometimes have to resort to standart method. For example, image files does not have any associated exe, it's just dll being loaded into explorer's process. So we can't outright kill process in this case.
to answer your question: "What is wrong?"
I can say the underline cause of this is related to Windows Apps that are launched to handle the type of file (.mp4).
From what I can determine.. there isn't anything wrong with your code sample except that it doesn't account for this scenario (in which, admittingly, I do not understand why it behaves this way).
To replicate this, I used your code sample and a image file (.png). the program launches with 'Photos' by default.
I changed .png files to launch with Paint application by default, then ran the program again. The code sample you've provided worked fine on the desktop application.

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

Debugging DLLImport in C# pt. 2

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.

"MoveFile" function in C# (Delete file after reboot)

I am in need of an example, that can let me pass a parameter
e.g. executing delete.exe /killme.txt
So it will use the "MoveFile" to delete killme.txt after reboot.
Although please not the MS precompiled version, as it has an annoying disclaimer, every time its run on a different computer.
You'll need the P/Invoke declarations for MoveFileEx:
[Flags]
internal enum MoveFileFlags
{
None = 0,
ReplaceExisting = 1,
CopyAllowed = 2,
DelayUntilReboot = 4,
WriteThrough = 8,
CreateHardlink = 16,
FailIfNotTrackable = 32,
}
internal static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern bool MoveFileEx(
string lpExistingFileName,
string lpNewFileName,
MoveFileFlags dwFlags);
}
And some example code:
if (!NativeMethods.MoveFileEx("a.txt", null, MoveFileFlags.DelayUntilReboot))
{
Console.Error.WriteLine("Unable to schedule 'a.txt' for deletion");
}
Because you want to perform this after reboot as a requirement, you could use the Windows Task Scheduler API. You can invoke this in C# by adding a reference to the COM library TaskScheduler 1.1 Type Library. Below is a full code example on running Notepad.exe at logon.
Also, here is another resource: http://bartdesmet.net/blogs/bart/archive/2008/02/23/calling-the-task-scheduler-in-windows-vista-and-windows-server-2008-from-managed-code.aspx
You could call the system command DEL from Windows Command line, potentially with this code.
namespace TaskSchedulerExample {
using System;
using TaskScheduler;
class Program {
static void Main(string[] args) {
var scheduler = new TaskSchedulerClass();
scheduler.Connect(null, null, null, null);
ITaskDefinition task = scheduler.NewTask(0);
task.RegistrationInfo.Author = "DCOM Productions";
task.RegistrationInfo.Description = "Demo";
ILogonTrigger trigger = (ILogonTrigger)task.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_LOGON);
trigger.Id = "Logon Demo";
IExecAction action = (IExecAction)task.Actions.Create(_TASK_ACTION_TYPE.TASK_ACTION_EXEC);
action.Id = "Delete";
action.Path = "c:\\delete.exe"; // Or similar path
action.WorkingDirectory = "c:\\"; // Working path
action.Arguments = "c:\\killme.txt"; // Path to your file
ITaskFolder root = scheduler.GetFolder("\\");
IRegisteredTask regTask = root.RegisterTaskDefinition("Demo", task, (int)_TASK_CREATION.TASK_CREATE_OR_UPDATE, null, null, _TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN, "");
//Force run task
//IRunningTask runTask = regTask.Run(null);
}
}
}
This gives you some flexibility. You could run your own delete.exe, or you could potentially invoke the Windows Command Line to execute the DEL command.

Categories

Resources