In my WPF c# program, I'm trying to implement a custom pattern for Windows UIAutomation, which means I need to import something that knows about IUIAutomationPatternHandler, and the only way I can find to that is to add a COM reference to UIAutomationCore, and I'm not sure which DLL it's actually importing, but what gets included seems wrong.
For example, the IUIAutomationRegistrar.RegisterPattern method that gets imported looks like this:
void RegisterPattern([In] ref UIAutomationPatternInfo pattern,
out int pPatternId, out int pPatternAvailablePropertyId,
[In] uint propertyIdCount, out int pPropertyIds,
[In] uint eventIdCount, out int pEventIds);
but the C++ documentation says this:
HRESULT RegisterPattern(
[in] const UIAutomationPatternInfo *pattern,
[out] PATTERNID *pPatternId,
[out] PROPERTYID *pPatternAvailablePropertyId,
[in] UINT propertyIdCount,
[out] PROPERTYID *pPropertyIds,
[in] UINT eventIdCount,
[out] EVENTID *pEventIds
);
which is kind of standard COM stuff, where you allocate an array to receive the information, and then pass in the array itself (pPropertyIds) and the size of the array (propertyIdCount) which means the c# import where it thinks pPropertyIds is an "out int" is wrong.
Is adding a COM reference to UIAutomationCore the right way to do this? Or is there a different process that is recommended, to get the correct signature? Or do I just have to do my own manual importing so the parameters are correct?
EDIT
In order to call RegisterPattern, I need a CUIAutomationRegistrar object from the DLL. Normally you just call
var registrar = new UIA.CUIAutomationRegistrar();
and that works, but then in order to call my own version of RegisterPattern(), I need to call InvokeMethod (right?) and when I call
registrar.GetType().GetMethods()
or
registrar.GetType().GetMethod("RegisterPattern")
It claims there's no method named "RegisterPattern", probably because the method is marked
[MethodImpl(MethodImplOptions.InternalCall)]
so it's not actually a "real" method.
EDIT 2
So this works:
var registrar = new UIA.CUIAutomationRegistrar();
MyCopyPaste.IUIAutomationRegistrar myRegistar = (MyCopyPaste.IUIAutomationRegistrar)registrar;
I assume the compiler is actually doing the QueryInterface() behind the scenes, and since MyCopyPaste.IUIAutomationRegistrar has the same UUID as UIA.IUIAutomationRegistrar, the compiler is happy.
But then using 'myRegistrar' is tricky. I modified the RegisterPattern() method in MyCopyPaste.IUIAutomationRegstrar like so:
[MethodImpl(MethodImplOptions.InternalCall)]
void RegisterPattern([In] ref UIAutomationPatternInfo pattern,
out int pPatternId, out int pPatternAvailablePropertyId,
[In] uint propertyIdCount,
[MarshalAs(UnmanagedType.LPArray)] int[] pPropertyIds,
[In] uint eventIdCount, out int pEventIds);
which I call like this:
int[] propertyIds = new int[10];
myRegistar.RegisterPattern(ref patternInfo,
out int patternId, out int patternAvailablePropertyId,
10, propertyIds, 0, out int eventIds);
and it throws a "Value does not fall within the expected range." at runtime.
EDIT 3
Here's my entire copy/paste section:
namespace MyCopyPaste
{
// [TypeIdentifier("930299ce-9965-4dec-b0f4-a54848d4b667", "UIA.UIAutomationType")]
public enum UIAutomationType
{
UIAutomationType_Int = 1,
UIAutomationType_Bool = 2,
UIAutomationType_String = 3,
UIAutomationType_Double = 4,
UIAutomationType_Point = 5,
UIAutomationType_Rect = 6,
UIAutomationType_Element = 7,
UIAutomationType_Array = 65536,
UIAutomationType_Out = 131072,
UIAutomationType_IntArray = 65537,
UIAutomationType_BoolArray = 65538,
UIAutomationType_StringArray = 65539,
UIAutomationType_DoubleArray = 65540,
UIAutomationType_PointArray = 65541,
UIAutomationType_RectArray = 65542,
UIAutomationType_ElementArray = 65543,
UIAutomationType_OutInt = 131073,
UIAutomationType_OutBool = 131074,
UIAutomationType_OutString = 131075,
UIAutomationType_OutDouble = 131076,
UIAutomationType_OutPoint = 131077,
UIAutomationType_OutRect = 131078,
UIAutomationType_OutElement = 131079,
UIAutomationType_OutIntArray = 196609,
UIAutomationType_OutBoolArray = 196610,
UIAutomationType_OutStringArray = 196611,
UIAutomationType_OutDoubleArray = 196612,
UIAutomationType_OutPointArray = 196613,
UIAutomationType_OutRectArray = 196614,
UIAutomationType_OutElementArray = 196615
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
// [TypeIdentifier("930299ce-9965-4dec-b0f4-a54848d4b667", "UIA.UIAutomationParameter")]
public struct UIAutomationParameter
{
public UIAutomationType type;
public IntPtr pData;
}
[Guid("C03A7FE4-9431-409F-BED8-AE7C2299BC8D")]
[InterfaceType(1)]
// [TypeIdentifier]
public interface IUIAutomationPatternInstance
{
}
[Guid("D97022F3-A947-465E-8B2A-AC4315FA54E8")]
[InterfaceType(1)]
// [TypeIdentifier]
public interface IUIAutomationPatternHandler
{
[MethodImpl(MethodImplOptions.InternalCall)]
void CreateClientWrapper([In][MarshalAs(UnmanagedType.Interface)] IUIAutomationPatternInstance pPatternInstance, [MarshalAs(UnmanagedType.IUnknown)] out object pClientWrapper);
[MethodImpl(MethodImplOptions.InternalCall)]
void Dispatch([In][MarshalAs(UnmanagedType.IUnknown)] object pTarget, [In] uint index, [In] ref UIAutomationParameter pParams, [In] uint cParams);
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
// [TypeIdentifier("930299ce-9965-4dec-b0f4-a54848d4b667", "UIA.UIAutomationPatternInfo")]
public struct UIAutomationPatternInfo
{
public Guid guid;
[MarshalAs(UnmanagedType.LPWStr)]
public string pProgrammaticName;
public Guid providerInterfaceId;
public Guid clientInterfaceId;
public uint cProperties;
public IntPtr pProperties;
public uint cMethods;
public IntPtr pMethods;
public uint cEvents;
public IntPtr pEvents;
[MarshalAs(UnmanagedType.Interface)]
public IUIAutomationPatternHandler pPatternHandler;
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
// [TypeIdentifier("930299ce-9965-4dec-b0f4-a54848d4b667", "UIA.UIAutomationPropertyInfo")]
public struct UIAutomationPropertyInfo
{
public Guid guid;
[MarshalAs(UnmanagedType.LPWStr)]
public string pProgrammaticName;
public UIAutomationType type;
}
[InterfaceType(1)]
[Guid("8609C4EC-4A1A-4D88-A357-5A66E060E1CF")]
// [TypeIdentifier]
[ComImport]
public interface IUIAutomationRegistrar
{
[MethodImpl(MethodImplOptions.InternalCall)]
void RegisterProperty([In] ref UIAutomationPropertyInfo property, out int propertyId);
void _VtblGap1_1();
[MethodImpl(MethodImplOptions.InternalCall)]
void RegisterPattern([In] ref UIAutomationPatternInfo pattern,
out int pPatternId, out int pPatternAvailablePropertyId,
[In] uint propertyIdCount,
[MarshalAs(UnmanagedType.LPArray)] int[] pPropertyIds,
[In] uint eventIdCount, out int pEventIds);
}
[Guid("8609C4EC-4A1A-4D88-A357-5A66E060E1CF")]
// [TypeIdentifier]
public interface CUIAutomationRegistrar : IUIAutomationRegistrar
{
}
}
and here's how I'm using it:
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("BFEF93B9-0CAD-4B0F-8FB7-51CFF6891BCD")] // GUID of IValidatingPattern
public interface IValidatingPattern
{
static readonly Guid PatternGUID = new(0x66e36682, 0x42f6, 0x4266, 0xaf, 0x27, 0xf, 0x34, 0xfa, 0xe6, 0x58, 0x3e);
static readonly string PatternName = "Validating";
static readonly Guid IsValidGUID = new(0x5dbe95c5, 0xb18c, 0x4904, 0xa3, 0xae, 0x57, 0xcb, 0xe4, 0xc2, 0x41, 0x5e);
public bool IsValid { get; }
}
internal class ValidatingPattern : IValidatingPattern
{
public bool IsValid
{
get
{
Debug.WriteLine("Not implemented (yet)");
throw new NotImplementedException();
}
}
}
UIAutomationPatternInfo patternInfo = new()
{
guid = IValidatingPattern.PatternGUID,
pProgrammaticName = IValidatingPattern.PatternName,
providerInterfaceId = typeof(IValidatingPattern).GUID,
cProperties = 0, // (uint)properties.Length,
// struct UIAutomationPropertyInfo *pProperties;
cMethods = 0,
// struct UIAutomationMethodInfo *pMethods;
cEvents = 0,
// struct UIAutomationEventInfo *pEvents;
pPatternHandler = Handler
};
var registrar = new UIA.CUIAutomationRegistrar();
IUIAutomationRegistrar myRegistar = (IUIAutomationRegistrar)registrar;
int[] propertyIds = new int[10];
myRegistar.RegisterPattern(ref patternInfo,
out int patternId, out int patternAvailablePropertyId,
10/*(uint)properties.Length*/, propertyIds, 0, out int eventIds);
Note that if I change the 'propertyIdCount' to zero, the call works perfectly and returns patternId=50000 and patternAvailablePropertyId=500001:
myRegistar.RegisterPattern(ref patternInfo,
out int patternId, out int patternAvailablePropertyId,
0/* <-- WORKS WHEN ZERO*/, propertyIds, 0, out int eventIds);
EDIT 4
I also tried defining a single method:
UIAutomationType[] methodOutParamTypes = { UIAutomationType.UIAutomationType_Bool };
GCHandle methodOutParamTypesHandle = GCHandle.Alloc(methodOutParamTypes);
string[] methodOutParamNames = { "IsValid" };
GCHandle methodOutParamNamesHandle = GCHandle.Alloc(methodOutParamNames);
UIAutomationMethodInfo getIsValidMethod = new()
{
pProgrammaticName = "get_IsValid",
doSetFocus = 0, // int
cInParameters = 0,
cOutParameters = 1,
pParameterTypes = (IntPtr)methodOutParamTypesHandle,
pParameterNames = (IntPtr)methodOutParamNamesHandle
};
UIAutomationMethodInfo[] methods = { getIsValidMethod };
GCHandle methodsHandle = GCHandle.Alloc(methods);
UIAutomationPatternInfo patternInfo = new()
{
guid = IValidatingPattern.PatternGUID,
pProgrammaticName = IValidatingPattern.PatternName,
providerInterfaceId = typeof(IValidatingPattern).GUID,
cProperties = 0, // (uint)properties.Length,
// struct UIAutomationPropertyInfo *pProperties;
cMethods = 1,
pMethods = (IntPtr)methodsHandle, // struct UIAutomationMethodInfo *pMethods;
cEvents = 0,
// struct UIAutomationEventInfo *pEvents;
pPatternHandler = Handler
};
var registrar = new UIA.CUIAutomationRegistrar();
MyCopyPaste.IUIAutomationRegistrar myRegistar = (MyCopyPaste.IUIAutomationRegistrar)registrar;
int[] propertyIds = new int[10];
myRegistar.RegisterPattern(ref patternInfo,
out int patternId, out int patternAvailablePropertyId,
0/*(uint)properties.Length*/, propertyIds,
0, out int eventIds);
That throws a NullReferenceException.
I am trying to marshal data from C# to C++ DLL. For which, I have following C++ code.
// C++ Function in DLL
int __declspec(dllexport) DataAnalyze(AllInfo *&all)
{
return 0;
}
// Structure
typedef struct
{
char model[100];
char log[_MAX_PATH];
char path[_MAX_PATH];
}AllInfo;
Here is my C# code.
public unsafe void Execute()
{
// Assigning values
public AllInfo ana_alls = new AllInfo();
AllInfo adt = new AllInfo(0);
adt.Eye_img_num = 5;
adt.Flow_log = #"D:\\HTML\\File.txt";
adt.Pano_path = #"D:\\HTML\\Img.bmp";
ana_alls = adt;
// Preparing data for marshalling
IntPtr intPtrsAll = new IntPtr();
intPtrsAll = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(AllInfo)));
Marshal.StructureToPtr<AllInfo>(ana_alls, intPtrsAll, false);
// DLL calling
int ret = 0 - DataAnalyze(ref intPtrsAll);
}
// Structure
[StructLayout(LayoutKind.Sequential)]
public struct AllInfo
{
public AllInfo(int n)
{
this.model = string.Empty;
this.path = string.Empty;
this.log = string.Empty;
}
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string model;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string log;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string path;
}
// DLL Calling
[DllImport(#"D:\\HTML\\Data\\DLL\\Dara_Dll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DataAnalyze(ref IntPtr ana_all);
In the C++ method, I am getting data in path of AllInfo *&all as follow:
path :{'\0','\0','\0','\0','\0','\0','\0','\0','D',':','\\','H','T','M','L','\\','I','m','g','.','b','m','p','\0','\0',........}
I am getting similar output for log also. I don't know why '\0' added from 0 to 7 index of each char[] data in C++ that is coming from C#. Where am I doing it wrong?
Using TwinCAT 3 ADS.Net for reading from PLC, I'm trying to read a struct containing array of structs, but the ReadAny command crashes with "Unable to marshal type" exception.
Reading directly an array of structs works fine though.
public object ReadAny(long indexGroup, long indexOffset, Type type, int[] args);
The header remark of the ReadAny method says:
“If the Type of the object to be read is an array type, the number of elements for each dimension has to be specified in the parameter args."
But what should args be for a struct containing array of structs?
(Without 'args' it fails too.)
I currently work with .NET 4.7, VS 2013.
Is there an option?
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public class WholeData
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public Station[] StationArray;
// Potentially more fields...
}
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public class Station
{
[MarshalAs(UnmanagedType.I1)]
public bool isPass;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 81)]
public string name;
// More fields...
}
// -- Main --
int[] args = { 5 };
// Works fine:
Station[] stationArray = (Station[])m_AdsClient.ReadAny(indexGroup, indexOffset, typeof(Station[]), args);
// Fail:
WholeData wholeData = (WholeData)m_AdsClient.ReadAny(indexGroup, indexOffset, typeof(WholeData), args);
// - OR -
WholeData wholeData = (WholeData)m_AdsClient.ReadAny(m_VarHandle, typeof(WholeData), args);
I tested successfully following code:
c# code:
class Program
{
public static TcAdsClient client;
static void Main(string[] args)
{
// Create the ADS Client
using (client = new TcAdsClient())
{
// Establish Connection
client.Connect(new AmsAddress("10.1.2.95.1.1", 851));
int handle = client.CreateVariableHandle("PRG_AIS.stAds");
AdsClass ads = (AdsClass)client.ReadAny(handle, typeof(AdsClass));
ads.boolArr[0] = 1;
client.WriteAny(handle, ads);
Console.ReadLine();
}
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
class AdsClass
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public byte[] boolArr = new byte[10];
}
ST code:
TYPE AdsStruct :
STRUCT
bTestArray : ARRAY[0..9] OF BOOL;
END_STRUCT
END_TYPE
AdsStruct is defined as stAds in PRG_AIS.
OR if you have an array of structs modify the code the following way:
c# code:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
class AdsClass
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public InnerStruct[] strArr = new InnerStruct[10];
}
struct InnerStruct
{
public byte bBoolTest;
public int nIntTest;
}
ST code:
TYPE AdsStruct :
STRUCT
stTestArray : ARRAY[0..9] OF InnerStruct;
END_STRUCT
END_TYPE
TYPE InnerStruct :
STRUCT
bBoolTest : BOOL;
nIntTest : DINT;
END_STRUCT
END_TYPE
I am calling a C library from a C# code. The function I am calling take as parameter a struct containing arrays of struct :
struct Example1Struct
{
char* a;
uint16_t b;
AnotherStruct* c;
}
c here is an array of pointer to AnotherStruct.
the struct in my C# code look like this
public struct Example1Struct
{
public IntPtr StationName;//is char*
public UInt16 IdCode;
public IntPtr AnotherStruct; //array of struct AnotherStruct
}
public static IntPtr MarshalToPointer(object data)
{
Type valueType = data.GetType();
IntPtr buf = IntPtr.Zero;
if (valueType.IsArray)
{
if (data is char[])
{
var d = data as char[];
buf = Marshal.AllocHGlobal(Marshal.SizeOf(d.GetType().GetElementType()) * d.Length);
}
else if (data is char[,])
{
var d = data as char[,];
buf = Marshal.AllocHGlobal(Marshal.SizeOf(d.GetType().GetElementType()) * d.Length);
}
else
{
buf = Marshal.AllocHGlobal(Marshal.SizeOf(data.GetType().GetElementType()) * count);
long LongPtr = buf.ToInt64(); // Must work both on x86 and x64
for (int I = 0; I < data.Lenght; I++)
{
IntPtr RectPtr = new IntPtr(LongPtr);
Marshal.StructureToPtr(data[I], RectPtr, false); // You do not need to erase struct in this case
LongPtr += Marshal.SizeOf(typeof(Rect));
}
}
return buf;
}
else
buf = Marshal.AllocHGlobal(Marshal.SizeOf(data));
Marshal.StructureToPtr(data, buf, false);
return buf;
}
my problem here is that I cannot cast data (who is an array of AnotherStruct) to object[] , neither in IEnumerable. So I cannot access to data[I] and don't have data.Lenght
Any idea ?
Usually I'd recommend using the MarshalAs attribute rather than writing manual marshalling code. It looks like:
public struct Example1Struct
{
public IntPtr StationName;//is char*
public UInt16 IdCode;
public IntPtr AnotherStruct; //array of struct AnotherStruct
}
Could be:
public struct Example1Struct
{
[MarshalAs(UnmanagedType.LPStr)]
public string StationName;
public UInt16 IdCode;
[MarshalAs(UnmanagedType.LPArray)]
public AnotherStruct[] OtherStructs;
}
And the marshaller should do the right thing for you when you pass it to unmanaged code.
You can get the length of the array like this:
if (data is Array a)
Console.WriteLine(a.Length);
Arrays in c# always derive from Array, so you can cast it to that.
But if possible in your real code, I'd recommend Damien's answer
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct sName_d
{
[MarshalAs(UnmanagedType.LPStr)]
public string szCountry;
[MarshalAs(UnmanagedType.LPStr)]
public string szCommonName;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct sCertData_d
{
public Int32 dwVersion;
public sName_d sIssuer;
public sName_d sSubject;
}
public void GenerateCert()
{
sCertData_d cert = new sCertData_d();
sName_d caIssuer = new sName_d();
caIssuer.szCountry = "US";
caIssuer.szCommonName = "John";
sName_d caSubject = new sName_d();
caSubject.szCountry = "UK";
caSubject.szCommonName = "Johann";
cert.sIssuer = caIssuer;
cert.sSubject= caSubject;
NativeMethods.GenerateCert(ref cert);
}
In the above code, NativeMethods.GenerateCert is an unmanaged function of C.
When Call reaches inside of this function, I am not getting the string values "John", "UK", "Johann" and "US".
[DllImport("AuthLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int GenerateCert(ref sCertData_d cert);
Unmanaged function prototype is like this -
typedef struct sName_d
{
char szCountry[0x35];
char szCommonName[0x35];
}sName_d;
typedef struct sCertData_d
{
int version;
sName_d sIssuer;
sName_d sSubject;
}sCertData_d;
int GenerateCert(const sCertData_d *psCert);
Your translation of sName_d is wrong. The unmanaged structure is:
typedef struct sName_d
{
char szCountry[0x35];
char szCommonName[0x35];
} sName_d;
These are inline character arrays. You marshaled these as UnmanagedType.LPStr. That's a pointer to null-terminated string. You need to use UnmanagedType.ByValTStr.
Used for in-line, fixed-length character arrays that appear within a structure. The character type used with ByValTStr is determined by the System.Runtime.InteropServices.CharSet argument of the System.Runtime.InteropServices.StructLayoutAttribute attribute applied to the containing structure. Always use the MarshalAsAttribute.SizeConst field to indicate the size of the array.
.NET Framework ByValTStr types behave like C-style, fixed-size strings inside a structure (for example, char s[5]).
Your translation should be:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct sName_d
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x35)]
public string szCountry;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x35)]
public string szCommonName;
}