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.
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;
}
2
Answers
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:
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.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.