How to copy from pointer to structure in VB.NET - vb.net

I am using a C DLL from my VB.NET program. The C function has a callback which sends a pointer to a large, complex structure.
typedef int (CALLBACK *fAnalyzerDataCallBack)(
LLONG lAnalyzerHandle, DWORD dwAlarmType, void* pAlarmInfo, BYTE *pBuffer,
DWORD dwBufSize, LDWORD dwUser, int nSequence, void *reserved );
The AlarmInfo points to different structures based on the value of dwAlarmType. I defined the structure but am stuck at that point. I tried
Marshal.PtrToStructure(Of DEV_EVENT_FACERECOGNITION_INFO)(AlarmInfo, structFaceRecognitionInfo)
but that doesn't work as the PtrToStructure requires a class in the second parameter.
If pertinent, I am pasting part of the structure definition here:
<StructLayout(LayoutKind.Sequential)>
Public Structure DEV_EVENT_FACERECOGNITION_INFO
Public nChannelID As Int16 ' ChannelId
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=128)>
Public szName() As Byte '[128] Event name
Public nEventID As Int16 ' Event ID
Public UTC As NET_TIME ' the Event happen time
Public stuObject As DH_MSG_OBJECT ' have being detected Object
Public nCandidateNum As Int16 ' candidate number
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=50)>
Public stuCandidates() As CANDIDATE_INFO '50 [DH_MAX_CANDIDATE_NUM]; // candidate info
<<SNIP>>
Public stuCustomProjects As NET_CUSTOM_PROJECTS_INFO ' Face recognition project customization information
Public bIsDuplicateRemove As Boolean ' Smart retail, whether it conforms To the de duplication strategy (True: conforms to false: does Not conform to)
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)>
Public byReserved24() As Byte '[4] Byte alaignment
End Structure
So, how do I copy from the pointer into my structure?
This is a 3rd party DLL and the source is not available.

Related

Can the XML Documentation added in a Com Visible dll be viewed in the VBA editor or Object Browser?

I have a COM visible dll which works fine with VBA. I would like to be able to see the XML documentation in the VBA editor and VBA object browser, is this possible?
by XML Documentation I mean for example Summary, param and remarks.
''' <summary>
''' Method to add some string.
''' </summary>
''' <param name="aString">The text to add.</param>
''' <remarks>Some remarks go here.</remarks>
Public Sub SomeSub(ByVal aString As String)
End Sub
If it is not possible what are my options for giving a user who wants to use my dll in VBA information about the classes, methods and functions available? Is there something that Visual Studio offers here? (Thus I have added Visual Studio as a tag)
As Hans Passant says 'No. You have to use the [Description] attribute'. Just to expand and include information from the SO question Hans then referenced it seems the Description attribute has to be the vehicle for all the documentation about the method parameters because COM Interface Definition Language IDL does not support such granularity.
Don't worry it seems one can put plenty of text in the Description field.
UPDATE: Additional information some VB.NET sample code.
Open Visual Studio with Administrator Rights (Admin required to register)
Create a VB.NET Class Library
Go to Project Properties
On the build tab check the Register for Interop check box
Change the code for the default class to be the following
Imports System.ComponentModel
Public Interface Interface1
<Description("Interface Description - Here we can put method information as well as parameter information")>
Function AddNumbers(ByVal X As Integer, ByVal Y As Integer)
<Description("Interface Description - Attempt to put description on Name")>
Property Name() As String
End Interface
'https://msdn.microsoft.com/en-us/library/microsoft.visualbasic.comclassattribute(v=vs.110).aspx
Public Class ComClass1
Implements Interface1
Dim sName As String
' These GUIDs provide the COM identity for this class
' and its COM interfaces. You can generate
' these guids using guidgen.exe
Public Const ClassId As String = "7666AC25-855F-4534-BC55-27BF09D49D46"
Public Const InterfaceId As String = "54388137-8A76-491e-AA3A-853E23AC1217"
Public Const EventsId As String = "EA329A13-16A0-478d-B41F-47583A761FF2"
Public Sub New()
MyBase.New()
End Sub
<Description("Here we can put method information as well as parameter information")>
Function AddNumbers(ByVal X As Integer, ByVal Y As Integer) Implements Interface1.AddNumbers
AddNumbers = X + Y
End Function
Property Name() As String Implements Interface1.Name
<Description("Attempt to put description on Name")>
Get
Return sName
End Get
<Description("Attempt to put description on Name")>
Set(ByVal Value As String)
sName = Value
End Set
End Function
End Class
Also add a new project item, an interface, and paste in this code
After successful build open Excel, create a workbook
In the VBA go Tools->References , navigate to the ClassLibrary and check
Go to the Object Browser and find your ClassLibrary and your ComClass1.
For the method AddNumbers you should now see the Description
Also using Hans Passant's tip about using the Description attribute on both the getter and the setter of a property can be verified.
BUT YOU ARE RIGHT #DARBID, the object browser shows the Description attribute is missing from the Interface Name.
I can only suggest that you use the MIDL compiler to get your assembly's Interface Definition Language (IDL), amend this IDL to give the Description attribute and then recompile and invite your users to browse your amended type library. Here is what IDL looks like
[
odl,
uuid(54388137-8A76-491E-AA3A-853E23AC1217),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary4.ComClass1+_ComClass1")
]
interface _ComClass1 : IDispatch {
[id(0x00000001), helpstring("Class Description Here we can put method information as well as parameter information")]
HRESULT AddNumbers(
[in] long X,
[in] long Y,
[out, retval] VARIANT* pRetVal);
[id(0x00000002), propget, helpstring("Class Description Attempt to put description on Name")]
HRESULT Name([out, retval] BSTR* pRetVal);
[id(0x00000002), propput, helpstring("Class Description Attempt to put description on Name")]
HRESULT Name([in] BSTR pRetVal);
};
You can view the IDL of any COM library on your computer with OLEView.exe which is part of the Windows SDK.
Or perhaps someone can find a way to put gets and sets into a VB interface. I don't program in VB.NET I do VBA and C#. Sorry, I'm out of ideas now.

Using VB.net Collection class via COM returns error: "Class doesn't support automation"

I have an existing VB.net class library which has a public property with a type of VB's Collection class. I'm exposing the class library as a COM-object to be able to use it in Progress.
When I access the Collection-property with an integer index (e.g. comObj.OutputCol.Item(1)) it works fine, but when I try to use the string indexer (e.g. comObj.OutputCol.Item("FirstCol")) I get the following error (from a VBScript I use for testing):
Error message: Class doesn't support automation
Error code: 800A01AE
Is it possible to use the string indexer in any way via COM?
Sample code, COM-object i VB.net:
<ComClass(TestClass.ClassId, TestClass.InterfaceId, TestClass.EventsId)>
Public Class TestClass
Public Const ClassId As String = "063CA388-9926-44EC-B3A6-856D5299C210"
Public Const InterfaceId As String = "094ECC57-4E84-423A-B20E-BD109AEDBC20"
Public Const EventsId As String = "038B18BD-54B4-42D3-B868-71F4C52345B0"
Private _sOutputCol As Collection = Nothing
Private Property sOutputCol() As Collection
Get
If _sOutputCol Is Nothing Then
_sOutputCol = New Collection()
End If
Return _sOutputCol
End Get
Set(ByVal Value As Collection)
_sOutputCol = Value
End Set
End Property
Public ReadOnly Property OutputCol() As Collection
Get
Return sOutputCol
End Get
End Property
Public Sub New()
sOutputCol.Add("First object", "FirstCol")
sOutputCol.Add(2, "SecondCol")
End Sub
End Class
Sample test-code in VBScript:
Set comObj = WScript.CreateObject("VbComTest.TestClass")
wscript.echo comObj.OutputCol.Item(1) ' Works
wscript.echo comObj.OutputCol.Item(CStr("FirstCol")) ' Gives the error
I have registred the dll with: >regasm "...path...\VbComTest.dll" /codebase
OK, the problem was that the indexer is overloaded and you shouldn't use that in COM-visible interfaces: https://msdn.microsoft.com/en-us/library/ms182197.aspx
Extract from the page about what happens to overloaded methods:
When overloaded methods are exposed to COM clients, only the first
method overload retains its name. Subsequent overloads are uniquely
renamed by appending to the name an underscore character '_' and an
integer that corresponds to the order of declaration of the overload.
For example, consider the following methods.
void SomeMethod(int valueOne); void SomeMethod(int valueOne, int
valueTwo, int valueThree); void SomeMethod(int valueOne, int
valueTwo);
These methods are exposed to COM clients as the following.
void SomeMethod(int valueOne); void SomeMethod_2(int valueOne,
int valueTwo, int valueThree); void SomeMethod_3(int valueOne, int
valueTwo);
Visual Basic 6 COM clients cannot implement interface methods by using
an underscore in the name.
So to use the string indexer I have to write:
wscript.echo comObj.OutputCol.Item_3("FirstCol")
(Item_2 takes an Object as parameter and will also work, if the documentation is correct).

How to construct single character constants

How do I define a constant Char value, similar to vbCr? This does not work...
Public Const ctrM As Char = "\m"C
This says the constant must have exactly one character. Well, ok, isn't that what "\m" is?, what about the following
Public Const ctrM as Char = Convert.ToChar(9)
That's not allowed because it's a function. Huh. Luckily this does work:
Public Dim ctrM as Char = Convert.ToChar(9)
But this seems sub-optimal. Am I missing something here?
The answer by fsintegral is fine, but can be slightly simpler. And you can use the Framework functions if you prefer them to the VB Functions.
Class method:
Public Class AppConsts
Public Shared ReadOnly CtrlEM As Char = Convert.ToChar(25)
Public Shared ReadOnly CtrlT As Char = Convert.ToChar(9)
Public Shared ReadOnly CtrlN As Char = Convert.ToChar(10)
Public Shared ReadOnly CtrlM As Char = Convert.ToChar(13)
Public Shared ReadOnly CrLf As String = CtrlN & CtrlM
...
End Class
'Usage:
Dim s as string = "..." & AppConts.CtrlEM
They will even show up in intellisense. If you dont like the Type/Class name intruding, you can import the class (I kind of like the Type portion included because it narrows the IntelliSense list rapidly to the relevant values):
Imports AppConsts
....
Dim s As String = CtrlEM
Alternatively, you can use the module method:
Module Program
Friend ReadOnly CtrlM As Char = Convert.ToChar(25)
End Module
' usage:
Dim s2 As String = "xxxx..." & CtrlM
They are not really constants as far as how the compiler treats them at compile time because they aren't -- they are just ReadOnly fields. But as far as your code is concerned in the IDE, they will act, feel and taste like constants.
It is the use of Const statement which limits how you can define them and require you to use (some) the VB functions rather than .NET ones.
Replace:
Public Const ctrM as Char = "\m"C
for this:
Public Const ctrM As Char = "m"c
Credits goes to Plutonix for giving a working/workable solution in a comment.
Used the following approach when I made large use of Modules long ago.
Add a Public Module to your Project :
Public Module MyConsts
' Define your constant Char
Public Const vbTabC As Char = Microsoft.VisualBasic.Chr(9) ' For Tabulation
Public Const vbEMC As Char = Microsoft.VisualBasic.Chr(25) ' For EM (End of Medium)
' ^^ if you know the ASCII Char Code.
' Use Microsoft.VisualBasic.ChrW() for Unicode (unsure of that)
Public Const vbCharQM As Char = "?"c
Public Const vbComma As Char = ","c
Public Const vbDot As Char = "."c
' or
Public Const vbCharQM2 As Char = CChar("?")
' ^^ if you can actually write the char as String in the Compiler IDE
End Module
Then use the constants identifier anywhere in your project like any VB constant string, but, they are of type Char of course (To combine them with String, you'll have to use .ToString())
Public Sub TestConstChar()
MessageBox.Show("[" + vbEMC.ToString() + "]")
' But hey ! What's the purpose of using End of Medium Char ?
End sub
Note that you have Environment.NewLine that automatically returns the valid Line Feed, or Carriage Return/Line Feed, or only Carriage Return, or even another control Char/String/Stream that is on use on your Operating System.
Based on the Environment.NewLine example, you can also define a (wandering) Class
Public Class MyConstChars
Public Shared ReadOnly Property Tab() As Char
Get
Return Microsoft.VisualBasic.ControlChars.Tab
End Get
End Property
' ...
End Class
' And use it anywhere like myString = "1" + MyConstChars.Tab.ToString() + "One"
This approach allows you to have more control over the actual value of the static/shared Property, like with Environment.NewLine, and allows your Class to propose much more options (Members) than a simple Constant. However, writing the LambdaClassName.LambdaClassProperty isn't very intuitive I reckon.
One another way to ease coding by using constant tags/identifiers in the IDE is to define Code Templates. A code template (piece of code) can be defined in the options of your IDE. You may already know what it is about : you type a keyword, then the IDE replace that keyword with one block of code (that you use often enough to require that shortcut) That's what is happening when you redefines (Overrides) a .ToString() Function in classes.
' I have for example one code template keyword...
PlaceholderChecker
' ...that generates the following Code :
#If IsDebugMode Then
''' <summary>
''' Placeholder Routine to check wether ALL Class Components are included in Solution.
''' </summary>
Private Shared Sub PlaceholderChecker()
p_ClassVariableName_ClassPartialSuffix = True
End Sub
#End If
In some cases, you don't have to define constants - or have to write more complex code - to get where you want.

Marshalling structures containing byte array

I have been struggling 2nd day with the task how to pass a structure, containing array of byte to c++ dll. Namely, I don't know how to marshal C# structure. Here is my code:
in C++ dll:
struct Image
{
int Width;
int Height;
int Depth;
uchar *Data;
};
.....
__declspec(dllexport) void DirectTransform(Image *InImage, Image*DestImage)
{
...Implementation...
}
in C# program:
[StructLayout(LayoutKind.Sequential)]
struct ImageData
{
public int Width;
public int Height;
public int Depth;
[MarshalAs(UnmanagedType.LPArray)]
public byte[] Data;
}
[DllImport("MyDll.dll",CallingConvention=CallingConvention.Cdecl)]
public static extern void DirectTransform(ImageData Src, ImageData Dest);
//Fill out both structures..
DirectTransform(Image, DestImage);
Exception throws at calling of DirectTransform and says that:
Cannot marshal field 'Data' of type'ImageData': Invalid managed/unmanaged
type combination (Array fields must be paired with ByValArray or
SafeArray).
When I change LPArray to ByValArray and point the size of array(in this case 202500) it also doesn't work, because the size is too large. When SafeArray is used, the program fails inside DLL with the message :
Attempted to read or write protected memory. This is often an
indication that other memory is corrupt.
And data in structures are erroneous.
Could anyone help me?
Look at this question
You can try passing a pointer to your array and, for example an integer length field.
Doing that, you'll need to manually allocate unmanaged memory using Marshal.AllocHGlobal, call your function and after that in finally block - clear after yourself with Marshal.FreeHGlobal
With Marshal.Copy method you are copying your data into allocated memory.

Casting an Integer to a Single, preserving bit representation

Is there a fast way in VB.NET to take a 32-bit int and cast to a 32-bit float while preserving the underlying bit structure? BitConverter will do this, but I'd like to cast it directly without involving byte arrays.
Damn, how could I possibly forget about The C-style Union?
<Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Explicit)> _
Public Structure IntFloatUnion
<Runtime.InteropServices.FieldOffset(0)> Public i As Integer
<Runtime.InteropServices.FieldOffset(0)> Public f As Single
End Structure
Sub Main()
Dim u As IntFloatUnion
u.i = 42
Console.WriteLine(u.f)
Console.ReadLine()
End Sub
Well, how about writing a helper function in C# similar to one shown here:
public static class FancyConverter
{
public static unsafe float FloatFromBytes(int i)
{
return *((float*)(void*)(&i));
}
}
This can be compiled to a separate dll and referenced from the VB project.