Event Handling form C# to vb.net - vb.net

I inherited this code from a C# sdk and I'm trying to convert it to vb.net. As I'm a bit new to vb.net, with the aid of some online converters I managed to convert it as below:
Original Code:
private void ScanThreadPorc()
{
while (true)
{
uint evt = Win32.WaitForMultipleObjects(2, hEvent, false, Win32.INFINITE);
switch (evt)
{
case 0://return thread
return;
case 1://disable network
uint bytesRead;
uint flags;
byte[] buf = new byte[256];
if (Win32.ReadMsgQueue(hEvent[1], buf, 256, out bytesRead, Win32.INFINITE, out flags))
{
EventHandler<DecodeEventArgs> temp = DecodeEvent;
if (temp != null)
{
temp(this, new DecodeEventArgs(Encoding.Default.GetString(buf, 2, (int)bytesRead), buf[1]));
}
}
break;
}
}
}
Converted Code
Private Sub ScanThreadPorc()
While (True)
Dim evt As UInteger = Win32.WaitForMultipleObjects(2, hEvent, False, Win32.INFINITE)
Select Case evt
Case 0 '://return thread
Return
Case 1 '://disable network
Dim bytesRead As UInteger
Dim flags As UInteger
Dim buf As Byte() = New Byte(255) {}
If Win32.ReadMsgQueue(hEvent(1), buf, 256, bytesRead, Win32.INFINITE, flags) Then
Dim temp As EventHandler(Of DecodeEventArgs) = DecodeEvent
RaiseEvent temp(Me, New DecodeEventArgs(Encoding.[Default].GetString(buf, 2, CInt(bytesRead)), buf(1)))
End If
End Select
End While
End Sub
I have 2 errors on DecodeEvent that says
Public Event DecodeDevent (sender as object, e as DecodeEventArgs) is an event and cannot be called directly. Use a raise statement to raise an event.
And the second error on temp that says
temp is not an event of myapplication.mynamespace.myclass.
I have read about 10 related topics on this and other forums but I'm unable to adapt the answers to my code.
Please help. Thanks

Related

UWP: write file to disk using the simple and fast PInvoke calls CreateFile and WriteFile

We have an unmanaged memory C++ dll that generates video data. The user interface is in UWP VB.
I need to write the video data to the disk. The user gives the location to write from a file picker and this path and file name is passed to the dll. Using CreateFile and WriteFile the data will be written to disk.
This is the VB code
<DllImport("mycplus.dll", CallingConvention:=CallingConvention.StdCall)>
Public Function MainFrozen(ByVal EvF() As UInteger) As UInteger
End Function
Private Async Sub btnSaveAs_Click(sender As Object, e As RoutedEventArgs) Handles btnSaveAs.Click
Dim savePicker = New Windows.Storage.Pickers.FileSavePicker()
savePicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary
savePicker.FileTypeChoices.Add("Video Files", New List(Of String)() From {".bin"})
savePicker.SuggestedFileName = gVideoFileName
Dim file As Windows.Storage.StorageFile = Await savePicker.PickSaveFileAsync()
If file IsNot Nothing Then
Dim fileName As String = file.Path
Dim d(0 To 15) As UInt32
d(0) = VIDEO_FILE_WRITE
d(1) = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(fileName)
d(2) = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(gAuthorName)
d(3) = gReserved
d(8) = MainFrozen(d)
Else
'txtDeubg.Text = "Operation cancelled."
End If
End Sub
And the following is the C++ code
extern "C" __declspec (dllexport) ULONG __stdcall MainFrozen(PULONG ptrEv)
{
ULONG eve = ptrEv[0];
switch (eve)
{
case VIDEO_FILE_WRITE:
SaveScanVideosToFile((LPCSTR)ptrEv[1], (LPCSTR)ptrEv[2], ptrEv[3]);
break;
}
return 0;
}
ULONG SaveScanVideosToFile(LPCSTR lpFileName,
LPCSTR lpAuthorName,
ULONG reserved)
{
HANDLE hFile;
ULONG bytesWritten;
ULONG n;
hFile = CreateFile(lpFileName, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
for (n=0; n<gScanVideoCnt; n++)
{
WriteFile(hFile, ScanVideos[n].pImg->Bcine.pCineMem, SCAN_VIDEO_MEM_SIZ, &bytesWritten, NULL);
WriteFile(hFile, ScanVideos[n].pImg, sizeof(IMG_TYPE), &bytesWritten, NULL);
}
CloseHandle(hFile);
return 0;
}
The problem is I am unable to write to any location other than: Windows.Storage.ApplicationData.Current.LocalFolder
I.e C:\Users\ravi\AppData\Local\Packages\2d6e339b-614b-4161-8c7d-3570f07fc01f_2ygbv2t9jdb9y\LocalState\
At other locations, CreateFile fails with access denied.
However using the .net file functions I am able to write other test files to almost all locations in my PC in the latest creators edition of win10, which is most welcome and heartening.
The question is: what security attributes should be set so that CreateFile will succeed and write to all location like the native .net file functions do?
There could be workarounds like marshal the entire data to managed memory or writing the file to the ApplicationData.Current.LocalFolder and then moving it using vb.net calls. But nothing like making CreateFile succeed.
PS: Our app is a side loaded one for enterprise use only and we do not mind if it fails windows stores test. (our customers are trusting us from “windows NT” days :-)
Help from Microsoft forum got this working.click here for the full thread
Basically Cast the file object to IStorageItemHandleAccess and call Create to obtain a file HANDLE. Then use WriteFile and Close in unmanaged code.
Here is the code:
<ComImport>
<Guid("5CA296B2-2C25-4D22-B785-B885C8201E6A")>
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Friend Interface IStorageItemHandleAccess
Function Create(ao As Int32, so As Int32, o As Int32, oploc As Int32,
ByRef ptrHandle As IntPtr) As IntPtr
End Interface
' just 2 lines of extra code in your function to get the HANDLE which works magic!
Dim storageHandleIface = DirectCast(file, IStorageItemHandleAccess)
Dim myfileHandle As IntPtr
Dim res = storageHandleIface.Create(&H120116, 2, 0, 0, myfileHandle) ' WindowsStorageCom.h

WinAPI CredEnumerate Marshal.PtrToStructure Exception

I have problems with enumerating Windows Credentials from VB.Net application with WinAPI functions. My code is below.
<DllImport("advapi32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
Private Shared Function CredEnumerate(filter As String, flag As Integer, ByRef count As Integer, ByRef pCredentials As IntPtr) As Boolean
End Function
Public Enum CRED_PERSIST As UInteger
SESSION = 1
LOCAL_MACHINE = 2
ENTERPRISE = 3
End Enum
Public Enum CRED_TYPE As UInteger
GENERIC = 1
DOMAIN_PASSWORD = 2
DOMAIN_CERTIFICATE = 3
DOMAIN_VISIBLE_PASSWORD = 4
GENERIC_CERTIFICATE = 5
DOMAIN_EXTENDED = 6
MAXIMUM = 7
' Maximum supported cred type
MAXIMUM_EX = (MAXIMUM + 1000)
' Allow new applications to run on old OSes
End Enum
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Public Structure CREDENTIAL_ATTRIBUTE
Private Keyword As String
Private Flags As UInteger
Private ValueSize As UInteger
Private Value As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
Private Class Credential
Public Flags As UInt32
Public Type As CRED_TYPE
Public TargetName As String
Public Comment As String
Public LastWritten As ComTypes.FILETIME
Public CredentialBlobSize As UInt32
Public CredentialBlob As IntPtr
Public Persist As CRED_PERSIST
Public AttributeCount As UInt32
Public Attributes As IntPtr
Public TargetAlias As String
Public UserName As String
End Class
Private Function GetCredentials() As Credential()
Dim count As Integer = 0
Dim pCredentials As IntPtr = IntPtr.Zero
Dim credentials As List(Of Credential) = New List(Of Credential)
Dim ret As Boolean = CredEnumerate(Nothing, 0, count, pCredentials)
If ret <> False Then
Dim p As IntPtr = pCredentials
For n As Integer = 0 To count - 1
If Marshal.SizeOf(p) = 4 Then
p = New IntPtr(p.ToInt32() + n)
Else
p = New IntPtr(p.ToInt64() + n)
End If
credentials.Add(Marshal.PtrToStructure(Marshal.ReadIntPtr(p), GetType(Credential)))
Next
End If
Return credentials.ToArray
End Function
Marshal.PtrToStructure function throws System.ExecetionEngineException without any useful information. I suspected with wrong credential structure but it seems correct to me. If you have any idea about what is wrong, I'm waiting your answers.
Thanks
Edit: Thanks to #Zaggler here is my corrected function now it adds credentials to array but whole structure is empty.
Here is new function.
Private Function GetCredentials() As Credential()
Dim count As Integer = 0
Dim pCredentials As IntPtr = IntPtr.Zero
Dim credentials As List(Of Credential) = New List(Of Credential)
Dim ret As Boolean = CredEnumerate(Nothing, 0, count, pCredentials)
If ret <> False Then
Dim p As IntPtr = pCredentials
For n As Integer = 0 To count - 1
Dim cred As Credential = New Credential
Dim pnt As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(cred))
Try
If Marshal.SizeOf(p) = 4 Then
p = New IntPtr(p.ToInt32() + n)
Else
p = New IntPtr(p.ToInt64() + n)
End If
Marshal.StructureToPtr(cred, pnt, False)
credentials.Add(Marshal.PtrToStructure(pnt, GetType(Credential)))
Finally
Marshal.FreeHGlobal(pnt)
End Try
Next
End If
Return credentials.ToArray
End Function
Your first attempt was better. Don't use AllocHGlobal and FreeHGlobal. Winapi is allocating memory. Lookup CredFree to release allocated memory (after marshalling structs).
There is a mistake in your pointer arithmetic. You have to increment with pointer size so try:
...
Dim p As IntPtr = pCredentials
For n As Integer = 0 To count - 1
credentials.Add(Marshal.PtrToStructure(Marshal.ReadIntPtr(p), GetType(Credential)))
p = p + IntPtr.Size
Next
...
UInt32 and UInteger are the same, so be consistent and choose one.
You could try to use Charset.Auto for everything, if that doesn't work, try Charset.Unicode and use CredEnumerateW function.

Dropbox error using upload session

This is the modified code about uploadsession soo at small size file it working like a charm but when I try larger file like 5mb up. The following error keep showing up :
+$exception {"lookup_failed/closed/..."} System.Exception {Dropbox.Api.ApiException}
Private Async Sub UploadToolStripMenuItem2_Click(sender As Object, e As EventArgs) Handles UploadToolStripMenuItem2.Click
Dim C As New OpenFileDialog
C.Title = "Choose File"
C.Filter = "All Files (*.*)|*.*"
If C.ShowDialog = Windows.Forms.DialogResult.OK Then
Dim fileinfos = Path.GetFileName(C.FileName)
Dim filetempat = Path.GetFullPath(C.FileName)
Dim tempat As String = direktori.Text & "/" & fileinfos
Await Upload(filetempat, tempat)
End If
End Sub
Async Function Upload(localPath As String, remotePath As String) As Task
Const ChunkSize As Integer = 4096 * 1024
Using fileStream = File.Open(localPath, FileMode.Open)
If fileStream.Length <= ChunkSize Then
Await A.Files.UploadAsync(remotePath, body:=fileStream)
Else
Await Me.ChunkUpload(remotePath, fileStream, ChunkSize)
End If
End Using
End Function
Private Async Function ChunkUpload(path As [String], stream As FileStream, chunkSize As Integer) As Task
Dim numChunks As Integer = CInt(Math.Ceiling(CDbl(stream.Length) / chunkSize))
Dim buffer As Byte() = New Byte(chunkSize - 1) {}
Dim sessionId As String = Nothing
For idx As Integer = 0 To numChunks - 1
Dim byteRead = stream.Read(buffer, 0, chunkSize)
Using memStream = New MemoryStream(buffer, 0, byteRead)
If idx = 0 Then
Dim result = Await A.Files.UploadSessionStartAsync(True, memStream)
sessionId = result.SessionId
kondisi.Text=byteRead
Else
Dim cursor = New UploadSessionCursor(sessionId, CULng(CUInt(chunkSize) * CUInt(idx)))
If idx = numChunks - 1 Then
Dim fileMetadata As FileMetadata = Await A.Files.UploadSessionFinishAsync(cursor, New CommitInfo(path), memStream)
MessageBox.Show("Upload Complete")
Console.WriteLine(fileMetadata.PathDisplay)
Else
Await A.Files.UploadSessionAppendV2Async(cursor, True, memStream)
MessageBox.Show("Upload Failed")
End If
End If
End Using
Next
End Function
okay now its fixed, but when i got home, and try this code at my home,i got error, is this method are affected by slow internet connection ?.cause my campus have a decent speed.
this is the error message
+$exception {"Cannot access a disposed object.\r\nObject name: 'System.Net.Sockets.Socket'."} System.Exception {System.ObjectDisposedException}
this Cannot obtain value of local or argument '<this>' as it is not available at this instruction pointer, possibly because it has been optimized away. System.Net.Sockets.Socket
asyncResult Cannot obtain value of local or argument 'asyncResult' as it is not available at this instruction pointer, possibly because it has been optimized away. System.IAsyncResult
errorCode Cannot obtain value of local or argument 'errorCode' as it is not available at this instruction pointer, possibly because it has been optimized away. System.Net.Sockets.SocketError
this error window i got
A first chance exception of type 'System.ObjectDisposedException' occurred in System.dll
Additional information: Cannot access a disposed object.
This Closed error indicates you can't continue uploading to the upload session because it's already been closed.
The UploadSessionStartAsync and UploadSessionAppendV2Async both take a bool parameter called close, which closes the session.
You're always setting that to True when you call those, closing the sessions. You shouldn't close a session until you're finished uploading data for it.

How to I fix my deserialize error?

Why, hello everyone!
I've got this program i've been working on for months. Basic back story of it is, its supposed to be able to transport and install applications for windows in the background, like iCloud does for apps!
Anywho, i'm using a serialize/deserialize method to save the properties (eg admin username and passwordhash, directories, ports, etc.).
I have a class called 'PropertyNest' representing the properties and links to memory allocations. I'll cut it down to only the parts that the XMLSerializer looks at and saves.
Public Class PropertyNest
'Huge bunch of functions that we dont need to look at
'#######################
Public _wasLoadedFromFile As Boolean = False
Private _port As Integer = 201
Private _httpPort As Integer = 202
Private _rootFolder As String = "\appstreamRoot"
Private _adminUser As String = "Admin"
Private _adminPass As String = "21232F297A57A5A743894A0E4A801FC3" 'admin
Private _appstreamServerType As appStreamServerType = appStreamServerType.http
Private _useDES3forserver As Boolean = True
Private _encDES3pswd As String = "21232F297A57A5A743894A0E4A801FC3" 'admin
'Properties and descriptors for 'PropertyGrid' object go here \|/
'=================================================================
End Class
And its declared in the main window, serverMain like this,
Public Shared Property_Nest As AdvancedSettings.PropertyNest
and initialized later in like this,
If settingsfilename = "" Then
Property_Nest = New AdvancedSettings.PropertyNest()
Else
If propFileEncrypted = False Then
WriteLog("From unprotected file...", False)
Try
Property_Nest = AdvancedSettings.PropertyNest.LoadFrom(settingsfilename)
Catch ex As Exception
WriteLog("FAILED! Making default property nest...")
Property_Nest = New AdvancedSettings.PropertyNest()
End Try
Else
WriteLog("From encrypted file...", False)
Try
Property_Nest = AdvancedSettings.PropertyNest.LoadFrom(settingsfilename, True, propFilePswd)
Catch ex As Exception
WriteLog("FAILED! Making default property nest...", False)
Property_Nest = New AdvancedSettings.PropertyNest()
End Try
End If
End If
Thats all well and good. Loading it from the file that its saved to is the problem. Inside the PropertyNest class, I have 2 serializers programmed like so:
(Sorry its a bunch, there's optional encrypting of the serialized products with TrippleDES)
Public Sub SaveAs(ByVal filename As String, Optional ByVal Encrypted As Boolean = False)
Dim extra As String
If Encrypted = True Then : extra = "Encrypted? : Yes." : Else : extra = "Encrypted? : No."
End If
If filename = Nothing Then
Exit Sub
End If
writeLog2("Saving Property Nest to: " & filename & vbCrLf & extra, False)
If Encrypted = False Then
Dim writer As New Xml.Serialization.XmlSerializer(GetType(PropertyNest))
Dim file As New System.IO.StreamWriter(filename)
writer.Serialize(file, Me)
file.Close()
Else
Dim writer As New Xml.Serialization.XmlSerializer(GetType(PropertyNest))
Dim memstream As New System.IO.MemoryStream
writer.Serialize(memstream, Me)
memstream.Seek(0, IO.SeekOrigin.Begin)
Dim file As New System.IO.StreamWriter(filename)
Dim memstreamReader As New System.IO.StreamReader(memstream)
Do
file.WriteLine(serverMain.admin_des3Manager.Encrypt(memstreamReader.ReadLine()))
Loop Until memstreamReader.EndOfStream = True
file.Close()
End If
writeLog2("OK!")
End Sub
Shared Function LoadFrom(ByVal filename As String, Optional ByVal EncryptedWithPswd As Boolean = False, Optional ByVal Password As String = "") As PropertyNest
Dim reader As New Xml.Serialization.XmlSerializer(GetType(PropertyNest))
Dim file As New System.IO.StreamReader(filename)
Dim newPropNest As PropertyNest
If EncryptedWithPswd = False Then
newPropNest = reader.Deserialize(file) 'Error in XML Document(11, 3)
Else
If Password = "" Then
Dim convertedStream As New System.IO.MemoryStream
Dim convertedWriter As New System.IO.StreamWriter(convertedStream)
Do
convertedWriter.WriteLine(serverMain.admin_des3Manager.Decrypt(file.ReadLine()))
Loop Until file.EndOfStream = True
convertedWriter.Close()
newPropNest = reader.Deserialize(convertedStream)
Else
Dim tempDES3 As New DES3(Password)
Dim convertedStream As New System.IO.MemoryStream
Dim convertedWriter As New System.IO.StreamWriter(convertedStream)
Do
convertedWriter.WriteLine(tempDES3.Decrypt(file.ReadLine()))
Loop Until file.EndOfStream = True
convertedWriter.Close()
newPropNest = reader.Deserialize(convertedStream)
End If
End If
Return newPropNest
End Function
I marked the error in there.
Phew. Almost done.
i'm only worried about unencrypted right now, so i did my duty to save a custom, non default property nest, and it wrote to the file like so:
<?xml version="1.0" encoding="utf-8"?>
<PropertyNest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<_wasLoadedFromFile>false</_wasLoadedFromFile>
<ServerPort>2010</ServerPort>
<AdminUser>Matthew</AdminUser>
<AdminPasswordHash>21232F297A57A5A743894A0E4A801FC3</AdminPasswordHash>
<AppStreamPort>2020</AppStreamPort>
<AppStream_ServerRoot>\appstreamRoot</AppStream_ServerRoot>
<UseDES3>true</UseDES3>
<EncDES3Pswd>21232F297A57A5A743894A0E4A801FC3</EncDES3Pswd>
</PropertyNest>
Awesome! now.... If you look at the 'LoadFrom' function, you'll see i commented the line where i get the error... I dont see an error at 11, 3. Please help!
Thanks so much :D
Your XML is valid, however the class you need to deserialise, should be like this according to visual studio, copy you XML to the clipboard, go to the edit menu, paste special and past XML as classes give you this, give it a try see if it works, you can use a c# to vb converter to change to VB if you need to.
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class PropertyNest
{
private bool _wasLoadedFromFileField;
private ushort serverPortField;
private string adminUserField;
private string adminPasswordHashField;
private ushort appStreamPortField;
private string appStream_ServerRootField;
private bool useDES3Field;
private string encDES3PswdField;
/// <remarks/>
public bool _wasLoadedFromFile
{
get
{
return this._wasLoadedFromFileField;
}
set
{
this._wasLoadedFromFileField = value;
}
}
/// <remarks/>
public ushort ServerPort
{
get
{
return this.serverPortField;
}
set
{
this.serverPortField = value;
}
}
/// <remarks/>
public string AdminUser
{
get
{
return this.adminUserField;
}
set
{
this.adminUserField = value;
}
}
/// <remarks/>
public string AdminPasswordHash
{
get
{
return this.adminPasswordHashField;
}
set
{
this.adminPasswordHashField = value;
}
}
/// <remarks/>
public ushort AppStreamPort
{
get
{
return this.appStreamPortField;
}
set
{
this.appStreamPortField = value;
}
}
/// <remarks/>
public string AppStream_ServerRoot
{
get
{
return this.appStream_ServerRootField;
}
set
{
this.appStream_ServerRootField = value;
}
}
/// <remarks/>
public bool UseDES3
{
get
{
return this.useDES3Field;
}
set
{
this.useDES3Field = value;
}
}
/// <remarks/>
public string EncDES3Pswd
{
get
{
return this.encDES3PswdField;
}
set
{
this.encDES3PswdField = value;
}
}
}

What is the equivalent of My.Resources in Visual-C++?

I need to iterate through all the resources in a project and basically output their names. I have this done in VB. But I can't figure out what the equivalent of My.Resources.ResourceManager is in VC++.
Here's the VB code.
Dim objResourceManager As Resources.ResourceManager = My.Resources.ResourceManager
Dim objResourceSet As Resources.ResourceSet = objResourceManager.GetResourceSet(CultureInfo.CurrentCulture, True, True)
Dim iterator As IDictionaryEnumerator = objResourceSet.GetEnumerator()
Private Sub go()
Dim s As String = iterator.Key
Debug.WriteLine(s)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If iterator.MoveNext Then
go()
Else
iterator.Reset()
If iterator.MoveNext Then
go()
Else
Throw New Exception("No elements to display")
End If
End If
End Sub
And this is how far I am in VC++.
private:
Resources::ResourceManager^ rmgnr;
Resources::ResourceSet^ rSet;
public:
Form1(void)
{
rmgnr = gcnew System::Resources::ResourceManager(L"Resources ProjectCPP",Reflection::Assembly::GetExecutingAssembly());
//This is the problem as I can't find the equivalent in c++
rSet = rmgnr->GetResourceSet(CultureInfo::CurrentCulture,true,true);
Please help me figure this out.
I think you just want:
rmgnr = gcnew System::Resources::ResourceManager(GetType());
You can use something like the following for unmanaged C++:
HRSRC hResInfo = FindResource(hInstance, MAKEINTRESOURCE(resourceId), type);
HGLOBAL hRes = LoadResource(hInstance, hResInfo);
LPVOID memRes = LockResource(hRes);
DWORD sizeRes = SizeofResource(hInstance, hResInfo);
You will need to change the type and resourceId to match your resource. Not sure if its an image or icon or what kind of resource, but you would use something like:
FindResource(hInstance, MAKEINTRESOURCE(bitmapId), _T("PNG"));
For Managed C++, try something like the following:
Bitmap *MyBitmap;
String *Msg;
Reflection::Assembly *MyAssembly;
IO::Stream *ResourceStream;
MyAssembly = System::Reflection::Assembly::GetExecutingAssembly();
ResourceStream = MyAssembly->GetManifestResourceStream(ImageName);
if (ResourceStream != NULL)
{
MyBitmap = new Bitmap(ResourceStream);
Msg = String::Format("GetIcon: {0}, OK", ImageName);
}
else
Msg = String::Format("GetIcon: {0}, Failed", ImageName);
// MyBitmap countains your resource
You will need to replace ImageName with the name of your resource you are trying to grab. Again, I'm assuming its an image resource you are trying to grab.