C++ DLL function does not seem to work in C# - c#

I have developed a small program using C# and bird.dll, but the birdRS232WakeUp() function seem not to be working.
When I call the birdRS232WakeUp() function in C++ the program will stop for a while (8-10 seconds). It looks like it is beginning to do the process connecting with the hardware (Flock of bird).
But in C#, it does not stop when calling birdRS232WakeUp(). How do I fix this problem?
The C# code is like the following.
[DllImport(#"Bird.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool birdRS232WakeUp(int nGroupID, Boolean bStandAlone, int nNumDevices,
ref ushort[] pwComport, uint dwBaudRate,
uint dwReadTimeout, uint dwWriteTimeout);
ushort[] COM_port = new ushort[]{0,16,0,0,0};
if ((!birdRS232WakeUp(GROUP_ID, false, DEVCOUNT, ref COM_port, BAUD_RATE, READ_TIMEOUT, WRITE_TIMEOUT)))
{
LWakeUpStatus.Text = "Failde to wake up FOB";
}
And the C++ code is looking like the following.
WORD COM_port[5] = {0,15,0,0,0}
if ((!birdRS232WakeUp(GROUP_ID,
FALSE, // Not stand-alone
DEVCOUNT, // Number of Devices
COM_port, // COM Port
BAUD_RATE, // BAUD
READ_TIMEOUT,WRITE_TIMEOUT, // Reponses timeouts
GMS_GROUP_MODE_ALWAYS)))
{
printf("Can't Wake Up Flock!\n");
Sleep(3000);
exit(-1);}
C++ header file for this function:
birdRS232WakeUp(int nGroupID, BOOL bStandAlone, int nNumDevices,
WORD *pwComport, DWORD dwBaudRate, DWORD dwReadTimeout,
DWORD dwWriteTimeout, int nGroupMode = GMS_GROUP_MODE_ALWAYS);
And the manual states that "pwComport" points to an array of words, each of which is the number of the COM port attached to one of the birds (for example, COM1 = 1, COM2 = 2, etc.)
Update 1:
I have taken a suggestion from elder_george, but the problem still exist. I had to change the C# code to the following.
public static extern bool birdRS232WakeUp(int nGroupID, Boolean bStandAlone, int nNumDevices,
ushort[] pwComport, uint dwBaudRate, uint dwReadTimeout,
uint dwWriteTimeout,int nGroupMode);
if ((!birdRS232WakeUp(GROUP_ID, false, DEVCOUNT, COM_port, BAUD_RATE, READ_TIMEOUT, WRITE_TIMEOUT,2)))
{
LWakeUpStatus.Text = "Failde to wake up FOB";
}
BTW, the int nGroupMode is equal to 2, based on the enum type below .
enum GroupModeSettings
{
// GMS_DEFAULT, // Driver will determine whether or not to use RS232 group mode.
GMS_GROUP_MODE_NEVER, // RS232 group mode will never be used
GMS_GROUP_MODE_ALWAYS, // RS232 group mode will always be used
NUM_GROUP_MODE_SETTINGS
};

Not sure if these points will solve your problem, but:
1) pwComport should be declared as ushort[] pwComport, not ref ushort[] pwComport
2) you need to pass nGroupMode parameter from C#. You can set it to default value if you use C#4, but don't ignore it at all.

Related

Issue with native C++ dll in C#

I have native C++ dll with function that finds the number of cameras connected to the computer and returns their serial number. I am trying to use native C++ dll in C# application but I keep getting the Access Violation error(Attempted to read or write protected memory).
The function in question is
uint32_t GetSerialNumList(char** theBufList, int theBufSize, int theListLength);
The way I am using PInvoke is as follows:
[DllImport(CameraDll, EntryPoint = "GetSerialNumList", CallingConvention = CallingConvention.Cdecl)]
private static extern uint GetSerialNumList(out byte[] pBuf, int BufSize, int ListLength);
If I create native C++ application to use the dll and use the function as follows:
char* theSerialNumb;
theSerialNumb = (char *) malloc(sizeof(char)* 8);
status = TRI_GetSerialNumList(&theSerialNumb, 8, 1);
It works fine however, if I use as follows in C# it give me above mentioned error:
byte[] BufList;
BufList = new byte[8];
rv = GetSerialNumList(out BufList, 8, 1);
The parameter you're passing in c# is a pointer to a byte array. What you're passing in c++ is a pointer to a pointer to a byte array. Also, in the C++ example, you're passing data to the function, but in the C# example, you're passing it as an out instead of a ref.
Although I'm not sure this would work, I would try to create a struct containing a byte array and pass the struct to the external function.
To answer some of the above comments, these functions typically modify memory passed to it rather than try to allocate additional memory due to the different ways programs create heaps.
The first thing I'd check is the C# import signature being used. There's the P/Invoke Interop Assistant tool available for free here.
Loading your function signature into the tool, translates it to:
public partial class NativeMethods {
/// Return Type: unsigned int
///theBufList: char**
///theBufSize: int
///theListLength: int
[System.Runtime.InteropServices.DllImportAttribute("<Unknown>", EntryPoint="GetSerialNumList")]
public static extern uint GetSerialNumList(ref System.IntPtr theBufList, int theBufSize, int theListLength) ;
}
The second thing, is that since you are allocating memory for the buffer in the C++/native version; perhaps you need to pass a pre-allocated buffer as well, when using C#.
Hope this helps.
Okay, I took pointers from Russell and kvr and did some digging around and following is the scheme that I came up with.
Original native function call:
uint32_t GetSerialNumList(char** theBufList, int theBufSize, int theListLength);
The way I am using PInvoke is as follows:
[DllImport(CameraDll, EntryPoint = "GetSerialNumList", CallingConvention = CallingConvention.Cdecl)]
private static extern int GetSerialNumList(ref IntPtr pBuf, int BufSize, int ListLength);
byte[] BufIn;
BufIn = new byte[8 * ListLength];
IntPtr pBuf = IntPtr.Zero;
pBuf = Marshal.AllocHGlobal(8 * ListLength);
Console.WriteLine("Calling GetSerialNumList");
rv = GetSerialNumList(ref pBuf, 8, ListLength);
Marshal.Copy(pBuf, BufIn, 0, 8*ListLength);
I feel this is somewhat long, but it gives me the desired result.

char** callback from c++ to C# and send data back

I've got a problem and I hope you guys can help me with this. I'm relatively new to programming in C# and have never worked with c++, so i can't figure out the following problem:
I'm using a SDK (which is writen in c++) to receive and request DDNS updates from a specific type of device. Setting the callback is working and the callback is triggered at the expected time.
The documentation says the callback contains a char** parameter, but I don't know what do do with this. I've tried a lot of options and the only way the application doesn't crash is by using an IntPtr for that parameter. But when I do this I'm not able to set a value for that IntPtr.
Original code I found using the callback:
int CALLBACK CBGetDeviceAddr(int *pResult, char** pIPAddr,unsigned short *pSdkPort, char *szDeviceID, char*szDeviceName)
{
int nCount=g_pDeviceList->GetItemCount();
memset(g_szIPAddr, 0, MAX_IP_LEN+1);
*pIPAddr=g_szIPAddr;
BOOL bRet=FALSE;
for(int i = 0; i< nCount; i++)
{
if (strcmp(szDeviceID,(LPCTSTR)g_pDeviceList->GetItemText(i, 2).GetBuffer(0)) == 0)//,g_pDeviceList->GetItemText(i,2).GetLength()
{
*pResult=1;
strcpy(*pIPAddr,g_pDeviceList->GetItemText(i,0).GetBuffer(0));
*pSdkPort = atoi(g_pDeviceList->GetItemText(i,4).GetBuffer(0));
TRACE("CALLBACK CBGetDeviceAddrInfo:in DeviceID=%s, DeviceName=%s,out DeviceIP=%s,DevicePort = %d\n",\
szDeviceID, szDeviceName, *pIPAddr, *pSdkPort);
bRet = TRUE;
break;
}
}
if (!bRet)
{
*pResult=0;
*pIPAddr=NULL;
TRACE("CALLBACK CBGetDeviceAddrInfo:in DeviceID=%s, DeviceName=%s,out no such device online!!!\n",\
szDeviceID, szDeviceName);
}
return 0;
}
What I'm currently using:
private int CBF_GetADeviceIP(ref int pResult, ref IntPtr pIPAddr, ref ushort pSdkPort, string szDeviceID, string szDeviceName)
This is working for pResult and pSdkPort (so I can send data back) but can't do anything with pIPAddr.
Any help?
Thanks
Normally when you see char**, it means "This parameter will be used to return a string".
When writing the P/Invoke for such an argument, you can usually use ref string (if the string is input, or input and output) or out string (if the string is output only).
The other thing you need to know is whether the native method is expecting ANSI or Unicode strings.
To specify which string encoding to use, use the CharSet attribute parameter:
[DllImport("MyDLL.dll", CharSet = CharSet.Unicode)]
or
[DllImport("MyDLL.dll", CharSet = CharSet.Ansi)]
and so on.
I'm not that good about C#, but isn't pIPAddr a pointer to a string?
So you should declare that parameter as ref string pIPAddr.

Trouble marshaling array of struct data from C++ to C#

I have searched for days and have tried everything I could find, but still cannot get this to work.
Details:
I have a 3rd party stock trading app that is calling into my unmanaged dll. It is supplying data that the dll processes/filters and then saves into a global ring buffer. The ring buffer is an array of structures, 100 long. All of this runs in the stock trading apps process.
I also have a managed C# app calling into the same dll in a different process that needs to get the info in the global ring buffer as quickly and efficiently as possible. Everything works except that I can only get data for the first structure in the array. Also after the call to the dll from C# the C# code no longer knows that arrayMD is an array of structs, it shows up in the debugger as a simple structure. Could it be the memcpy in the dll causing the problem? I’ve tried all kinds of combinations with [In, Out], IntPtr, and Marchal.PtrToStructure combinations. I am greatly fubar. Any help would be greatly appreciated.
Thanks
Here is what I am attempting.
On the dll side:
struct stMD
{
float Price;
unsigned int PriceDir;
unsigned int PriceDirCnt;
};
// Global memory
#pragma data_seg (".IPC")
bool NewPoint = false; // Flag used to signal a new point.
static stMD aryMD [100] = {{0}};
#pragma data_seg()
void __stdcall RetrieveMD (stMD *LatestMD [])
{
memcpy(*LatestMD, aryMD, sizeof(aryMD));
}
On the C# side:
[StructLayout(LayoutKind.Sequential)]
public struct stMD
{
public float Price;
public uint PriceDir;
public uint PriceDirCnt;
};
public static stMD[] arrayMD = new stMD[100];
[DllImport(#"Market.dll")]
public static extern void RetrieveMD(ref stMD[] arrayMD);
RetrieveMD(ref arrayMD);
The problem is the definition of your DLL entry point:
void __stdcall RetrieveMD (stMDP *LatestMD [])
You don't specify the size of the array, so how is C# supposed to know how many elements were copied into it? This is a problem in other languages too. Your implementation simply assumes that the provided memory is large enough to contain aryMD. But what if it's not? You've just created a buffer overrun.
If you want the caller to allocate the array, then the caller must also pass in the number of elements that the array contains.
Edit
The C++ declaration should look something like this:
// On input, length should be the number of elements in the LatestMD array.
// On output, length should be the number of valid records copied into the array.
void __stdcall RetrieveMD( stMDP * LatestMD, int * length );
The C# declaration would then look something like this:
[DllImport(#"Market.dll")]
public static extern void RetrieveMD(
[In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] ref stMD[] arrayMD,
[In, Out] ref int length);
Your problem, I think, is that you are trying to pass an array by reference from C# into C, and you almost never need to do that. In your case, all you want to happen is for C# to allocate memory for 100 of your structures, then pass that memory to C to be filled in. In your case, you're passing a pointer to an array of structures, which is an extra level of indirection that you don't really need.
You rarely see C functions that take a fixed sized array; instead, your function would typically be defined in C to accept a pointer and a length, e.g. something like:
void __stdcall RetrieveMD (stMDP *LatestMD, int size)
{
int count = 0;
if (size < sizeof(aryMD))
count = size;
else
count = sizeof(aryMD);
memcpy(LatestMD, aryMD, count);
}
To call this method from C#, you need to do two things. First, you need to allocate an array of the appropriate size to pass in. Second, you need to tell the marshaling code (that does the C# -> C copying) how to figure out how much data to be marshaled, via the [MarshalAs] attribute:
[DllImport(#"Market.dll")]
public static extern void RetrieveMD (
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] stMDP[] arrayMD,
int length
);
var x = new stMDP[100];
RetrieveMD(x, 100);
I got it working. Was I ever tring to make this harder than it is.
I re-read chapter 2 of ".NET 2.0 Interoperability Recipes: A Problem-Solution Approach".
Here's what works.
On C++ side I removed the pointers
struct stMD
{
float Price;
unsigned int PriceDir;
unsigned int PriceDirCnt;
};
// Global memory
#pragma data_seg (".IPC")
bool NewPoint = false; // Flag used to signal a new point.
static stMD aryMD [100] = {{0}};
#pragma data_seg()
void __stdcall RetrieveMD (stMD LatestMD [100])
{
memcpy(LatestMD, aryMD, sizeof(aryMD));
}
On the C# side I removed both (ref)s and added [In, Out]
[StructLayout(LayoutKind.Sequential)]
public struct stMD
{
public float Price;
public uint PriceDir;
public uint PriceDirCnt;
};
public static stMD[] arrayMD = new stMD[100];
[DllImport(#"Market.dll")]
public static extern void RetrieveMD([In, Out] stMD[] arrayMD);
RetrieveMD(arrayMD);
Thanks to everyone who offered help.

C# - Read Parallel Port State (Simple Push Switch)

I am updating in C# some software that was originally written in VC++ V1.0 for DOS6.
One aspect of the software is a check on the parallel port, where a simple push-button switch is connected. I don't currently know to which two pins the switch is connected, but I have the source for the old program, the relevant sections of which are below...
#define switch_mask 0x10
void main(int argc, char *argv[])
{
int NewState = 0, OldState = 0;
/* Check switch */
NewState = _bios_printer (_PRINTER_STATUS, 0, 0);
if (NewState != OldState)
{
checkParallelPort (NewState);
OldState = NewState;
}
}
void checkParallelPort (int portState)
{
int SwitchState;
/* Switch bit is Active LOW */
SwitchState = (portState & switch_mask) ? 1 : 0;
}
Now _bios_printer (within bios.h) is obviously not available to me in C#, but I'm struggling to find an alternative that can do this simple task.
Info on _bios_printer is here. I've done plenty of searching for reading/writing to/from the parallel port in .Net, but nothing seems to provide me with the port status.
Also, can you conclude from this code (and how it checks the 'status') where the two switch wires are connected on the DB25 plug?
I'd be grateful if anyone has some help/advice on this please.
Many thanks
It seems to be checking 'Error', pin 15. IIRC, this is pulled up internally, so your switch should pull down pin 15. Connect it between pins 15 and 18.
There are some drivers available that allow the reading of the I/O map ports. You will have to import and make DLL calls and then almost certainly poll the pin :((
I do wish this interface was dead and buried!
Thanks for the reply. Here's what I ended up with...
I used this tutorial on CodeProject...
http://www.codeproject.com/KB/vb/Inpout32_read.aspx
When converted to C#, I used something similar to below. (Apologies if there is an error - I 'paraphrased' the code below from what i've ended up with - it works for me!)
private void button1_Click(object sender, EventArgs e)
{
short InReg = 0x379; // Location of Port Status register
int NewState; // int named NewState
NewState = InpOut32_Declarations.Inp(InReg); //Now NewState has the values in 'Status port'
MessageBox.Show(NewState); //A popup will indicate the current value of NewState
}
static class InpOut32_Declarations
{
[DllImport("inpout32.dll", EntryPoint = "Inp32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
//Inp and Out declarations for port I/O using inpout32.dll.
public static extern int Inp(ushort PortAddress);
[DllImport("inpout32.dll", EntryPoint = "Out32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
public static extern void Out(ushort PortAddress, short Value);
}
Interestingly, as my Parallel Port starts at 0xE800 and not 0x378, I had to modify the source of inpout32.dll as the out32 method only accepts a short and 0xE800 is too big for a short! changed it to unsigned short.
Thanks for your help

How to read and modify NTFS Alternate Data Streams using .NET [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
How can I read and modify "NTFS Alternate Data Streams" using .NET?
It seems there is no native .NET support for it. Which Win32 API's would I use? Also, how would I use them, as I don't think this is documented?
Here is a version for C#
using System.Runtime.InteropServices;
class Program
{
static void Main(string[] args)
{
var mainStream = NativeMethods.CreateFileW(
"testfile",
NativeConstants.GENERIC_WRITE,
NativeConstants.FILE_SHARE_WRITE,
IntPtr.Zero,
NativeConstants.OPEN_ALWAYS,
0,
IntPtr.Zero);
var stream = NativeMethods.CreateFileW(
"testfile:stream",
NativeConstants.GENERIC_WRITE,
NativeConstants.FILE_SHARE_WRITE,
IntPtr.Zero,
NativeConstants.OPEN_ALWAYS,
0,
IntPtr.Zero);
}
}
public partial class NativeMethods
{
/// Return Type: HANDLE->void*
///lpFileName: LPCWSTR->WCHAR*
///dwDesiredAccess: DWORD->unsigned int
///dwShareMode: DWORD->unsigned int
///lpSecurityAttributes: LPSECURITY_ATTRIBUTES->_SECURITY_ATTRIBUTES*
///dwCreationDisposition: DWORD->unsigned int
///dwFlagsAndAttributes: DWORD->unsigned int
///hTemplateFile: HANDLE->void*
[DllImportAttribute("kernel32.dll", EntryPoint = "CreateFileW")]
public static extern System.IntPtr CreateFileW(
[InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
[InAttribute()] System.IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
[InAttribute()] System.IntPtr hTemplateFile
);
}
public partial class NativeConstants
{
/// GENERIC_WRITE -> (0x40000000L)
public const int GENERIC_WRITE = 1073741824;
/// FILE_SHARE_DELETE -> 0x00000004
public const int FILE_SHARE_DELETE = 4;
/// FILE_SHARE_WRITE -> 0x00000002
public const int FILE_SHARE_WRITE = 2;
/// FILE_SHARE_READ -> 0x00000001
public const int FILE_SHARE_READ = 1;
/// OPEN_ALWAYS -> 4
public const int OPEN_ALWAYS = 4;
}
A First, nothing in the Microsoft® .NET Framework provides this functionality. If you want it, plain and simple you'll need to do some sort of interop, either directly or using a third-party library.
If you're using Windows Server™ 2003 or later, Kernel32.dll exposes counterparts to FindFirstFile and FindNextFile that provide the exact functionality you're looking for. FindFirstStreamW and FindNextStreamW allow you to find and enumerate all of the Alternate Data Streams within a particular file, retrieving information about each, including its name and its length. The code for using these functions from managed code is very similar to that which I showed in my December column, and is shown in Figure 1.
Figure 1 Using FindFirstStreamW and FindNextStreamW
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid {
private SafeFindHandle() : base(true) { }
protected override bool ReleaseHandle() {
return FindClose(this.handle);
}
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
private static extern bool FindClose(IntPtr handle);
}
public class FileStreamSearcher {
private const int ERROR_HANDLE_EOF = 38;
private enum StreamInfoLevels { FindStreamInfoStandard = 0 }
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)]
private static extern SafeFindHandle FindFirstStreamW(string lpFileName, StreamInfoLevels InfoLevel, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData, uint dwFlags);
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool FindNextStreamW(SafeFindHandle hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private class WIN32_FIND_STREAM_DATA {
public long StreamSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)]
public string cStreamName;
}
public static IEnumerable<string> GetStreams(FileInfo file) {
if (file == null) throw new ArgumentNullException("file");
WIN32_FIND_STREAM_DATA findStreamData = new WIN32_FIND_STREAM_DATA();
SafeFindHandle handle = FindFirstStreamW(file.FullName, StreamInfoLevels.FindStreamInfoStandard, findStreamData, 0);
if (handle.IsInvalid) throw new Win32Exception();
try {
do {
yield return findStreamData.cStreamName;
} while (FindNextStreamW(handle, findStreamData));
int lastError = Marshal.GetLastWin32Error();
if (lastError != ERROR_HANDLE_EOF) throw new Win32Exception(lastError);
} finally {
handle.Dispose();
}
}
}
You simply call FindFirstStreamW, passing to it the full path to the target file. The second parameter to FindFirstStreamW dictates the level of detail you want in the returned data; currently, there is only one level (FindStreamInfoStandard), which has a numerical value of 0. The third parameter to the function is a pointer to a WIN32_FIND_STREAM_DATA structure (technically, what the third parameter points to is dictated by the value of the second parameter detailing the information level, but as there's currently only one level, for all intents and purposes this is a WIN32_FIND_STREAM_DATA). I've declared that structure's managed counterpart as a class, and in the interop signature I've marked it to be marshaled as a pointer to a struct. The last parameter is reserved for future use and should be 0.
If a valid handle is returned from FindFirstStreamW, the WIN32_FIND_STREAM_DATA instance contains information about the stream found, and its cStreamName value can be yielded back to the caller as the first stream name available. FindNextStreamW accepts the handle returned from FindFirstStreamW and fills the supplied WIN32_FIND_STREAM_DATA with information about the next stream available, if it exists. FindNextStreamW returns true if another stream is available, or false if not.
As a result, I continually call FindNextStreamW and yield the resulting stream name until FindNextStreamW returns false. When that happens, I double check the last error value to make sure that the iteration stopped because FindNextStreamW ran out of streams, and not for some unexpected reason.
Unfortunately, if you're using Windows® XP or Windows 2000 Server, these functions aren't available to you, but there are a couple of alternatives. The first solution involves an undocumented function currently exported from Kernel32.dll, NTQueryInformationFile. However, undocumented functions are undocumented for a reason, and they can be changed or even removed at any time in the future. It's best not to use them. If you do want to use this function, search the Web and you'll find plenty of references and sample source code. But do so at your own risk.
Another solution, and one which I've demonstrated in Figure 2, relies on two functions exported from Kernel32.dll, and these are documented. As their names imply, BackupRead and BackupSeek are part of the Win32® API for backup support:
BOOL BackupRead(HANDLE hFile, LPBYTE lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, BOOL bAbort, BOOL bProcessSecurity, LPVOID* lpContext);
BOOL BackupSeek(HANDLE hFile, DWORD dwLowBytesToSeek, DWORD dwHighBytesToSeek, LPDWORD lpdwLowByteSeeked, LPDWORD lpdwHighByteSeeked, LPVOID* lpContext);
Figure 2 Using BackupRead and BackupSeek
public enum StreamType {
Data = 1,
ExternalData = 2,
SecurityData = 3,
AlternateData = 4,
Link = 5,
PropertyData = 6,
ObjectID = 7,
ReparseData = 8,
SparseDock = 9
}
public struct StreamInfo {
public StreamInfo(string name, StreamType type, long size) {
Name = name;
Type = type;
Size = size;
}
readonly string Name;
public readonly StreamType Type;
public readonly long Size;
}
public class FileStreamSearcher {
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool BackupRead(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [MarshalAs(UnmanagedType.Bool)] bool bAbort, [MarshalAs(UnmanagedType.Bool)] bool bProcessSecurity, ref IntPtr lpContext);[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool BackupSeek(SafeFileHandle hFile, uint dwLowBytesToSeek, uint dwHighBytesToSeek, out uint lpdwLowByteSeeked, out uint lpdwHighByteSeeked, ref IntPtr lpContext); public static IEnumerable<StreamInfo> GetStreams(FileInfo file) {
const int bufferSize = 4096;
using (FileStream fs = file.OpenRead()) {
IntPtr context = IntPtr.Zero;
IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
try {
while (true) {
uint numRead;
if (!BackupRead(fs.SafeFileHandle, buffer, (uint)Marshal.SizeOf(typeof(Win32StreamID)), out numRead, false, true, ref context)) throw new Win32Exception();
if (numRead > 0) {
Win32StreamID streamID = (Win32StreamID)Marshal.PtrToStructure(buffer, typeof(Win32StreamID));
string name = null;
if (streamID.dwStreamNameSize > 0) {
if (!BackupRead(fs.SafeFileHandle, buffer, (uint)Math.Min(bufferSize, streamID.dwStreamNameSize), out numRead, false, true, ref context)) throw new Win32Exception(); name = Marshal.PtrToStringUni(buffer, (int)numRead / 2);
}
yield return new StreamInfo(name, streamID.dwStreamId, streamID.Size);
if (streamID.Size > 0) {
uint lo, hi; BackupSeek(fs.SafeFileHandle, uint.MaxValue, int.MaxValue, out lo, out hi, ref context);
}
} else break;
}
} finally {
Marshal.FreeHGlobal(buffer);
uint numRead;
if (!BackupRead(fs.SafeFileHandle, IntPtr.Zero, 0, out numRead, true, false, ref context)) throw new Win32Exception();
}
}
}
}
The idea behind BackupRead is that it can be used to read data from a file into a buffer, which can then be written to the backup storage medium. However, BackupRead is also very handy for finding out information about each of the Alternate Data Streams that make up the target file. It processes all of the data in the file as a series of discrete byte streams (each Alternate Data Stream is one of these byte streams), and each of the streams is preceded by a WIN32_STREAM_ID structure. Thus, in order to enumerate all of the streams, you simply need to read through all of these WIN32_STREAM_ID structures from the beginning of each stream (this is where BackupSeek becomes very handy, as it can be used to jump from stream to stream without having to read through all of the data in the file).
To begin, you first need to create a managed counterpart for the unmanaged WIN32_STREAM_ID structure:
typedef struct _WIN32_STREAM_ID {
DWORD dwStreamId; DWORD dwStreamAttributes;
LARGE_INTEGER Size;
DWORD dwStreamNameSize;
WCHAR cStreamName[ANYSIZE_ARRAY];
} WIN32_STREAM_ID;
For the most part, this is like any other structure you'd marshal through P/Invoke. However, there are a few complications. First and foremost, WIN32_STREAM_ID is a variable-sized structure. Its last member, cStreamName, is an array with length ANYSIZE_ARRAY. While ANYSIZE_ARRAY is defined to be 1, cStreamName is just the address of the rest of the data in the structure after the previous four fields, which means that if the structure is allocated to be larger than sizeof (WIN32_STREAM_ID) bytes, that extra space will in effect be part of the cStreamName array. The previous field, dwStreamNameSize, specifies exactly how long the array is.
While this is great for Win32 development, it wreaks havoc on a marshaler that needs to copy this data from unmanaged memory to managed memory as part of the interop call to BackupRead. How does the marshaler know how big the WIN32_STREAM_ID structure actually is, given that it's variable sized? It doesn't.
The second problem has to do with packing and alignment. Ignoring cStreamName for a moment, consider the following possibility for your managed WIN32_STREAM_ID counterpart:
[StructLayout(LayoutKind.Sequential)]
public struct Win32StreamID {
public int dwStreamId;
public int dwStreamAttributes;
public long Size;
public int dwStreamNameSize;
}
An Int32 is 4 bytes in size and an Int64 is 8 bytes. Thus, you would expect this struct to be 20 bytes. However, if you run the following code, you'll find that both values are 24, not 20:
int size1 = Marshal.SizeOf(typeof(Win32StreamID));
int size2 = sizeof(Win32StreamID); // in an unsafe context
The issue is that the compiler wants to make sure that the values within these structures are always aligned on the proper boundary. Four-byte values should be at addresses divisible by 4, 8-byte values should be at boundaries divisible by 8, and so on. Now imagine what would happen if you were to create an array of Win32StreamID structures. All of the fields in the first instance of the array would be properly aligned. For example, since the Size field follows two 32-bit integers, it would be 8 bytes from the start of the array, perfect for an 8-byte value. However, if the structure were 20-bytes in size, the second instance in the array would not have all of its members properly aligned. The integer values would all be fine, but the long value would be 28 bytes from the start of the array, a value not evenly divisible by 8. To fix this, the compiler pads the structure to a size of 24, such that all of the fields will always be properly aligned (assuming the array itself is).
If the compiler's doing the right thing, you might be wondering why I'm concerned about this. You'll see why if you look at the code in Figure 2. In order to get around the first marshaling issue I described, I do in fact leave the cStreamName out of the Win32StreamID structure. I use BackupRead to read in enough bytes to fill my Win32StreamID structure, and then I examine the structure's dwStreamNameSize field. Now that I know how long the name is, I can use BackupRead again to read in the string's value from the file. That's all well and dandy, but if Marshal.SizeOf returns 24 for my Win32StreamID structure instead of 20, I'll be attempting to read too much data.
To avoid this, I need to make sure that the size of Win32StreamID is in fact 20 and not 24. This can be accomplished in two different ways using fields on the StructLayoutAttribute that adorns the structure. The first is to use the Size field, which dictates to the runtime exactly how big the structure should be:
[StructLayout(LayoutKind.Sequential, Size = 20)]
The second option is to use the Pack field. Pack indicates the packing size that should be used when the LayoutKind.Sequential value is specified and controls the alignment of the fields within the structure. The default packing size for a managed structure is 8. If I change that to 4, I get the 20-byte structure I'm looking for (and as I'm not actually using this in an array, I don't lose efficiency or stability that might result from such a packing change):
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct Win32StreamID {
public StreamType dwStreamId;
public int dwStreamAttributes;
public long Size;
public int dwStreamNameSize; // WCHAR cStreamName[1];
}
With this code in place, I can now enumerate all of the streams in a file, as shown here:
static void Main(string[] args) {
foreach (string path in args) {
Console.WriteLine(path + ":");
foreach (StreamInfo stream in FileStreamSearcher.GetStreams(new FileInfo(path))) {
Console.WriteLine("\t{0}\t{1}\t{2}", stream.Name != null ? stream.Name : "(unnamed)", stream.Type, stream.Size);
}
}
}
You'll notice that this version of FileStreamSearcher returns more information than the version that uses FindFirstStreamW and FindNextStreamW. BackupRead can provide data on more than just the primary stream and Alternate Data Streams, also operating on streams containing security information, reparse data, and more. If you only want to see the Alternate Data Streams, you can filter based on the StreamInfo's Type property, which will be StreamType.AlternateData for Alternate Data Streams.
To test this code, you can create a file that has Alternate Data Streams using the echo command at the command prompt:
> echo ".NET Matters" > C:\test.txt
> echo "MSDN Magazine" > C:\test.txt:magStream
> StreamEnumerator.exe C:\test.txt
test.txt:
(unnamed) SecurityData 164
(unnamed) Data 17
:magStream:$DATA AlternateData 18
> type C:\test.txt
".NET Matters"
> more < C:\test.txt:magStream
"MSDN Magazine"
So, now you're able to retrieve the names of all Alternate Data Streams stored in a file. Great. But what if you want to actually manipulate the data in one of those streams? Unfortunately, if you attempt to pass a path for an Alternate Data Stream to one of the FileStream constructors, a NotSupportedException will be thrown: "The given path's format is not supported."
To get around this, you can bypass FileStream's path canonicalization checks by directly accessing the CreateFile function exposed from kernel32.dll (see Figure 3). I've used a P/Invoke for the CreateFile function to open and retrieve a SafeFileHandle for the specified path, without performing any of the managed permission checks on the path, so it can include Alternate Data Stream identifiers. This SafeFileHandle is then used to create a new managed FileStream, providing the required access. With that in place, it's easy to manipulate the contents of an Alternate Data Stream using the System.IO namespace's functionality. The following example reads and prints out the contents of the C:\test.txt:magStream created in the previous example:
string path = #"C:\test.txt:magStream";
using (StreamReader reader = new StreamReader(CreateFileStream(path, FileAccess.Read, FileMode.Open, FileShare.Read))) {
Console.WriteLine(reader.ReadToEnd());
}
Figure 3 Using P/Invoke for CreateFile
private static FileStream CreateFileStream(string path, FileAccess access, FileMode mode, FileShare share) {
if (mode == FileMode.Append) mode = FileMode.OpenOrCreate; SafeFileHandle handle = CreateFile(path, access, share, IntPtr.Zero, mode, 0, IntPtr.Zero);
if (handle.IsInvalid) throw new IOException("Could not open file stream.", new Win32Exception());
return new FileStream(handle, access);
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern SafeFileHandle CreateFile(string lpFileName, FileAccess dwDesiredAccess, FileShare dwShareMode, IntPtr lpSecurityAttributes, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
Stephen Toub in MSDN Magazine from January 2006.
There is no native .NET support for them. You have to use P/Invoke to call the native Win32 methods.
To create them, call CreateFile with a path like filename.txt:streamname. If you use the interop call that returns a SafeFileHandle, you can use that to construct a FileStream that you can then read & write to.
To list the streams that exist on a file, use FindFirstStreamW and FindNextStreamW (which exist only on Server 2003 and later - not XP).
I don't believe you can delete a stream, except by copying the rest of the file and leaving off one of the streams. Setting the length to 0 may also work, but I haven't tried it.
You can also have alternate data streams on a directory. You access them the same as with files - C:\some\directory:streamname.
Streams can have compression, encryption and sparseness set on them independent of the default stream.
This nuget package CodeFluent Runtime Client has (amongst other utilities) an NtfsAlternateStream Class that supports create/read/update/delete/enumeration operations.
Not in .NET:
http://support.microsoft.com/kb/105763
#include <windows.h>
#include <stdio.h>
void main( )
{
HANDLE hFile, hStream;
DWORD dwRet;
hFile = CreateFile( "testfile",
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
0,
NULL );
if( hFile == INVALID_HANDLE_VALUE )
printf( "Cannot open testfile\n" );
else
WriteFile( hFile, "This is testfile", 16, &dwRet, NULL );
hStream = CreateFile( "testfile:stream",
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
0,
NULL );
if( hStream == INVALID_HANDLE_VALUE )
printf( "Cannot open testfile:stream\n" );
else
WriteFile(hStream, "This is testfile:stream", 23, &dwRet, NULL);
}

Categories