Calling a Windows API Function

S

Stewart Berman

I am trying to develop a wrapper class for the Windows API functions in Visual Studio 2008:
GetOpenFileName
GetSaveFileName

I put together a starter class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

[System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public class OpenFileName
{
int lstructSize;
int hwndOwner;
int hInstance;
string lpstrFilter = null;
string lpstrCustomFilter = null;
int lMaxCustomFilter;
int lFilterIndex;
string lpstrFile = null;
int lMaxFile = 0;
string lpstrFiteTitle = null;
int lMaxFileTitle = 0;
string lpstrInitialDir = null;
string lpstrTitle = null;
int lFlags;
ushort nFileOffset;
ushort nFileExtension;
string lpstrDefExt = null;
int lCustData;
int lpfHook;
int lpTemplateName;
}

[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetSaveFileName([In, Out] OpenFileName ofn);

namespace myNameSpace
{
class GetFileNames
{
}
}

The compilier complains:

Error 1 Expected class, delegate, enum, interface, or struct
E:\myNameSpace\myNameSpace\GetFileNames.cs 33 15 myNameSpace
Error 2 Expected class, delegate, enum, interface, or struct
E:\myNameSpace\myNameSpace\GetFileNames.cs 33 46 myNameSpace
Error 3 Expected class, delegate, enum, interface, or struct
E:\myNameSpace\myNameSpace\GetFileNames.cs 35 15 myNameSpace
Error 4 Expected class, delegate, enum, interface, or struct
E:\myNameSpace\myNameSpace\GetFileNames.cs 35 46 myNameSpace
Error 5 The modifier 'extern' is not valid for this item
E:\myNameSpace\myNameSpace\GetFileNames.cs 32 70 myNameSpace
Error 6 The modifier 'extern' is not valid for this item
E:\myNameSpace\myNameSpace\GetFileNames.cs 34 70 myNameSpace

Errors 1 and 3 refer to the bool.qualifier
Errors 2 and 4 refer to the OpenFileName qualifier
Errors 5 and 6 refer to the extern

The DLLImport statements were originally retrieved from pinvoke.net using the PInvoke.net add-in.
They were modified to use the fully qualified System.Runtime.InteropServices.DllImport reference.

What am I doing wrong?
 
R

Rudy Velthuis

Stewart said:
I put together a starter class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

[System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential,
CharSet=CharSet.Auto)] public class OpenFileName
{
int lstructSize;
int hwndOwner;
int hInstance;
string lpstrFilter = null;
string lpstrCustomFilter = null;
int lMaxCustomFilter;
int lFilterIndex;
string lpstrFile = null;
int lMaxFile = 0;
string lpstrFiteTitle = null;
int lMaxFileTitle = 0;
string lpstrInitialDir = null;
string lpstrTitle = null;
int lFlags;
ushort nFileOffset;
ushort nFileExtension;
string lpstrDefExt = null;
int lCustData;
int lpfHook;
int lpTemplateName;
}

[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetSaveFileName([In, Out] OpenFileName ofn);

What am I doing wrong?

Your static extern etc. functions should be declared as static members
of a class (even if, in reality, they are extern). And I would really
make OpenFileName a struct instead, passing it by reference.

--
Rudy Velthuis http://rvelthuis.de

"There are only two tragedies in life: one is not getting what
one wants, and the other is getting it."
-- Oscar Wilde (1854-1900)
 
S

Stewart Berman

The definition you pointed to is:
[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetOpenFileName([In, Out] OpenFileName ofn);

Which is what I had as I got it from pinvoke.net:
[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetOpenFileName([In, Out] OpenFileName ofn);

Am I missing something?
 
S

Stewart Berman

Your static extern etc. functions should be declared as static members
of a class (even if, in reality, they are extern).

I am new to C# and do not understand your suggestions. Can you please provide a link to code?
And I would really
make OpenFileName a struct instead, passing it by reference.

My limited understanding is that the only difference between a class and a structure is that a class
is allocated in the heap and passed by reference while a structure is allocated on the stack and
passed by either value or reference. I have always thought that the stack should be limited to
simple arguments passed by value and pointers to complex structures. The OpenFileName structure
seems a bit much to put on the stack. However, I am new to C# and maybe a bit confused.

Is that another difference between a class and a stack?

Rudy Velthuis said:
Stewart said:
I put together a starter class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

[System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential,
CharSet=CharSet.Auto)] public class OpenFileName
{
int lstructSize;
int hwndOwner;
int hInstance;
string lpstrFilter = null;
string lpstrCustomFilter = null;
int lMaxCustomFilter;
int lFilterIndex;
string lpstrFile = null;
int lMaxFile = 0;
string lpstrFiteTitle = null;
int lMaxFileTitle = 0;
string lpstrInitialDir = null;
string lpstrTitle = null;
int lFlags;
ushort nFileOffset;
ushort nFileExtension;
string lpstrDefExt = null;
int lCustData;
int lpfHook;
int lpTemplateName;
}

[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetSaveFileName([In, Out] OpenFileName ofn);

What am I doing wrong?

Your static extern etc. functions should be declared as static members
of a class (even if, in reality, they are extern). And I would really
make OpenFileName a struct instead, passing it by reference.
 
S

Stewart Berman

No. I don't take free trials unless I actually believe the site will be worth paying for.
 
R

Rudy Velthuis

Stewart said:
The definition you pointed to is:
[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetOpenFileName([In, Out] OpenFileName ofn);

Which is what I had as I got it from pinvoke.net:
[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetOpenFileName([In, Out] OpenFileName ofn);

Am I missing something?

That in the example (OK, it is VB, but I assume you can read that) it
is part of a class.
 
R

Rudy Velthuis

Stewart said:
Your static extern etc. functions should be declared as static
members of a class (even if, in reality, they are extern).

I am new to C# and do not understand your suggestions. Can you
please provide a link to code?
And I would really
make OpenFileName a struct instead, passing it by reference.

My limited understanding is that the only difference between a class
and a structure is that a class is allocated in the heap and passed
by reference while a structure is allocated on the stack and passed
by either value or reference. I have always thought that the stack
should be limited to simple arguments passed by value and pointers to
complex structures. The OpenFileName structure seems a bit much to
put on the stack. However, I am new to C# and maybe a bit confused.

Is that another difference between a class and a stack?

Rudy Velthuis said:
Stewart said:
I put together a starter class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

[System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential,
CharSet=CharSet.Auto)] public class OpenFileName
{

Make all members public:

etc...

Like I said, they should be static functions of a class:

public class APILib
{
[DllImport("comdlg32.dll", SetLastError=true,
CharSet = CharSet.Auto)]
static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
[DllImport("comdlg32.dll", SetLastError=true,
CharSet = CharSet.Auto)]
static extern bool GetSaveFileName([In, Out]
OpenFileName ofn);
}

Now you can use them like below:

OpenFileName ofn = new OpenFileName();
ofn.structSize = Marshal.SizeOf(ofn);
ofn.filter = filter;
ofn.file = new string(new Char[256] {});
ofn.maxFile = ofn.file.Length;

// etc...

if (APILib.GetOpenFileName(ofn))
{
// get data from ofn
}
 
S

Stewart Berman

Rudy Velthuis said:
Stewart said:
Your static extern etc. functions should be declared as static
members of a class (even if, in reality, they are extern).

I am new to C# and do not understand your suggestions. Can you
please provide a link to code?
And I would really
make OpenFileName a struct instead, passing it by reference.

My limited understanding is that the only difference between a class
and a structure is that a class is allocated in the heap and passed
by reference while a structure is allocated on the stack and passed
by either value or reference. I have always thought that the stack
should be limited to simple arguments passed by value and pointers to
complex structures. The OpenFileName structure seems a bit much to
put on the stack. However, I am new to C# and maybe a bit confused.

Is that another difference between a class and a stack?

Rudy Velthuis said:
Stewart Berman wrote:

I put together a starter class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

[System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential,
CharSet=CharSet.Auto)] public class OpenFileName
{

Make all members public:

etc...

Like I said, they should be static functions of a class:

public class APILib
{
[DllImport("comdlg32.dll", SetLastError=true,
CharSet = CharSet.Auto)]
static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
[DllImport("comdlg32.dll", SetLastError=true,
CharSet = CharSet.Auto)]
static extern bool GetSaveFileName([In, Out]
OpenFileName ofn);
}

Now you can use them like below:

OpenFileName ofn = new OpenFileName();
ofn.structSize = Marshal.SizeOf(ofn);
ofn.filter = filter;
ofn.file = new string(new Char[256] {});
ofn.maxFile = ofn.file.Length;

// etc...

if (APILib.GetOpenFileName(ofn))
{
// get data from ofn
}

I assume the need to imbed the imports in a class is due to the dot net framework. I don't
understand the need to make the members of the OpenFileName class public. They will only be
referred to with the code that instantiated the class. Why do they need to be public?
 
R

Rudy Velthuis

Stewart said:
I assume the need to imbed the imports in a class is due to the dot
net framework.

It is actually due to the C# language. Not all languages on .NET
require this.
I don't understand the need to make the members of
the OpenFileName class public. They will only be referred to with
the code that instantiated the class. Why do they need to be public?

Because otherwise they'll be private (which is the default
accessibility for class members), and so no code in any other class
would be able to access them.
 
J

\Ji Zhou [MSFT]\

Hello Stewart Berman,

Thanks for using Microsoft Newsgroup Support Service, my name is Ji Zhou
[MSFT] and I will be working on this issue with you.

As you already found the imported function's declaration should be embedded
in a class. Otherwise we will receive the error you mentioned in your first
post. The reason is, in C# language, there is not a concept of global
function or global variable while the C++ does have. If we want to simulate
the global function using C#, we need to create a host class and put the
function declaration in that class as public static type. Then, we can
access it via NameSpace.Class.Function everywhere else in the project.

As to why the fields of the class OpenFileName should be declared as
public, the public fields can be accessed from the OpenFileName instance
directly. If we use the private field, after we create an instance of
OpenFileName, we cannot initialize the private fields value. The P/Invoke
may fails for the incorrect parameter.

One more thing to state is that, what is the business reason for calling
the native API GetOpenFileName and GetSaveFileName from C#. Based on my
experience, since the .NET Framework provide this function in the assembly
System.Windows.Forms.dll, why not directly use the SaveFileDialog and
OpenFileDialog in .NET?

Codes look like,

private void button1_Click(object sender, System.EventArgs e)
{
Stream myStream = null;
OpenFileDialog openFileDialog1 = new OpenFileDialog();

openFileDialog1.InitialDirectory = "c:\\" ;
openFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*" ;
openFileDialog1.FilterIndex = 2 ;
openFileDialog1.RestoreDirectory = true ;

if(openFileDialog1.ShowDialog() == DialogResult.OK)
{
try
{
if ((myStream = openFileDialog1.OpenFile()) != null)
{
using (myStream)
{
// Insert code to read the stream here.
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk. Original
error: " + ex.Message);
}
}
}

We firstly create an instance of OpenFileDialog and set its properties like
the InitialDirectory, Filter, Filter Index and so on. After that, we can
call its.ShowDialog() to pop up the open file dialog. You can get more
information in the following two MSDN articles.

http://msdn.microsoft.com/en-us/library/system.windows.forms.openfiledialog.
aspx
http://msdn.microsoft.com/en-us/library/system.windows.forms.savefiledialog.
aspx

If you have any future questions or concerns, please feel free to let me
know and I will try my best to follow up. Have a nice day, Stewart!


Best regards,
Ji Zhou ([email protected], remove 'online.')
Microsoft Online Community Support

Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided. You can send feedback directly to my manager at:
(e-mail address removed).

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/en-us/subscriptions/aa948868.aspx#notifications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://support.microsoft.com/select/default.aspx?target=assistance&ln=en-us.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
 
S

Stewart Berman

Rudy Velthuis said:
It is actually due to the C# language. Not all languages on .NET
require this.


Because otherwise they'll be private (which is the default
accessibility for class members), and so no code in any other class
would be able to access them.

I realized that about three seconds after I hit the send button on the message.
 
S

Stewart Berman

I am down to one last problem. If the user selects one file everything works fine. However, if the
user selects more that one file only the path is returned.

The OpenFileName.lpstrFile definition now looks like:
[MarshalAs(UnmanagedType.LPWStr)]
public string lpstrFile;

I have a buffer defined:
string m_sFile;

I initialize it:
m_sFile = new string('\0', 10240)

I then assign it to lpstrFile:
m_OFN.lpstrFile = m_sFile.Clone();

The problem is that GetOpenFileName (through the C# interface) does not populate the buffer with the
information. It apparently returns a pointer to a new string. Now if only one file is selected the
path and file name are in the returned string. However, if more than one file is selected the
return string only contains the path and the length of the returned string corresponds to the number
of characters in the path. Normally, the buffer would contain the path followed by a '\0' and then
a series of file names separated by a '\0'. It appears something in the C# interface to unmanaged
code is expecting a null terminated string and is truncating the information at the first '\0'.

I apparently missed something in the interface definition but I can't seem to spot it.

Any suggestions would be appreciated.


Stewart Berman said:
I am trying to develop a wrapper class for the Windows API functions in Visual Studio 2008:
GetOpenFileName
GetSaveFileName

I put together a starter class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

[System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public class OpenFileName
{
int lstructSize;
int hwndOwner;
int hInstance;
string lpstrFilter = null;
string lpstrCustomFilter = null;
int lMaxCustomFilter;
int lFilterIndex;
string lpstrFile = null;
int lMaxFile = 0;
string lpstrFiteTitle = null;
int lMaxFileTitle = 0;
string lpstrInitialDir = null;
string lpstrTitle = null;
int lFlags;
ushort nFileOffset;
ushort nFileExtension;
string lpstrDefExt = null;
int lCustData;
int lpfHook;
int lpTemplateName;
}

[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
[DllImport("comdlg32.dll", SetLastError=true, CharSet = CharSet.Auto)]
static extern bool GetSaveFileName([In, Out] OpenFileName ofn);

namespace myNameSpace
{
class GetFileNames
{
}
}

The compilier complains:

Error 1 Expected class, delegate, enum, interface, or struct
E:\myNameSpace\myNameSpace\GetFileNames.cs 33 15 myNameSpace
Error 2 Expected class, delegate, enum, interface, or struct
E:\myNameSpace\myNameSpace\GetFileNames.cs 33 46 myNameSpace
Error 3 Expected class, delegate, enum, interface, or struct
E:\myNameSpace\myNameSpace\GetFileNames.cs 35 15 myNameSpace
Error 4 Expected class, delegate, enum, interface, or struct
E:\myNameSpace\myNameSpace\GetFileNames.cs 35 46 myNameSpace
Error 5 The modifier 'extern' is not valid for this item
E:\myNameSpace\myNameSpace\GetFileNames.cs 32 70 myNameSpace
Error 6 The modifier 'extern' is not valid for this item
E:\myNameSpace\myNameSpace\GetFileNames.cs 34 70 myNameSpace

Errors 1 and 3 refer to the bool.qualifier
Errors 2 and 4 refer to the OpenFileName qualifier
Errors 5 and 6 refer to the extern

The DLLImport statements were originally retrieved from pinvoke.net using the PInvoke.net add-in.
They were modified to use the fully qualified System.Runtime.InteropServices.DllImport reference.

What am I doing wrong?
 
J

\Ji Zhou [MSFT]\

Hello Stewart Berman,

As I have stated in my previous reply, in the .NET environment, we have
already provided the corresponding API, OpenFileDialog class which is more
powerful and easier to use than the comdlg32.dll's GetOpenFileName. Why not
use the OpenFileDialog class?

The codes are very simple to get multiple selected files,

OpenFileDialog opf = new OpenFileDialog();
opf.Multiselect = true;
if (opf.ShowDialog() == DialogResult.OK)
{
foreach (string name in opf.FileNames)
{
Debug.Print(name + "\r\n");
}
}

If you have any business reason for having to choose the comdlg32's native
API, please feel free let me know. Then we can have a future discussion on
this issue. Have a good day!

Best regards,
Ji Zhou ([email protected], remove 'online.')
Microsoft Online Community Support

Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided. You can send feedback directly to my manager at:
(e-mail address removed).

This posting is provided "AS IS" with no warranties, and confers no rights.
 
S

Stewart Berman

If you have any business reason for having to choose the comdlg32's native

I need to understand the interface between C# and the underlying operating system. This is a
training exercise that consists of converting a few VB 6 applications that make a large number of
API calls to C#.

Is there a set of sample code for doing this? Where can I find the source code for the
OpenFileDialog class? Is there a sample application that drops into assembler?

BTW, I solved my problem with the OpenFileName interface using fixed. I now have some problems with
C# Classes but that is the subject of another posting.

Stewart Berman
 
T

Tim Roberts

Stewart Berman said:
I need to understand the interface between C# and the underlying operating
system.

That's going to be very difficult to do. C# is even farther removed from
the operating system than VB6 is.
Is there a set of sample code for doing this? Where can I find the source
code for the OpenFileDialog class?

You can't. It's part of the Common Language Runtime.
Is there a sample application that drops into assembler?

You're talking about managed code here. That's as far from assembler as
you can get today.
 
S

Stewart Berman

The conversion of the VB6 application was just a simple training exercise.

I am trying to get acquainted with the current possibilities and limitations of the "new" languages.
Is there a white paper on the underlying differences between C# and C++?

I have not used C++ in almost ten years. The last commercial application I developed dropped into
assembler from C++ to switch into real mode to talk to the hardware -- please note that this was
original developed for Windows 3.1 although it still works in XP . (Must make note to try it in
Vista.)

Is this -- dropping into assembler -- still available in C++? Maybe I should have gone there
instead of C#.
You're talking about managed code here. That's as far from assembler as
you can get today.

But where there's a will there's a way -- one can always put the transition to assembler in a DLL.
 
T

Tim Roberts

Stewart Berman said:
I am trying to get acquainted with the current possibilities and limitations of the "new" languages.
Is there a white paper on the underlying differences between C# and C++?

Yes, Google brings up several. However, there is more to it than just the
language. More important than the difference between the two languages is
the difference between "unmanaged code" and "managed code".

C and the C++ you know are what Microsoft now called "unmanaged". They
compile straight to machine code and run on their own. C# and the managed
version of C++ called C++/CLI compile to an intermediate language, very
much like Java. VB, C#, and C++/CLI all create this intermediate language
(as do several other more obscure languages; J#, F#, IronPython, IronRuby,
among others). The IL is then compiled at run-time, and runs under the
control of the .NET Common Language Runtime. In a sense, it's another
layer removed from the hardware.
Is this -- dropping into assembler -- still available in C++? Maybe I should have gone there
instead of C#.

Yes, for 32-bit apps (again, assuming traditional unmanaged C++).
Microsoft's 64-bit compilers do support inline assembler at all.
But where there's a will there's a way -- one can always put the transition to assembler in a DLL.

Yes, even with managed code, although you have to use some magic to get
there.
 
S

Stewart Berman

You're talking about managed code here. That's as far from assembler as
Yes, even with managed code, although you have to use some magic to get
there.

Getting there is usually not the greatest challenge -- it's getting back out without leaving a trail
of broken pointers, unfreed memory and unpopped arguments on the stack. Watch closely -- at no
times do my fingers leave my hands.

Sometimes you need to be able to go under the covers to do problem determination. Microsoft's first
C++ product (C7) had a little problem. If you had an exe that called a dll to create an object and
you then destroy the object while running code in the exe the garbage collector freed the memory
pointed to by the object. Unfortunately it freed it from the exe's heap instead of the dll's. So,
a few thousand instructions later when that area of the exe's heap got reused and your program tried
to use what was originally there it crashed. Debugging that one by following the code through the
thunking layer was a lot of "fun". Given the current layers of abstraction a problem like that
would be almost impossible for a developer to track down. The program just dies in unreproducable
ways.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top