I have a problem about production of a C# wrapper starting from C++ unmanaged library. Everytime I fall into a System.AccessViolationException:Attempted to read or write protected memory. I know probably it depends on the way I translate structures and their elements from C++ to C#, but Idon't know how to solve.
Could someone help me?
C++ code:
typedef struct VBLEnvironmentVariable_t
{
VBLObjectHeader mHeader;
DWORD mNameLength;
DWORD mDataLength;
LPSTR mName;
LPBYTE mData;
} VBLEnvironmentVariable;
typedef struct VBLAppTrigger_t
{
VBLObjectHeader mHeader;
ULONGLONG mPreTriggerTime;
ULONGLONG mPostTriggerTime;
WORD mChannel;
WORD mFlags;
DWORD mAppSecific2;
} VBLAppTrigger;
//signature
BLAPI( BOOL) BLWriteObject( HANDLE hFile, VBLObjectHeaderBase* pBase);
//how function is called
int write_test( LPCTSTR pFileName, LPDWORD pWritten)
{
if ( NULL == pWritten)
{
return -1;
}
*pWritten = 0;
/* open file */
hFile = BLCreateFile( pFileName, GENERIC_WRITE);
if ( INVALID_HANDLE_VALUE == hFile)
{
return -1;
}
/* set applicaton information */
/* bSuccess = BLSetApplication( hFile, BL_APPID_UNKNOWN, 1, 0, 0); */
bSuccess = BLSetApplication( hFile, BL_APPID_CANCASEXLLOG, 1, 0, 1);
GetSystemTime( &systemTime);
bSuccess = bSuccess && BLSetMeasurementStartTime( hFile, &systemTime);
/* set write options */
bSuccess = bSuccess && BLSetWriteOptions( hFile, 6, 0);
if ( bSuccess)
{
// setup object headers
appTrigger.mHeader.mBase.mSignature = BL_OBJ_SIGNATURE;
appTrigger.mHeader.mBase.mHeaderSize = sizeof( appTrigger.mHeader);
appTrigger.mHeader.mBase.mHeaderVersion = 1;
appTrigger.mHeader.mBase.mObjectSize = sizeof( VBLAppTrigger);
appTrigger.mHeader.mBase.mObjectType = BL_OBJ_TYPE_APP_TRIGGER;
appTrigger.mHeader.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
message.mHeader.mBase.mSignature = BL_OBJ_SIGNATURE;
message.mHeader.mBase.mHeaderSize = sizeof( message.mHeader);
message.mHeader.mBase.mHeaderVersion = 1;
message.mHeader.mBase.mObjectSize = sizeof( VBLCANMessage);
message.mHeader.mBase.mObjectType = BL_OBJ_TYPE_CAN_MESSAGE;
message.mHeader.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
variable_s.mHeader.mBase.mSignature = BL_OBJ_SIGNATURE;
variable_s.mHeader.mBase.mHeaderSize = sizeof( variable_s.mHeader);
variable_s.mHeader.mBase.mHeaderVersion = 1;
variable_s.mHeader.mBase.mObjectType = BL_OBJ_TYPE_ENV_STRING;
variable_s.mHeader.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
variable_i.mHeader.mBase.mSignature = BL_OBJ_SIGNATURE;
variable_i.mHeader.mBase.mHeaderSize = sizeof( variable_i.mHeader);
variable_i.mHeader.mBase.mHeaderVersion = 1;
variable_i.mHeader.mBase.mObjectType = BL_OBJ_TYPE_ENV_INTEGER;
variable_i.mHeader.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
ethframe.mHeader.mBase.mSignature = BL_OBJ_SIGNATURE;
ethframe.mHeader.mBase.mHeaderSize = sizeof( ethframe.mHeader);
ethframe.mHeader.mBase.mHeaderVersion = 1;
ethframe.mHeader.mBase.mObjectType = BL_OBJ_TYPE_ETHERNET_FRAME;
ethframe.mHeader.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
appText.mHeader.mBase.mSignature = BL_OBJ_SIGNATURE;
appText.mHeader.mBase.mHeaderSize = sizeof( appText.mHeader);
appText.mHeader.mBase.mHeaderVersion = 1;
appText.mHeader.mBase.mObjectType = BL_OBJ_TYPE_APP_TEXT;
appText.mHeader.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
for ( i = 0; i < 1000; ++i)
{
ethbuffer[i] = ( BYTE)i;
}
for ( i = 0; i < 1000 && bSuccess; ++i)
{
// increment in milliseconds
time = i * 10000000;
// setup app trigger object header
appTrigger.mHeader.mObjectTimeStamp = time;
// write app trigger object
bSuccess = BLWriteObject( hFile, &appTrigger.mHeader.mBase);
*pWritten += bSuccess ? 1 : 0;
// setup CAN object header
message.mHeader.mObjectTimeStamp = time;
// setup CAN message
message.mChannel = 1;
message.mFlags = CAN_MSG_FLAGS( 0, 0);
message.mDLC = 8;
message.mID = 0x100;
memcpy( message.mData, ( i % 2) ? _T( "01234567") : _T( "76543210"), message.mDLC);
// write CAN message
bSuccess = BLWriteObject( hFile, &message.mHeader.mBase);
*pWritten += bSuccess ? 1 : 0;
if ( 0 == ( i % 3) && bSuccess)
{
// setup environment variable object headers
variable_s.mHeader.mObjectTimeStamp = time;
variable_i.mHeader.mObjectTimeStamp = time;
// setup environment variables
variable_s.mNameLength = strlen( ENV_NAME1);
variable_s.mDataLength = strlen( ( i % 2) ? ENV_DATA1 : ENV_DATA2);
variable_s.mName = ENV_NAME1;
variable_s.mData = ( i % 2) ? ENV_DATA1 : ENV_DATA2;
variable_s.mHeader.mBase.mObjectSize = sizeof( VBLEnvironmentVariable) + variable_s.mNameLength + variable_s.mDataLength;
variable_i.mNameLength = strlen( ENV_NAME2);
variable_i.mDataLength = sizeof( int);
variable_i.mName = ENV_NAME2;
variable_i.mData = ( LPBYTE)&i;
variable_i.mHeader.mBase.mObjectSize = sizeof( VBLEnvironmentVariable) + variable_i.mNameLength + variable_i.mDataLength;
// write environment variables
bSuccess = BLWriteObject( hFile, &variable_s.mHeader.mBase);
*pWritten += bSuccess ? 1 : 0;
bSuccess = bSuccess && BLWriteObject( hFile, &variable_i.mHeader.mBase);
*pWritten += bSuccess ? 1 : 0;
// write ethernet frame
memcpy( ethframe.mSourceAddress, src, sizeof( ethframe.mSourceAddress));
ethframe.mReserved1 = 0;
memcpy( ethframe.mDestinationAddress, dst, sizeof( ethframe.mDestinationAddress));
ethframe.mReserved2 = 0;
ethframe.mType = 0x0800;
ethframe.mTPID = 0;
ethframe.mTCI = 0;
ethframe.mPayLoadLength = ( WORD)i;
ethframe.mPayLoad = ethbuffer;
ethframe.mHeader.mBase.mObjectSize = sizeof( VBLEthernetFrame) + ethframe.mPayLoadLength;
bSuccess = bSuccess && BLWriteObject( hFile, ðframe.mHeader.mBase);
*pWritten += bSuccess ? 1 : 0;
// write text
if ( ( i % 100) == 0)
{
char text[128];
sprintf( text, "%d objects written...", *pWritten);
appText.mText = text;
appText.mTextLength = strlen( appText.mText);
appText.mHeader.mBase.mObjectSize = sizeof( VBLAppText) + appText.mTextLength;
bSuccess = bSuccess && BLWriteObject( hFile, &appText.mHeader.mBase);
*pWritten += bSuccess ? 1 : 0;
}
}
}
appText.mText = "All objects written...";
appText.mTextLength = strlen( appText.mText);
appText.mHeader.mBase.mObjectSize = sizeof( VBLAppText) + appText.mTextLength;
bSuccess = bSuccess && BLWriteObject( hFile, &appText.mHeader.mBase);
*pWritten += bSuccess ? 1 : 0;
}
/* close file */
if ( !BLCloseHandle( hFile))
{
return -1;
}
return bSuccess ? 0 : -1;
}`
C# code:
//translation of C++ struct into C# class
public class VBLEnvVar
{
public VBLEnvVarStruct variable_s;
public VBLEnvVar()
{
variable_s = new BLF_Function.VBLEnvVar.VBLEnvVarStruct();
}
public struct VBLEnvVarStruct
{
public VBLObjectHeader.VBLObjectHeaderStruct mHeader;
public uint NameLength;
public uint DataLength;
public string Name;
[MarshalAsAttribute(UnmanagedType.LPArray)]
public byte[] Data;
}
}
public class VBLAppTrigger
{
public VBLAppTriggerStruct apptrigger;
public VBLAppTrigger()
{
apptrigger = new BLF_Function.VBLAppTrigger.VBLAppTriggerStruct(null);
}
public struct VBLAppTriggerStruct
{
public VBLObjectHeader.VBLObjectHeaderStruct mHeader;
public UInt64 mPreTriggerTime;
public UInt64 mPostTriggerTime;
public ushort mFlags;
public ushort mChannel;
public uint mAppSpecific2;
}
}
[DllImport("binlog.dll")]
public static extern bool BLWriteObject( int Handle,ref BLF_Function.ObjectHeader.ObjHeader pBase);
//how function is called into C# code
public static void Main(string[] args)
{
int written=0;
BLF_Function b = new BLF_Function();
UInt64 time=0;
byte[] ethbuffer = new byte[1500];
bool success=false;
string filename = "provamia.blf";
int Handle = MyBLF.BLCreateFile(filename,b.GENERIC_WRITE);
if (Handle != -1)
{
success = MyBLF.BLSetApplication( Handle, (byte)BLF_Function.FileStatistics.APPID.BL_APPID_UNKNOWN, 1, 0, 1);
//***********
MyBLF.SYSTEMTIME d = new MyBLF.SYSTEMTIME();
MyBLF.GetLocalTime(out d);
MyBLF.SYSTEMTIME* s = &d;
success = MyBLF.BLSetMeasurementStartTime( Handle,ref s);
//*************
if (success)
{
success = MyBLF.BLSetWriteOptions( Handle, 6,0);
if (success)
{
BLF_Function.VBLObjectHeader vblobjectheaderclass = new BLF_Function.VBLObjectHeader();
BLF_Function.ObjectHeader objectheaderclass = new BLF_Function.ObjectHeader();
vblobjectheaderclass.mHeader.baseheader=objectheaderclass.baseheader;
BLF_Function.VBLAppTrigger apptriggerclass = new BLF_Function.VBLAppTrigger();
apptriggerclass.apptrigger.mFlags=(ushort)vblobjectheaderclass.BL_OBJ_FLAG_TIME_ONE_NANS;
apptriggerclass.apptrigger.mHeader=vblobjectheaderclass.mHeader;
apptriggerclass.apptrigger.mHeader.baseheader.HeaderSize = apptriggerclass.apptrigger.mHeader.GetSize();
apptriggerclass.apptrigger.mHeader.baseheader.HeaderVersion = 1;
apptriggerclass.apptrigger.mHeader.baseheader.signature = objectheaderclass.BL_OBJ_SIGNATURE;
apptriggerclass.apptrigger.mHeader.baseheader.ObjectSize = (ushort)apptriggerclass.GetSize();
apptriggerclass.apptrigger.mHeader.baseheader.ObjectType = (ushort)BLF_Function.ObjectHeader.OBJ_TYPR.BL_OBJ_TYPE_APP_TRIGGER;
apptriggerclass.apptrigger.mHeader.mObjectFlags = (uint)vblobjectheaderclass.BL_OBJ_FLAG_TIME_ONE_NANS;
BLF_Function.VBLCANMessage messageclass = new BLF_Function.VBLCANMessage();
messageclass.message.mHeader=vblobjectheaderclass.mHeader;
messageclass.message.mHeader.baseheader.HeaderSize = messageclass.message.mHeader.GetSize();
messageclass.message.mHeader.baseheader.HeaderVersion = 1;
messageclass.message.mHeader.baseheader.signature = objectheaderclass.BL_OBJ_SIGNATURE;
messageclass.message.mHeader.baseheader.ObjectSize = (ushort)messageclass.GetSize();
messageclass.message.mHeader.baseheader.ObjectType = (ushort)BLF_Function.ObjectHeader.OBJ_TYPR.BL_OBJ_TYPE_CAN_MESSAGE;
messageclass.message.mHeader.mObjectFlags = (uint)vblobjectheaderclass.BL_OBJ_FLAG_TIME_ONE_NANS;
BLF_Function.VBLEnvVar variable_sclass = new BLF_Function.VBLEnvVar();
variable_sclass.variable_s.mHeader=vblobjectheaderclass.mHeader;
variable_sclass.variable_s.mHeader.baseheader.HeaderSize = variable_sclass.variable_s.mHeader.GetSize();
variable_sclass.variable_s.mHeader.baseheader.HeaderVersion = 1;
variable_sclass.variable_s.mHeader.baseheader.signature = objectheaderclass.BL_OBJ_SIGNATURE;
variable_sclass.variable_s.mHeader.baseheader.ObjectType = (ushort)BLF_Function.ObjectHeader.OBJ_TYPR.BL_OBJ_TYPE_ENV_STRING;
variable_sclass.variable_s.mHeader.mObjectFlags = (uint)vblobjectheaderclass.BL_OBJ_FLAG_TIME_ONE_NANS;
BLF_Function.VBLEnvVar variable_iclass = new BLF_Function.VBLEnvVar();
variable_iclass.variable_s.mHeader=vblobjectheaderclass.mHeader;
variable_iclass.variable_s.mHeader.baseheader.HeaderSize = variable_iclass.variable_s.mHeader.GetSize();
variable_iclass.variable_s.mHeader.baseheader.HeaderVersion = 1;
variable_iclass.variable_s.mHeader.baseheader.signature = objectheaderclass.BL_OBJ_SIGNATURE;
variable_iclass.variable_s.mHeader.baseheader.ObjectType = (ushort)BLF_Function.ObjectHeader.OBJ_TYPR.BL_OBJ_TYPE_ENV_INTEGER;
variable_iclass.variable_s.mHeader.mObjectFlags = (uint)vblobjectheaderclass.BL_OBJ_FLAG_TIME_ONE_NANS;
BLF_Function.VBLEthernetFrame ethframeclass = new BLF_Function.VBLEthernetFrame();
ethframeclass.ethframe.mHeader=vblobjectheaderclass.mHeader;
ethframeclass.ethframe.mHeader.baseheader.HeaderSize = ethframeclass.ethframe.mHeader.GetSize();
ethframeclass.ethframe.mHeader.baseheader.HeaderVersion = 1;
ethframeclass.ethframe.mHeader.baseheader.signature = objectheaderclass.BL_OBJ_SIGNATURE;
ethframeclass.ethframe.mHeader.baseheader.ObjectType = (ushort)BLF_Function.ObjectHeader.OBJ_TYPR.BL_OBJ_TYPE_ETHERNET_FRAME;
ethframeclass.ethframe.mHeader.mObjectFlags = (uint)vblobjectheaderclass.BL_OBJ_FLAG_TIME_ONE_NANS;
BLF_Function.VBLAppText appTextclass = new BLF_Function.VBLAppText();
appTextclass.appText.mHeader=vblobjectheaderclass.mHeader;
appTextclass.appText.mHeader.baseheader.HeaderSize = appTextclass.appText.mHeader.GetSize();
appTextclass.appText.mHeader.baseheader.HeaderVersion = 1;
appTextclass.appText.mHeader.baseheader.signature = objectheaderclass.BL_OBJ_SIGNATURE;
appTextclass.appText.mHeader.baseheader.ObjectSize = (ushort)appTextclass.GetSize();
appTextclass.appText.mHeader.baseheader.ObjectType = (ushort)BLF_Function.ObjectHeader.OBJ_TYPR.BL_OBJ_TYPE_APP_TEXT;
appTextclass.appText.mHeader.mObjectFlags = (uint)vblobjectheaderclass.BL_OBJ_FLAG_TIME_ONE_NANS;
for (int h=0;h<ethbuffer.Length;h++)
ethbuffer[h]=Convert.ToByte(h & 0xFF);
for (int i = 0; i < 1000 ; ++i)
{
/* increment in milliseconds */
time = (uint)(i * 100000);
/* setup app trigger object header */
apptriggerclass.apptrigger.mHeader.mObjectTimestamp = time;
/* write app trigger object */
success = Scrivi(Handle, apptriggerclass.apptrigger.mHeader.baseheader);////NO ERROR
written += success ? 1 : 0;
if (success)
{
string envdata1="01234567";
string envdata2="76543210";
/* setup CAN object header */
messageclass.message.mHeader.mObjectTimestamp = time;
/* setup CAN message */
messageclass.message.mChannel=1;
messageclass.message.mFlags=(byte)vblobjectheaderclass.BL_OBJ_FLAG_TIME_ONE_NANS;
messageclass.message.mDLC = 8;
messageclass.message.mID = 0x100;
char[] supp = envdata1.ToCharArray();
char[] supp2 = envdata2.ToCharArray();
messageclass.message.Data = new byte[messageclass.message.mDLC];
if ((i%2)==0)
{
for (int g=0;g<supp.Length;g++)
messageclass.message.Data[g] = (byte)supp[g];
}
else
{
for (int g=0;g<supp2.Length;g++)
messageclass.message.Data[g] = (byte)supp2[g];
}
/* write CAN message */
success = Scrivi(Handle, messageclass.message.mHeader.baseheader);////NO ERROR
written += success ? 1 : 0;
if (success)
{
if ((i%3)==0)
{
/* setup environment variable object headers */
variable_sclass.variable_s.mHeader.mObjectTimestamp= time;
/* setup environment variables */
string envname1="EnvString";
string envname2="EnvInt";
char[] suppstring1 = envname1.ToCharArray();
char[] suppstring2 = envname2.ToCharArray();
variable_sclass.variable_s.NameLength = (uint)envname1.Length;
variable_sclass.variable_s.DataLength = (uint)(((i%2)==0)?envdata1.Length:envdata2.Length);
variable_sclass.variable_s.Name = envname1;
variable_sclass.variable_s.Data = new byte[variable_sclass.variable_s.DataLength];
if ((i%2)==0)
{
for (int g=0;g<supp.Length;g++)
variable_sclass.variable_s.Data[g] = Convert.ToByte(supp[g]);
}
else
{
for (int g=0;g<supp2.Length;g++)
variable_sclass.variable_s.Data[g] = Convert.ToByte(supp2[g]);
}
variable_sclass.variable_s.mHeader.baseheader.ObjectSize = 65;
success = Scrivi(Handle, variable_sclass.variable_s.mHeader.baseheader);////ERROR
...........
}
public static bool Scrivi(int a, BLF_Function.ObjectHeader.ObjHeader b)
{
return MyBLF.BLWriteObject( a, ref b);
}
CB
Firstly, the type HANDLE should translate to IntPtr, not int.
Also the sizes of the structs may differ to the original due to structure padding. Use the [Structlayout()]-Attribute in your C#-code to control this.
Change "ref" to "out" everywhere in C# and see that there is no memory allocation issues in c++ code.
Related
I'm creating a wrapper for an unmanaged C++ function to be called using C#.
The function returns a vector of structs.
When returning from the function, the strings are all ok, but after the 'return' to the wrapper, the strings break returning weird characters.
The weird thing is, if I call it a second time, without closing Visual Studio, it works!
I have saved all the files as Unicode UTF-8, set Unicode as the character set on the Visual Studio project, defined UNICODE and _UNICODE, but still the problem persists.
This is the unmanaged struct:
typedef struct SessionEnumOutput {
SessionEnumOutput() {};
wchar_t *UserName;
wchar_t *SessionName;
WtsSessionState SessionState;
}SessionEnumOutput, *PSessionEnumOutput;
It being built on the unmanaged function:
for (DWORD i = 0; i < pCount; i++)
{
WTS_SESSION_INFO_1 innerSes = sessionInfo[i];
if (innerSes.State == WTSActive)
{
wchar_t *sessionName;
wchar_t *sessUserName;
SessionEnumOutput outObj;
if (innerSes.pUserName == NULL) { sessUserName = L"System"; }
else { sessUserName = innerSes.pUserName; }
if (innerSes.pSessionName == NULL) { sessionName = L""; }
else { sessionName = innerSes.pSessionName; }
Unmanaged::SessionEnumOutput inner;
inner.UserName = sessUserName;
inner.SessionName = sessionName;
inner.SessionState = (WtsSessionState)innerSes.State;
output.push_back(inner);
}
}
The managed wrapper:
ref class wSessionEnumOutput
{
public:
String^ UserName;
String^ SessionName;
wWtsSessionState SessionState;
};
List<wSessionEnumOutput^>^ GetEnumeratedSession(String^ computerName, bool onlyActive, bool excludeSystemSessions)
{
pin_ptr<const wchar_t> wName = PtrToStringChars(computerName);
List<wSessionEnumOutput^>^ output = gcnew List<wSessionEnumOutput^>();
vector<Unmanaged::SessionEnumOutput> *result = new vector<Unmanaged::SessionEnumOutput>;
*result = ptr->GetEnumeratedSession((LPWSTR)wName, onlyActive, excludeSystemSessions);
for (size_t it = 0; it < result->size(); it++)
{
Unmanaged::SessionEnumOutput single = result->at(it);
wSessionEnumOutput^ inner = gcnew wSessionEnumOutput();
inner->UserName = Marshal::PtrToStringUni((IntPtr)single.UserName);
inner->SessionName = Marshal::PtrToStringUni((IntPtr)single.SessionName);
inner->SessionState = (wWtsSessionState)single.SessionState;
output->Add(inner);
}
I can see the strings broken at *vectorUnmanaged::SessionEnumOutput result = new vectorUnmanaged::SessionEnumOutput;
I have created a test console to call the C# function twice to analyze the heap:
List<Managed.wSessionEnumOutput> thing = Utilities.GetComputerSession();
Console.WriteLine("###################################################");
for (int i = 0; i < thing.Count; i++)
{
Console.WriteLine("User name: " + thing[i].UserName);
Console.WriteLine("Session name: " + thing[i].SessionName);
Console.WriteLine("Session state: " + thing[i].SessionState);
Console.WriteLine("###################################################");
}
Console.WriteLine("\n");
thing = Utilities.GetComputerSession();
Console.WriteLine("###################################################");
for (int i = 0; i < thing.Count; i++)
{
Console.WriteLine("User name: " + thing[i].UserName);
Console.WriteLine("Session name: " + thing[i].SessionName);
Console.WriteLine("Session state: " + thing[i].SessionState);
Console.WriteLine("###################################################");
}
The difference is, on the second call, I can see Unicode and UTF-8 decoders loaded on the heap.
On the first call, they are not there.
here are both call's results:
I'm not a developer, just a curious system administrator, so pardon my coding habilities.
What am I missing?
EDIT:
Function definition:
vector<Unmanaged::SessionEnumOutput> Unmanaged::GetEnumeratedSession(
LPWSTR computerName = NULL,
BOOL onlyActive = 0,
BOOL excludeSystemSessions = 0
)
{
HANDLE session;
BOOL enumResult;
DWORD pCount = 0;
DWORD pLevel = 1;
vector<SessionEnumOutput> output;
PWTS_SESSION_INFO_1 sessionInfo = (PWTS_SESSION_INFO_1)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WTS_SESSION_INFO_1));
if (computerName != NULL)
{
session = WTSOpenServer(computerName);
if (session == NULL) { goto END; }
}
else { session = WTS_CURRENT_SERVER_HANDLE; }
enumResult = WTSEnumerateSessionsEx(session, &pLevel, 0, &sessionInfo, &pCount);
if (enumResult == 0) { goto END; }
switch (onlyActive)
{
case 1:
for (DWORD i = 0; i < pCount; i++)
{
WTS_SESSION_INFO_1 innerSes = sessionInfo[i];
if (innerSes.State == WTSActive)
{
wchar_t *sessionName;
wchar_t *sessUserName;
SessionEnumOutput outObj;
if (innerSes.pUserName == NULL) { sessUserName = L"System"; }
else { sessUserName = innerSes.pUserName; }
if (innerSes.pSessionName == NULL) { sessionName = L""; }
else { sessionName = innerSes.pSessionName; }
Unmanaged::SessionEnumOutput inner;
inner.UserName = sessUserName;
inner.SessionName = sessionName;
inner.SessionState = (WtsSessionState)innerSes.State;
output.push_back(inner);
}
}
break;
default:
if (excludeSystemSessions == 0)
{
for (DWORD i = 0; i < pCount; i++)
{
WTS_SESSION_INFO_1 innerSes = sessionInfo[i];
wchar_t* sessionName;
wchar_t* sessUserName;
SessionEnumOutput outObj;
if (innerSes.pUserName == NULL) { sessUserName = L"System"; }
else { sessUserName = innerSes.pUserName; }
if (innerSes.pSessionName == NULL) { sessionName = L""; }
else { sessionName = innerSes.pSessionName; }
Unmanaged::SessionEnumOutput inner;
inner.UserName = sessUserName;
inner.SessionName = sessionName;
inner.SessionState = (WtsSessionState)innerSes.State;
output.push_back(inner);
}
}
else
{
for (DWORD i = 0; i < pCount; i++)
{
WTS_SESSION_INFO_1 innerSes = sessionInfo[i];
wstring sessUserName;
if (innerSes.pUserName == NULL) { sessUserName = L""; }
else { sessUserName = innerSes.pUserName; }
if (sessUserName.length() > 0)
{
wchar_t *innerUser = (wchar_t*)sessUserName.c_str();
wchar_t *sessionName;
SessionEnumOutput outObj;
WTS_SESSION_INFO_1 innerSes = sessionInfo[i];
if (innerSes.pSessionName == NULL) { sessionName = L""; }
else { sessionName = innerSes.pSessionName; }
Unmanaged::SessionEnumOutput inner;
inner.UserName = innerUser;
inner.SessionName = sessionName;
inner.SessionState = (WtsSessionState)innerSes.State;
output.push_back(inner);
}
}
}
break;
}
END:
if (session != NULL) { WTSCloseServer(session); }
if (pCount > 0) { WTSFreeMemoryEx(WTSTypeSessionInfoLevel1, sessionInfo, pCount); }
return output;
}
Solved the mystery!
Mr. CharlieFace on the comments posted another question where they are discussing the unespected behavior of the function WTSEnumerateSessionsEx.
Unfortunately, this is an issue happening on Windows for some time now.
I've followed the approach of calling WTSEnumerateSessions and then WTSQuerySessionInformation to get the user name.
Fragment:
PWTS_SESSION_INFO sessionInfo = (PWTS_SESSION_INFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WTS_SESSION_INFO));
if (computerName != NULL)
{
session = WTSOpenServer(computerName);
if (session == NULL) { goto END; }
}
else { session = WTS_CURRENT_SERVER_HANDLE; }
enumResult = WTSEnumerateSessions(session, 0, 1, &sessionInfo, &pCount);
if (enumResult == 0) { goto END; }
switch (onlyActive)
{
case 1:
for (DWORD i = 0; i < pCount; i++)
{
WTS_SESSION_INFO innerSes = sessionInfo[i];
if (innerSes.State == WTSActive)
{
wchar_t *sessionName;
wchar_t *sessUserName;
LPWSTR ppBuffer;
DWORD pBytesReturned;
BOOL thisResult;
thisResult = WTSQuerySessionInformation(session, innerSes.SessionId, WTSUserName, &ppBuffer, &pBytesReturned);
if (thisResult == 0) { goto END; }
if (ppBuffer == NULL) { sessUserName = L"System"; }
else { sessUserName = ppBuffer; }
if (innerSes.pWinStationName == NULL) { sessionName = L""; }
else { sessionName = innerSes.pWinStationName; }
Unmanaged::SessionEnumOutput inner;
inner.UserName = sessUserName;
inner.SessionName = sessionName;
inner.SessionState = (WtsSessionState)innerSes.State;
output.push_back(inner);
WTSFreeMemory(&ppBuffer);
}
}
break;
Look how pretty that is!
Sources:
Why does WTSFreeMemoryExA always return ERROR_INVALID_PARAMETER when passed a WTSTypeClass of WTSTypeSessionInfoLevel1?
Memory leak issues with Windows API call - Delphi
Thank you very much for the help!
As I mentioned, there is an outstanding bug involving the WTS functions when used with the ANSI versions. Instead you should use the Unicode versions.
See Why does WTSFreeMemoryExA always return ERROR_INVALID_PARAMETER when passed a WTSTypeClass of WTSTypeSessionInfoLevel1?
I think using a C++/CLI wrapper for this to be able to use in C# is overkill. You should be able to do this using standard PInvoke marshalling in C#.
It's best not to rely on BestFitMapping, and instead specify the function names explicitly.
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)]
static extern IntPtr WTSOpenServerExW (string pServerName);
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)]
static extern void WTSCloseServer(IntPtr hServer);
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)]
static extern bool WTSEnumerateSessionsExW(
IntPtr hServer,
ref int pLevel,
int Filter,
out IntPtr ppSessionInfo,
out int pCount
);
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)]
static extern bool WTSFreeMemoryExW(
WTS_TYPE_CLASS WTSTypeClass,
IntPtr pMemory,
int NumberOfEntries
);
enum WTS_TYPE_CLASS
{
WTSTypeProcessInfoLevel0,
WTSTypeProcessInfoLevel1,
WTSTypeSessionInfoLevel1
}
enum WtsSessionState
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct WTS_SESSION_INFO
{
public int ExecEnvId;
public WtsSessionState State;
public int SessionId;
[MarshalAs(UnmanagedType.LPTStr)]
public string pSessionName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pHostName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pUserName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pDomainName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pFarmName;
}
List<SessionEnumOutput> GetEnumeratedSession(
string computerName = null,
bool onlyActive = false,
bool excludeSystemSessions = false
)
{
IntPtr server = default;
IntPtr sessionInfo = default;
int pCount = default;
List<SessionEnumOutput> output = new List<SessionEnumOutput>();
if (computerName != null)
{
server = WTSOpenServerExW(computerName);
if (server == IntPtr.Zero || server == new IntPtr(-1))
throw new Exception("Invalid computer name");
}
try
{
int pLevel = 1;
if (!WTSEnumerateSessionsExW(server, ref pLevel, 0, out sessionInfo, out pCount))
throw new Win32Exception(Marshal.GetLastWin32Error());
for (var i = 0; i < pCount; i++)
{
WTS_SESSION_INFO innerSes = Marshal.PtrToStructure<WTS_SESSION_INFO>(sessionInfo + i * Marshal.SizeOf<WTS_SESSION_INFO>());
if (onlyActive && innerSes.State != WtsSessionState.WTSActive
|| excludeSystemSessions && innerSes.pSessionName == null)
continue;
SessionEnumOutput inner = new SessionEnumOutput
{
UserName = innerSes.pUserName ?? "System",
SessionName = innerSes.pSessionName ?? "",
SessionState = innerSes.State,
};
output.Add(inner);
};
}
finally
{
if (sessionInfo != default)
WTSFreeMemoryExW(WTS_TYPE_CLASS.WTSTypeSessionInfoLevel1, sessionInfo, pCount);
if (server != default)
WTSCloseServer(server);
}
return output;
}
Note the use of a finally to enure memory is freed correctly.
There is also a huge amount of duplicated code. I have cleaned up the rest of the function significantly.
Before anybody mentions it, I refered to this link to find out how I needed to copy the backbuffer to a bitmap.
Current situation
I am injected to the target process
Target process' FeatureLevel = Level_11_0
Target SwapChain is being made with DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH flag.
SwapChain::Present function is hooked.
Screenshot turns out black and target process crashes. without screenshot process runs fine.
Desired situation
Make the screenshot properly and let the target process continue with its normal execution.
Code
NOTE Hook class is the same as in the link. I only added an UnmodifiableHook version of it which does what its name says. I left out all unimportant bits.
TestSwapChainHook.cs
using System;
using System.Runtime.InteropServices;
namespace Test
{
public sealed class TestSwapChainHook : IDisposable
{
private enum IDXGISwapChainVirtualTable
{
QueryInterface = 0,
AddRef = 1,
Release = 2,
SetPrivateData = 3,
SetPrivateDataInterface = 4,
GetPrivateData = 5,
GetParent = 6,
GetDevice = 7,
Present = 8,
GetBuffer = 9,
SetFullscreenState = 10,
GetFullscreenState = 11,
GetDesc = 12,
ResizeBuffers = 13,
ResizeTarget = 14,
GetContainingOutput = 15,
GetFrameStatistics = 16,
GetLastPresentCount = 17,
}
public static readonly int VIRTUAL_METHOD_COUNT_LEVEL_DEFAULT = 18;
private static IntPtr[] SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES;
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
public delegate int DXGISwapChainPresentDelegate(IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags);
public delegate int DXGISwapChainPresentHookDelegate(UnmodifiableHook<DXGISwapChainPresentDelegate> hook, IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags);
private DXGISwapChainPresentHookDelegate _present;
private Hook<DXGISwapChainPresentDelegate> presentHook;
static TestSwapChainHook()
{
SharpDX.DXGI.Rational rational = new SharpDX.DXGI.Rational(60, 1);
SharpDX.DXGI.ModeDescription modeDescription = new SharpDX.DXGI.ModeDescription(100, 100, rational, SharpDX.DXGI.Format.R8G8B8A8_UNorm);
SharpDX.DXGI.SampleDescription sampleDescription = new SharpDX.DXGI.SampleDescription(1, 0);
using (SharpDX.Windows.RenderForm renderForm = new SharpDX.Windows.RenderForm())
{
SharpDX.DXGI.SwapChainDescription swapChainDescription = new SharpDX.DXGI.SwapChainDescription();
swapChainDescription.BufferCount = 1;
swapChainDescription.Flags = SharpDX.DXGI.SwapChainFlags.None;
swapChainDescription.IsWindowed = true;
swapChainDescription.ModeDescription = modeDescription;
swapChainDescription.OutputHandle = renderForm.Handle;
swapChainDescription.SampleDescription = sampleDescription;
swapChainDescription.SwapEffect = SharpDX.DXGI.SwapEffect.Discard;
swapChainDescription.Usage = SharpDX.DXGI.Usage.RenderTargetOutput;
SharpDX.Direct3D11.Device device = null;
SharpDX.DXGI.SwapChain swapChain = null;
SharpDX.Direct3D11.Device.CreateWithSwapChain(SharpDX.Direct3D.DriverType.Hardware, SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport, swapChainDescription, out device, out swapChain);
try
{
IntPtr swapChainVirtualTable = Marshal.ReadIntPtr(swapChain.NativePointer);
SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES = new IntPtr[VIRTUAL_METHOD_COUNT_LEVEL_DEFAULT];
for (int x = 0; x < VIRTUAL_METHOD_COUNT_LEVEL_DEFAULT; x++)
{
SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES[x] = Marshal.ReadIntPtr(swapChainVirtualTable, x * IntPtr.Size);
}
device.Dispose();
swapChain.Dispose();
}
catch (Exception)
{
if (device != null)
{
device.Dispose();
}
if (swapChain != null)
{
swapChain.Dispose();
}
throw;
}
}
}
public TestSwapChainHook()
{
this._present = null;
this.presentHook = new Hook<DXGISwapChainPresentDelegate>(
SWAP_CHAIN_VIRTUAL_TABLE_ADDRESSES[(int)IDXGISwapChainVirtualTable.Present],
new DXGISwapChainPresentDelegate(hookPresent),
this);
}
public void activate()
{
this.presentHook.activate();
}
public void deactivate()
{
this.presentHook.deactivate();
}
private int hookPresent(IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags)
{
lock (this.presentHook)
{
if (this._present == null)
{
return this.presentHook.original(thisPtr, syncInterval, flags);
}
else
{
return this._present(new UnmodifiableHook<DXGISwapChainPresentDelegate>(this.presentHook), thisPtr, syncInterval, flags);
}
}
}
public DXGISwapChainPresentHookDelegate present
{
get
{
lock (this.presentHook)
{
return this._present;
}
}
set
{
lock (this.presentHook)
{
this._present = value;
}
}
}
}
}
Using code
initialization
private TestSwapChain swapChainHook;
private bool capture = false;
private object captureLock = new object();
this.swapChainHook = new TestSwapChainHook();
this.swapChainHook.present = presentHook;
this.swapChainHook.activate();
EDIT
I used a different method to capture a screenshot described in this link. However my screenshot turns out like this:
Now this seems to be a problem with my conversion settings or whatever but I'm unable to find out what exactly I need to do to fix it. I know that the surface I'm converting to a bitmap uses the DXGI_FORMAT_R10G10B10A2_UNORM format (32-bits, 10 bits per color and 2 for alpha I think?). But I'm not sure how this even works in the for loops (skipping bytes and stuff). I just plain copy pasted it.
new hook function
private int presentHook(UnmodifiableHook<IDXGISwapChainHook.DXGISwapChainPresentDelegate> hook, IntPtr thisPtr, uint syncInterval, SharpDX.DXGI.PresentFlags flags)
{
try
{
lock (this.captureLock)
{
if (this.capture)
{
SharpDX.DXGI.SwapChain swapChain = (SharpDX.DXGI.SwapChain)thisPtr;
using (SharpDX.Direct3D11.Texture2D backBuffer = swapChain.GetBackBuffer<SharpDX.Direct3D11.Texture2D>(0))
{
SharpDX.Direct3D11.Texture2DDescription texture2DDescription = backBuffer.Description;
texture2DDescription.CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.Read;
texture2DDescription.Usage = SharpDX.Direct3D11.ResourceUsage.Staging;
texture2DDescription.OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None;
texture2DDescription.BindFlags = SharpDX.Direct3D11.BindFlags.None;
using (SharpDX.Direct3D11.Texture2D texture = new SharpDX.Direct3D11.Texture2D(backBuffer.Device, texture2DDescription))
{
//DXGI_FORMAT_R10G10B10A2_UNORM
backBuffer.Device.ImmediateContext.CopyResource(backBuffer, texture);
using (SharpDX.DXGI.Surface surface = texture.QueryInterface<SharpDX.DXGI.Surface>())
{
SharpDX.DataStream dataStream;
SharpDX.DataRectangle map = surface.Map(SharpDX.DXGI.MapFlags.Read, out dataStream);
try
{
byte[] pixelData = new byte[surface.Description.Width * surface.Description.Height * 4];
int lines = (int)(dataStream.Length / map.Pitch);
int dataCounter = 0;
int actualWidth = surface.Description.Width * 4;
for (int y = 0; y < lines; y++)
{
for (int x = 0; x < map.Pitch; x++)
{
if (x < actualWidth)
{
pixelData[dataCounter++] = dataStream.Read<byte>();
}
else
{
dataStream.Read<byte>();
}
}
}
GCHandle handle = GCHandle.Alloc(pixelData, GCHandleType.Pinned);
try
{
using (Bitmap bitmap = new Bitmap(surface.Description.Width, surface.Description.Height, map.Pitch, PixelFormat.Format32bppArgb, handle.AddrOfPinnedObject()))
{
bitmap.Save(#"C:\Users\SOMEUSERNAME\Desktop\test.bmp");
}
}
finally
{
if (handle.IsAllocated)
{
handle.Free();
}
}
}
finally
{
surface.Unmap();
dataStream.Dispose();
}
}
}
}
this.capture = false;
}
}
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString());
}
return hook.original(thisPtr, syncInterval, flags);
}
Answer
Turns out the DXGI_FORMAT_R10G10B10A2_UNORM format is in this bit format:
A=alpha
B=blue
G=green
R=red
AABBBBBB BBBBGGGG GGGGGGRR RRRRRRRR
And Format32bppArgb is in this byte order:
BGRA
So the final loop code would be:
while (pixelIndex < pixelData.Length)
{
uint currentPixel = dataStream.Read<uint>();
uint r = (currentPixel & 0x3FF);
uint g = (currentPixel & 0xFFC00) >> 10;
uint b = (currentPixel & 0x3FF00000) >> 20;
uint a = (currentPixel & 0xC0000000) >> 30;
pixelData[pixelIndex++] = (byte)(b >> 2);
pixelData[pixelIndex++] = (byte)(g >> 2);
pixelData[pixelIndex++] = (byte)(r >> 2);
pixelData[pixelIndex++] = (byte)(a << 6);
while ((pixelIndex % map.Pitch) >= actualWidth)
{
dataStream.Read<byte>();
pixelIndex++;
}
}
That screenshot does look like R10G10B10A2 is getting stuffed into R8G8B8A8. I haven't tested your code but we should have this bit layout
xxxxxxxx yyyyyyyy zzzzzzzz wwwwwwww
RRRRRRRR RRGGGGGG GGGGBBBB BBBBBBAA
and you can extract them as follows
byte x = data[ptr++];
byte y = data[ptr++];
byte z = data[ptr++];
byte w = data[ptr++];
int r = x << 2 | y >> 6;
int g = (y & 0x3F) << 4 | z >> 4;
int b = (z & 0xF) << 6 | w >> 2;
int a = w & 0x3;
where r, g, b now have 10 bit resolution. If you want to scale them back to bytes you can do that with (byte)(r >> 2).
Update
This would replace your double for loop. I have no way of testing this so I don't want to push it further, but I believe the idea is correct. The last check should skip the padding bytes in each row.
while(dataCounter < pixelData.Length)
{
byte x = dataStream.Read<byte>();
byte y = dataStream.Read<byte>();
byte z = dataStream.Read<byte>();
byte w = dataStream.Read<byte>();
int r = x << 2 | y >> 6;
int g = (y & 0x3F) << 4 | z >> 4;
int b = (z & 0xF) << 6 | w >> 2;
int a = w & 0x3;
pixelData[dataCounter++] = (byte)(r >> 2);
pixelData[dataCounter++] = (byte)(g >> 2);
pixelData[dataCounter++] = (byte)(b >> 2);
pixelData[dataCounter++] = (byte)(a << 6);
while((dataCounter % map.Pitch) >= actualWidth)
{
dataStream.Read<byte>();
dataCounter++;
}
}
I'm trying to hook the winsock connect function and route the TCP connection through socks5 proxy /w auth.
This works if the socket is a blocking socket, but while using firefox ( nonblocking sockets ) I get a lot of 10035, 10022 winsock Errors.
How can i determine if it's a nonblocking / blocking socket?
I would really appreciate any hints or ideas to achieve the functionality to hook the wsock connect function and route tcp traffic through a socks5 server.
I can put the demo application on github if anybody wants to test it. ( Works with any version of firefox )
Edit1: https://github.com/duketwo/WinsockConnectHookSocks5/
( You have to edit the proxy information in WSockConnectHook/HookManager.cs and the path of firefox in Injector/MainForm.cs )
Edit2: It's easyhook which is causing the trouble, anything after the original function call doesn't work properly.
Edit3: Seems like i got it working with many flaws, in fact it is required differentiate between nonblocking sockets and blocking sockets. Any ideas how to achieve this?
Edit4: Windows doesn't offer any method to retrieve the blocking-attribute of a socket, so I might have to hook the ioctlsocket function to keep track of the blocking status of the sockets.
Thanks
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using EasyHook;
using System.IO;
using System.Windows.Forms;
namespace WSockConnectHook
{
public class WinSockConnectController : IDisposable, IHook
{
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi, SetLastError = true)]
private delegate int WinsockConnectDelegate(IntPtr s, IntPtr addr, int addrsize);
[DllImport("WS2_32.dll", SetLastError = true)]
public static extern int connect(IntPtr s, IntPtr addr, int addrsize);
[StructLayout(LayoutKind.Sequential, Size = 16)]
public struct sockaddr_in
{
public const int Size = 16;
public short sin_family;
public ushort sin_port;
public struct in_addr
{
public uint S_addr;
public struct _S_un_b
{
public byte s_b1, s_b2, s_b3, s_b4;
}
public _S_un_b S_un_b;
public struct _S_un_w
{
public ushort s_w1, s_w2;
}
public _S_un_w S_un_w;
}
public in_addr sin_addr;
}
[DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int WSAGetLastError();
[DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern void WSASetLastError(int set);
[DllImport("Ws2_32.dll", CharSet = CharSet.Ansi)]
public static extern uint inet_addr(string cp);
[DllImport("Ws2_32.dll")]
public static extern ushort htons(ushort hostshort);
[DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr socket(short af, short socket_type, int protocol);
[DllImport("Ws2_32.dll")]
public static extern int send(IntPtr s, IntPtr buf, int len, int flags);
[DllImport("Ws2_32.dll")]
public static extern int recv(IntPtr s, IntPtr buf, int len, int flags);
[DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int closesocket(IntPtr s);
[DllImport("Ws2_32.dll")]
public static extern ushort ntohs(ushort netshort);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern void SetLastError(int errorCode);
private string _name;
private LocalHook _hook;
public bool Error { get; set; }
public string Name { get; set; }
private string proxyIp, proxyPort, proxyUser, proxyPass;
public WinSockConnectController(IntPtr address, string proxyIp, string proxyPort, string proxyUser, string proxyPass)
{
this.Name = typeof(WinSockConnectController).Name;
this.proxyIp = proxyIp;
this.proxyPort = proxyPort;
this.proxyUser = proxyUser;
this.proxyPass = proxyPass;
try
{
_name = string.Format("WinsockHook_{0:X}", address.ToInt32());
_hook = LocalHook.Create(address, new WinsockConnectDelegate(WinsockConnectDetour), this);
_hook.ThreadACL.SetExclusiveACL(new Int32[] { 1 });
}
catch (Exception)
{
this.Error = true;
}
}
private object wSockLock = new object();
private int WinsockConnectDetour(IntPtr s, IntPtr addr, int addrsize)
{
lock (wSockLock)
{
// retrieve remote ip
sockaddr_in structure = (sockaddr_in)Marshal.PtrToStructure(addr, typeof(sockaddr_in));
string remoteIp = new System.Net.IPAddress(structure.sin_addr.S_addr).ToString();
ushort remotePort = ntohs(structure.sin_port);
HookManager.Log("Ip: " + remoteIp + " Port: " + remotePort.ToString() + " Addrsize: " + addrsize);
if (!proxyIp.Equals(""))
//if (!proxyIp.Equals(""))
{
// connect to socks5 server
SetAddr(s, addr, proxyIp, proxyPort);
var result = Connect(s, addr, addrsize);
if (result == -1)
return -1;
// send socks 5 request
IntPtr socksProtocolRequest = SetUpSocks5Request();
result = send(s, socksProtocolRequest, 4, 0);
if (result == -1)
return -1;
// retrieve server repsonse
var response = Recieve(s, 2);
if (response == IntPtr.Zero)
return -1;
byte[] recvBytes = new byte[2] { Marshal.ReadByte(response), Marshal.ReadByte(response, 1) };
if (recvBytes[1] == 255)
{
HookManager.Log("No authentication method was accepted by the proxy server");
return -1;
}
if (recvBytes[0] != 5)
{
HookManager.Log("No SOCKS5 proxy");
return -1;
}
// if auth request response, send authenicate request
if (recvBytes[1] == 2)
{
int length = 0;
var authenticateRequest = SetUpAuthenticateRequest(proxyUser, proxyPass, out length);
result = Send(s, authenticateRequest, length);
response = Recieve(s, 2);
if (response == IntPtr.Zero)
return -1;
recvBytes = new byte[2] { Marshal.ReadByte(response), Marshal.ReadByte(response, 1) };
if (recvBytes[1] != 0)
{
HookManager.Log("Proxy: incorrect username/password");
return -1;
}
}
// request bind with server
var bindRequest = SetUpBindWithRemoteHost(remoteIp, remotePort);
result = Send(s, bindRequest, 10);
if (result == -1)
return -1;
// response
response = Recieve(s, 10);
if (response == IntPtr.Zero)
return -1;
if (!VerifyBindResponse(response))
return -1;
// success
WSASetLastError(0);
SetLastError(0);
// clean memory
foreach (var ptr in allocatedMemory)
Marshal.FreeHGlobal(ptr);
allocatedMemory.Clear();
return 0;
}
else
{
var result = connect(s, addr, addrsize);
return result;
}
}
}
private int Connect(IntPtr socket, IntPtr addr, int addrsize)
{
var result = connect(socket, addr, addrsize);
while (result == -1)
{
var errorcode = WSAGetLastError();
HookManager.Log("Error: " + errorcode);
if (errorcode == 10056)
break;
if (errorcode == 10037)
break;
if (errorcode != 10035 && errorcode != 10037)
return -1;
//flag = 1;
result = connect(socket, addr, addrsize);
}
return result;
}
private int Send(IntPtr socket, IntPtr buf, int len)
{
var result = send(socket, buf, len, 0);
while (result == -1)
{
var errorcode = WSAGetLastError();
HookManager.Log("Error: " + errorcode);
if (errorcode == 10056)
break;
if (errorcode == 10037)
break;
if (errorcode != 10035 && errorcode != 10037)
return -1;
result = send(socket, buf, 4, 0);
}
return result;
}
private List<IntPtr> allocatedMemory = new List<IntPtr>();
private IntPtr Recieve(IntPtr socket, int len)
{
var buffer = Marshal.AllocHGlobal(len);
allocatedMemory.Add(buffer);
var result = recv(socket, buffer, len, 0);
if (result == -1)
{
HookManager.Log("Error2: " + WSAGetLastError());
return IntPtr.Zero;
}
return buffer;
}
private IntPtr RecieveAuth(IntPtr socket, int len)
{
var buffer = Marshal.AllocHGlobal(len);
allocatedMemory.Add(buffer);
var result = recv(socket, buffer, len, 0);
if (result == -1)
{
HookManager.Log("Error3: " + WSAGetLastError());
return IntPtr.Zero; ;
}
if (result == 0)
return buffer;
if (result != 2)
{
HookManager.Log("Proxy: Bad response from server");
return IntPtr.Zero;
}
return buffer;
}
private IntPtr RecieveBind(IntPtr socket, int len)
{
var buffer = Marshal.AllocHGlobal(len);
allocatedMemory.Add(buffer);
var result = recv(socket, buffer, len, 0);
if (result == -1)
{
HookManager.Log("Error3: " + WSAGetLastError());
return IntPtr.Zero; ;
}
if (result == 0)
return buffer;
if (result != 10)
{
HookManager.Log("Proxy: Bad response from server");
return IntPtr.Zero;
}
return buffer;
}
private void SetAddr(IntPtr socket, IntPtr addr, string ip, string port)
{
sockaddr_in structure = (sockaddr_in)Marshal.PtrToStructure(addr, typeof(sockaddr_in));
string originalip = new System.Net.IPAddress(structure.sin_addr.S_addr).ToString();
ushort originalport = ntohs(structure.sin_port);
structure.sin_addr.S_addr = inet_addr(ip);
structure.sin_port = htons(Convert.ToUInt16(port));
Marshal.StructureToPtr(structure, addr, true);
structure = (sockaddr_in)Marshal.PtrToStructure(addr, typeof(sockaddr_in));
}
private IntPtr SetUpSocks5Request()
{
var initialRequest = Marshal.AllocHGlobal(4);
Marshal.WriteByte(initialRequest, Convert.ToByte(5));
Marshal.WriteByte(initialRequest + 1, Convert.ToByte(2));
Marshal.WriteByte(initialRequest + 2, Convert.ToByte(0));
Marshal.WriteByte(initialRequest + 3, Convert.ToByte(2));
return initialRequest;
}
private IntPtr SetUpAuthenticateRequest(string username, string password, out int index)
{
index = 0;
var size = 3 + Encoding.Default.GetBytes(username).Length + Encoding.Default.GetBytes(password).Length;
var authenticateBuffer = Marshal.AllocHGlobal(size);
Marshal.WriteByte(authenticateBuffer + index++, Convert.ToByte(1));
Marshal.WriteByte(authenticateBuffer + index++, Convert.ToByte(username.Length));
byte[] rawBytes;
if (username.Length > 0)
{
rawBytes = Encoding.Default.GetBytes(username);
for (int i = 0; i < rawBytes.Length; i++)
{
Marshal.WriteByte(authenticateBuffer + index++, rawBytes[i]);
}
}
Marshal.WriteByte(authenticateBuffer + index++, Convert.ToByte(password.Length));
if (password.Length > 0)
{
rawBytes = Encoding.Default.GetBytes(password);
for (int i = 0; i < rawBytes.Length; i++)
{
Marshal.WriteByte(authenticateBuffer + index++, rawBytes[i]);
}
}
return authenticateBuffer;
}
private IntPtr SetUpBindWithRemoteHost(string eveIP, ushort evePort)
{
var bindWithEveBuffer = Marshal.AllocHGlobal(10);
var iplist = eveIP.Split('.').ToList();
byte[] portbyte = BitConverter.GetBytes(evePort).Reverse().ToArray();
byte[] newbyte = new byte[2];
int indexy = 0;
foreach (var byty in portbyte)
{
newbyte[indexy] = byty;
indexy++;
}
// bind with remote server
Marshal.WriteByte(bindWithEveBuffer, Convert.ToByte(5));
Marshal.WriteByte(bindWithEveBuffer + 1, Convert.ToByte(1));
Marshal.WriteByte(bindWithEveBuffer + 2, Convert.ToByte(0));
Marshal.WriteByte(bindWithEveBuffer + 3, Convert.ToByte(1));
Marshal.WriteByte(bindWithEveBuffer + 4, Convert.ToByte(iplist[0]));
Marshal.WriteByte(bindWithEveBuffer + 5, Convert.ToByte(iplist[1]));
Marshal.WriteByte(bindWithEveBuffer + 6, Convert.ToByte(iplist[2]));
Marshal.WriteByte(bindWithEveBuffer + 7, Convert.ToByte(iplist[3]));
Marshal.WriteByte(bindWithEveBuffer + 8, newbyte[0]);
Marshal.WriteByte(bindWithEveBuffer + 9, newbyte[1]);
return bindWithEveBuffer;
}
private bool VerifyBindResponse(IntPtr buffer)
{
var recvBytes = new byte[10] { Marshal.ReadByte(buffer), Marshal.ReadByte(buffer, 1), Marshal.ReadByte(buffer, 2), Marshal.ReadByte(buffer, 3), Marshal.ReadByte(buffer, 4), Marshal.ReadByte(buffer, 5), Marshal.ReadByte(buffer, 6), Marshal.ReadByte(buffer, 7), Marshal.ReadByte(buffer, 8), Marshal.ReadByte(buffer, 9) };
if (recvBytes[1] != 0)
{
if (recvBytes[1] == 1)
HookManager.Log("General failure");
if (recvBytes[1] == 2)
HookManager.Log("connection not allowed by ruleset");
if (recvBytes[1] == 3)
HookManager.Log("network unreachable");
if (recvBytes[1] == 4)
HookManager.Log("host unreachable");
if (recvBytes[1] == 5)
HookManager.Log("connection refused by destination host");
if (recvBytes[1] == 6)
HookManager.Log("TTL expired");
if (recvBytes[1] == 7)
HookManager.Log("command not supported / protocol error");
if (recvBytes[1] == 8)
HookManager.Log("address type not supported");
HookManager.Log("Proxy: Connection error binding eve server");
return false;
}
return true;
}
public void Dispose()
{
if (_hook == null)
return;
_hook.Dispose();
_hook = null;
}
}
}
I have a c++ method which creates, fills and returns SAFEARRAY:
SAFEARRAY* TestClass::GetResult(long& size)
{
return GetSafeArrayList(size);
}
How should I export that function in a DLL so that c# could take it
How should I write c# method signature?
I have in c++ something along these lines:
extern "C" __declspec(dllexport) void GetResult(SAFEARRAY*& data, long& size)
{
size = 0;
data = handle->GetResult(size);
}
Is it correct, isn't it?
Thanks for help!
EDIT:
c# call:
public static extern void GetResult(IntPtr handle, [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_USERDEFINED)] TestStruct[] data, ref int size);
Full example of use of a SAFEARRAY(int) C#->C++->C# (so the array is initialized with some data in C#, passed to C++, modified there and returned to C#).
C++:
// For the various _t classes for handling BSTR and IUnknown
#include <comdef.h>
struct ManagedUDT
{
BSTR m_str01;
int m_int01;
~ManagedUDT()
{
::SysFreeString(m_str01);
m_str01 = NULL;
}
};
extern "C" __declspec(dllexport) void GetResult(SAFEARRAY*& data)
{
if (data != NULL)
{
// Begin print content of SAFEARRAY
VARTYPE vt;
HRESULT hr = SafeArrayGetVartype(data, &vt);
if (SUCCEEDED(hr))
{
// To make this code simple, we print only
// SAFEARRAY(VT_I4)
if (vt == VT_I4)
{
int *pVals;
hr = SafeArrayAccessData(data, (void**)&pVals); // direct access to SA memory
if (SUCCEEDED(hr))
{
long lowerBound, upperBound; // get array bounds
SafeArrayGetLBound(data, 1, &lowerBound);
SafeArrayGetUBound(data, 1, &upperBound);
long cnt_elements = upperBound - lowerBound + 1;
for (int i = 0; i < cnt_elements; i++) // iterate through returned values
{
int val = pVals[i];
printf("C++: %d\n", val);
}
SafeArrayUnaccessData(data);
}
else
{
// Error
}
}
}
else
{
// Error
}
// End print content of SAFEARRAY
// Delete the SAFEARRAY if already present
SafeArrayDestroy(data);
data = NULL;
}
{
// Creation of a new SAFEARRAY
SAFEARRAYBOUND bounds;
bounds.lLbound = 0;
bounds.cElements = 10;
data = SafeArrayCreate(VT_I4, 1, &bounds);
int *pVals;
HRESULT hr = SafeArrayAccessData(data, (void**)&pVals); // direct access to SA memory
if (SUCCEEDED(hr))
{
for (ULONG i = 0; i < bounds.cElements; i++)
{
pVals[i] = i + 100;
}
}
else
{
// Error
}
}
}
C#
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void GetResult([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4)] ref int[] ar);
and
var data = new int[] { 1, 2, 3, 4, 5 };
GetResult(ref data);
if (data != null)
{
for (int i = 0; i < data.Length; i++)
{
Console.WriteLine("C#: {0}", data[i]);
}
}
else
{
Console.WriteLine("C#: data is null");
}
Code partially taken from https://stackoverflow.com/a/12484259/613130 and https://stackoverflow.com/a/3735438/613130
SAFEARRAY(VT_RECORD)
It is doable... Very hard... but doable. Please don't do it. You can't hate enough the world to do it. I do hope you don't!
C++:
// For the _com_util
#include <comdef.h>
extern "C"
{
__declspec(dllexport) void GetResultSafeArray(SAFEARRAY *&psa)
{
// All the various hr results should be checked!
HRESULT hr;
// Begin sanity checks
if (psa == NULL)
{
// Error
}
VARTYPE pvt;
hr = ::SafeArrayGetVartype(psa, &pvt);
if (pvt != VT_RECORD)
{
// Error
}
UINT size;
size = ::SafeArrayGetElemsize(psa);
if (size != sizeof(ManagedUDT))
{
// Error
}
// From tests done, it seems SafeArrayGetRecordInfo does a AddRef
_com_ptr_t<_com_IIID<IRecordInfo, NULL> > prinfo;
// The_com_ptr_t<>::operator& is overloaded
hr = ::SafeArrayGetRecordInfo(psa, &prinfo);
// From tests done, it seems GetName returns a new instance of the
// BSTR
// It is ok to use _bstr_t.GetAddress() here, see its description
_bstr_t name1;
hr = prinfo->GetName(name1.GetAddress());
const _bstr_t name2 = _bstr_t(L"ManagedUDT");
if (name1 != name2)
{
// Error
}
// End sanity checks
long lowerBound, upperBound; // get array bounds
hr = ::SafeArrayGetLBound(psa, 1, &lowerBound);
hr = ::SafeArrayGetUBound(psa, 1, &upperBound);
long cnt_elements = upperBound - lowerBound + 1;
// Begin print
ManagedUDT *pVals;
hr = ::SafeArrayAccessData(psa, (void**)&pVals);
printf("C++:\n");
for (int i = 0; i < cnt_elements; ++i)
{
ManagedUDT *pVal = pVals + i;
// If you are using a recent VisualC++, you can
// #include <memory>, and then
//std::unique_ptr<char[]> pstr(_com_util::ConvertBSTRToString(pVal->m_str01));
// and you don't need the char *pstr line and the delete[]
// line
char *pstr = _com_util::ConvertBSTRToString(pVal->m_str01);
printf("%s, %d\n", pstr, pVal->m_int01);
delete[] pstr;
}
hr = ::SafeArrayUnaccessData(psa);
// End print
// Begin free
SAFEARRAYBOUND sab;
sab.lLbound = 0;
sab.cElements = 0;
// SafeArrayRedim will call IRecordInfo::RecordClear
hr = ::SafeArrayRedim(psa, &sab);
// End Free
// Begin create
int numElements = 10;
sab.cElements = numElements;
hr = ::SafeArrayRedim(psa, &sab);
hr = ::SafeArrayAccessData(psa, (void**)&pVals);
for (int i = 0; i < numElements; i++)
{
ManagedUDT *pVal = pVals + i;
char pstr[100];
sprintf(pstr, "Element #%d", i);
pVal->m_str01 = _com_util::ConvertStringToBSTR(pstr);
pVal->m_int01 = 100 + i;
}
hr = ::SafeArrayUnaccessData(psa);
// End create
}
__declspec(dllexport) void GetResultSafeArrayOut(SAFEARRAY *&psa, ITypeInfo *itypeinfo)
{
// All the various hr results should be checked!
HRESULT hr;
// Begin sanity checks
if (psa != NULL)
{
// Begin free
// SafeArrayDestroy will call IRecordInfo::RecordClear
// if necessary
hr = ::SafeArrayDestroy(psa);
// End Free
}
// Begin create
int numElements = 10;
SAFEARRAYBOUND sab;
sab.lLbound = 0;
sab.cElements = numElements;
// The_com_ptr_t<>::operator& is overloaded
_com_ptr_t<_com_IIID<IRecordInfo, NULL> > prinfo;
hr = ::GetRecordInfoFromTypeInfo(itypeinfo, &prinfo);
psa = ::SafeArrayCreateVectorEx(VT_RECORD, 0, numElements, prinfo);
ManagedUDT *pVals;
hr = ::SafeArrayAccessData(psa, (void**)&pVals);
for (int i = 0; i < numElements; i++)
{
ManagedUDT *pVal = pVals + i;
char pstr[100];
sprintf(pstr, "Element #%d", i);
pVal->m_str01 = _com_util::ConvertStringToBSTR(pstr);
pVal->m_int01 = 100 + i;
}
hr = ::SafeArrayUnaccessData(psa);
// End create
}
}
C#:
[ComVisible(true)]
[Guid("BBFE1092-A90C-4b6d-B279-CBA28B9EDDFA")]
[StructLayout(LayoutKind.Sequential)]
public struct ManagedUDT
{
[MarshalAs(UnmanagedType.BStr)]
public string m_str01;
public Int32 m_int01;
}
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void GetResultSafeArray([MarshalAs(UnmanagedType.SafeArray)] ref ManagedUDT[] array);
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void GetResultSafeArrayOut([MarshalAs(UnmanagedType.SafeArray)] out ManagedUDT[] array, IntPtr itypeinfo);
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "GetResultSafeArrayOut")]
static extern void GetResultSafeArrayRef([MarshalAs(UnmanagedType.SafeArray)] ref ManagedUDT[] array, IntPtr itypeinfo);
and
var arr = new[]
{
new ManagedUDT { m_str01 = "Foo", m_int01 = 1},
new ManagedUDT { m_str01 = "Bar", m_int01 = 2},
};
{
Console.WriteLine("C#:");
for (int i = 0; i < arr.Length; i++)
{
Console.WriteLine("{0}, {1}", arr[i].m_str01, arr[i].m_int01);
}
}
{
Console.WriteLine();
var arr2 = (ManagedUDT[])arr.Clone();
GetResultSafeArray(ref arr2);
Console.WriteLine();
Console.WriteLine("C#:");
for (int i = 0; i < arr2.Length; i++)
{
Console.WriteLine("{0}, {1}", arr2[i].m_str01, arr2[i].m_int01);
}
}
{
Console.WriteLine();
ManagedUDT[] arr2;
IntPtr itypeinfo = Marshal.GetITypeInfoForType(typeof(ManagedUDT));
GetResultSafeArrayOut(out arr2, itypeinfo);
Console.WriteLine();
Console.WriteLine("C#:");
for (int i = 0; i < arr2.Length; i++)
{
Console.WriteLine("{0}, {1}", arr2[i].m_str01, arr2[i].m_int01);
}
}
{
Console.WriteLine();
var arr2 = (ManagedUDT[])arr.Clone();
IntPtr itypeinfo = Marshal.GetITypeInfoForType(typeof(ManagedUDT));
GetResultSafeArrayRef(ref arr2, itypeinfo);
Console.WriteLine();
Console.WriteLine("C#:");
for (int i = 0; i < arr2.Length; i++)
{
Console.WriteLine("{0}, {1}", arr2[i].m_str01, arr2[i].m_int01);
}
}
There is a single big caveat for GetResultSafeArray: you must pass from C# at least an empty array (like a new ManagedUDT[0]). This because to create a SAFEARRAY(ManagedUDT) from nothing in C++ you would need a IRecordInfo object. I don't know how to retrieve it from C++. If you already have a SAFEARRAY(ManagedUDT) then clearly it has the IRecordInfo already set, so there is no problem. In the example given, in C++ there are first some sanity checks, then the passed array is printed, then it is emptied, then it is re-filled. The GetResultSafeArrayOut/GetResultSafeArrayRef "cheat": they receive from C# a ITypeInfo pointer (that is easy to retrieve in C#, with Marshal.GetITypeInfoForType()), and from taht the C++ can retrieve the IRecordInfo interface.
Some notes:
I wrote Ansi-charset-C++. Normally for myself I always write Unicode-ready C++ (or directy Unicode-C++, because all the Windows NT support Unicode), but I've noticed that I'm an exception... So in various parts of the code there are conversions BSTR->Ansi->BSTR.
I'm retrieving the HRESULT of all the function calls. They should be checked, and the failure handled.
The most complex thing in C++/COM is knowing when to free something... In general always free/Release() everything! (be it BSTR/IUnknown derived interfaces, ...)
Unless there is a bug, there is no support for this code. Consider it to be a proof of concept. I already lost various hours on it out of curiosity. You break it, you repair it.
Below is the code sample which I got from online resource but it's suppose to work with fullframework, but when I try to build it using C# smart device, it throws exception saying it's out of memory. Does anybody know how can I fix it to use on compact? the out of memory exception when I make the second call to VerQueryValue which is the last one.
thanks,
[DllImport("coredll.dll")]
public static extern bool VerQueryValue(byte[] buffer, string subblock, out IntPtr blockbuffer, out uint len);
[DllImport("coredll.dll")]
public static extern bool VerQueryValue(byte[] pBlock, string pSubBlock, out string pValue, out uint len);
//
private static void GetAssemblyVersion()
{
string filename = #"\Windows\MyLibrary.dll";
if (File.Exists(filename))
{
try {
int handle = 0;
Int32 size = 0;
size = GetFileVersionInfoSize(filename, out handle);
if (size > 0)
{
bool retValue;
byte[] buffer = new byte[size];
retValue = GetFileVersionInfo(filename, handle, size, buffer);
if (retValue == true)
{
bool success = false;
IntPtr blockbuffer = IntPtr.Zero;
uint len = 0;
//success = VerQueryValue(buffer, "\\", out blockbuffer, out len);
success = VerQueryValue(buffer, #"\VarFileInfo\Translation", out blockbuffer, out len);
if(success)
{
int p = (int)blockbuffer;
//Reads a 16-bit signed integer from unmanaged memory
int j = Marshal.ReadInt16((IntPtr)p);
p += 2;
//Reads a 16-bit signed integer from unmanaged memory
int k = Marshal.ReadInt16((IntPtr)p);
string sb = string.Format("{0:X4}{1:X4}", j, k);
string spv = #"\StringFileInfo\" + sb + #"\ProductVersion";
string versionInfo;
VerQueryValue(buffer, spv, out versionInfo, out len);
}
}
}
}
catch (Exception err)
{
string error = err.Message;
}
}
}
After adding these two statements:
Int32 dwVerMinor = j & 0xffff;
Int32 dwVerBuild = k & 0xffff;
it's able to retrieve the DLL version.
Here's an implementation:
using DWORD = System.UInt32;
public static class NativeFile
{
public struct NativeFileInfo
{
public Version Version;
public NameValueCollection StringTable;
}
public unsafe static NativeFileInfo GetFileInfo(string path)
{
if (!File.Exists(path))
{
throw new FileNotFoundException();
}
IntPtr handle;
var size = GetFileVersionInfoSize(path, out handle);
var buffer = Marshal.AllocHGlobal(size);
try
{
if (!GetFileVersionInfo(path, handle, size, buffer))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
IntPtr pVersion;
int versionLength;
VerQueryValue(buffer, “\”, out pVersion, out versionLength);
var versionInfo = (VS_FIXEDFILEINFO)Marshal.PtrToStructure(pVersion, typeof(VS_FIXEDFILEINFO));
var version = new Version((int)versionInfo.dwFileVersionMS >> 16,
(int)versionInfo.dwFileVersionMS & 0xFFFF,
(int)versionInfo.dwFileVersionLS >> 16,
(int)versionInfo.dwFileVersionLS & 0xFFFF);
// move to the string table and parse
var pStringTable = ((byte*)pVersion.ToPointer()) + versionLength;
var strings = ParseStringTable(pStringTable, size – versionLength);
return new NativeFileInfo
{
Version = version,
StringTable = strings
};
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
private unsafe static NameValueCollection ParseStringTable(byte* pStringTable, int length)
{
NameValueCollection nvc = new NameValueCollection();
byte* p = pStringTable;
short stringFileInfoLength = (short)*p;
byte* end = pStringTable + length;
p += (2 + 2 + 2); // length + valuelength + type
// verify key
var key = Marshal.PtrToStringUni(new IntPtr(p), 14);
if (key != "StringFileInfo") throw new ArgumentException();
// move past the key to the first string table
p += 30;
short stringTableLength = (short)*p;
p += (2 + 2 + 2); // length + valuelength + type
// get locale info
key = Marshal.PtrToStringUni(new IntPtr(p), 8);
// move to the first string
p += 18;
while (p < end)
{
short stringLength = (short)*p;
p += 2;
short valueChars = (short)*p;
p += 2;
short type = (short)*p;
p += 2;
if (stringLength == 0) break;
if ((valueChars == 0) || (type != 1))
{
p += stringLength;
continue;
}
var keyLength = stringLength – (valueChars * 2) – 6;
key = Marshal.PtrToStringUni(new IntPtr(p), keyLength / 2).TrimEnd(”);
p += keyLength;
var value = Marshal.PtrToStringUni(new IntPtr(p), valueChars).TrimEnd(”);
p += valueChars * 2;
if ((int)p % 4 != 0) p += 2;
nvc.Add(key, value);
}
return nvc;
}
private const string COREDLL = "coredll.dll";
[DllImport(COREDLL, SetLastError = true)]
private static extern int GetFileVersionInfoSize(string lptstrFilename, out IntPtr lpdwHandle);
[DllImport(COREDLL, SetLastError = true)]
private static extern bool GetFileVersionInfo(string lptstrFilename, IntPtr dwHandle, int dwLen, IntPtr lpData);
[DllImport(COREDLL, SetLastError = true)]
private static extern bool VerQueryValue(IntPtr pBlock, string lpSubBlock, out IntPtr lplpBuffer, out int puLen);
[StructLayout(LayoutKind.Sequential)]
private struct VS_FIXEDFILEINFO
{
public DWORD dwSignature;
public DWORD dwStrucVersion;
public DWORD dwFileVersionMS;
public DWORD dwFileVersionLS;
public DWORD dwProductVersionMS;
public DWORD dwProductVersionLS;
public DWORD dwFileFlagsMask;
public DWORD dwFileFlags;
public FileOS dwFileOS;
public FileType dwFileType;
public DWORD dwFileSubtype;
public DWORD dwFileDateMS;
public DWORD dwFileDateLS;
};
public enum FileOS : uint
{
Unknown = 0x00000000,
DOS = 0x00010000,
OS2_16 = 0x00020000,
OS2_32 = 0x00030000,
NT = 0x00040000,
WindowsCE = 0x00050000,
}
public enum FileType : uint
{
Unknown = 0x00,
Application = 0x01,
DLL = 0x02,
Driver = 0x03,
Font = 0x04,
VXD = 0x05,
StaticLib = 0x07
}
}
And an example of usage:
class Program
{
static void Main(string[] args)
{
string target = “\FlashFX Disk\ARMv4i\conmanclient2.exe”;
var version = NativeFile.GetFileInfo(target);
Console.WriteLine(string.Format(“File: { 0}”, Path.GetFileName(target)));
Console.WriteLine(string.Format(“Version: { 0}”, version.Version.ToString(4)));
foreach (var key in version.StringTable.AllKeys)
{
Console.WriteLine(string.Format(“{ 0}: { 1}”, key, version.StringTable[key]));
}
Console.ReadLine();
}