Importing native methods in Xamarin.Mac - xamarin

I'm looking for a way to use SCNetworkInterfaceCopyAll in my Xamarin.Mac app.
I've imported it
[DllImport("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration")]
public static extern IntPtr SCNetworkInterfaceCopyAll();
Then I get an array by calling var array = NSArray.ArrayFromHandle<NSObject>(pointer).
But can't figure out how to get values from the output array of SCNetworkInterface. I've tried to marshal it as
[StructLayout(LayoutKind.Sequential)]
public struct Test
{
IntPtr interface_type;
IntPtr entity_hardware;
}
And then call Marshal.PtrToStructure<Test>(i.Handle) but it gives random pointers instead of meaningful values.

Can you use the information provided from System.Net.NetworkInformation. NetworkInterface (or do you need actually need a SCNetworkInterface?)
Xamarin.Mac Example:
foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.OperationalStatus == OperationalStatus.Up)
Console.WriteLine(nic.GetPhysicalAddress());
}

Looking at SCNetworkConfiguration.h it appears there are a number of C APIs you are to call on your IntPtr to retrieve the specific information you need.
CoreFoundation APIs often return "baton" pointers that you need to pass to other functions. Where did you see that struct definition?

You can use Objective-C methods in Xamarin to get the MAC Address as C# gives a different MAC Address:
[DllImport("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration")]
public static extern IntPtr SCNetworkInterfaceCopyAll();
[DllImport("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration")]
public static extern IntPtr SCNetworkInterfaceGetHardwareAddressString(IntPtr scNetworkInterfaceRef);
private string MacAddress()
{
string address = string.Empty;
using (var interfaces = Runtime.GetNSObject<NSArray>(SCNetworkInterfaceCopyAll()))
{
for (nuint i = 0; i < interfaces.Count; i++)
{
IntPtr nic = interfaces.ValueAt(i);
var addressPtr = SCNetworkInterfaceGetHardwareAddressString(nic);
address = Runtime.GetNSObject<NSString>(addressPtr);
if (address != null) break;
}
}
return address;
}

Related

How to create a Windows Shortcut in Powershell to a file with special characters in filename?

I usually never have programming problems because I can easily find the answer to most of them. But I am at my wits' end on this issue.
The well known way of creating a Windows shortcut in Powershell is as follows:
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("F:\path\to\shortcut.lnk")
$Shortcut.TargetPath = "F:\some\other\path\to\targetfile.txt"
$Shortcut.Save()
However, this method has a shortcoming which I'm starting to be bothered by more and more: it does not work when the filename has special characters eg a smiley face 😍 in filename:
"targetfile 😍.txt"
I researched the issue and discovered here that the WshShortcut object and WScript cannot accept unicode in the filenames. It only seems to work for a simple set of characters. Of course, when you right-click the file in Windows and select "Create Shortcut" Windows has no problems creating the shortcut with the special character in it.
Someone scripted in C# an alternative way to create shortcuts using Shell32 but I don't know if it can be done in Powershell. And it looks like an old method which might not work in newer builds of Windows.
Would someone please help me with this issue? How to create a Windows shortcut in Powershell to a file whose filename has special characters in it?
When in doubt, use C#:
$ShellLinkCSharp = #'
namespace Shox
{
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214F9-0000-0000-C000-000000000046")]
[CoClass(typeof(CShellLinkW))]
interface IShellLinkW
{
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out IntPtr pfd, uint fFlags);
IntPtr GetIDList();
void SetIDList(IntPtr pidl);
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxName);
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
ushort GetHotKey();
void SetHotKey(ushort wHotKey);
uint GetShowCmd();
void SetShowCmd(uint iShowCmd);
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, [Optional] uint dwReserved);
void Resolve(IntPtr hwnd, uint fFlags);
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
}
[ComImport]
[Guid("00021401-0000-0000-C000-000000000046")]
[ClassInterface(ClassInterfaceType.None)]
class CShellLinkW { }
public static class ShellLink
{
public static void CreateShortcut(
string lnkPath,
string targetPath,
string description,
string workingDirectory)
{
if (string.IsNullOrWhiteSpace(lnkPath))
throw new ArgumentNullException("lnkPath");
if (string.IsNullOrWhiteSpace(targetPath))
throw new ArgumentNullException("targetPath");
IShellLinkW link = new IShellLinkW();
link.SetPath(targetPath);
if (!string.IsNullOrWhiteSpace(description))
{
link.SetDescription(description);
}
if (!string.IsNullOrWhiteSpace(workingDirectory))
{
link.SetWorkingDirectory(workingDirectory);
}
IPersistFile file = (IPersistFile)link;
file.Save(lnkPath, true);
Marshal.FinalReleaseComObject(link);
}
}
}
'#
# Check if Shox.ShellLink class already exists; if not, import it:
if (-not ([System.Management.Automation.PSTypeName]'Shox.ShellLink').Type)
{
Add-Type -TypeDefinition $ShellLinkCSharp
}
[Shox.ShellLink]::CreateShortcut(
'F:\path\to\shortcut1.lnk',
'F:\some\other\path\to\targetfile1 😍.txt',
'😍😍 my description 😍😍',
'F:\some\another\path\my_working_directory')
[Shox.ShellLink]::CreateShortcut(
'F:\path\to\shortcut2.lnk',
'F:\some\other\path\to\targetfile2 😍.txt',
$null, # No description
$null) # No working directory

Xamarin.iOS UIApperance setDefaultTextAttributes

I'm trying to figure out how to implement following code in Xamarin:
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:#{NSForegroundColorAttributeName:[UIColor greenColor]}];
But I cannot find a way to setDefaultTextAttributes on UIApperance class.
There are a number of missing UIAppearance features in Xamarin.iOS and in regards to your question, there is a missing API.
This is a bug, 🍣 I wrote my own UIAppearance.cs to add the missing features and correct the missing API and assume no other Xamarin.iOS coders really use the newer UIAppearance features as it has been broken since iOS 9 in Xamarin.
First, appearanceWhenContainedIn is deprecated and you should be using appearanceWhenContainedInInstancesOfClasses instead.
AppearanceWhenContainedIn - was deprecated in iOS 9 and is not recommended for use.
Second, appearanceWhenContainedInInstancesOfClasses is incorrectly defined within Xamarin.iOS as only available in tvOS and that is just not true.
#if TVOS
// new in iOS9 but the only option for tvOS
const string selAppearanceWhenContainedInInstancesOfClasses = "appearanceWhenContainedInInstancesOfClasses:";
~~~
UIAppearance.cs#L77
Update: I submitted a Github issue regarding this.
https://github.com/xamarin/xamarin-macios/issues/3230
Thus it is not available via the Xamarin.iOS wrapper API, but of course is available directly from the ObjC runtime as such:
var NSForegroundColorAttributeName = Dlfcn.GetStringConstant(UIKitLibraryHandle, "NSForegroundColorAttributeName");
var defaultAttributes = NSDictionary.FromObjectsAndKeys(new NSObject[] { UIColor.Red }, new NSObject[] { NSForegroundColorAttributeName });
var styleHandle = GetAppearanceEx(Class.GetHandle("UITextField"), typeof(UISearchBar));
void_objc_msgSend_IntPtr(styleHandle, Selector.GetHandle("setDefaultTextAttributes:"), defaultAttributes.Handle);
The next problem there are a number of Xamarin.iOS methods marked internal that are needed for the above code to function, so some copy/paste/modify of some source is needed:
public const string selAppearanceWhenContainedInInstancesOfClasses = "appearanceWhenContainedInInstancesOfClasses:";
public static readonly IntPtr UIKitLibraryHandle = Dlfcn.dlopen("/System/Library/Frameworks/UIKit.framework/UIKit", 0);
[DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
public static extern IntPtr IntPtr_objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1);
[DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
public static extern void void_objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1);
public static IntPtr GetAppearanceEx(IntPtr class_ptr, params Type[] whenFoundIn)
{
var ptrs = TypesToPointers(whenFoundIn);
var handles = NSArray.FromIntPtrs(ptrs);
using (var array = handles)
{
return IntPtr_objc_msgSend_IntPtr(class_ptr, Selector.GetHandle(selAppearanceWhenContainedInInstancesOfClasses), array.Handle);
}
}
public static IntPtr[] TypesToPointers(Type[] whenFoundIn)
{
IntPtr[] ptrs = new IntPtr[whenFoundIn.Length];
for (int i = 0; i < whenFoundIn.Length; i++)
{
if (whenFoundIn[i] == null)
throw new ArgumentException(String.Format("Parameter {0} was null, must specify a valid type", i));
if (!typeof(NSObject).IsAssignableFrom(whenFoundIn[i]))
throw new ArgumentException(String.Format("Type {0} does not derive from NSObject", whenFoundIn[i]));
var classHandle = Class.GetHandle(whenFoundIn[i]);
if (classHandle == IntPtr.Zero)
throw new ArgumentException(string.Format("Could not find the Objective-C class for {0}", whenFoundIn[i].FullName));
ptrs[i] = classHandle;
}
return ptrs;
}

How can I uniquely identify textbox controls retrieved from another application?

I'm writing an application in C# to remote control another application. I don't have access to that application yet, but it's most likely written in either Visual C++ or VB6. I'm currently remote controlling a simple C# winforms program I created instead. So far, I can get all of the controls in a manner similar to Spy++ using PInvoke. I can also do things like setting the text in a textbox. The problem I have is that I can't uniquely identify a specific text box between executions of the application I'm controlling. I can't use hWnd for example because it's different every time you run the application. GetWindowLong returns whatever the hWnd is so that's no help. The internet speaks of this message called WM_GETCONTROLNAME. The scary code below attempts to use that message to get the name the developer used to uniquely identify the controls. The SendMessage seems like it's returning the number of bytes in the name. But the buffer containing that name comes back all zeros.
So here's the question. How can I fix the code below so that it returns that name correctly? (Hopefully it's a boneheaded mistake that's easy to fix) Or, just as good, is there some other ID that will be guaranteed to be the same every time I run the program? Without something to uniquely identify the textbox controls, my code can't tell them apart.
I do have a hack in mind. It seems to me that using the position of the textboxes to tell them apart would work. But I'd much prefer to have the code below working.
public static string GetWindowText(IntPtr Handle)
{
int BufferSize = 256;
uint Message = RegisterWindowMessage("WM_GETCONTROLNAME");
StringBuilder sb = new StringBuilder(BufferSize);
byte[] ControlName = new byte[BufferSize];
long BytesRead = 0;
IntPtr BytesReadPointer = new IntPtr(BytesRead);
IntPtr OtherMem = IntPtr.Zero;
try
{
OtherMem = VirtualAllocEx(Handle, IntPtr.Zero, new IntPtr(sb.Capacity), AllocationType.Commit, MemoryProtection.ExecuteReadWrite);
var Result = SendMessage(Handle, Message, new IntPtr(sb.Capacity), OtherMem);
//Result contains different numbers which seem like the correct lengths
var Result2 = ReadProcessMemory(Handle, OtherMem, ControlName, BufferSize, out BytesReadPointer);
//ControlName always comes back blank.
}
finally
{
var Result3 = VirtualFreeEx(Handle, OtherMem, BufferSize, FreeType.Release);
}
return ""; // Convert ControlName to a string
}
So after a good night's sleep and a little tinkering, I managed to fix the problem myself. There were two problems. First, I was passing in the handle to the control to VirtualAllocEx. But in the documentation for that function, it says that it takes a handle to the process. So started supplying that. Then VirtualAllocEx was telling me that I had an invalid handle. So it turns out the handle you pass to VirualAllocEx has to have PROCESS_VM_OPERATION set. So I made a call to OpenProcess to get that handle, then passed that to VirtualAllocEx. After that, everything worked. I just had to convert the byte array coming back into a string, and trim a bunch of nulls. This returns the Name property on the controls. I'll post the code here in case anyone else needs something like this.
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}
public static string GetWindowText(IntPtr ControlHandle, IntPtr ProcessHandle)
{
int BufferSize = 256;
StringBuilder sb = new StringBuilder(BufferSize);
byte[] ControlNameByteArray = new byte[BufferSize];
long BytesRead = 0;
IntPtr BytesReadPointer = new IntPtr(BytesRead);
IntPtr RemoteProcessMemoryAddress = IntPtr.Zero;
IntPtr RemoteProcessHandle = IntPtr.Zero;
try
{
uint Message = RegisterWindowMessage("WM_GETCONTROLNAME");
RemoteProcessHandle = OpenProcess(ProcessAccessFlags.CreateThread |
ProcessAccessFlags.QueryInformation |
ProcessAccessFlags.VirtualMemoryOperation |
ProcessAccessFlags.VirtualMemoryWrite |
ProcessAccessFlags.VirtualMemoryRead, false, Process.Id);
RemoteProcessMemoryAddress = VirtualAllocEx(RemoteProcessHandle, IntPtr.Zero, new IntPtr(sb.Capacity), AllocationType.Commit, MemoryProtection.ExecuteReadWrite);
if (RemoteProcessMemoryAddress == IntPtr.Zero)
{
throw new Exception("GetWindowText: " + GetLastWin32Error());
}
SendMessage(ControlHandle, Message, new IntPtr(sb.Capacity), RemoteProcessMemoryAddress);
ReadProcessMemory(RemoteProcessHandle, RemoteProcessMemoryAddress, ControlNameByteArray, BufferSize, out BytesReadPointer);
string ControlName = Encoding.Unicode.GetString(ControlNameByteArray).TrimEnd('\0');
return ControlName;
}
finally
{
VirtualFreeEx(RemoteProcessHandle, RemoteProcessMemoryAddress, BufferSize, FreeType.Release);
}
}

xamarin.ios binding Segment-Analytics errors

I am working on the iOSbinding for Segment/Analytics and meeting a block on this.
The oc code I cannot find a good idea to convert to C# code.
-(SEGReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref;
when i converted code to below:
[Export("initWithReachabilityRef:")]
unsafe IntPtr Constructor(ref NetworkReachability #ref);
it happened a error:
Error CS0311: The type SystemConfiguration.NetworkReachability' cannot be used as type parameterT' in the generic type or method ObjCRuntime.Runtime.GetNSObject<T>(System.IntPtr)'. There is no implicit reference conversion fromSystemConfiguration.NetworkReachability' to `Foundation.NSObject' (CS0311) (Anayltics.IOS)
[Export ("initWithReachabilityRef:")]
[CompilerGenerated]
public SEGReachability (ref SCNetworkReachabilityRef #ref)
: base (NSObjectFlag.Empty)
{
IntPtr #refValue = IntPtr.Zero;
IsDirectBinding = GetType ().Assembly == global::ApiDefinition.Messaging.this_assembly;
if (IsDirectBinding) {
InitializeHandle (global::ApiDefinition.Messaging.IntPtr_objc_msgSend_ref_IntPtr (this.Handle, Selector.GetHandle ("initWithReachabilityRef:"), ref refValue), "initWithReachabilityRef:");
} else {
InitializeHandle (global::ApiDefinition.Messaging.IntPtr_objc_msgSendSuper_ref_IntPtr (this.SuperHandle, Selector.GetHandle ("initWithReachabilityRef:"), ref refValue), "initWithReachabilityRef:");
}
#ref = #refValue != IntPtr.Zero ? Runtime.GetNSObject<Anayltics.SCNetworkReachabilityRef> (#refValue) : null; //**--**error happened in this line.****
}
Any one can help me?

How to make Xamarin.Mac app "Open at Login"?

I have a Xamarin.Mac app that needs to open automatically at login.
How do I have my application get this setting without having to manually click on it?
I can give you a hint how to do it programmatically.
For this approach you need to make use of calls to native libraries via DllImport.
Following code will give you an idea how to proceed:
//needed library
const string DllName = "/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices";
static LSSharedFileList ()
{
dllHandle = Dlfcn.dlopen (DllName, 0);
kLSSharedFileListSessionLoginItems = Dlfcn.GetStringConstant (dllHandle, "kLSSharedFileListSessionLoginItems");
kLSSharedFileListItemLast = Dlfcn.GetStringConstant (dllHandle, "kLSSharedFileListItemLast");
}
[DllImport(DllName)]
public static extern IntPtr LSSharedFileListCreate (
IntPtr inAllocator,
IntPtr inListType,
IntPtr listOptions);
[DllImport(DllName, CharSet=CharSet.Unicode)]
public static extern void CFRelease (
IntPtr cf
);
[DllImport(DllName)]
public extern static IntPtr LSSharedFileListInsertItemURL (
IntPtr inList,
IntPtr insertAfterThisItem,
IntPtr inDisplayName,
IntPtr inIconRef,
IntPtr inURL,
IntPtr inPropertiesToSet,
IntPtr inPropertiesToClear);
And here the actual snippet:
public static void EnableLogInItem ()
{
IntPtr pFileList = IntPtr.Zero;
IntPtr pItem = IntPtr.Zero;
try
{
pFileList = LSSharedFileListCreate (
IntPtr.Zero,
kLSSharedFileListSessionLoginItems,
IntPtr.Zero);
pItem = LSSharedFileListInsertItemURL (
pFileList,
kLSSharedFileListItemLast,
IntPtr.Zero,
IntPtr.Zero,
NSBundle.MainBundle.BundleUrl.Handle,
IntPtr.Zero,
IntPtr.Zero);
}
finally
{
CFRelease (pItem);
CFRelease (pFileList);
}
}
Please keep in mind that it is not the whole solution, it is just a snippet to put an app in the login items list. Of course you have to handle errors, check for IntPtr.Zero after every call and so on, but this should give you an idea how it works.
Hope that helps!
Since the LSSharedFileList library is not supported on Xamarin.Mac you have to create a dylib with Xcode and bind it in your Xamarin.Mac application.
1) Create a Dylib project on Xcode. Add This function:
-(BOOL)AddLoginItem:(NSString *) AppPath{
// Your have to send this string as argument(i.e: on a textbox, write: /Applications/Calculator.app/)
// Get Login Items
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
NSLog(#"[DEBUG]Your Application Path:%#",AppPath);
// Covert String to CFURLRef (It adds "file://" to your itemUrl1)
CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:(NSString*) AppPath];
NSLog(#"[DEBUG] appUrl:%#", appUrl);
// Now we add the requested Login Item
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
// Confirm that your path was correct and that you got a valid Login Item Reference
NSLog(#"[DEBUG]Item:%#", itemRef);
if (itemRef) CFRelease(itemRef);
// Your item just got added to the user's Login Items List
CFRelease(loginItems);
return true;
}
return false;
}
2) Create a Cocoa project with an TextBox, a Push button and their Actions and Outlets controls in it.
Use this link to help you with the C# bindings from a objective-c DLL.
3) On your Xamarin.Mac project, in MainWindow.cs, add the following code and make the necessary changes to fit on your code.
Don't forget to add your .Net Assembly reference, created on the previous link and also your: using DLLloginItem; line.
// Some variables
private string TestItemName;
private bool rem;
// Button Click
partial void AddItemButton (Foundation.NSObject sender) {
LoginItemsDLL loginItem = new LoginItemsDLL();
// Enter this string on your TextBox TxtName /Applications/Calculator.app/
TestItemName = TxtName.StringValue;
rem=loginItem.AddLoginItem(TestItemName);
Console.WriteLine(rem);
}
In order to get your application path entered, use another function that just receives the application name and returns its path into AddLoginItem argument. Hope it helps!

Resources