Link to home
Start Free TrialLog in
Avatar of emi_sastra
emi_sastra

asked on

Dynamic compiled Application Name.

Hi,

Usually the compiled application name is set on project.

Is it possible to do it using code and using variable to change it?

Thank you.
Avatar of abel
abel
Flag of Netherlands image

It is possible but only if you create a dynamic assembly with dynamic classes and dynamic methods. That's a lot of work, requires in-depth understanding of CLR and is hardly ever necessary.

The compiled application name is something that's returned from header information in your DLL or EXE. No code is run to get that information. As a result, it is not possible to make that dynamic. If you change it dynamically, you need to save the changes (or recompile) and reload it to see the changes.

There are major differences between compile time and runtime. This is a strictly compile time feature (compare it with the name of a file) and cannot be changed during runtime.

-- Abel --
Avatar of emi_sastra
emi_sastra

ASKER

Hi Abel,

If you change it dynamically, you need to save the changes (or recompile) and reload it to see the changes.
Yes, it is what I need to.

Let me describe what I am facing.
I create an application which is used for several companies at the same server.
Each of the company use different database.
Thus I need to compiled the application based on the short name  of the company for example:

1. GL_ABC.exe
2. GL_IMM.exe
3. etc

How to solve this? Any other better way is welcome.

Thank you.

You can start your application as many times as you want, you don't need different names for that. You could put the company name on the commandline so that your application knows for what company it is working, assuming the applications are all equal:

GL_GENERIC.exe /company:ABC

GL = General Ledger?

-- Abel --
Ok.   GL - General Ledger.

Another scenario:

The application is delivered to many clients, the clients could exchange data with the consultant using folder like:

1. Export To Consultant  <Client Code>.
2. Import From Consultant <Client Code>.

The Client Name should be flexible.
The MDI title could be "GL - Company Name"

Thus we need Company Code and Company Name for each of the company using the same application.

How to solve this problem?

Thank you.
I'm not sure, you got me lost a bit, I need a bit more info: Are we talking about many apps on one system? These winforms apps, how can many people use one app on one computer? How do they open the MDI form? If you have many MDI forms, then I assume you mean the forms inside one MDI app, right? It sounds to me that you are building a client-server application and that on the server application you want to monitor something, is that correct? Is the application already there and do you need to make changes or are you starting designing it?
Let me describe it.

There is one Consultant Company and Several Companies Of Client. Not Server and Client as usually we understand.

I have develop 2 app, one for The Consultant and the other one is for The Clients.

The app for Client is solved already. The problem is the app for the Clients.
I need to compile it every time for each of the company, because I don't want them to change their Company Information by them self. The mdi Form of the app should display Company Name without interference from the user.


Thank you.


I don't like the approach of using separate compiles, it is time consuming and it is hard to put a checksum on your file for downloads or virus scanners. But anyway, I'm not here to judge your approach, it may suit your needs just fine. Here's a possible solution:

  1. Add a string resource to your assembly which contains the name
  2. Create a simple helper app that takes an assembly (Assembly type) and changes the resource
  3. Save the changed Assembly
that's all. Much easier than recompile and you can do as many renames as you like.

-- Abel --
I am interested in your approach.

How should I do for the 3 points?

Would you please provide step for each of the point?

Thank you.
Hi Abel,

Are your still there to help?

Thank you.
Yes, I'm still here. We're volunteers, you know, and do this next to our jobs, so we won't always be around...

Anyway, what looked and sounded easy (and was inspired by Dynamic Assemblies and methods like GetMethodBody to get the IL of a body, I figured the same existed for assemblies) appears very hard in practice. Even something simple like changing a name is not trivial unless you know how to read and write PE or unless you dissect the assembly with reflection and re-emit it in a dynamic assembly and save that.

Sorry for pointing into a no-go area. I tried it, spent quite some time figuring out how to do it and ended up using RAIL: http://rail.dei.uc.pt/downloads.htm, which unfortunately does not work well together with newer versions of .NET and has not seen new development since 2005. From all I researched, RAIL seems the only library around that can read assemblies and modify existing assemblies.

Isn't it strange that it is real easy to create a new assembly, but that it is next to impossible to change an assembly? Maybe I'll research it once a bit more and come with some nice solution for it.

For now, you'll need another approach. What about using a resource-only assembly, which we can create dynamically? Then we don't have the problem of having to change a large existing assembly. You just create one assembly with a manifestresource which you can load dynamically. The resource just contains one string, the company name. Because it is an assembly (a dll) it is not likely that people can easily change it. You can put other info in the assembly as well. I'll try to create an example of how you can do that.

-- Abel --
Hi Abel,

Glad to hear you again.

I am waiting for your great solution.

Thank you.
I decided to make a little project for you. I know you posted with VS 2005, I only have 2008, which I add here as a solution (remove ".txt"). It shouldn't be too hard to open the application: all you need is Form1.cs, actually. I had some trouble whilst making it because it *seemed* as though it didn't work, but that had to do with the wrong naming of the resource section (see comment in the CreateAssembly event).

I removed all rubbish, I think, and the remaining code is quite self-explanatory. See the screenshot: just add a few strings for testing and it will be generated in the DLL. When the application loads, the resource dll is loaded, so that you see the saved entries (and you can see how to dynamically read an assembly and its resources).

Have fun with it! ;-)

PS: when you load an assembly, it is locked and cannot be unloaded. That's why I copy the file in the Form_Load, otherwise you would not be able to save the file later on.

Dynamically-Create-Assemblies.ra.txt
oh, and the compiled executable in the Debug dir requires .NET 3.5. Not sure whether I accidentally used anything from 3.5, if you get some errors, use the .NET 2.0 equivalents... (high bedtime on this side of the earth, I'll check back later if you have any problems).
I'll be back to your later as soon as I've tested it.

Thank you.
I just noticed that there's something wrong with the filename, it ends on ".ra.txt" but should end on ".rar.txt". In case you have troubles: it is a RAR type of package, so rename it to "*.rar". You can get a free-to-use version of RAR at http://www.rarlab.com (but I'm sure you know).
Yes, I've got it already when I saw "ra".
Unfortunately it is in C, I am really not familiar with it.
When I try to open it, I get message "It is not supported by this version of VS".

Would you please paste those code here (Code Snippet), thus I could learn it immediately from you.
May be I could translate it using translator.

Thank you.
> Unfortunately it is in C, I am really not familiar with it.

I didn't use C.... ;-)

> "It is not supported by this version of VS".

See my comment in my other text, explaining why. Just create a new project and open the *.cs file. It is C#, really, not C.

Of course I can copy the code if that makes things easier. Here you go. It will work if you add a btnCreateAssembly, btnAddField, btnDeleteField with Click events, plust a lstResourceItems with a SelectedChanged event on a form Form1. But I wanted to save you the trouble of doing that all yourself:

PS: I'm sure you already tried the executable. Was that about what you expected?

public partial class Form1 : Form
{
    Dictionary<string, string> fieldsDictionary = new Dictionary<string, string>();
 
    public Form1()
    {
        InitializeComponent();
    }
 
    private void Form1_Load(object sender, EventArgs e)
    {
        try
        {
            File.Copy(Path.GetFullPath("resource.dll"), Path.GetFullPath("resource.dll.copy"), true);
            Assembly assembly = Assembly.ReflectionOnlyLoadFrom(Path.GetFullPath("resource.dll.copy"));
            ResourceReader resourceReader = new ResourceReader(assembly.GetManifestResourceStream("CompanyInfo.resources"));
            foreach (DictionaryEntry resource in resourceReader)
            {
                fieldsDictionary.Add((string) resource.Key, (string) resource.Value);                    
            }
        } 
        catch(FileNotFoundException)
        {
            fieldsDictionary.Add("company-name", "My Very Cool Company");
            fieldsDictionary.Add("company-address", "Some street 204");
        }
        loadFields();
    }
 
    private void loadFields()
    {
        lstResourceItems.Items.Clear();
        foreach (string item in fieldsDictionary.Keys)
            lstResourceItems.Items.Add(item);
    }
 
    private void btnAddField_Click(object sender, EventArgs e)
    {
        fieldsDictionary[txtName.Text] = txtValue.Text;
        loadFields();
    }
 
    private void btnDeleteField_Click(object sender, EventArgs e)
    {
        if (fieldsDictionary.ContainsKey(txtName.Text))
            fieldsDictionary.Remove(txtName.Text);
        loadFields();
    }
 
    private void lstResourceItems_SelectedIndexChanged(object sender, EventArgs e)
    {
        string selected = (string) lstResourceItems.SelectedItem;
        if(fieldsDictionary.ContainsKey(selected))
        {
            txtName.Text = selected;
            txtValue.Text = fieldsDictionary[selected];
        }
    }
 
    private void btnCreateAssembly_Click(object sender, EventArgs e)
    {
        AssemblyName assemblyName = new AssemblyName("CompanyInfoResources");
        AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
            assemblyName, 
            AssemblyBuilderAccess.Save);
 
 
        // contrary to what the references say, the FIRST parameter is vital
        // and MUST end on ".resources" to create a readable resource
        IResourceWriter resourceWriter = assemblyBuilder.DefineResource(
            "CompanyInfo.resources",
            "Company information",
            "CompanyInfoResources.CompanyInfo.resources");
 
        foreach (KeyValuePair<string, string> de in fieldsDictionary)
            resourceWriter.AddResource(de.Key, de.Value);
 
        assemblyBuilder.DefineVersionInfoResource("CompanyInfoResourceExample", "1:0:0:1", "Metacarpus", "(c) Metacarpus", "");
        assemblyBuilder.Save("resource.dll");
 
    }
 
}

Open in new window

ScreenShot416.png
I just tried it, but It can not Create Assembly, because it is in used by other process.

I called MainApplication.exe.

I am not quite understand, there are several files that I don't know how to use it.

1. CompanyInfoResources.CompanyInfo.resources
2. resource.dll
3. resource.dll.copy

Which files should I copy to client computers and how my application use the file created by the app above?

Thank you.
In the screenshot it says "the assembly will be placed in the working dir as "resource.dll". That's what you need.

In my text above I tried to explain: "when you load an assembly, it is locked and cannot be unloaded. That's why I copy the file in the Form_Load, otherwise you would not be able to save the file later on."

In so many words that meant: to load the dll, I copy it, to prevent it from being locked. The copy it called "resource.dll.copy".

Nr 1 above can be ignored. It is an intermediate file that is created by the dynamic assembly creator.

Be aware that what you are doing here is considered by many as very complex: dynamic emit of type and assembly creation is usually about the last chapter in understanding everything you could possibly need to know about MSIL, IL compilers and CLI in general. If this all sounds like magic to you, don't be alarmed, to most programmers these things stay like magic their whole life.
> I just tried it, but It can not Create Assembly, because it is in used by other process.
what exactly did you try? If it is in use, then it has been loaded. That's not necessarily bad, but you can only create it when it is not loaded. My code creates a copy to prevent that. However, I placed that code in the form_load, because the loading-code will only work once in the lifetime of the application because it creates a lock on the copy-file. Close and restart to release that lock. I don't know of any workaround to that, but for your code to work properly you do not need to read back the contents of the DLL other then in your final application.
Ignore (parts of) my last two comments. The *.resources file is important, it apparently has to stick with the resource.dll. I hoped it would go inside it. Though it is a binary file, it is not embedded. I'll check whether that's possible (it is, but not sure how to do it dynamically).

The reason that the file is in use is that it opens the file resource.dll.copy and the *.resources file. The *.resources file is the problem, because that is locked when loaded. To prevent that, change my copy code to copy it to a new directory and copy both the dll and the resources file. No need to rename it.
I see, correct me if I am wrong.

1. The resource.dll has the info what I want to store..
2. In my application I just read the ddl using :
     ResourceReader resourceReader = new ResourceReader(assembly.GetManifestResourceStream("CompanyInfo.resources"));
            foreach (DictionaryEntry resource in resourceReader)
            {
                fieldsDictionary.Add((string) resource.Key, (string) resource.Value);                    
            }

Thank you.
ASKER CERTIFIED SOLUTION
Avatar of abel
abel
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Btw, after quite some research, the only reference that pointed to the fact that the names must be equal I found here: http://www.dotnet247.com/247reference/msgs/58/290731.aspx, so thanks to Nick Carter in that thread :)
Hi Abel,

I have completed this project using VB Net Code.

Great help and explanation.

Thank you very much for your help.
Sorry, missed that you were on VB, I know you posted in that zone, but probably forgot.

Consider this tool, that does most of the trivial cases pretty well: http://www.developerfusion.com/tools/convert/csharp-to-vb/
Glad you completed it, btw and tx for the pts :-)