I am trying to find a way of printing images to a zebra and having a lot of trouble.
According to the docs:
The first encoding, known as B64, encodes the data using the MIME
Base64 scheme. Base64 is used to encode e-mail atachedments ...
Base64 encodes six bits to the byte, for an expantion of 33 percent
over the un-enclosed data. The second encoding, known as Z64,
first compresses the data using the LZ77 algorithm to reduce its size.
(This algorithm is used by the PKZIP and is intergral to the PNG
graphics format.) The compressed data is then encoded using the
MIME Base64 scheme as described above. A CRC will be calculated
accross the Base64-encoded data.
But it doesn't have a great deal more info.
Basically I was trying encoding with
private byte[] GetItemFromPath(string filepath)
{
using (MemoryStream ms = new MemoryStream())
{
using (Image img = Image.FromFile(filepath))
{
img.Save(ms, ImageFormat.Png);
return ms.ToArray();
}
}
}
Then trying to print with something like:
var initialArray = GetItemFromPath("C:\\RED.png");
string converted = Convert.ToBase64String(b);
PrintThis(string.Format(#"~DYRED.PNG,P,P,{1},0,:B64:
{0}
^XA
^F0200,200^XGRED.PNG,1,1^FS
^XZ", converted .ToString(), initialArray.Length));
From the sounds of it, either B64 or Z64 are both accepted.
I've tried a few variations, and a couple of methods for generating the CRC and calculating the 'size'.
But none seem to work and the download of the graphics to the printer is always getting aborted.
Has anyone managed to accomplish something like this? Or knows where I am going wrong?
All credit for me coming to this answer was from LabView Forum user Raydur. He posts a LabView solution that can be opened up in LabView to send images down. I personally didn't run it with my printer, I just used it to figure out the correct image code so I could replicate it in my code. The big thing that I was missing was padding my Hexadecimal code. For example, 1A is fine, but if you have just A, you need to pad a 0 in front of it to send 0A. The size of the file in the ZPL you are sending is also the original size of the byte array, not the final string representation of the data.
I've scoured many, many, many forums and Stackoverflow posts trying to figure this out because it seems like such a simple thing to do. I've tried every single solution posted elsewhere but I really wanted to just print a.PNG because the manual for my printer(Mobile QLN320) has support for it built-in. It says to either send it in Base64 or Hexadecimal, and I tried both to no avail. For anyone wanting to do Base64, I found in an older manual that you need to manually calculate CRC codes for each packet you send so I chose to go with the easier Hexadecimal route. So here is the code I got to work!
string ipAddress = "192.168.1.30";
int port = 6101;
string zplImageData = string.Empty;
//Make sure no transparency exists. I had some trouble with this. This PNG has a white background
string filePath = #"C:\Users\Path\To\Logo.png";
byte[] binaryData = System.IO.File.ReadAllBytes(filePath);
foreach (Byte b in binaryData)
{
string hexRep = String.Format("{0:X}", b);
if (hexRep.Length == 1)
hexRep = "0" + hexRep;
zplImageData += hexRep;
}
string zplToSend = "^XA" + "^MNN" + "^LL500" + "~DYE:LOGO,P,P," + binaryData.Length + ",," + zplImageData+"^XZ";
string printImage = "^XA^FO115,50^IME:LOGO.PNG^FS^XZ";
try
{
// Open connection
System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient();
client.Connect(ipAddress, port);
// Write ZPL String to connection
System.IO.StreamWriter writer = new System.IO.StreamWriter(client.GetStream(),Encoding.UTF8);
writer.Write(zplToSend);
writer.Flush();
writer.Write(printImage);
writer.Flush();
// Close Connection
writer.Close();
client.Close();
}
catch (Exception ex)
{
// Catch Exception
}
The ZPL II Programming Guide documents the ~DG command and GRF format (page 124) to download images. Volume Two adds details on an optional compression format (page 52).
First, you have to convert the image to a 1bpp bi-level image, then convert it to a hex-encoded string. You can further compress the image to reduce transmission time. You can then print the image with the ^ID command.
While there is inherent support for PNG images in the ~DY command, it is poorly documented and does not seem to work on certain models of printers. The ZB64 format is basically not documented, and attempts to get more information from Zebra support have been fruitless. If you have your heart set on ZB64, you can use the Java based Zebralink SDK (look to ImagePrintDemo.java and com.zebra.sdk.printer.internal.GraphicsConversionUtilZpl.sendImageToStream).
Once you have the command data, it can be sent via TCP/IP if the printer has a print-server, or it can be sent by writing in RAW format to the printer.
The code below prints a 5 kB PNG as a 13 kB compressed GRF (60 kB uncompressed):
class Program
{
static unsafe void Main(string[] args)
{
var baseStream = new MemoryStream();
var tw = new StreamWriter(baseStream, Encoding.UTF8);
using (var bmpSrc = new Bitmap(Image.FromFile(#"label.png")))
{
tw.WriteLine(ZplImage.GetGrfStoreCommand("R:LBLRA2.GRF", bmpSrc));
}
tw.WriteLine(ZplImage.GetGrfPrintCommand("R:LBLRA2.GRF"));
tw.WriteLine(ZplImage.GetGrfDeleteCommand("R:LBLRA2.GRF"));
tw.Flush();
baseStream.Position = 0;
var gdipj = new GdiPrintJob("ZEBRA S4M-200dpi ZPL", GdiPrintJobDataType.Raw, "Raw print", null);
gdipj.WritePage(baseStream);
gdipj.CompleteJob();
}
}
class ZplImage
{
public static string GetGrfStoreCommand(string filename, Bitmap bmpSource)
{
if (bmpSource == null)
{
throw new ArgumentNullException("bmpSource");
}
validateFilename(filename);
var dim = new Rectangle(Point.Empty, bmpSource.Size);
var stride = ((dim.Width + 7) / 8);
var bytes = stride * dim.Height;
using (var bmpCompressed = bmpSource.Clone(dim, PixelFormat.Format1bppIndexed))
{
var result = new StringBuilder();
result.AppendFormat("^XA~DG{2},{0},{1},", stride * dim.Height, stride, filename);
byte[][] imageData = GetImageData(dim, stride, bmpCompressed);
byte[] previousRow = null;
foreach (var row in imageData)
{
appendLine(row, previousRow, result);
previousRow = row;
}
result.Append(#"^FS^XZ");
return result.ToString();
}
}
public static string GetGrfDeleteCommand(string filename)
{
validateFilename(filename);
return string.Format("^XA^ID{0}^FS^XZ", filename);
}
public static string GetGrfPrintCommand(string filename)
{
validateFilename(filename);
return string.Format("^XA^FO0,0^XG{0},1,1^FS^XZ", filename);
}
static Regex regexFilename = new Regex("^[REBA]:[A-Z0-9]{1,8}\\.GRF$");
private static void validateFilename(string filename)
{
if (!regexFilename.IsMatch(filename))
{
throw new ArgumentException("Filename must be in the format "
+ "R:XXXXXXXX.GRF. Drives are R, E, B, A. Filename can "
+ "be alphanumeric between 1 and 8 characters.", "filename");
}
}
unsafe private static byte[][] GetImageData(Rectangle dim, int stride, Bitmap bmpCompressed)
{
byte[][] imageData;
var data = bmpCompressed.LockBits(dim, ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
try
{
byte* pixelData = (byte*)data.Scan0.ToPointer();
byte rightMask = (byte)(0xff << (data.Stride * 8 - dim.Width));
imageData = new byte[dim.Height][];
for (int row = 0; row < dim.Height; row++)
{
byte* rowStart = pixelData + row * data.Stride;
imageData[row] = new byte[stride];
for (int col = 0; col < stride; col++)
{
byte f = (byte)(0xff ^ rowStart[col]);
f = (col == stride - 1) ? (byte)(f & rightMask) : f;
imageData[row][col] = f;
}
}
}
finally
{
bmpCompressed.UnlockBits(data);
}
return imageData;
}
private static void appendLine(byte[] row, byte[] previousRow, StringBuilder baseStream)
{
if (row.All(r => r == 0))
{
baseStream.Append(",");
return;
}
if (row.All(r => r == 0xff))
{
baseStream.Append("!");
return;
}
if (previousRow != null && MatchByteArray(row, previousRow))
{
baseStream.Append(":");
return;
}
byte[] nibbles = new byte[row.Length * 2];
for (int i = 0; i < row.Length; i++)
{
nibbles[i * 2] = (byte)(row[i] >> 4);
nibbles[i * 2 + 1] = (byte)(row[i] & 0x0f);
}
for (int i = 0; i < nibbles.Length; i++)
{
byte cPixel = nibbles[i];
int repeatCount = 0;
for (int j = i; j < nibbles.Length && repeatCount <= 400; j++)
{
if (cPixel == nibbles[j])
{
repeatCount++;
}
else
{
break;
}
}
if (repeatCount > 2)
{
if (repeatCount == nibbles.Length - i
&& (cPixel == 0 || cPixel == 0xf))
{
if (cPixel == 0)
{
if (i % 2 == 1)
{
baseStream.Append("0");
}
baseStream.Append(",");
return;
}
else if (cPixel == 0xf)
{
if (i % 2 == 1)
{
baseStream.Append("F");
}
baseStream.Append("!");
return;
}
}
else
{
baseStream.Append(getRepeatCode(repeatCount));
i += repeatCount - 1;
}
}
baseStream.Append(cPixel.ToString("X"));
}
}
private static string getRepeatCode(int repeatCount)
{
if (repeatCount > 419)
throw new ArgumentOutOfRangeException();
int high = repeatCount / 20;
int low = repeatCount % 20;
const string lowString = " GHIJKLMNOPQRSTUVWXY";
const string highString = " ghijklmnopqrstuvwxyz";
string repeatStr = "";
if (high > 0)
{
repeatStr += highString[high];
}
if (low > 0)
{
repeatStr += lowString[low];
}
return repeatStr;
}
private static bool MatchByteArray(byte[] row, byte[] previousRow)
{
for (int i = 0; i < row.Length; i++)
{
if (row[i] != previousRow[i])
{
return false;
}
}
return true;
}
}
internal static class NativeMethods
{
#region winspool.drv
#region P/Invokes
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool OpenPrinter(string szPrinter, out IntPtr hPrinter, IntPtr pd);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern UInt32 StartDocPrinter(IntPtr hPrinter, Int32 level, IntPtr di);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool WritePrinter(
// 0
IntPtr hPrinter,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pBytes,
// 2
UInt32 dwCount,
out UInt32 dwWritten);
#endregion
#region Structs
[StructLayout(LayoutKind.Sequential)]
internal struct DOC_INFO_1
{
[MarshalAs(UnmanagedType.LPWStr)]
public string DocName;
[MarshalAs(UnmanagedType.LPWStr)]
public string OutputFile;
[MarshalAs(UnmanagedType.LPWStr)]
public string Datatype;
}
#endregion
#endregion
}
/// <summary>
/// Represents a print job in a spooler queue
/// </summary>
public class GdiPrintJob
{
IntPtr PrinterHandle;
IntPtr DocHandle;
/// <summary>
/// The ID assigned by the print spooler to identify the job
/// </summary>
public UInt32 PrintJobID { get; private set; }
/// <summary>
/// Create a print job with a enumerated datatype
/// </summary>
/// <param name="PrinterName"></param>
/// <param name="dataType"></param>
/// <param name="jobName"></param>
/// <param name="outputFileName"></param>
public GdiPrintJob(string PrinterName, GdiPrintJobDataType dataType, string jobName, string outputFileName)
: this(PrinterName, translateType(dataType), jobName, outputFileName)
{
}
/// <summary>
/// Create a print job with a string datatype
/// </summary>
/// <param name="PrinterName"></param>
/// <param name="dataType"></param>
/// <param name="jobName"></param>
/// <param name="outputFileName"></param>
public GdiPrintJob(string PrinterName, string dataType, string jobName, string outputFileName)
{
if (string.IsNullOrWhiteSpace(PrinterName))
throw new ArgumentNullException("PrinterName");
if (string.IsNullOrWhiteSpace(dataType))
throw new ArgumentNullException("PrinterName");
IntPtr hPrinter;
if (!NativeMethods.OpenPrinter(PrinterName, out hPrinter, IntPtr.Zero))
throw new Win32Exception();
this.PrinterHandle = hPrinter;
NativeMethods.DOC_INFO_1 docInfo = new NativeMethods.DOC_INFO_1()
{
DocName = jobName,
Datatype = dataType,
OutputFile = outputFileName
};
IntPtr pDocInfo = Marshal.AllocHGlobal(Marshal.SizeOf(docInfo));
RuntimeHelpers.PrepareConstrainedRegions();
try
{
Marshal.StructureToPtr(docInfo, pDocInfo, false);
UInt32 docid = NativeMethods.StartDocPrinter(hPrinter, 1, pDocInfo);
if (docid == 0)
throw new Win32Exception();
this.PrintJobID = docid;
}
finally
{
Marshal.FreeHGlobal(pDocInfo);
}
}
/// <summary>
/// Write the data of a single page or a precomposed PCL document
/// </summary>
/// <param name="data"></param>
public void WritePage(Stream data)
{
if (data == null)
throw new ArgumentNullException("data");
if (!data.CanRead && !data.CanWrite)
throw new ObjectDisposedException("data");
if (!data.CanRead)
throw new NotSupportedException("stream is not readable");
if (!NativeMethods.StartPagePrinter(this.PrinterHandle))
throw new Win32Exception();
byte[] buffer = new byte[0x14000]; /* 80k is Stream.CopyTo default */
uint read = 1;
while ((read = (uint)data.Read(buffer, 0, buffer.Length)) != 0)
{
UInt32 written;
if (!NativeMethods.WritePrinter(this.PrinterHandle, buffer, read, out written))
throw new Win32Exception();
if (written != read)
throw new InvalidOperationException("Error while writing to stream");
}
if (!NativeMethods.EndPagePrinter(this.PrinterHandle))
throw new Win32Exception();
}
/// <summary>
/// Complete the current job
/// </summary>
public void CompleteJob()
{
if (!NativeMethods.EndDocPrinter(this.PrinterHandle))
throw new Win32Exception();
}
#region datatypes
private readonly static string[] dataTypes = new string[]
{
// 0
null,
"RAW",
// 2
"RAW [FF appended]",
"RAW [FF auto]",
// 4
"NT EMF 1.003",
"NT EMF 1.006",
// 6
"NT EMF 1.007",
"NT EMF 1.008",
// 8
"TEXT",
"XPS_PASS",
// 10
"XPS2GDI"
};
private static string translateType(GdiPrintJobDataType type)
{
return dataTypes[(int)type];
}
#endregion
}
public enum GdiPrintJobDataType
{
Unknown = 0,
Raw = 1,
RawAppendFF = 2,
RawAuto = 3,
NtEmf1003 = 4,
NtEmf1006 = 5,
NtEmf1007 = 6,
NtEmf1008 = 7,
Text = 8,
XpsPass = 9,
Xps2Gdi = 10
}
For some reason I cannot get B64 to work, but luckily I was able to Google my way into making Z64 work (in 3 soul-searching days or so) using plain old JavaScript.
Somewhere else on the ZPL programming Guide you stumble upon the The CISDFCRC16 command--let's be cryptic, why not--section, which states:
"The value of the field is calculated the CRC-16 for the
contents of a specified file using the CRC16-CCITT polynomial which is
x^16 + x^12 + x^5 + 1. It is calculated using an initial CRC of
0x0000."
Japanglish aside, you can now check out the Catalogue of parametrised CRC algorithms with 16 bits (http://reveng.sourceforge.net/crc-catalogue/16.htm) and look for the XMODEM algorithm, which happens to be
width=16 poly=0x1021 init=0x0000 refin=false refout=false
xorout=0x0000 check=0x31c3 name="XMODEM"
Aha. I then started looking for the rest of the code I needed and stumbled upon the following:
LZ77-Algorithm-Based JavaScript Compressor (http://lab.polygonpla.net/js/tinylz77.html)
base-64.js
(https://github.com/beatgammit/base64-js/blob/master/lib/b64.js)
Lammert Bies' 2008 CRC Library
(http://www.lammertbies.nl/comm/info/crc-calculation.html) ported
from ANSI C--with the precaution to bitwise-AND
with 0xffff the update function return value since JavaScript
treats every number as a 32-bit signed integer.
So I read the file as a byte array (Uint8Array), parse it as a string, compress it with LZ77, turn that back into a byte array and encode it using base64, at which point I calculate the CRC and paste it all into my ZPL ~DT command for savings of about 40%. Beautiful.
Unfortunately I'm developing a proprietary solution so I cannot post any code.
Good luck!
-What one man did another can do.
After looking at the ZPL manual you need to calculate the Cyclic Redundancy Check (CRC) for the image. Here is some C Code that calculates the CRC (source):
// Update the CRC for transmitted and received data using
// the CCITT 16bit algorithm (X^16 + X^12 + X^5 + 1).
unsigned char ser_data;
static unsigned int crc;
crc = (unsigned char)(crc >> 8) | (crc << 8);
crc ^= ser_data;
crc ^= (unsigned char)(crc & 0xff) >> 4;
crc ^= (crc << 8) << 4;
crc ^= ((crc & 0xff) << 4) << 1;
You can also refer to Wikipedia's page on CRC, as it contains other code examples as well.
https://en.wikipedia.org/wiki/Cyclic_redundancy_check
Everything else you are sending down looks good. I would look into using one of the Zebra SDKs. I know the Android one will send an image to the printer and save it for you.
Although this question has the C# tag, several other answers are not strictly C#, so here is an answer using Node 8.5+ (javascript), using java and the Zebra SDK. The same steps are very similar for any .NET language that can also use the SDK and perform a POST request.
const { promisify } = require('util');
const java = require('java');
java.asyncOptions = {
asyncSuffix: "",
syncSuffix: "Sync",
promiseSuffix: "Promise", // Generate methods returning promises, using the suffix Promise.
promisify
};
// Include all .jar's under C:\Program Files\Zebra Technologies\link_os_sdk\PC\v2.14.5198\lib
// in your lib folder
java.classpath.push(__dirname + "/lib/ZSDK_API.jar");
var ByteArrayOutputStream = java.import('java.io.ByteArrayOutputStream');
var ZebraImageFactory = java.import('com.zebra.sdk.graphics.ZebraImageFactory');
var PrinterUtil = java.import('com.zebra.sdk.printer.PrinterUtil');
const main = async function () {
let path = `C:\\images\\yourimage.png`;
let os = new ByteArrayOutputStream();
let image = await ZebraImageFactory.getImagePromise(path);
PrinterUtil.convertGraphicPromise("E:IMAGE.PNG", image, os);
console.log(os.toStringSync()); // junk:Z64:~:CRC
console.log('done');
};
main();
Then you can print the image via ZPL like:
^XA
~DYE:IMAGE,P,P,1,,:B64:<YOURB64>:<YOURCRC>
^FO0,0^IME:IMAGE.PNG
^XZ
Using something like
await axios.post(`${printer.ip}/pstprnt`, zpl);
In this GitHub project you will find everything you need. https://github.com/BinaryKits/BinaryKits.Zpl
There is also a PNG to GRF image converter with additional data compression available.
var elements = new List<ZplElementBase>();
elements.Add(new ZplDownloadGraphics('R', "SAMPLE", System.IO.File.ReadAllBytes("sample.png")));
elements.Add(new ZplRecallGraphic(100, 100, 'R', "SAMPLE"));
var renderEngine = new ZplEngine(elements);
var zpl = renderEngine.ToZplString(new ZplRenderOptions());
Related
As several blogposts and Stackoverflow answers suggest, it is trivial to get this information via a combination of get-process and Get-NetTCPConnection commandlets. It is possible to execute these commands via .net code, parse the output and retrieve the information.
Is it not possible to get the port number a process is listening on in pure .net code using just the .net libraries? System.Diagnostics.Process.GetProcesByName returns a ton of information via the Process object, except for the port the process is listening on.
Any leads highly appreciated.
Unfortunately, IPGlobalProperties.GetIPGlobalProperties() does not return any information on which process is holding the socket, as it uses GetTcpTable not GetTcpTable2.
You would need to code it yourself. The below code works for TCP over IPv4. You would need similar code for UDP and IPv6.
[DllImport("Iphlpapi.dll", ExactSpelling = true)]
static extern int GetTcpTable2(
IntPtr TcpTable,
ref int SizePointer,
bool Order
);
[StructLayout(LayoutKind.Sequential)]
struct MIB_TCPTABLE
{
public int dwNumEntries;
}
[StructLayout(LayoutKind.Sequential)]
struct MIB_TCPROW2
{
public MIB_TCP_STATE dwState;
public int dwLocalAddr;
public byte localPort1;
public byte localPort2;
// Ports are only 16 bit values (in network WORD order, 3,4,1,2).
// There are reports where the high order bytes have garbage in them.
public byte ignoreLocalPort3;
public byte ignoreLocalPort4;
public int dwRemoteAddr;
public byte remotePort1;
public byte remotePort2;
// Ports are only 16 bit values (in network WORD order, 3,4,1,2).
// There are reports where the high order bytes have garbage in them.
public byte ignoreremotePort3;
public byte ignoreremotePort4;
public int dwOwningPid;
public TCP_CONNECTION_OFFLOAD_STATE dwOffloadState;
}
public enum MIB_TCP_STATE
{
Closed = 1,
Listen,
SynSent,
SynRcvd,
Established,
FinWait1,
FinWait2,
CloseWait,
Closing,
LastAck,
TimeWait,
DeleteTcb
}
enum TCP_CONNECTION_OFFLOAD_STATE
{
TcpConnectionOffloadStateInHost,
TcpConnectionOffloadStateOffloading,
TcpConnectionOffloadStateOffloaded,
TcpConnectionOffloadStateUploading,
TcpConnectionOffloadStateMax
}
static List<IPEndPoint> GetSocketsForProcess(int pid, MIB_TCP_STATE state = MIB_TCP_STATE.Established)
{
const int ERROR_INSUFFICIENT_BUFFER = 0x7A;
var size = 0;
var result = GetTcpTable2(IntPtr.Zero, ref size, false);
if (result != ERROR_INSUFFICIENT_BUFFER)
throw new Win32Exception(result);
var ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(size);
result = GetTcpTable2(ptr, ref size, false);
if (result != 0)
throw new Win32Exception(result);
var list = new List<IPEndPoint>();
var count = Marshal.ReadInt32(ptr);
var curPtr = ptr + Marshal.SizeOf<MIB_TCPTABLE>();
var length = Marshal.SizeOf<MIB_TCPROW2>();
for(var i = 0; i < count; i++)
{
var row = Marshal.PtrToStructure<MIB_TCPROW2>(curPtr);
if(row.dwOwningPid == pid && row.dwState == state)
list.Add(new IPEndPoint(row.dwLocalAddr, row.localPort1 << 8 | row.localPort2));
curPtr += length;
}
return list;
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
I had similar task not long ago. Here is complete .NET 6 code that you should be able to adopt for your particular needs:
public static int CheckConnection(string[] servers)
{
try
{
var srvs = servers.ToDictionary(k => k.Split("/", StringSplitOptions.RemoveEmptyEntries)[1], v => false);
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
IPEndPoint[] endPoints = ipProperties.GetActiveTcpListeners();
TcpConnectionInformation[] tcpConnections = ipProperties.GetActiveTcpConnections();
var count = 0;
foreach (var info in tcpConnections)
{
var ep = string.Format("{0}:{1}", info.RemoteEndPoint.Address, info.RemoteEndPoint.Port);
if (info.State.ToString().ToUpper() != "ESTABLISHED")
continue;
if (srvs.ContainsKey(ep))
{
count++;
srvs[ep] = true;
}
}
var sb = new StringBuilder();
foreach (var kvp in srvs)
{
sb.AppendFormat("{0,-21}: {1}", kvp.Key, kvp.Value ? "OK" : "FAIL");
sb.AppendLine();
}
Program.logger.Trace($"ZMQF. Connection check:\n{sb}");
return count;
}
catch (Exception ex)
{
Program.logger.Fatal(ex, $"ZMQF. CheckConnection exception. Servers: {(string.Join(",", servers))}");
return -1;
}
}
I am having the most difficult time marshaling this struct between C# and C++.
What makes it very hard to troubleshoot is that SOMETIMES the strings are populated with data (wtf), but most of the time they are not.
I've tried sending over an Array of structs as well as a IntPtr, but the results are similar, the strings in the struct are almost always empty and I can't figure out what I'm doing wrong in the marshaling. The code is posted below. Any help would be appreciated.
Edit***
Turns out the problem was on the C++ side and all the marshaling stuff was correct. Thanks for the tip Hans. ***
C++:
#pragma pack (push, 1)
typedef struct
{
char FirmwareVers[FS_MAX_FIRMWARE_VER];
char SerialNum[FS_MAX_SERIAL_NUM];
char HardwareVers[FS_MAX_HW_VER];
ULONG StatusFlags;
int LMIndex;
} FS_LMON_STATUS, *PFS_LMON_STATUS;
DllExport int _stdcall FS_GetLMs(PFS_LMON_STATUS pLaunchMonInfo, int MaxLaunchMons, int *pNumLaunchMons)
{
int Cnt;
FS_LMON_STATUS LMStatus;
if(!g_IsInitalized)
return FS_NOT_INITALIZED;
*pNumLaunchMons = 0;
if(MaxLaunchMons == 0)
return FS_ERROR;
for(Cnt = 0; Cnt < MAX_LM_CONNECTIONS; Cnt++)
{
if(g_CreatedClasses.pLMList->GetLMStatus(Cnt, &LMStatus) != FS_SUCCESS)
continue;
if(LMStatus.LMIndex != INVALID_LM_INDEX)
{
memcpy(pLaunchMonInfo, &LMStatus, sizeof(LMStatus));
pLaunchMonInfo++;
(*pNumLaunchMons)++;
MaxLaunchMons--;
if(MaxLaunchMons == 0)
{
return FS_SUCCESS;
}
}
}
return FS_SUCCESS;
}
C#:
[DllImport("FSADLL", SetLastError = false)]
private static extern int FS_GetLMs([Out] IntPtr pLaunchMonInfo, int MaxLaunchMons, ref int pNumLaunchMons);
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] //, Size = 38)]
public struct FS_LMON_STATUS
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = FS_MAX_FIRMWARE_VER)] //10 bytes
public string FirmwareVers;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = FS_MAX_SERIAL_NUM)] // 15 bytes
public string SerialNum;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = FS_MAX_HW_VER)] // 5 bytes
public string HardwareVers;
public uint StatusFlags; //4 bytes
public int LMIndex; // identifies which index //4 bytes
}
const int max_launch_monitors = 8;
FS_LMON_STATUS[] dev_info = new FS_LMON_STATUS[max_launch_monitors];
int num_launch_monitors = 0;
IntPtr pAddr = Marshal.AllocHGlobal(max_launch_monitors * Marshal.SizeOf(typeof(FS_LMON_STATUS)));
Marshal.StructureToPtr(dev_info, pAddr, false);
int result = FS_GetLMs(pAddr, max_launch_monitors, ref num_launch_monitors);
UnityEngine.Debug.Log("Result of FS_GetLMs: " + result);
FS_LMON_STATUS[] device_info = new FS_LMON_STATUS[max_launch_monitors];
//Marshal.Copy(pAddr, device_info, (int)0, num_launch_monitors * (int)Marshal.SizeOf(typeof(FS_LMON_STATUS)));
//Marshal.ReadIntPtr(pAddr, 0);
//device_info = (FS_LMON_STATUS[]) Marshal.PtrToStructure(Marshal.AllocHGlobal(max_launch_monitors * Marshal.SizeOf(typeof(FS_LMON_STATUS[]))), typeof(FS_LMON_STATUS[]));
if (num_launch_monitors > 0)
UnityEngine.Debug.Log("GC2 Device Found.");
else // If there is no devices found, remove the previous device from the holder variable
GC2Device = null;
for (int i = 0; i < num_launch_monitors; i++)
{
device_info[i] = (FS_LMON_STATUS)Marshal.PtrToStructure(pAddr, typeof(FS_LMON_STATUS));
pAddr = new IntPtr(Marshal.SizeOf(typeof(FS_LMON_STATUS)) + pAddr.ToInt64());
}
//*** There will only ever be 1 device in the list until the old SDK is fixed ***
for (int lm_index = 0; lm_index < num_launch_monitors; lm_index++)
{
if (device_info[lm_index].StatusFlags != LM_STATUS_DISCONNECTED)
{
UnityEngine.Debug.Log("device_info.SerialNum: " + device_info[lm_index].SerialNum);
//assign each LM to a LM data structure
LaunchMonitor logical_device = new LaunchMonitor(inst);
logical_device.mLaunchMonitorType = LaunchMonitorType.LAUNCH_MONITOR_TYPE_GC2;
logical_device.mConnectionType = ConnectionType.USB_CONNECTION;
IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(device_info[lm_index]));
Marshal.StructureToPtr(device_info[lm_index], pnt, false);
//Marshal.Copy(device_info[lm_index], dv_info, 0, (uint)Marshal.SizeOf(typeof(FS_LMON_STATUS)));
logical_device.mConnectionToken = pnt;
//GC2Devices.Add(logical_device);
logical_device.Serial = logical_device.GetSerialNumber();
GC2Device = logical_device;
}
}
Turns out the problem was on the C++ side and all the marshaling stuff was correct. Thanks for the tip Hans.
I want to record the audio stream from Kinect and save it in my computer , so I refer the sample code here.
http://channel9.msdn.com/Series/KinectQuickstart/Audio-Fundamentals
Here is my partial code.
public static void WriteWavHeader(Stream stream, int dataLength)
{
using (var memStream = new MemoryStream(64))
{
int cbFormat = 18; //sizeof(WAVEFORMATEX)
WAVEFORMATEX format = new WAVEFORMATEX()
{
//wFormatTag = 0,
nChannels = 1,
nSamplesPerSec = 16000,
nAvgBytesPerSec = 32000,
nBlockAlign = 2,
wBitsPerSample = 16,
cbSize = 0
};
using (var bw = new BinaryWriter(memStream))
{
//RIFF header
WriteString(memStream, "data");
bw.Write(cbFormat);
WriteString(memStream, "RIFF");
bw.Write(dataLength + cbFormat + 4); //File size - 8
WriteString(memStream, "WAVE");
WriteString(memStream, "fmt ");
bw.Write(cbFormat);
//WAVEFORMATEX
bw.Write(format.wFormatTag);
bw.Write(format.nChannels);
bw.Write(format.nSamplesPerSec);
bw.Write(format.nAvgBytesPerSec);
bw.Write(format.nBlockAlign);
bw.Write(format.wBitsPerSample);
bw.Write(format.cbSize);
//data header
WriteString(memStream, "data");
bw.Write(dataLength);
memStream.WriteTo(stream);
}
}
}
There's an error occur when in line "bw.Write(format.wFormatTag);" when I compile the code , but no error with other line in WAVEFORMATEX , I have add "Bass.Net" to my reference and using "Un4seen.Bass" , but I still can't fix it.
It seems you are mixing several different SDKs. In the link you provided, the WAVEFORMATEX is defined as follows:
struct WAVEFORMATEX
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
}
So, no enum values or Un4seen.Bass references. The function to write the header file is slighty different (only differs in the wFormatTag value):
/// <summary>
/// A bare bones WAV file header writer
/// </summary>
static void WriteWavHeader(Stream stream, int dataLength)
{
//We need to use a memory stream because the BinaryWriter will close the underlying stream when it is closed
using (var memStream = new MemoryStream(64))
{
int cbFormat = 18; //sizeof(WAVEFORMATEX)
WAVEFORMATEX format = new WAVEFORMATEX()
{
wFormatTag = 1,
nChannels = 1,
nSamplesPerSec = 16000,
nAvgBytesPerSec = 32000,
nBlockAlign = 2,
wBitsPerSample = 16,
cbSize = 0
};
using (var bw = new BinaryWriter(memStream))
{
//RIFF header
WriteString(memStream, "RIFF");
bw.Write(dataLength + cbFormat + 4); //File size - 8
WriteString(memStream, "WAVE");
WriteString(memStream, "fmt ");
bw.Write(cbFormat);
//WAVEFORMATEX
bw.Write(format.wFormatTag);
bw.Write(format.nChannels);
bw.Write(format.nSamplesPerSec);
bw.Write(format.nAvgBytesPerSec);
bw.Write(format.nBlockAlign);
bw.Write(format.wBitsPerSample);
bw.Write(format.cbSize);
//data header
WriteString(memStream, "data");
bw.Write(dataLength);
memStream.WriteTo(stream);
}
}
}
An WriteString also takes care of converting it to ASCII. I'm refering to the .zip file which is referenced in the presentation above ("Download the Quickstart slides and samples", KinectforWindowsSDKV1\6.Audio\AudioRecorder\AudioRecorder\MainWindow.xaml.cs)
C++ Function header in DLL this two function to get some information about the wifi stations around me using win mobile 6.5 device and i need to invoke them to use them in C# code
// (adapter names , pointer to destination buffer ,and the size , returned structs)
bool __declspec(dllexport) GetBBSIDs(LPWSTR pAdapter, struct BSSIDInfo *pDest, DWORD &dwBufSizeBytes, DWORD &dwReturnedItems);
bool __declspec(dllexport) RefreshBSSIDs(LPWSTR pAdapter);
bool __declspec(dllexport) GetAdapters(LPWSTR pDest, DWORD &dwBufSizeBytes);
C# sample
[DllImport(#"\Storage Card\Work\Beaad.dll", EntryPoint = "GetAdapters", SetLastError = true)]
public static extern bool getAdapters([MarshalAs(UnmanagedType.LPWStr)] String buf, ref UInt32 dwBufSizeBytes);
[DllImport(#"\Storage Card\Work\Beaad.dll", EntryPoint = "RefreshBSSIDs", SetLastError = true)]
public static extern bool refreshBSSIDs([MarshalAs(UnmanagedType.LPWStr)]String buf);
[DllImport(#"\Storage Card\Work\Beaad.dll", EntryPoint = "GetBBSIDs", SetLastError = true)]
public static extern bool getBBSIDs([MarshalAs(UnmanagedType.LPWStr)]String buf,BSSIDInfo [] nfo, ref UInt32 dwBufSizeBytes, ref UInt32 dwReturnedItems);
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Auto)]
public struct BSSIDInfo
{
public byte[] BSSID; //mac
public char[] SSID;
public BSSIDInfo(byte[]bs,char[] ss)
{
this.RSSI = 0;
this.Infastructure = 0;
this.Channel = 0;
this.Auth = 0;
bs = new byte[6];
ss = new char[32];
BSSID = bs;
SSID = ss;
}
public int RSSI;
public int Channel;
public int Infastructure;
public int Auth;
}
public static byte[] StrToByteArray(string str)
{
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
return encoding.GetBytes(str);
}
public static char[] c = new char[1024];
string buf = new string(c);
public void button1_Click(object sender, EventArgs e)
{
BSSIDInfo[] nfo = new BSSIDInfo[128];
byte[] bytee=StrToByteArray(buf);
UInt32 dwsize= new UInt32();
UInt32 dwTmp = new UInt32();
UInt32 dwCount = new UInt32();
dwTmp = Convert.ToUInt32(Marshal.SizeOf(typeof(BSSIDInfo)) * nfo.Length);
dwCount =0;
dwsize=Convert.ToUInt32(bytee.Length);
if (false == getAdapters(buf,ref dwsize) || dwsize == 0)
{
label1.Text = "no adabters";
}
else
{
String [] strList=new String[15];
if (buf.Contains(',') == false)// one adapter
{
textBox1.Text = buf;
}
else
{
strList = buf.Split(',');
for (int i = 0; i < strList.Length; i++)
{
textBox1.Text+= strList[i]+Environment.NewLine;
}
}
if (refreshBSSIDs(buf) && getBBSIDs(buf, nfo, ref dwTmp, ref dwCount) && dwCount > 0)
{
//refreshBSSIDs(buf) &&
for (int i = 0; i < dwCount; i++)
{
textBox2.Text += nfo.GetValue(i).ToString() + Environment.NewLine;
}
}
else
{
//make another thing
}
}
}
and when i put this dll on the mobile and the C# app.exe the first function that named as Getadapters(..) return to me the name of the adapter in the first textbox1 then the app stopped and give me not supported exception when the mobile tries to execute the other two function that named as refreshBSSID() and getBSSIDs() so what is the problem ? or is there another solution to get this information (BSSID ,SS ..etc) ?
C++ by default unless changed uses a caller( Cdecl ) calling convention. Your C++ code does not change the calling convention. Your C# code by default ( unless you change it ) will use a callee convention ( StdCall ).
While this might not be exactly the problem your having it still is technically incorrect. Even if you were to fix your current problem you likely will end up having a problem because of the calling convention.
I am going to guess your C# BSSIDInfo structure does not match the C++ structure. Why do the method StrToByteArray when all it does is GetBytes on the given string...
when the mobile tries to execute the
other two function that named as
refreshBSSID() and getBSSIDs() so what
is the problem ? or is there another
solution to get this information
I thought I knew the reason took another look and I was wrong.
I wish to allocate more than MaxInteger bytes of memory.
Marshall.AllocHGlobal() expects an integer - so I cannot use this. Is there another way?
Update
I changed the platform to x64, and then I ran the code below.
myp appears to have the right length: about 3.0G. But stubbornly "buffer" maxes out at 2.1G.
Any idea why?
var fileStream = new FileStream(
"C:\\big.BC2",
FileMode.Open,
FileAccess.Read,
FileShare.Read,
16 * 1024,
FileOptions.SequentialScan);
Int64 length = fileStream.Length;
Console.WriteLine(length);
Console.WriteLine(Int64.MaxValue);
IntPtr myp = new IntPtr(length);
//IntPtr buffer = Marshal.AllocHGlobal(myp);
IntPtr buffer = VirtualAllocEx(
Process.GetCurrentProcess().Handle,
IntPtr.Zero,
new IntPtr(length),
AllocationType.Commit | AllocationType.Reserve,
MemoryProtection.ReadWrite);
unsafe
{
byte* pBytes = (byte*)myp.ToPointer();
var memoryStream = new UnmanagedMemoryStream(pBytes, (long)length, (long)length, FileAccess.ReadWrite);
fileStream.CopyTo(memoryStream);
That's not possible on current mainstream hardware. Memory buffers are restricted to 2 gigabytes, even on 64-bit machines. Indexed addressing of the buffer is still done with a 32-bit signed offset. It is technically possible to generate machine code that can index more, using a register to store the offset, but that's expensive and slows down all array indexing, even for the ones that aren't larger than 2 GB.
Furthermore, you can't get a buffer larger than about 650MB out of the address space available to a 32-bit process. There aren't enough contiguous memory pages available because virtual memory contains both code and data at various addresses.
Companies like IBM and Sun sell hardware that can do this.
I have been involved in one of the other questions you asked, and I honestly think you are fighting a losing battle here. You need to possibly explore other avenues of processing this data other than reading everything into memory.
If I understand correctly, you have multiple threads that process the data concurrently and that is why you do not want to work off the file directly because of I/O contention I assume.
Have you considered or would the possibility exist to reading a block of data into memory, have the threads process the block and then read the next block or processing by the threads? This way, at any one time, you never have more than a block in memory, but all threads can access the block. This is not optimal, but I put it out there as a starting point. If this is feasible then options to optimize this can be explored.
Update: Example using platform invoke to allocate unmanaged memory and use it from .NET.
Since you are are so certain you need to load this much data into memory I thought I would write a small test application to verify that this will work. For this you will need the following
Compile with the /unsafe compile option
If you want to allocate more that 2 GB you will also need to switch your target platform to x64
*Point 2 above is a little more complicated, on a 64-bit OS you could still target the x86 platform and get access to the full 4 GB memory. This will require you to use a tool like EDITBIN.EXE to set the LargeAddressAware flag in the PE header.
This code uses VirtualAllocEx to allocate unmanaged memory and UnmanagedMemoryStream to access the unmanaged memory using the .NET stream metaphor. Note this code has only had some very basic quick tests done and only on the target 64-bit environment with 4 GB RAM. And most importantly I only went up to about 2.6 GB memory utilization for the process.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.ComponentModel;
namespace MemoryMappedFileTests
{
class Program
{
static void Main(string[] args)
{
IntPtr ptr = IntPtr.Zero;
try
{
// Allocate and Commit the memory directly.
ptr = VirtualAllocEx(
Process.GetCurrentProcess().Handle,
IntPtr.Zero,
new IntPtr(0xD0000000L),
AllocationType.Commit | AllocationType.Reserve,
MemoryProtection.ReadWrite);
if (ptr == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Query some information about the allocation, used for testing.
MEMORY_BASIC_INFORMATION mbi = new MEMORY_BASIC_INFORMATION();
IntPtr result = VirtualQueryEx(
Process.GetCurrentProcess().Handle,
ptr,
out mbi,
new IntPtr(Marshal.SizeOf(mbi)));
if (result == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Use unsafe code to get a pointer to the unmanaged memory.
// This requires compiling with /unsafe option.
unsafe
{
// Pointer to the allocated memory
byte* pBytes = (byte*)ptr.ToPointer();
// Create Read/Write stream to access the memory.
UnmanagedMemoryStream stm = new UnmanagedMemoryStream(
pBytes,
mbi.RegionSize.ToInt64(),
mbi.RegionSize.ToInt64(),
FileAccess.ReadWrite);
// Create a StreamWriter to write to the unmanaged memory.
StreamWriter sw = new StreamWriter(stm);
sw.Write("Everything seems to be working!\r\n");
sw.Flush();
// Reset the stream position and create a reader to check that the
// data was written correctly.
stm.Position = 0;
StreamReader rd = new StreamReader(stm);
Console.WriteLine(rd.ReadLine());
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
if (ptr != IntPtr.Zero)
{
VirtualFreeEx(
Process.GetCurrentProcess().Handle,
ptr,
IntPtr.Zero,
FreeType.Release);
}
}
Console.ReadKey();
}
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
IntPtr dwSize,
AllocationType dwAllocationType,
MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool VirtualFreeEx(
IntPtr hProcess,
IntPtr lpAddress,
IntPtr dwSize,
FreeType dwFreeType);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualQueryEx(
IntPtr hProcess,
IntPtr lpAddress,
out MEMORY_BASIC_INFORMATION lpBuffer,
IntPtr dwLength);
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public int AllocationProtect;
public IntPtr RegionSize;
public int State;
public int Protect;
public int Type;
}
[Flags]
public enum AllocationType
{
Commit = 0x1000,
Reserve = 0x2000,
Decommit = 0x4000,
Release = 0x8000,
Reset = 0x80000,
Physical = 0x400000,
TopDown = 0x100000,
WriteWatch = 0x200000,
LargePages = 0x20000000
}
[Flags]
public enum MemoryProtection
{
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
NoAccess = 0x01,
ReadOnly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
GuardModifierflag = 0x100,
NoCacheModifierflag = 0x200,
WriteCombineModifierflag = 0x400
}
[Flags]
public enum FreeType
{
Decommit = 0x4000,
Release = 0x8000
}
}
}
This is not possible from managed code without a pinvoke call and for good reason. Allocating that much memory is usually a sign of a bad solution that needs revisiting.
Can you tell us why you think you need this much memory?
Use Marshal.AllocHGlobal(IntPtr). This overload treats the value of the IntPtr as the amount of memory to allocate and IntPtr can hold a 64 bit value.
From a comment:
How do I create a second binaryreader that can read the same memorystream independantly?
var fileStream = new FileStream("C:\\big.BC2",
FileMode.Open,
FileAccess.Read,
FileShare.Read,
16 * 1024,
FileOptions.SequentialScan);
Int64 length = fileStream.Length;
IntPtr buffer = Marshal.AllocHGlobal(length);
unsafe
{
byte* pBytes = (byte*)myp.ToPointer();
var memoryStream = new UnmanagedMemoryStream(pBytes, (long)length, (long)length, FileAccess.ReadWrite);
var binaryReader = new BinaryReader(memoryStream);
fileStream.CopyTo(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
// Create a second UnmanagedMemoryStream on the _same_ memory buffer
var memoryStream2 = new UnmanagedMemoryStream(pBytes, (long)length, (long)length, FileAccess.Read);
var binaryReader2 = new BinaryReader(memoryStream);
}
If you can't make it work the way you want it to directly, create a class to provide the type of behaviour you want. So, to use big arrays:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace BigBuffer
{
class Storage
{
public Storage (string filename)
{
m_buffers = new SortedDictionary<int, byte []> ();
m_file = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read);
}
public byte [] GetBuffer (long address)
{
int
key = GetPageIndex (address);
byte []
buffer;
if (!m_buffers.TryGetValue (key, out buffer))
{
System.Diagnostics.Trace.WriteLine ("Allocating a new array at " + key);
buffer = new byte [1 << 24];
m_buffers [key] = buffer;
m_file.Seek (address, SeekOrigin.Begin);
m_file.Read (buffer, 0, buffer.Length);
}
return buffer;
}
public void FillBuffer (byte [] destination_buffer, int offset, int count, long position)
{
do
{
byte []
source_buffer = GetBuffer (position);
int
start = GetPageOffset (position),
length = Math.Min (count, (1 << 24) - start);
Array.Copy (source_buffer, start, destination_buffer, offset, length);
position += length;
offset += length;
count -= length;
} while (count > 0);
}
public int GetPageIndex (long address)
{
return (int) (address >> 24);
}
public int GetPageOffset (long address)
{
return (int) (address & ((1 << 24) - 1));
}
public long Length
{
get { return m_file.Length; }
}
public int PageSize
{
get { return 1 << 24; }
}
FileStream
m_file;
SortedDictionary<int, byte []>
m_buffers;
}
class BigStream : Stream
{
public BigStream (Storage source)
{
m_source = source;
m_position = 0;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanTimeout
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Length
{
get { return m_source.Length; }
}
public override long Position
{
get { return m_position; }
set { m_position = value; }
}
public override void Flush ()
{
}
public override long Seek (long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
m_position = offset;
break;
case SeekOrigin.Current:
m_position += offset;
break;
case SeekOrigin.End:
m_position = Length + offset;
break;
}
return m_position;
}
public override void SetLength (long value)
{
}
public override int Read (byte [] buffer, int offset, int count)
{
int
bytes_read = (int) (m_position + count > Length ? Length - m_position : count);
m_source.FillBuffer (buffer, offset, bytes_read, m_position);
m_position += bytes_read;
return bytes_read;
}
public override void Write(byte[] buffer, int offset, int count)
{
}
Storage
m_source;
long
m_position;
}
class IntBigArray
{
public IntBigArray (Storage storage)
{
m_storage = storage;
m_current_page = -1;
}
public int this [long index]
{
get
{
int
value = 0;
index <<= 2;
for (int offset = 0 ; offset < 32 ; offset += 8, ++index)
{
int
page = m_storage.GetPageIndex (index);
if (page != m_current_page)
{
m_current_page = page;
m_array = m_storage.GetBuffer (m_current_page);
}
value |= (int) m_array [m_storage.GetPageOffset (index)] << offset;
}
return value;
}
}
Storage
m_storage;
int
m_current_page;
byte []
m_array;
}
class Program
{
static void Main (string [] args)
{
Storage
storage = new Storage (#"<some file>");
BigStream
stream = new BigStream (storage);
StreamReader
reader = new StreamReader (stream);
string
line = reader.ReadLine ();
IntBigArray
array = new IntBigArray (storage);
int
value = array [0];
BinaryReader
binary = new BinaryReader (stream);
binary.BaseStream.Seek (0, SeekOrigin.Begin);
int
another_value = binary.ReadInt32 ();
}
}
}
I split the problem into three classes:
Storage - where the actual data is stored, uses a paged system
BigStream - a stream class that uses the Storage class for its data source
IntBigArray - a wrapper around the Storage type that provides an int array interface
The above can be improved significantly but it should give you ideas about how to solve your problems.